Skip to content

Nested callbacks are not contextually typed with a type parameter dependent type query #52575

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
dragomirtitian opened this issue Feb 2, 2023 · 3 comments Β· Fixed by #52611
Closed
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@dragomirtitian
Copy link
Contributor

Bug Report

πŸ”Ž Search Terms

nested callbacks contextual types

πŸ•— Version & Regression Information

  • This changed between versions 5.0.0-dev.20221216 and 5.0.0-dev.20221217

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

export interface Event<T> {
    callback: (response: T) => void;
    nested: {
        callback: (response: T) => void;
    }
}

export type CustomEvents = {
    a: Event<string>
    b: Event<number> // If only one candidate type exists it works
};

declare function emit<T extends keyof CustomEvents>(type: T, data: CustomEvents[T]): void

emit('a', {
    callback: (r) => {}, // r is string in 4.9 and 5.0
    nested: {
        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

@Andarist
Copy link
Contributor

Andarist commented Feb 3, 2023

Somehow the PR with const type parameters introduced this regression. cc @ahejlsberg

@Andarist
Copy link
Contributor

Andarist commented Feb 3, 2023

This happens within checkExpressionWithContextualType called from here

  1. Each of those properties goes through getContextualTypeForObjectLiteralElement and thus through getApparentTypeOfContextualType.
  2. Each of those receive CustomEvents[T] back but they both try to instantiate it
  3. 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
  4. 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>
  5. 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.

@ahejlsberg
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
5 participants