Repository: TypeScript
Issue: Liskov Substitution checks for overloads behave inconsistently
Labels: Bug,Help Wanted,Cursed?

# Bug Report

### 🔎 Search Terms

- liskov
- addEventListener
- overload

### 🕗 Version & Regression Information

- This is the behavior in every version I tried, and I reviewed the FAQ for entries about everything. I've checked the full FAQ.

### ⏯ Playground Link

[Playground link with relevant code](https://www.typescriptlang.org/play?ts=5.0.0-dev.20230222#code/JYOwLgpgTgZghgYwgAgGIHt0CE5QKIBuE4AsnAA7IDeAUMsggDbAIDWAXMgMICuAzmHQBbQsTABuGgF8aNJnD580mHPkYQhY5BAAekEABMlACQAqJADJ51m8NVkyaoSLEQoM2XNY1bd+o8hmlt62YPb0cAYGouAWwALE0AA8ptp6xAGsEACe6DDKnvhEpBQAfAAUdPTIYNnkEJymADRV9MwJINCc5WAAFvGcHqohYk3aBIMqXsVgZOQA2qYAugCUyAC8pchwINkt1cjo5GDA6CB8APycAEaY6jvIAD7IAIJRMWBxHdAA8sen532yBWnAI6GABkkjmc0HgSEC5isNjEpjgAHMAHJwTRzcLVABEMEwAFprrh8ZNCiNwFDZOowmcGsgMegDBAvmAfjAkiy2Vt1sgDOgEDxQgA6ACOPGg2QAyhB1AhBFAXoxGOUAORE9Ck3AalYAQkkNEZYqJ+EQvXKCo2WwVYsi0RmHMSUE1TBYrA1YxAPDVKxWxvpNQA7uhOLz2fFOdzI-zBcLRWJJdKoHKFRAlegVWrNQZgAR9UbZGAw2bs3hLdbGLbtIwHe9ndHXe7mGxvchff7A3SIGE+lAIEzIxyuUkhl5keB40KReKpTL5Yrlar1VqSWSoEXjQOh+WLQgrTbNnWG06xC7Om6NR72z6-YwA0G+8giTwoBHWVGBGOghYACIFtSYAzom86pumy7ZqueYFtushvlA+6Voe1a1vajofJe0Ctp6HZdo+PZAA)

### 💻 Code

```ts
interface FooBarEventMap {
  click: CustomEvent;
}

class FooBarElement extends HTMLElement {

}

interface FooBarElement extends HTMLElement {
  addEventListener<T extends keyof FooBarEventMap>(
    type: T,
    listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
    options?: boolean | AddEventListenerOptions,
  ): void;
}

interface HTMLElementTagNameMap {
    "foo-bar": FooBarElement;
}

let one: NodeListOf<Node> = document.querySelectorAll('foo-bar')!;

one.forEach(el => el.addEventListener('click', null));

let two: NodeListOf<Node> = document.querySelectorAll('div')!;

two.forEach(el => el.addEventListener('click', null));

let three: NodeListOf<FooBarElement> = document.querySelectorAll('foo-bar')!;

three.forEach(el => el.addEventListener('click', null));

let four: NodeListOf<HTMLDivElement> = document.querySelectorAll('div')!;

four.forEach(el => el.addEventListener('click', null));
```

### 🙁 Actual behavior

An error is reported for the assignment to `one`.

This is technically correct: `FooBarElement` is a subtype of `HTMLElement` which in turn is a subtype of `Node`. `FooBarElement.addEventListener()` does not accept all parameters that are accepted by `Node.addEventListener()`, making this a LSP violation.

However the same is true for `HTMLDivElement`. The assignment to `two` is accepted, but as evidenced by the assignment to `four`, an `HTMLDivElement` does not actually accept `null`.

When I duplicate the `addEventListener` overload to:

```
interface FooBarElement extends HTMLElement {
  addEventListener<T extends keyof FooBarEventMap>(
    type: T,
    listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
    options?: boolean | AddEventListenerOptions,
  ): void;
  addEventListener<T extends keyof FooBarEventMap>(
    type: T,
    listener: (this: FooBarElement, ev: FooBarEventMap[T]) => any,
    options?: boolean | AddEventListenerOptions,
  ): void;
}
```

… both overloads are 100% identical. Then the assignment to `one` is no longer reported as an error.

### 🙂 Expected behavior

I would not expect this inconsistent behavior:

- Either both HTMLDivElement and FooBarElement should report an error when assigning to a `NodeListOf<Node>`, or neither should.
- Adding an additional identical overload should not make the error go away.

### Additional context

The real world use case is consuming the [WoltLab/d.ts](https://github.com/WoltLab/d.ts/) repository with a tsconfig.json that has `strict: true` configured.

Within that repository there is an element with an incompatible overload in:

https://github.com/WoltLab/d.ts/blob/57f923f03e37885885326bbd622d15b554feb9b5/WoltLabSuite/Core/Element/woltlab-core-dialog.d.ts#L40

and it is registered in global.d.ts in:

https://github.com/WoltLab/d.ts/blob/57f923f03e37885885326bbd622d15b554feb9b5/global.d.ts#L122

Now when attempting to compile a project that has the repository as a dependency, the following error is reported:

```
node_modules/typescript/lib/lib.dom.d.ts:10535:87 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Node'.
  Type 'HTMLInputElement | HTMLElement | WoltlabCoreDialogElement | HTMLDialogElement | WoltlabCoreDialogControlElement | ... 66 more ... | HTMLVideoElement' is not assignable to type 'Node'.
    Type 'WoltlabCoreDialogElement' is not assignable to type 'Node'.
      Types of property 'addEventListener' are incompatible.
        Type '<T extends keyof WoltlabCoreDialogEventMap>(type: T, listener: (this: WoltlabCoreDialogElement, ev: WoltlabCoreDialogEventMap[T]) => any, options?: boolean | ... 1 more ... | undefined) => void' is not assignable to type '(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined) => void'.
          Types of parameters 'listener' and 'callback' are incompatible.
            Type 'EventListenerOrEventListenerObject | null' is not assignable to type '(this: WoltlabCoreDialogElement, ev: CustomEvent<any> | CustomEvent<ValidateCallback[]>) => any'.
              Type 'null' is not assignable to type '(this: WoltlabCoreDialogElement, ev: CustomEvent<any> | CustomEvent<ValidateCallback[]>) => any'.

10535     querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
```