Skip to content

Commit 2c4cbd9

Browse files
Andaristsandersn
andauthored
Defer index types on remapping mapped types (#55140)
Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 3258d75 commit 2c4cbd9

File tree

6 files changed

+478
-247
lines changed

6 files changed

+478
-247
lines changed

src/compiler/checker.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,12 @@ const enum MappedTypeModifiers {
13041304
ExcludeOptional = 1 << 3,
13051305
}
13061306

1307+
const enum MappedTypeNameTypeKind {
1308+
None,
1309+
Filtering,
1310+
Remapping,
1311+
}
1312+
13071313
const enum ExpandingFlags {
13081314
None = 0,
13091315
Source = 1,
@@ -13741,7 +13747,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1374113747
const constraintType = getConstraintTypeFromMappedType(type);
1374213748
const mappedType = (type.target as MappedType) || type;
1374313749
const nameType = getNameTypeFromMappedType(mappedType);
13744-
const shouldLinkPropDeclarations = !nameType || isFilteringMappedType(mappedType);
13750+
const shouldLinkPropDeclarations = getMappedTypeNameTypeKind(mappedType) !== MappedTypeNameTypeKind.Remapping;
1374513751
const templateType = getTemplateTypeFromMappedType(mappedType);
1374613752
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
1374713753
const templateModifiers = getMappedTypeModifiers(type);
@@ -13923,9 +13929,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1392313929
return false;
1392413930
}
1392513931

13926-
function isFilteringMappedType(type: MappedType): boolean {
13932+
function getMappedTypeNameTypeKind(type: MappedType): MappedTypeNameTypeKind {
1392713933
const nameType = getNameTypeFromMappedType(type);
13928-
return !!nameType && isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type));
13934+
if (!nameType) {
13935+
return MappedTypeNameTypeKind.None;
13936+
}
13937+
return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping;
1392913938
}
1393013939

1393113940
function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
@@ -17700,7 +17709,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1770017709
function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) {
1770117710
return !!(type.flags & TypeFlags.InstantiableNonPrimitive ||
1770217711
isGenericTupleType(type) ||
17703-
isGenericMappedType(type) && !hasDistributiveNameType(type) ||
17712+
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) ||
1770417713
type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) ||
1770517714
type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType));
1770617715
}
@@ -18282,7 +18291,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1828218291
// K is generic and N is assignable to P, instantiate E using a mapper that substitutes the index type for P.
1828318292
// For example, for an index access { [P in K]: Box<T[P]> }[X], we construct the type Box<T[X]>.
1828418293
if (isGenericMappedType(objectType)) {
18285-
if (!getNameTypeFromMappedType(objectType) || isFilteringMappedType(objectType)) {
18294+
if (getMappedTypeNameTypeKind(objectType) !== MappedTypeNameTypeKind.Remapping) {
1828618295
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
1828718296
}
1828818297
}
@@ -40108,7 +40117,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4010840117
// Check if the index type is assignable to 'keyof T' for the object type.
4010940118
const objectType = (type as IndexedAccessType).objectType;
4011040119
const indexType = (type as IndexedAccessType).indexType;
40111-
if (isTypeAssignableTo(indexType, getIndexType(objectType, IndexFlags.None))) {
40120+
// skip index type deferral on remapping mapped types
40121+
const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping
40122+
? getIndexTypeForMappedType(objectType, IndexFlags.None)
40123+
: getIndexType(objectType, IndexFlags.None);
40124+
if (isTypeAssignableTo(indexType, objectIndexType)) {
4011240125
if (
4011340126
accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
4011440127
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly

tests/baselines/reference/mappedTypeAsClauses.errors.txt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mappedTypeAsClauses.ts(130,3): error TS2345: Argument of type '"a"' is not assignable to parameter of type '"b"'.
1+
mappedTypeAsClauses.ts(131,3): error TS2345: Argument of type '"a"' is not assignable to parameter of type '"b"'.
22

33

44
==== mappedTypeAsClauses.ts (1 errors) ====
@@ -30,7 +30,8 @@ mappedTypeAsClauses.ts(130,3): error TS2345: Argument of type '"a"' is not assig
3030
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
3131
type TD1 = DoubleProp<{ a: string, b: number }>; // { a1: string, a2: string, b1: number, b2: number }
3232
type TD2 = keyof TD1; // 'a1' | 'a2' | 'b1' | 'b2'
33-
type TD3<U> = keyof DoubleProp<U>; // `${keyof U & string}1` | `${keyof U & string}2`
33+
type TD3<U> = keyof DoubleProp<U>; // keyof DoubleProp<U>
34+
type TD4 = TD3<{ a: string, b: number }>; // 'a1' | 'a2' | 'b1' | 'b2'
3435

3536
// Repro from #40619
3637

@@ -155,4 +156,27 @@ mappedTypeAsClauses.ts(130,3): error TS2345: Argument of type '"a"' is not assig
155156
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
156157
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
157158
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };
159+
160+
// repro from https://github.com/microsoft/TypeScript/issues/55129
161+
type Fruit =
162+
| {
163+
name: "apple";
164+
color: "red";
165+
}
166+
| {
167+
name: "banana";
168+
color: "yellow";
169+
}
170+
| {
171+
name: "orange";
172+
color: "orange";
173+
};
174+
type Result1<T extends {name: string | number; color: string | number }> = {
175+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown
176+
};
177+
type Result2<T extends {name: string | number; color: string | number }> = keyof {
178+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown
179+
}
180+
type Test1 = keyof Result1<Fruit> // "apple:red" | "banana:yellow" | "orange:orange"
181+
type Test2 = Result2<Fruit> // "apple:red" | "banana:yellow" | "orange:orange"
158182

tests/baselines/reference/mappedTypeAsClauses.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ type TM1 = Methods<{ foo(): number, bar(x: string): boolean, baz: string | numbe
2929
type DoubleProp<T> = { [P in keyof T & string as `${P}1` | `${P}2`]: T[P] }
3030
type TD1 = DoubleProp<{ a: string, b: number }>; // { a1: string, a2: string, b1: number, b2: number }
3131
type TD2 = keyof TD1; // 'a1' | 'a2' | 'b1' | 'b2'
32-
type TD3<U> = keyof DoubleProp<U>; // `${keyof U & string}1` | `${keyof U & string}2`
32+
type TD3<U> = keyof DoubleProp<U>; // keyof DoubleProp<U>
33+
type TD4 = TD3<{ a: string, b: number }>; // 'a1' | 'a2' | 'b1' | 'b2'
3334

3435
// Repro from #40619
3536

@@ -152,6 +153,29 @@ type TN2<T> = keyof { [P in keyof T as 'a' extends P ? 'x' : 'y']: string };
152153
type TN3<T> = keyof { [P in keyof T as Exclude<Exclude<Exclude<P, 'c'>, 'b'>, 'a'>]: string };
153154
type TN4<T, U> = keyof { [K in keyof T as (K extends U ? T[K] : never) extends T[K] ? K : never]: string };
154155
type TN5<T, U> = keyof { [K in keyof T as keyof { [P in K as T[P] extends U ? K : never]: true }]: string };
156+
157+
// repro from https://github.com/microsoft/TypeScript/issues/55129
158+
type Fruit =
159+
| {
160+
name: "apple";
161+
color: "red";
162+
}
163+
| {
164+
name: "banana";
165+
color: "yellow";
166+
}
167+
| {
168+
name: "orange";
169+
color: "orange";
170+
};
171+
type Result1<T extends {name: string | number; color: string | number }> = {
172+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown
173+
};
174+
type Result2<T extends {name: string | number; color: string | number }> = keyof {
175+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown
176+
}
177+
type Test1 = keyof Result1<Fruit> // "apple:red" | "banana:yellow" | "orange:orange"
178+
type Test2 = Result2<Fruit> // "apple:red" | "banana:yellow" | "orange:orange"
155179

156180

157181
//// [mappedTypeAsClauses.js]
@@ -217,6 +241,10 @@ type TD1 = DoubleProp<{
217241
}>;
218242
type TD2 = keyof TD1;
219243
type TD3<U> = keyof DoubleProp<U>;
244+
type TD4 = TD3<{
245+
a: string;
246+
b: number;
247+
}>;
220248
type Lazyify<T> = {
221249
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
222250
};
@@ -337,3 +365,27 @@ type TN5<T, U> = keyof {
337365
[P in K as T[P] extends U ? K : never]: true;
338366
}]: string;
339367
};
368+
type Fruit = {
369+
name: "apple";
370+
color: "red";
371+
} | {
372+
name: "banana";
373+
color: "yellow";
374+
} | {
375+
name: "orange";
376+
color: "orange";
377+
};
378+
type Result1<T extends {
379+
name: string | number;
380+
color: string | number;
381+
}> = {
382+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown;
383+
};
384+
type Result2<T extends {
385+
name: string | number;
386+
color: string | number;
387+
}> = keyof {
388+
[Key in T as `${Key['name']}:${Key['color']}`]: unknown;
389+
};
390+
type Test1 = keyof Result1<Fruit>;
391+
type Test2 = Result2<Fruit>;

0 commit comments

Comments
 (0)