Skip to content

Type inference failing for window.onbeforeunload #39057

Description

@amcasey

TypeScript Version: playground nightly (2020-06-12)

Search Terms: window event inference any

Code

window.onbeforeunload = event => event;

Expected behavior: event has type BeforeUnloadEvent

Actual behavior: event has type any (resulting in an erro)

Playground Link: https://www.typescriptlang.org/play/?ts=4.0.0-dev.20200611#code/O4SwdgJg9sB0VgEYFMBmUBOyCuYA2UAhhAAQC8JyAbsmAC7kB8lN9A3EA

Related Issues:

Activity

  1. amcasey commented on Jun 12, 2020

    @amcasey
    MemberAuthor

    I understand Wesley Wigham (@weswigham) may have been working on this kind of signature intersection.

  2. fatcerberus commented on Jun 13, 2020

    @fatcerberus

    I just have a question: Why is onbeforeunload defined as an intersection of function types? What does it even mean to intersect function types, practically speaking? The concept seems kind of mind-bending to me.

    edit: Nevermind, ignore me, I'm stupid. I forgot that intersection is how TS represents overloaded functions.

  3. amcasey commented on Jun 15, 2020

    @amcasey
    MemberAuthor

    Bruce Pascoe (@fatcerberus) The immediate answer to your question is that it's declared both here and here . I don't know why that's the case though - something about global this.

  4. weswigham commented on Jun 24, 2020

    @weswigham
    Member

    This can be fixed by making the lib.dom.d.ts types more precise, namely

    declare var onafterprint: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare var onbeforeprint: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare var onbeforeunload: ((this: Window & typeof globalThis, ev: BeforeUnloadEvent) => any) | null;
    declare var onhashchange: ((this: Window & typeof globalThis, ev: HashChangeEvent) => any) | null;
    declare var onlanguagechange: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare var onmessage: ((this: Window & typeof globalThis, ev: MessageEvent) => any) | null;
    declare var onmessageerror: ((this: Window & typeof globalThis, ev: MessageEvent) => any) | null;
    declare var onoffline: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare var ononline: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare var onpagehide: ((this: Window & typeof globalThis, ev: PageTransitionEvent) => any) | null;
    declare var onpageshow: ((this: Window & typeof globalThis, ev: PageTransitionEvent) => any) | null;
    declare var onpopstate: ((this: Window & typeof globalThis, ev: PopStateEvent) => any) | null;
    declare var onrejectionhandled: ((this: Window & typeof globalThis, ev: PromiseRejectionEvent) => any) | null;
    declare var onstorage: ((this: Window & typeof globalThis, ev: StorageEvent) => any) | null;
    declare var onunhandledrejection: ((this: Window & typeof globalThis, ev: PromiseRejectionEvent) => any) | null;
    declare var onunload: ((this: Window & typeof globalThis, ev: Event) => any) | null;
    declare function addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window & typeof globalThis, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
    declare function addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    declare function removeEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window & typeof globalThis, ev: WindowEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
    declare function removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;

    and

    interface WindowEventHandlers {
        onafterprint: ((this: this, ev: Event) => any) | null;
        onbeforeprint: ((this: this, ev: Event) => any) | null;
        onbeforeunload: ((this: this, ev: BeforeUnloadEvent) => any) | null;
        onhashchange: ((this: this, ev: HashChangeEvent) => any) | null;
        onlanguagechange: ((this: this, ev: Event) => any) | null;
        onmessage: ((this: this, ev: MessageEvent) => any) | null;
        onmessageerror: ((this: this, ev: MessageEvent) => any) | null;
        onoffline: ((this: this, ev: Event) => any) | null;
        ononline: ((this: this, ev: Event) => any) | null;
        onpagehide: ((this: this, ev: PageTransitionEvent) => any) | null;
        onpageshow: ((this: this, ev: PageTransitionEvent) => any) | null;
        onpopstate: ((this: this, ev: PopStateEvent) => any) | null;
        onrejectionhandled: ((this: this, ev: PromiseRejectionEvent) => any) | null;
        onstorage: ((this: this, ev: StorageEvent) => any) | null;
        onunhandledrejection: ((this: this, ev: PromiseRejectionEvent) => any) | null;
        onunload: ((this: this, ev: Event) => any) | null;
        addEventListener<K extends keyof WindowEventHandlersEventMap>(type: K, listener: (this: this, ev: WindowEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
        addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
        removeEventListener<K extends keyof WindowEventHandlersEventMap>(type: K, listener: (this: this, ev: WindowEventHandlersEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
        removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
    }

    By using this: this in WindowEventHandlers (which, in turn, becomes Window & typeof globalThis for the interface's use in the window object) and Window & typeof globalThis in the actual global variable definitions (rather than just Window, which is technically incomplete), we ensure the this types of both signatures are identical, which then allows us to apply contextual types as expected.

  5. weswigham commented on Jun 24, 2020

    @weswigham
    Member

    Nathan Shively-Sanders (@sandersn) how does making the above changes sound to you? This regression would seem to be a consequence of using declare var window: Window & typeof globalThis; for window, which is a year old (circa #32335).

    Context: Error here when upgrading to TS 3.9:

    I'm not so sure the error is from upgrading to 3.9 - I can actually see the same behavior in 3.7.2, the current version of TS on felixrieseberg/windows95. (Not that it may not still stand to be improved) Indeed, I don't think electron-forge make (what runs on CI) actually reports TS errors at build time. So the project just... has TS errors.

  6. sandersn commented on Jun 24, 2020

    @sandersn
    Member

    The first change sounds good. The second change adds this to an interface, so we'd need to check performance, but I don't think there will be THAT many instantiations of WindowEventHandlers.

    Let's do this in 4.1 since it's been around without a lot of people noticing.

  7. 3 remaining items

  8. RyanCavanaugh commented on Feb 15, 2023

    @RyanCavanaugh
    Member

    This works as expected now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Needs InvestigationThis issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestone

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions