π Search Terms
infer heritage extends
β
Viability Checklist
β Suggestion
The compiler's stepwise approach to type inference includes a check for the case where it's inferring type parameters, and the inference source and inference target reference the same type. If so, a call to inferFromTypeArguments() grabs the types directly from the inference target. A silly example:
interface Nothing<T> {}
declare function inferFromNothing<T>(arg: Nothing<T>): T;
declare const n: Nothing<boolean>;
inferFromNothing(n); // returns `boolean`
Even though there's absolutely zilch in the Nothing interface from which to infer T, the compiler identifies that the type of e and the type of arg are both references to the generic type Nothing, and infers the type parameter directly via inferFromTypeArguments().
The same does not apply to heritage types, though. Following on from the same example:
interface AlsoNothing<T> extends Nothing<T> {}
declare const a: AlsoNothing<boolean>;
inferFromNothing(a); // returns `unknown`
The type of a and the type of arg are no longer TypeReferences to the same type, so the checker doesn't call inferFromTypeArguments() and goes down its alternative inference strategies. In this instance, inferFromObjectTypes() is chosen, but this obviously has nothing to work with, and yields unknown.
These are nonsense examples, but it's not difficult to construct more complex generic object types where the type parameter doesn't appear as a straightforward property type or return type, which can cause inferFromObjectTypes() to fail. (See the motivating example below for where this originally arose.)
Would it be possible for the compiler to traverse the heritage elements of the inference source, and infer the type parameters directly via inferFromTypeArguments() if it finds the target among those elements?
π Motivating Example
The case that prompted this question was related to attempting to infer a type parameter from the EventEmitter class in @types/node.
A simplified reduction:
type EventMap<T> = Record<keyof T, unknown[]>;
declare function inferEventMap<T extends EventMap<T>>(emitter: EventEmitter<T>): T;
interface MyEventMap { event1: [boolean]; event2: [string] }
declare class EventEmitter<Events extends EventMap<Events>> {
on<T extends string | symbol>(eventName: T, listener: T extends keyof Events ? (...args: Events[T]) => void : (...args: any[]) => void): this;
emit<T extends string | symbol>(eventName: T, ...args: T extends keyof Events ? Events[T] : any[]): boolean;
// ...
eventNames(): Array<string | symbol> & Array<keyof Events>;
}
declare class DerivedEventEmitter<Events extends EventMap<Events>> extends EventEmitter<Events> {}
const baseEventEmitter = new EventEmitter<MyEventMap>();
inferEventMap(baseEventEmitter); // returns MyEventMap
const derivedEventEmitter = new DerivedEventEmitter<MyEventMap>();
inferEventMap(derivedEventEmitter); // returns { event1: any; event2: any }
This is one of those cases where inferFromObjectTypes() just doesn't have enough information to infer the type accurately, even though the type checker should have enough information to identify that the type of derivedEventEmitter is a heritage descendent of EventEmitter.
π» Use Cases
Hopefully covered above.
π Search Terms
infer heritage extends
β Viability Checklist
β Suggestion
The compiler's stepwise approach to type inference includes a check for the case where it's inferring type parameters, and the inference source and inference target reference the same type. If so, a call to
inferFromTypeArguments()grabs the types directly from the inference target. A silly example:Even though there's absolutely zilch in the
Nothinginterface from which to inferT, the compiler identifies that the type ofeand the type ofargare both references to the generic typeNothing, and infers the type parameter directly viainferFromTypeArguments().The same does not apply to heritage types, though. Following on from the same example:
The type of
aand the type ofargare no longer TypeReferences to the same type, so the checker doesn't callinferFromTypeArguments()and goes down its alternative inference strategies. In this instance,inferFromObjectTypes()is chosen, but this obviously has nothing to work with, and yieldsunknown.These are nonsense examples, but it's not difficult to construct more complex generic object types where the type parameter doesn't appear as a straightforward property type or return type, which can cause
inferFromObjectTypes()to fail. (See the motivating example below for where this originally arose.)Would it be possible for the compiler to traverse the heritage elements of the inference source, and infer the type parameters directly via
inferFromTypeArguments()if it finds the target among those elements?π Motivating Example
The case that prompted this question was related to attempting to infer a type parameter from the EventEmitter class in @types/node.
A simplified reduction:
This is one of those cases where
inferFromObjectTypes()just doesn't have enough information to infer the type accurately, even though the type checker should have enough information to identify that the type ofderivedEventEmitteris a heritage descendent ofEventEmitter.π» Use Cases
Hopefully covered above.