Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nicky1038 opened this issue Sep 27, 2020 · 5 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@nicky1038
Copy link

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

@nicky1038
Copy link
Author

nicky1038 commented Sep 27, 2020

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

@nicky1038 nicky1038 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 Sep 27, 2020
@jcalz
Copy link
Contributor

jcalz commented Sep 27, 2020

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.

@nicky1038
Copy link
Author

@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.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Sep 28, 2020
@RyanCavanaugh
Copy link
Member

Thanks @jcalz

@typescript-bot
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants