Skip to content

Unable to read properties off object spread in generic function parameter (regression with 4.6.1-rc) #47865

Closed

Description

@danvk

Bug Report

I noticed a regression between TypeScript 4.6.0-beta and 4.6.1-rc in my repo where parameters to generic functions that use object spread sometimes get an unusable type.

🔎 Search Terms

  • object spread
  • 4.6.1-rc

🕗 Version & Regression Information

  • This changed between versions 4.6.0-beta and 4.6.1-rc.

⏯ Playground Link

Playground link with relevant code

💻 Code

(see comment below for a simpler repro: #47865 (comment))

/** Extract params from an express path (e.g. '/students/:studentId'). */
export type ExtractRouteParams<T extends string> = string extends T
  ? Record<string, string>
  : T extends `${infer _Start}:${infer Param}/${infer Rest}`
  ? {[k in Param | keyof ExtractRouteParams<Rest>]: string}
  : T extends `${infer _Start}:${infer Param}`
  ? {[k in Param]: string}
  : {};

declare function get<T extends string>(
  path: T,
  handler: (params: ExtractRouteParams<T>) => void,
): void;

get('/path/:foo/to/:bar', ({foo, ...slugs}) => {
  const {bar} = slugs;  // ok in TS 4.6.0-beta but not in TS 4.6.1-rc
  console.log({bar});
});

get('/path/:foo/to/:bar', slugs => {
  const {bar} = slugs;  // ok in both
  console.log({bar});
});

🙁 Actual behavior

Using object spread in a function parameter produces an unusable type in TS 4.6.1-rc.

The type of slugs in the first example is:

 slugs: { bar: string; }

in TypeScript 4.6.0-beta but:

slugs: Omit<ExtractRouteParams<T>, "foo">

in TypeScript 4.6.1-rc.

These are both the same (assuming T = "/path/:foo/to/:bar") but the latter doesn't let me read any properties off of it.

🙂 Expected behavior

I should be able to read bar off of slugs.

Activity

  1. danvk commented on Feb 14, 2022

    @danvk
    ContributorAuthor

    Here's a simpler repro that doesn't involve template literal types. This (correctly) passes the type checker with TS 4.5.5 but raises a spurious error with 4.6.1-rc.

    interface Slugs {
      foo: string;
      bar: string;
    }
    
    function call<T extends object>(obj: T, cb: (val: T) => void) {
      cb(obj);
    }
    
    declare let obj: Slugs;
    call(obj, ({foo, ...rest}) => {
      console.log(rest.bar);
      //               ~~~ Property 'bar' does not exist on type 'Omit<T, "foo">'. ts(2339)
    });
  2. RyanCavanaugh commented on Feb 14, 2022

    @RyanCavanaugh
    Member

    Bisected to #47337

  3. RyanCavanaugh commented on Feb 14, 2022

    @RyanCavanaugh
    Member

    Gabriela Araujo Britto (@gabritto) seems like we're failing to assign the inferred type to rest here; T shouldn't be appearing at all

  4. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions