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
Incorrect inferred return type when returning object literal from lambda bound to generic parameter #5487
When passing a function as a generic type T, the compiler correctly infers the return type when passing a previously declared function, but not when the same function is passed as an inline lambda. This seems to only happens with object literals. Classes and builtin return types are inferred properly.
functionwrapper<T>(fn: T): T{returnfn};varfn=function(x: number){return{ x }};// Correct: type of fnWrapper is (x: number) => { x: number }varfnWrapper=wrapper(fn);// Incorrect: type of inlineWrapper is (x: number) => any varinlineWrapper=wrapper(function(x: number){return{ x }});
Unfortunately, this is a real circularity in the contextual typing algorithm:
To find the return type of the inline function in the call to wrapper, the compiler looks at the type of each return statement.
To find the type of the return { x } statement, the compiler first checks the contextual type of { x }.
To find the contextual type of { x }, the compiler checks the contextual type of the containing statement, then the contextual return type of the containing function.
To find the contextual return type of the containing function, the compiler checks its return type. At this point, circularity detection notices that we started checking the return type of the function at (1) and gives up on finding the contextual type.
It returns unknownType to (3) and records the circularity for when the compiler gets back to (1).
The funny part is that after the circularity is detected, the compiler can get the type of the object literal in checkObjectLiteralwithout a contextual type -- that's why var fn = function ... works. But the circularity detection back at (1) throws this result away and returns any instead.
I'm not sure if the circularity detection that throws away the result is correct. I'll look at that next.
Anders Hejlsberg (@ahejlsberg), in getReturnTypeOfSignature, if popTypeResolution returns false, then it always ignores the type found earlier in the function and just uses any, even if the previous type turns out to be correct as in this case. Is this correct when the circularity arises from contextual typing like it does in this case?
I came across what I believe this issue is today, where literals could be contextually inferred, but don't seem to be... Even more surprising, is where they appear to be inferred contextually they suddenly widen without implicit types. The real world use case was using Promise.resolve() to return a literal value. Here is the example in the playground
functiontruePromise(): Promise<true>{returnPromise.resolve(true);// Type 'boolean' is not assignable to type 'true'.}interfaceWrap<T>{value: T;}functionwrap<T>(value: T): Wrap<T>{return{ value };}functionwrappedFoo(): Wrap<'foo'>{returnwrap('foo');// Type 'string' is not assignable to type '"foo"'.}functionwrapBar(value: 'bar'): Wrap<'bar'>{return{ value };}functionwrappedBar(): Wrap<'bar'>{constvalue='bar';// const value: "bar"constinferred=wrapBar(value);// const inferred: Wrap<"bar">constliteral=wrapBar('bar');// no error!constvalue2: string='bar';constliteral2=wrapBar(value2);// Argument of type 'string' is not assignable to parameter of type '"bar"'.returnwrap(value);// Type 'string' is not assignable to type '"bar"'. ?!?!?!}functionwrappedBaz(): Wrap<'baz'>{constvalue: 'baz'='baz';returnwrap(value);}
The surprising one was wrappedBar() where the contextually inferred type gets widened when passed as an argument. It only seems to widen when checking assignability to generics, in other situations, where the value could be assignable to the literal, it is treated as such.
Activity
sandersn commentedon Mar 2, 2016
I guess that function return type inference (maybe kicked off by contextual typing of the object literal?) interferes with fixing the type parameters.
sandersn commentedon Mar 4, 2016
Unfortunately, this is a real circularity in the contextual typing algorithm:
wrapper, the compiler looks at the type of eachreturnstatement.return { x }statement, the compiler first checks the contextual type of{ x }.{ x }, the compiler checks the contextual type of the containing statement, then the contextual return type of the containing function.unknownTypeto (3) and records the circularity for when the compiler gets back to (1).The funny part is that after the circularity is detected, the compiler can get the type of the object literal in
checkObjectLiteralwithout a contextual type -- that's whyvar fn = function ...works. But the circularity detection back at (1) throws this result away and returnsanyinstead.I'm not sure if the circularity detection that throws away the result is correct. I'll look at that next.
sandersn commentedon Mar 4, 2016
Anders Hejlsberg (@ahejlsberg), in
getReturnTypeOfSignature, ifpopTypeResolutionreturns false, then it always ignores the type found earlier in the function and just usesany, even if the previous type turns out to be correct as in this case. Is this correct when the circularity arises from contextual typing like it does in this case?kitsonk commentedon Aug 28, 2017
I came across what I believe this issue is today, where literals could be contextually inferred, but don't seem to be... Even more surprising, is where they appear to be inferred contextually they suddenly widen without implicit types. The real world use case was using
Promise.resolve()to return a literal value. Here is the example in the playgroundThe surprising one was
wrappedBar()where the contextually inferred type gets widened when passed as an argument. It only seems to widen when checking assignability to generics, in other situations, where the value could be assignable to the literal, it is treated as such.aleclarson commentedon Jan 14, 2019
Kitson Kelly (@kitsonk) This issue might interest you: #10195
Here's the workaround for your example: (playground)
The error for
wrapBar(value2)is intended.ahejlsberg commentedon Jan 30, 2019
Closing as the bug no longer reproduces. We now infer the correct type.