microsoft / microsoft/TypeScript
[bug] A method return type of 'this' is incorrectly converted to 'any' in declaration output
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
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:
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the issue with TypeScript 3.3.0-dev.20181129 using the supplied IEventEmitter and makeEventEmitterClass example, then compare the emitted declaration output. Trace declaration generation for the inferred method return types; done means the generated methods preserve the IEventEmitter return type instead of any, with regression coverage for this case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 30/100