microsoft / microsoft/TypeScript

[bug] A method return type of 'this' is incorrectly converted to 'any' in declaration output

Offen
#29,122 3 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bug Domain: Declaration Emit
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.4k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

TypeScript Version: ^3.3.0-dev.20181129

Code

import * as events from 'events'

// EventArgs is used to create the event payload arguments for methods like
// EventEmitter.emit. This makes it possible to require a payload argument if
// the type is not `undefined`, or to not require an event payload argument if
// the type is `undefined`.
type EventArgs<
  EventTypes,
  K extends keyof EventTypes
> = EventTypes[K] extends undefined ? [] : [EventTypes[K]]

// our own version of EventEmitter with type-safe events (because the
// @types/node definition makes all events type `any`).
export interface IEventEmitter<EventTypes> {
  new (): IEventEmitter<EventTypes>

  addListener<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this

  on<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this

  once<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this

  removeListener<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this

  removeAllListeners<EventName extends keyof EventTypes>(
    event?: EventName,
  ): this

  emit<EventName extends keyof EventTypes>(
    event: EventName,
    ...args: EventArgs<EventTypes, EventName>
  ): boolean

  eventNames<EventName extends keyof EventTypes>(): Array<EventName>
  setMaxListeners(n: number): this
  getMaxListeners(): number

  listeners<EventName extends keyof EventTypes>(
    event: EventName,
  ): (...args: EventArgs<EventTypes, EventName>) => void[]

  listenerCount<EventName extends keyof EventTypes>(type: EventName): number

  prependListener<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this

  prependOnceListener<EventName extends keyof EventTypes>(
    event: EventName,
    listener: (...args: EventArgs<EventTypes, EventName>) => void,
  ): this
}

// This creates an EventEmitter class that uses our type definition. The
// implementation still comes from Node's EventEmitter.
//
// eslint-disable-next-line typescript/explicit-function-return-type
export function makeEventEmitterClass<EventTypes>() {
  // merge the IEventEmitter interface and EventEmitter as IEventEmitter
  // implementation into one.
  // XXX Is there a better way?
  const EventEmitter = class
    extends ((events.EventEmitter as unknown) as IEventEmitter<EventTypes>) {}
    // implements IEventEmitter<EventTypes> {}

  return EventEmitter
}

Expected behavior:

Declaration file should not use any for method return types that return this.

I expect output like the following:

declare type EventArgs<EventTypes, K extends keyof EventTypes> = EventTypes[K] extends undefined ? [] : [EventTypes[K]];
export interface IEventEmitter<EventTypes> {
    new (): IEventEmitter<EventTypes>;
    addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeAllListeners<EventName extends keyof EventTypes>(event?: EventName): IEventEmitter<EventTypes>;
    emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
    eventNames<EventName extends keyof EventTypes>(): Array<EventName>;
    setMaxListeners(n: number): IEventEmitter<EventTypes>;
    getMaxListeners(): number;
    listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
    listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
    prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
}
export declare function makeEventEmitterClass<EventTypes>(): {
    new (): {
        new (): IEventEmitter<EventTypes>;
        addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
        on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
        once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
        removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
        removeAllListeners<EventName extends keyof EventTypes>(event?: EventName | undefined): IEventEmitter<EventTypes>;
        emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
        eventNames<EventName extends keyof EventTypes>(): EventName[];
        setMaxListeners(n: number): IEventEmitter<EventTypes>;
        getMaxListeners(): number;
        listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
        listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
        prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
        prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    };
    addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeAllListeners<EventName extends keyof EventTypes>(event?: EventName | undefined): IEventEmitter<EventTypes>;
    emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
    eventNames<EventName extends keyof EventTypes>(): EventName[];
    setMaxListeners(n: number): IEventEmitter<EventTypes>;
    getMaxListeners(): number;
    listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
    listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
    prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
};
export {};
//# sourceMappingURL=index.d.ts.map

Actual behavior:

Example of wrong output:

declare type EventArgs<EventTypes, K extends keyof EventTypes> = EventTypes[K] extends undefined ? [] : [EventTypes[K]];
export interface IEventEmitter<EventTypes> {
    new (): IEventEmitter<EventTypes>;
    addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
    on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
    once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
    removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
    removeAllListeners<EventName extends keyof EventTypes>(event?: EventName): this;
    emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
    eventNames<EventName extends keyof EventTypes>(): Array<EventName>;
    setMaxListeners(n: number): this;
    getMaxListeners(): number;
    listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
    listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
    prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
    prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): this;
}
export declare function makeEventEmitterClass<EventTypes>(): {
    new (): {
        new (): IEventEmitter<EventTypes>;
// RIGHT HERE, the next lines have 'any'
        addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
        on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
        once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
        removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
        removeAllListeners<EventName extends keyof EventTypes>(event?: EventName | undefined): any;
        emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
        eventNames<EventName extends keyof EventTypes>(): EventName[];
        setMaxListeners(n: number): any;
        getMaxListeners(): number;
        listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
        listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
        prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
        prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): any;
    };
    addListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    on<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    once<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    removeAllListeners<EventName extends keyof EventTypes>(event?: EventName | undefined): IEventEmitter<EventTypes>;
    emit<EventName extends keyof EventTypes>(event: EventName, ...args: EventArgs<EventTypes, EventName>): boolean;
    eventNames<EventName extends keyof EventTypes>(): EventName[];
    setMaxListeners(n: number): IEventEmitter<EventTypes>;
    getMaxListeners(): number;
    listeners<EventName extends keyof EventTypes>(event: EventName): (...args: EventArgs<EventTypes, EventName>) => void[];
    listenerCount<EventName extends keyof EventTypes>(type: EventName): number;
    prependListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
    prependOnceListener<EventName extends keyof EventTypes>(event: EventName, listener: (...args: EventArgs<EventTypes, EventName>) => void): IEventEmitter<EventTypes>;
};
export {};
//# sourceMappingURL=index.d.ts.map

This caused chaining method calls in my app not to work.

Playground Link: N/A

Related Issues:

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Reproduziere das Problem mit TypeScript 3.3.0-dev.20181129 anhand des bereitgestellten IEventEmitter- und makeEventEmitterClass-Beispiels und vergleiche anschließend die erzeugte Declaration-Ausgabe. Verfolge die Declaration-Generierung für die inferierten Rückgabetypen der Methoden; als erledigt gilt die Aufgabe, wenn die generierten Methoden den Rückgabetyp IEventEmitter statt any beibehalten und eine Regression-Testabdeckung für diesen Fall vorhanden ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Klar beschrieben
Anfängerfreundlichkeit
30/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.