Description
Bug Report
π Search Terms
defer, index access, index signature, nonnullable
π Version & Regression Information
- This somewhat changed between versions 4.7 and 4.8
β― Playground Link
Playground link with relevant code
π» Code
interface StateSchema {
states?: {
[key: string]: StateSchema;
};
}
declare class StateNode<TStateSchema extends StateSchema> {
schema: TStateSchema;
}
// worked in 4.7 but perhaps accidentally
type StateNodesConfigCurrent<TStateSchema extends StateSchema> = {
[K in keyof TStateSchema["states"]]: StateNode<TStateSchema["states"][K]>;
};
// this OTOH should work right now but it doesn't
type StateNodesConfigNew<TStateSchema extends StateSchema> = {
[K in keyof TStateSchema["states"]]: StateNode<
NonNullable<TStateSchema["states"]>[K]
>;
};
π Actual behavior
NonNullable
disappears when computing getConstraintFromIndexedAccess
. Essentially this type:
NonNullable<TStateSchema["states"]>[keyof TStateSchema["states"]]
gets simplified/distributed to
TStateSchema["states"][string] | TStateSchema["states"][number] | TStateSchema["states"][symbol]
As we might notice here - NonNullable
is gone from here because of the simplification rules:
// (T | U)[K] -> T[K] | U[K] (reading)
// (T | U)[K] -> T[K] & U[K] (writing)
// (T & U)[K] -> T[K] & U[K]
The third case applies here so we end up with something like this
TStateSchema["states"][string] & {}[string] | TStateSchema["states"][number] & {}[number] | TStateSchema["states"][symbol] & {}[symbol]
In here, those {}[string | number | symbol]
are just reduced to unknown
and T & unknown
gets reduced to T
. So that's how NonNullable
"disappears" here.
Note that even though T[number | symbol]
is not valid for this T
this indexed access is allowed based on this logic:
https://github.dev/microsoft/TypeScript/blob/488d0eebd0556fcc6e5f4cfc69c2ef7e5c2708ed/src/compiler/checker.ts#L16036-L16040
π Expected behavior
NonNullable
should be correctly retained/deferred in this case to preserve the correct constraint of the generic type.