You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can't assign result of function of type T to ReturnType<T> #31811
TypeScript seems to treat the return type of a generic not as the ReturnType<T> but the type it extends from. Basically in this example we get a type error (even though instance is correctly inferred as Foo) because IFoo is not assignable to ReturnType<T>:
interfaceIFoo{someMethod(): string;}classFooimplementsIFoo{staticcreate(p: number){returnnewFoo(p)}constructor(readonlyp: number){}someMethod=()=>'a';}functioncreateSomeInterfaceInstance<Textends(p: number)=>IFoo>(factoryFunction: T): ReturnType<T>{// This is a TypeError that IFoo is not assignable to ReturnType<T>returnfactoryFunction(12)}constinstance=createSomeInterfaceInstance(Foo.create);
Now this is hackable-around in two different ways, one by explicitly adding a generic for the return type of T e.g.:
function createSomeInterfaceInstance<K extends IFoo, T extends (p: number) => K>(factoryFunction: T): K
Or just by using a type intersection on the return type e.g.:
But both of these solutions feel pretty hacky, it would instead be better if TypeScript inferred the type of factoryFunction(10) to be IFoo & ReturnType<T> instead of just IFoo.
changed the title [-]Can't assign result of function of T to ReturnType<T>[/-][+]Can't assign result of function of type T to ReturnType<T>[/+]on Jun 7, 2019
The error actually kind of makes sense to me, at least.
T extends (p: number) => IFoo
factoryFunction (as far as TypeScript knows) returns an IFoo. Return types are covariant, so T could in fact end up to be (p: number) => ISpecialFoo and then assigning IFoo to that wouldn't be sound.
I guess the compiler isn't smart enough to realize that ReturnType<T> is itself generic.
Hmm, the plot thickens. The IntelliSense hover text for factoryFunction says:
(p: number)=>IFoo
Normally for variables of generic types it will say the type is T extends ..., here it seems to have just decided the function is the concrete type of its constraint. So it's nothing to do with ReturnType but rather how TS has inferred the type of the function call itself.
but rather how TS has inferred the type of the function call itself.
It seems to infer the type of instance just fine and T is (p: number) => Foo rather than (p: number) => IFoo so the fact it can't do it within the function seems surprising:
Well, within the body of the generic function it doesn't actually know what T is (hence why it's generic 😉), so that much makes sense at least. What doesn't make sense to me is why it thinks factoryFunction is concretely (p: number) => IFoo instead of T extends (p: number) => IFoo, like it would usually be.
Let's see if someone else smarter comes along to solve this mystery because I'm out of ideas. 😛
The type-checking looks reasonable - it knows that factory function is generic, it's just the quick info that is slightly off.
functioncreateSomeInterfaceInstance<Textends(p: number)=>IFoo>(factoryFunction: T): ReturnType<T>{constfun: T=factoryFunction;// hover factoryFunction: (parameter) factoryFunction: T extends (p: number) => IFooreturnfun(12);// hover fun: const fun: T (p: number) => IFoo// ^ the generic is here, but in a weird place.}
TypeScript doesn't know how to apply a generic parameter, so it attempts to find a function-like constraint and apply that - this is why the return type is IFoo. I guess you want the application of a parameter T to return ReturnType<T>? Not sure how tractable this is once things get more complicated; it is hard to do anything with a type ReturnType<T>.
The solution:
function createSomeInterfaceInstance<K extends IFoo, T extends (p: number) => K>(factoryFunction: T): K
Doesn't feel hacky to me and is inline with TypeScript guidelines that suggests pushing type-parameters down as far as possible. Though I would write it as follows, unless you really need T:
it is hard to do anything with a type ReturnType<T>.
You're right about that - I overlooked that ReturnType is conditional; you can't actually assign stuff to a conditional type (without a cast) until it's been resolved to a real type, but since there's a generic type variable T involved, that hasn't happened yet.
ReturnType<T> in this context is essentially an existential type (also this) and TypeScript doesn't actually support those. 😅
In order for the original example to work the type checker would have to consider the type of the call expression factoryFunction(12) to be a higher order ReturnType<T> instead of falling back to the return type of the constraint of T (i.e. IFoo). This is not something that is currently on our radar.
Activity
[-]Can't assign result of function of T to ReturnType<T>[/-][+]Can't assign result of function of type T to ReturnType<T>[/+]fatcerberus commentedon Jun 7, 2019
The error actually kind of makes sense to me, at least.
factoryFunction(as far as TypeScript knows) returns anIFoo. Return types are covariant, soTcould in fact end up to be(p: number) => ISpecialFooand then assigningIFooto that wouldn't be sound.I guess the compiler isn't smart enough to realize that
ReturnType<T>is itself generic.fatcerberus commentedon Jun 7, 2019
Hmm, the plot thickens. The IntelliSense hover text for
factoryFunctionsays:Normally for variables of generic types it will say the type is
T extends ..., here it seems to have just decided the function is the concrete type of its constraint. So it's nothing to do withReturnTypebut rather how TS has inferred the type of the function call itself.Jamesernator commentedon Jun 7, 2019
It seems to infer the type of
instancejust fine andTis(p: number) => Foorather than(p: number) => IFooso the fact it can't do it within the function seems surprising:fatcerberus commentedon Jun 7, 2019
Well, within the body of the generic function it doesn't actually know what
Tis (hence why it's generic 😉), so that much makes sense at least. What doesn't make sense to me is why it thinksfactoryFunctionis concretely(p: number) => IFooinstead ofT extends (p: number) => IFoo, like it would usually be.Let's see if someone else smarter comes along to solve this mystery because I'm out of ideas. 😛
jack-williams commentedon Jun 7, 2019
The type-checking looks reasonable - it knows that factory function is generic, it's just the quick info that is slightly off.
TypeScript doesn't know how to apply a generic parameter, so it attempts to find a function-like constraint and apply that - this is why the return type is
IFoo. I guess you want the application of a parameterTto returnReturnType<T>? Not sure how tractable this is once things get more complicated; it is hard to do anything with a typeReturnType<T>.The solution:
Doesn't feel hacky to me and is inline with TypeScript guidelines that suggests pushing type-parameters down as far as possible. Though I would write it as follows, unless you really need
T:fatcerberus commentedon Jun 7, 2019
You're right about that - I overlooked that
ReturnTypeis conditional; you can't actually assign stuff to a conditional type (without a cast) until it's been resolved to a real type, but since there's a generic type variableTinvolved, that hasn't happened yet.ReturnType<T>in this context is essentially an existential type (also this) and TypeScript doesn't actually support those. 😅Thanks Jack Williams (@jack-williams)!
ahejlsberg commentedon Jun 7, 2019
In order for the original example to work the type checker would have to consider the type of the call expression
factoryFunction(12)to be a higher orderReturnType<T>instead of falling back to the return type of the constraint ofT(i.e.IFoo). This is not something that is currently on our radar.I'll echo what Jack Williams (@jack-williams) said above. The best way to write it is: