Skip to content

Infer keyword should work with unused generic type parameter of a superclass #40796

Closed

Description

Search Terms: infer keyword subclass superclass generic type parameter

Code

class A<T1, T2, T3> {  }

class B<T1, T2> extends A<T1, T2, boolean> { }

type FirstAParam = B<string, number> extends A<infer T1, any, any>
    ? T1
    : never;

Expected behavior: FirstAParam is string.

Actual behavior: FirstAParam is unknown.

Playground Link

Activity

  1. nicky1038 commented on Sep 27, 2020

    @nicky1038
    Author

    Possible, not pretty, but still usable workaround that solves this problem on the user code side is to add a property to the base class so that it will be used by compiler for type inference.

    class A<T1, T2, T3> { 
      readonly stub: T1 = {} as T1;
    }
    
    class B<T1, T2> extends A<T1, T2, boolean> { }
    
    type FirstAParam = B<string, number> extends A<infer T1, any, any> // FirstAParam is string
        ? T1
        : never;
    
    // or
    type FirstAParam2 = B<string, number>['stub']   // FirstAParam2 is string

    Playground link

  2. changed the title [-]Infer keyword should work with generic type parameter of a superclass[/-] [+]Infer keyword should work with unused generic type parameter of a superclass[/+] on Sep 27, 2020
  3. jcalz commented on Sep 27, 2020

    @jcalz
    Contributor

    This is working as intended. See this entry in the (somewhat outdated) FAQ. The type system is structural, not nominal; two types are the same iff they have the same structure or shape, not iff they have the same name or declaration.

    In your code above, the type A<T1, T2, T3> is exactly the same as the empty object type {}. They are just two different names for the same type. To expect the compiler to be able to infer string from the type A<string, number, boolean> is to expect that it should be able to infer string from the type {}, which obviously doesn't work... just because the name you used to refer to {} mentioned string it doesn't imply that the type itself depends on string. (In practice the compiler sometimes does remember the name you used to refer to a type, and these in-principle-impossible inferences actually do happen; but you really can't rely on them.)

    Your workaround is the recommended way to deal with cases like this: make sure your types are structurally dependent on the generic type parameters... the easiest way being to give them members of those types, like your stub. In any case this behavior is not a bug, but the expected behavior of TypeScript's structural type system.

  4. nicky1038 commented on Sep 27, 2020

    @nicky1038
    Author

    Joe Calzaretta (@jcalz) Thank you for explanation and sorry for not reading the entire FAQ!

    It's a sort of sad because I have a complex scenario where one generic parameter of a class should be used later by class consumers, though there are no any properties of its type inside the class. OK, I understand, there is nothing to do except using the workaround :(

    BTW, in this case:

    type FirstAParam = A<string, number, boolean> extends A<infer T1, any, any>
        ? T1
        : never;
    

    FirstAParam is string, and it becomes a bit confusing why does this work one way in the first example and another way in this one.

  5. RyanCavanaugh commented on Sep 28, 2020

    @RyanCavanaugh
    Member
  6. typescript-bot commented on Sep 30, 2020

    @typescript-bot
    Contributor

    This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions