Skip to content
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

Type inference considers only type amendments (if any are present), ignoring original typings #59576

Closed
nicky1038 opened this issue Aug 9, 2024 · 4 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@nicky1038
Copy link

nicky1038 commented Aug 9, 2024

πŸ”Ž Search Terms

type inference amended types

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about type inference

⏯ Playground Link

Link

πŸ’» Code

interface GenericEventTarget<TEventName extends string, TEvent extends Event> {
	addEventListener: (type: TEventName, listener: (event: TEvent) => void) => void

        // somehow, if addEventListener is declared this way, there is no error
        // addEventListener(type: TEventName, listener: (event: TEvent) => void): void
}

const addListener = <TEventName extends string, TEvent extends Event>(
	source: GenericEventTarget<TEventName, TEvent>,
    eventName: TEventName,
): void => {
	source.addEventListener(eventName, () => {})
}

// type amendment
interface Window {
	addEventListener(type: 'customEvent', listener: (this: Window, event: Event) => unknown, options?: {}): void
}

// works: it means that type amendment doesn't replace original typings
window.addEventListener('message', () => {})
// doesn't work: type inference only considers type amendment but not original typings
addListener(window, 'message')
// explicit specification of type parameters makes it work
addListener<'message', Event>(window, 'message')

πŸ™ Actual behavior

TS-2345 error in the line addListener(window, 'message'): Argument of type '"message"' is not assignable to parameter of type '"customEvent"'.
It is wrong because Typescript infers TEventName as customEvent, considering only type amendment and ignoring original typings.

πŸ™‚ Expected behavior

No errors are expected, because original typings for Window should also be considered

Additional information about the issue

I have a generic utility function that subscribes to certain event of provided event source, it looks like one in the example.

I use this utility function including for subscribing to events of window. After vite added its own type amendment for Window interface, errors in my code occurred, because in the case there are any type amendments, Typescript doesn't consider original typings for Window interface during type inference.

@RyanCavanaugh
Copy link
Member

The original typings are considered, but the newly-added overload in this case is chosen when there's no other inference information available. I think you want to augment the underlying map instead here

interface WindowEventMap {
	customEvent: Event & { neat: string };
}
window.addEventListener("customEvent", x => {
	x.neat; // works
});

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Aug 9, 2024
@nicky1038
Copy link
Author

@RyanCavanaugh thank you for this suggestion! I'll offer using this way of adding new event type to Window interface in vite repo.
But could you please clarify why is no other inference information available? As shown in the example, the original typings are visible, so it seems like TEventName should also be inferred from them?

@RyanCavanaugh
Copy link
Member

It'd take a very long time to step through the inference process and figure out what all the candidates are, etc.. The example is reproducible without interface augmentation, so is therefore not related to it:

interface Thing {
	// Your definition (goes in first):
	addEventListener(type: 'customEvent', listener: (this: Window, event: Event) => unknown, options?: {}): void

	// lib.dom.d.ts definitions:
    addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
}
interface GlobalThing {
	addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
	addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
}

// Local version
declare const someThing: GlobalThing & Thing;
someThing.addEventListener('message', () => {})
addListener(someThing, 'message')
addListener<'message', Event>(someThing, 'message')

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

3 participants