Skip to content

Commit a586c34

Browse files
Fixed crash related to index type deferral on generic mapped types with name types (#60528)
Co-authored-by: Ryan Cavanaugh <[email protected]>
1 parent 7affa9e commit a586c34

22 files changed

+723
-116
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15332,6 +15332,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1533215332
undefined;
1533315333
}
1533415334
if (t.flags & TypeFlags.Index) {
15335+
if (isGenericMappedType((t as IndexType).type)) {
15336+
const mappedType = (t as IndexType).type as MappedType;
15337+
if (getNameTypeFromMappedType(mappedType) && !isMappedTypeWithKeyofConstraintDeclaration(mappedType)) {
15338+
return getBaseConstraint(getIndexTypeForMappedType(mappedType, IndexFlags.None));
15339+
}
15340+
}
1533515341
return stringNumberSymbolType;
1533615342
}
1533715343
if (t.flags & TypeFlags.TemplateLiteral) {
@@ -18824,7 +18830,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1882418830
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic.
1882518831
if (isGenericIndexType(constraintType)) {
1882618832
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
18827-
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer
18833+
// We have a generic index and a homomorphic mapping and a key remapping - we need to defer
1882818834
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type.
1882918835
return getIndexTypeForGenericType(type, indexFlags);
1883018836
}
@@ -18854,25 +18860,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1885418860
}
1885518861
}
1885618862

18857-
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N<P>]: X }, to simply N<K>. This however presumes
18858-
// that N distributes over union types, i.e. that N<A | B | C> is equivalent to N<A> | N<B> | N<C>. Specifically, we only
18859-
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable
18860-
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because
18861-
// they're the same type regardless of what's being distributed over.
18862-
function hasDistributiveNameType(mappedType: MappedType) {
18863-
const typeVariable = getTypeParameterFromMappedType(mappedType);
18864-
return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable);
18865-
function isDistributive(type: Type): boolean {
18866-
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true :
18867-
type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable :
18868-
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) :
18869-
type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) :
18870-
type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint) :
18871-
type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) :
18872-
false;
18873-
}
18874-
}
18875-
1887618863
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) {
1887718864
if (isPrivateIdentifier(name)) {
1887818865
return neverType;
@@ -18924,7 +18911,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1892418911
function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) {
1892518912
return !!(type.flags & TypeFlags.InstantiableNonPrimitive ||
1892618913
isGenericTupleType(type) ||
18927-
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) ||
18914+
isGenericMappedType(type) && getNameTypeFromMappedType(type) ||
1892818915
type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) ||
1892918916
type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType));
1893018917
}
@@ -19445,6 +19432,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1944519432
function getSimplifiedType(type: Type, writing: boolean): Type {
1944619433
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) :
1944719434
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) :
19435+
type.flags & TypeFlags.Index ? getSimplifiedIndexType(type as IndexType) :
1944819436
type;
1944919437
}
1945019438

@@ -19544,6 +19532,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1954419532
return type;
1954519533
}
1954619534

19535+
function getSimplifiedIndexType(type: IndexType) {
19536+
if (isGenericMappedType(type.type) && getNameTypeFromMappedType(type.type) && !isMappedTypeWithKeyofConstraintDeclaration(type.type)) {
19537+
return getIndexTypeForMappedType(type.type, IndexFlags.None);
19538+
}
19539+
return type;
19540+
}
19541+
1954719542
/**
1954819543
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
1954919544
*/
@@ -42839,12 +42834,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4283942834
// Check if the index type is assignable to 'keyof T' for the object type.
4284042835
const objectType = (type as IndexedAccessType).objectType;
4284142836
const indexType = (type as IndexedAccessType).indexType;
42842-
// skip index type deferral on remapping mapped types
42843-
const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping
42844-
? getIndexTypeForMappedType(objectType, IndexFlags.None)
42845-
: getIndexType(objectType, IndexFlags.None);
4284642837
const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType);
42847-
if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) {
42838+
if (everyType(indexType, t => isTypeAssignableTo(t, getIndexType(objectType, IndexFlags.None)) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) {
4284842839
if (
4284942840
accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
4285042841
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6394,7 +6394,7 @@ export const enum TypeFlags {
63946394
/** @internal */
63956395
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
63966396
/** @internal */
6397-
Simplifiable = IndexedAccess | Conditional,
6397+
Simplifiable = IndexedAccess | Conditional | Index,
63986398
/** @internal */
63996399
Singleton = Any | Unknown | String | Number | Boolean | BigInt | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
64006400
// 'Narrowable' types are types where narrowing actually narrows.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable to type 'keyof Remapped'.
2+
Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'.
3+
4+
5+
==== keyRemappingKeyofResult.ts (1 errors) ====
6+
const sym = Symbol("")
7+
type Orig = { [k: string]: any, str: any, [sym]: any }
8+
9+
type Okay = Exclude<keyof Orig, never>
10+
// type Okay = string | number | typeof sym
11+
12+
type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
13+
/* type Remapped = {
14+
str: any;
15+
[sym]: any;
16+
} */
17+
// no string index signature, right?
18+
19+
type Oops = Exclude<keyof Remapped, never>
20+
declare let x: Oops;
21+
x = sym;
22+
x = "str";
23+
// type Oops = typeof sym <-- what happened to "str"?
24+
25+
// equivalently, with an unresolved generic (no `exclude` shenanigans, since conditions won't execute):
26+
function f<T>() {
27+
type Orig = { [k: string]: any, str: any, [sym]: any } & T;
28+
29+
type Okay = keyof Orig;
30+
let a: Okay;
31+
a = "str";
32+
a = sym;
33+
a = "whatever";
34+
// type Okay = string | number | typeof sym
35+
36+
type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
37+
/* type Remapped = {
38+
str: any;
39+
[sym]: any;
40+
} */
41+
// no string index signature, right?
42+
43+
type Oops = keyof Remapped;
44+
let x: Oops;
45+
x = sym;
46+
x = "str";
47+
}
48+
49+
// and another generic case with a _distributive_ mapping, to trigger a different branch in `getIndexType`
50+
function g<T>() {
51+
type Orig = { [k: string]: any, str: any, [sym]: any } & T;
52+
53+
type Okay = keyof Orig;
54+
let a: Okay;
55+
a = "str";
56+
a = sym;
57+
a = "whatever";
58+
// type Okay = string | number | typeof sym
59+
60+
type NonIndex<T extends PropertyKey> = {} extends Record<T, any> ? never : T;
61+
type DistributiveNonIndex<T extends PropertyKey> = T extends unknown ? NonIndex<T> : never;
62+
63+
type Remapped = { [K in keyof Orig as DistributiveNonIndex<K>]: any }
64+
/* type Remapped = {
65+
str: any;
66+
[sym]: any;
67+
} */
68+
// no string index signature, right?
69+
70+
type Oops = keyof Remapped;
71+
let x: Oops;
72+
x = sym;
73+
x = "str";
74+
x = "whatever"; // error
75+
~
76+
!!! error TS2322: Type 'string' is not assignable to type 'keyof Remapped'.
77+
!!! error TS2322: Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'.
78+
}
79+
80+
export {};

tests/baselines/reference/keyRemappingKeyofResult.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function g<T>() {
6969
let x: Oops;
7070
x = sym;
7171
x = "str";
72+
x = "whatever"; // error
7273
}
7374

7475
export {};
@@ -97,5 +98,6 @@ function g() {
9798
let x;
9899
x = sym;
99100
x = "str";
101+
x = "whatever"; // error
100102
}
101103
export {};

tests/baselines/reference/keyRemappingKeyofResult.symbols

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ function g<T>() {
190190

191191
x = "str";
192192
>x : Symbol(x, Decl(keyRemappingKeyofResult.ts, 65, 7))
193+
194+
x = "whatever"; // error
195+
>x : Symbol(x, Decl(keyRemappingKeyofResult.ts, 65, 7))
193196
}
194197

195198
export {};

tests/baselines/reference/keyRemappingKeyofResult.types

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ type Orig = { [k: string]: any, str: any, [sym]: any }
1717
>k : string
1818
> : ^^^^^^
1919
>str : any
20+
> : ^^^
2021
>[sym] : any
22+
> : ^^^
2123
>sym : unique symbol
2224
> : ^^^^^^^^^^^^^
2325

@@ -74,7 +76,9 @@ function f<T>() {
7476
>k : string
7577
> : ^^^^^^
7678
>str : any
79+
> : ^^^
7780
>[sym] : any
81+
> : ^^^
7882
>sym : unique symbol
7983
> : ^^^^^^^^^^^^^
8084

@@ -158,7 +162,9 @@ function g<T>() {
158162
>k : string
159163
> : ^^^^^^
160164
>str : any
165+
> : ^^^
161166
>[sym] : any
167+
> : ^^^
162168
>sym : unique symbol
163169
> : ^^^^^^^^^^^^^
164170

@@ -237,6 +243,14 @@ function g<T>() {
237243
> : ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
238244
>"str" : "str"
239245
> : ^^^^^
246+
247+
x = "whatever"; // error
248+
>x = "whatever" : "whatever"
249+
> : ^^^^^^^^^^
250+
>x : keyof { [K in keyof ({ [k: string]: any; str: any; [sym]: any; } & T) as K extends unknown ? {} extends Record<K, any> ? never : K : never]: any; }
251+
> : ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
252+
>"whatever" : "whatever"
253+
> : ^^^^^^^^^^
240254
}
241255

242256
export {};
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//// [tests/cases/compiler/keyRemappingKeyofResult2.ts] ////
2+
3+
=== keyRemappingKeyofResult2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/56239
5+
6+
type Values<T> = T[keyof T];
7+
>Values : Symbol(Values, Decl(keyRemappingKeyofResult2.ts, 0, 0))
8+
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12))
9+
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12))
10+
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12))
11+
12+
type ProvidedActor = {
13+
>ProvidedActor : Symbol(ProvidedActor, Decl(keyRemappingKeyofResult2.ts, 2, 28))
14+
15+
src: string;
16+
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 4, 22))
17+
18+
logic: unknown;
19+
>logic : Symbol(logic, Decl(keyRemappingKeyofResult2.ts, 5, 14))
20+
21+
};
22+
23+
interface StateMachineConfig<TActors extends ProvidedActor> {
24+
>StateMachineConfig : Symbol(StateMachineConfig, Decl(keyRemappingKeyofResult2.ts, 7, 2))
25+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 9, 29))
26+
>ProvidedActor : Symbol(ProvidedActor, Decl(keyRemappingKeyofResult2.ts, 2, 28))
27+
28+
invoke: {
29+
>invoke : Symbol(StateMachineConfig.invoke, Decl(keyRemappingKeyofResult2.ts, 9, 61))
30+
31+
src: TActors["src"];
32+
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 10, 11))
33+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 9, 29))
34+
35+
};
36+
}
37+
38+
declare function setup<TActors extends Record<string, unknown>>(_: {
39+
>setup : Symbol(setup, Decl(keyRemappingKeyofResult2.ts, 13, 1))
40+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23))
41+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
42+
>_ : Symbol(_, Decl(keyRemappingKeyofResult2.ts, 15, 64))
43+
44+
actors: {
45+
>actors : Symbol(actors, Decl(keyRemappingKeyofResult2.ts, 15, 68))
46+
47+
[K in keyof TActors]: TActors[K];
48+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 17, 5))
49+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23))
50+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23))
51+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 17, 5))
52+
53+
};
54+
}): {
55+
createMachine: (
56+
>createMachine : Symbol(createMachine, Decl(keyRemappingKeyofResult2.ts, 19, 5))
57+
58+
config: StateMachineConfig<
59+
>config : Symbol(config, Decl(keyRemappingKeyofResult2.ts, 20, 18))
60+
>StateMachineConfig : Symbol(StateMachineConfig, Decl(keyRemappingKeyofResult2.ts, 7, 2))
61+
62+
Values<{
63+
>Values : Symbol(Values, Decl(keyRemappingKeyofResult2.ts, 0, 0))
64+
65+
[K in keyof TActors as K & string]: {
66+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9))
67+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23))
68+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9))
69+
70+
src: K;
71+
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 23, 45))
72+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9))
73+
74+
logic: TActors[K];
75+
>logic : Symbol(logic, Decl(keyRemappingKeyofResult2.ts, 24, 17))
76+
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23))
77+
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9))
78+
79+
};
80+
}>
81+
>,
82+
) => void;
83+
};
84+

0 commit comments

Comments
 (0)