-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
Bug Report
π Search Terms
reverse mapped types, circular type, partially inferrable
β― Playground Link
Playground link with relevant code
π» Code
type AnyFunction = (...args: any[]) => any;
type InferNarrowest<T> = T extends any
? T extends AnyFunction
? T
: T extends object
? InferNarrowestObject<T>
: T
: never;
type InferNarrowestObject<T> = {
readonly [K in keyof T]: InferNarrowest<T[K]>;
};
type Config<TGlobal, TState = Prop<TGlobal, "states">> = {
states: {
[StateKey in keyof TState]: {
on?: {};
};
};
} & {
initial: keyof TState;
};
type Prop<T, K> = K extends keyof T ? T[K] : never;
const createMachine = <TConfig extends Config<TConfig>>(
_config: InferNarrowestObject<TConfig>
): void => {};
createMachine({
initial: "pending",
states: {
pending: {
on: {
done() {
return "noData";
},
},
},
},
});π Actual behavior
Inferred signature is:
const createMachine: <Config<{
initial: "pending";
states: unknown;
}, unknown>>(_config: InferNarrowestObject<Config<{
initial: "pending";
states: unknown;
}, unknown>>) => voidπ Expected behavior
states: unknown shouldn't be inferred here, the reverse mapped type should be able to infer an object literal type~. The type param should be inferred as something like:
const createMachine: <{
initial: "pending";
states: {
pending: {
on: {
done: () => "noData";
};
};
};
}>(_config: InferNarrowestObject<{
initial: "pending";
states: {
pending: {
on: {
done: () => "noData";
};
};
};
}>) => voidWe can simplify the repro a little bit, but it will then yield an error at a different position and I can't verify right now if the underlying issue is exactly the same in this case (although from what it looks like the root cause is super similar):
TS playground without conditional type applied at the argument position
Note that we can fix both playground by using an arrow function instead of a method (probably related to a possible "hidden" this type param that makes this context sensitive in the case of a method).
The first one can be fixed by adding a dummy property to the object containing a method (this makes the object partially inferrable):
TS playground with a dummy property added
Especially given that a dummy property fixes the problem it looks like a weird design limitations.
What I've learned when debugging this:
- methods + functions with arguments are context sensitive and in
checkFunctionExpressionOrObjectLiteralMethodthey returnanyFunctionType anyFunctionTypehasObjectFlags.NonInferrableTypeon it- this flag is "propagating" and thus is set on the "parent" object
uninstantiatedTypefor theonproperty of this argument gets computed to{}(so it's empty~)- and thus it doesn't pass
isPartiallyInferableTypecheck when resolving the structure of the reverse mapped type, since the object hasObjectFlags.NonInferrableTypeon it AND there are no other properties that would be treated as partially inferrable - since no structure is resolved for this mapped type the
unknownis returned for thestatesproperty - this in turn makes
initialproperty to error becausekeyof unknownisnever
while a workaround is "known" here (we've learned this hard way though)~ the whole thing still has some problems because we can't provide a param type for the arrow functions contained in on property because once we add any unannotated params to an arrow function it becomes context-sensitive and results in a similar problem:
TS playground with context-sensitive arrow function
Related to #40439