You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
RequiredProps<T>: Unexpected result when T = {} #21988
// Type-level filters to extract just the required or optional properties of a type// Defns from https://github.com/Microsoft/TypeScript/pull/21919#issuecomment-365491689typeRequiredPropNames<T>={[PinkeyofT]: undefinedextendsT[P] ? never : P}[keyofT];typeOptionalPropNames<T>={[PinkeyofT]: undefinedextendsT[P] ? P : never}[keyofT];typeRequiredProps<T>={[PinRequiredPropNames<T>]: T[P]};typeOptionalProps<T>={[PinOptionalPropNames<T>]: T[P]};// Some object types with different numbers of propstypeP2={a: string,b: number};// Two propstypeP1={a: string};// One proptypeP0={};// No props// Let's extract only the required properties of P0, P1, and P2typeP2Names=RequiredPropNames<P2>;// P2Names = "a" | "b" ✓😊typeP1Names=RequiredPropNames<P1>;// P1Names = "a" ✓😊typeP0Names=RequiredPropNames<P0>;// P0Names = any ?😕typeP2Props=RequiredProps<P2>;// P2Props = { a: string; b: number; } ✓😊typeP1Props=RequiredProps<P1>;// P1Props = { a: string; } ✓😊typeP0Props=RequiredProps<P0>;// P0Props = { [x: string]: any } ?😕
Expected behavior: P0Names = never and P0Props = {}
Actual behavior: P0Names = any and P0Props = {[x: string]: any}
Not 100% sure if this is a bug or not, but it's definitely counterintuitive. RequiredProps works for any object type except the empty one, when a string indexer suddenly appears. It certainly breaks the semantic expectations of RequiredProps.
Workaround:
The following version of RequiredPropNames achieves the expected behaviour by singling out the {} case:
typeRequiredPropNames<T>=keyofTextendsnever
? never
: {[PinkeyofT]: undefinedextendsT[P] ? never : P}[keyofT];
Index access notation T[K] is valid for all K extends keyof T. Given that never extends keyof T for all possible types T, then T[never] should always be a valid index access type. Its result should always be never, since the union of zero keys (never) maps to the union of zero values (never).
Wesley Wigham (@weswigham) I removed the constraint of T[string], which used to be any for unconstrained T. Maybe we have some more recent changes than that though?
Activity
falsandtru commentedon Feb 16, 2018
This means
{}[never]should returnnever.yortus commentedon Feb 17, 2018
falsandtru (@falsandtru) I agree. Actually I think
T[never]should returnneverfor any typeT.Index access notation
T[K]is valid for allK extends keyof T. Given thatnever extends keyof Tfor all possible typesT, thenT[never]should always be a valid index access type. Its result should always benever, since the union of zero keys (never) maps to the union of zero values (never).yortus commentedon Feb 17, 2018
In #11929 (the PR introducing indexed access types) it states:
...and in #8652 (the 'never' PR) it states that "
neveris a subtype of and assignable to every type."...and if n=0, then we effectively have an empty union / union of zero types, which is the type having zero values, which is
never.Well that's my reasoning anyway. #11929 doesn't mention
never, but I believe the reasoning here is consistent with the given type rules.weswigham commentedon Feb 18, 2018
cc Nathan Shively-Sanders (@sandersn) didn't we recently change how
T[any]behaved? Did we just leave outT[never]?yortus commentedon Feb 20, 2018
Closing in favour of #22042, which more simply addresses just the underlying issue.
sandersn commentedon Feb 20, 2018
Wesley Wigham (@weswigham) I removed the constraint of T[string], which used to be any for unconstrained T. Maybe we have some more recent changes than that though?