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
Nested callbacks are not contextually typed with a type parameter dependent type queryΒ #52575
exportinterfaceEvent<T>{callback: (response: T)=>void;nested: {callback: (response: T)=>void;}}exporttypeCustomEvents={a: Event<string>b: Event<number>// If only one candidate type exists it works};declarefunctionemit<TextendskeyofCustomEvents>(type: T,data: CustomEvents[T]): voidemit('a',{callback: (r)=>{},// r is string in 4.9 and 5.0nested: {callback: (r)=>{// r is string in 4.9 and an error in 5.0},},});
π Actual behavior
The parameter of the callback in nested is not typed and gives an error : Parameter 'r' implicitly has an 'any' type.
π Expected behavior
The parameter of the callback in nested is typed as string, same as in 4.9 and same as for the top level callback
This happens within checkExpressionWithContextualType called from here
Each of those properties goes through getContextualTypeForObjectLiteralElement and thus through getApparentTypeOfContextualType.
Each of those receive CustomEvents[T] back but they both try to instantiate it
the callback property is a function so the contextFlags & ContextFlags.Signature holds true for it and it instantiates the type here, it receives Event<string> back
the nested property is not a function, so nothing happens there, and CustomEvents[T] is returned to the caller. However, in there, it's "dumped" to the constraint of that type here and that is Event<string> | Event<number>
And thus this ends up with conflicting signatures for the inner callback (((response: string) => void) | ((response: number) => void)) and the contextual type fails to be provided
It seems that perhaps the contextual type for nested should get deferred instead of being resolved to the constraint. I've tried deferring it - and it fixes the problem but it also breaks a few of existing test cases. So the final fix would have to be more involved, perhaps there is some additional condition for deferring this generic indexed access that I miss and I didn't include in my experiment.
Reacted by Titian Cernicova-Dragomir, Ryan Cavanaugh and Toni Villena
Hmm, yeah I see what's going on here, and it's subtle. To improve performance #51865 introduced caching of contextual types in checkArrayLiteral and checkObjectLiteral. Those caches can really only be consulted by getContextualType calls with no contextFlags, but we aren't keeping track of whether an entry on the contextual type stack is a cache entry or not. I will put up a PR that adds that tracking.
Activity
Andarist commentedon Feb 3, 2023
Somehow the PR with
consttype parameters introduced this regression. cc Anders Hejlsberg (@ahejlsberg)Andarist commentedon Feb 3, 2023
This happens within
checkExpressionWithContextualTypecalled from heregetContextualTypeForObjectLiteralElementand thus throughgetApparentTypeOfContextualType.CustomEvents[T]back but they both try to instantiate itcallbackproperty is a function so thecontextFlags & ContextFlags.Signatureholds true for it and it instantiates the type here, it receivesEvent<string>backnestedproperty is not a function, so nothing happens there, andCustomEvents[T]is returned to the caller. However, in there, it's "dumped" to the constraint of that type here and that isEvent<string> | Event<number>callback(((response: string) => void) | ((response: number) => void)) and the contextual type fails to be providedIt seems that perhaps the contextual type for
nestedshould get deferred instead of being resolved to the constraint. I've tried deferring it - and it fixes the problem but it also breaks a few of existing test cases. So the final fix would have to be more involved, perhaps there is some additional condition for deferring this generic indexed access that I miss and I didn't include in my experiment.ahejlsberg commentedon Feb 4, 2023
Hmm, yeah I see what's going on here, and it's subtle. To improve performance #51865 introduced caching of contextual types in
checkArrayLiteralandcheckObjectLiteral. Those caches can really only be consulted bygetContextualTypecalls with nocontextFlags, but we aren't keeping track of whether an entry on the contextual type stack is a cache entry or not. I will put up a PR that adds that tracking.