Skip to content

Commit ac23642

Browse files
committed
Reuse the logic in getGenericMappedTypeKeys
1 parent 585091e commit ac23642

10 files changed

Lines changed: 118 additions & 45 deletions

src/compiler/checker.ts

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13984,6 +13984,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1398413984
return isTypeAssignableTo(nameType, getTypeParameterFromMappedType(type)) ? MappedTypeNameTypeKind.Filtering : MappedTypeNameTypeKind.Remapping;
1398513985
}
1398613986

13987+
// generic mapped types that don't simplify or have a constraint still have a very simple set of keys - their nameType or constraintType.
13988+
// In many ways, this is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types
13989+
function getGenericMappedTypeKeys(type: MappedType) {
13990+
const nameType = getNameTypeFromMappedType(type);
13991+
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(type)) {
13992+
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
13993+
// missing from the `constraintType` which will otherwise be mapped in the object
13994+
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type));
13995+
const mappedKeys: Type[] = [];
13996+
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
13997+
modifiersType,
13998+
TypeFlags.StringOrNumberLiteralOrUnique,
13999+
/*stringsOnly*/ false,
14000+
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t))),
14001+
);
14002+
// We still need to include the non-apparent (and thus still generic) keys since when this gets used in comparisons the other side might include them
14003+
return getUnionType([...mappedKeys, nameType]);
14004+
}
14005+
return nameType || getConstraintTypeFromMappedType(type);
14006+
}
14007+
1398714008
function resolveStructuredTypeMembers(type: StructuredType): ResolvedType {
1398814009
if (!(type as ResolvedType).members) {
1398914010
if (type.flags & TypeFlags.Object) {
@@ -22352,34 +22373,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2235222373
return Ternary.True;
2235322374
}
2235422375
}
22355-
else if (isGenericMappedType(targetType)) {
22356-
// generic mapped types that don't simplify or have a constraint still have a very simple set of keys we can compare against
22357-
// - their nameType or constraintType.
22358-
// In many ways, this comparison is a deferred version of what `getIndexTypeForMappedType` does to actually resolve the keys for _non_-generic types
22359-
22360-
const nameType = getNameTypeFromMappedType(targetType);
22361-
const constraintType = getConstraintTypeFromMappedType(targetType);
22362-
let targetKeys;
22363-
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) {
22364-
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
22365-
// missing from the `constraintType` which will otherwise be mapped in the object
22366-
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
22367-
const mappedKeys: Type[] = [];
22368-
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
22369-
modifiersType,
22370-
TypeFlags.StringOrNumberLiteralOrUnique,
22371-
/*stringsOnly*/ false,
22372-
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))),
22373-
);
22374-
// We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side)
22375-
targetKeys = getUnionType([...mappedKeys, nameType]);
22376-
}
22377-
else {
22378-
targetKeys = nameType || constraintType;
22379-
}
22380-
if (isRelatedTo(source, targetKeys, RecursionFlags.Target, reportErrors) === Ternary.True) {
22381-
return Ternary.True;
22382-
}
22376+
else if (isGenericMappedType(targetType) && isRelatedTo(source, getGenericMappedTypeKeys(targetType), RecursionFlags.Target, reportErrors) === Ternary.True) {
22377+
return Ternary.True;
2238322378
}
2238422379
}
2238522380
}
@@ -22565,12 +22560,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2256522560
}
2256622561
}
2256722562
else if (sourceFlags & TypeFlags.Index) {
22568-
const indexType = source as IndexType;
22569-
let sourceKeys = keyofConstraintType;
22570-
if (isGenericMappedType(indexType.type)) {
22571-
sourceKeys = getMappedTypeNameTypeKind(indexType.type) !== MappedTypeNameTypeKind.None ? getNameTypeFromMappedType(indexType.type)! : getConstraintTypeFromMappedType(indexType.type);
22563+
const sourceType = (source as IndexType).type;
22564+
if (isGenericMappedType(sourceType) && isRelatedTo(getGenericMappedTypeKeys(sourceType), target, RecursionFlags.Source, reportErrors) === Ternary.True) {
22565+
return Ternary.True;
2257222566
}
22573-
if (result = isRelatedTo(sourceKeys, target, RecursionFlags.Source, reportErrors)) {
22567+
if (result = isRelatedTo(keyofConstraintType, target, RecursionFlags.Source, reportErrors)) {
2257422568
return result;
2257522569
}
2257622570
}
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ function g<T>() {
170170
>x = "str" : "str"
171171
>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; }
172172
>"str" : "str"
173+
174+
x = "whatever"; // error
175+
>x = "whatever" : "whatever"
176+
>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; }
177+
>"whatever" : "whatever"
173178
}
174179

175180
export {};

tests/baselines/reference/substitutionTypeForIndexedAccessType2.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ type Str<T extends string> = T
1010
type Bar<T> =
1111
T extends Foo
1212
? T['foo'] extends string
13-
// Type 'T["foo"]' does not satisfy the constraint 'string'.
14-
// Type 'string | undefined' is not assignable to type 'string'.
15-
// Type 'undefined' is not assignable to type 'string'.(2344)
1613
? Str<T['foo']>
1714
: never
1815
: never

tests/baselines/reference/substitutionTypeForIndexedAccessType2.symbols

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ type Bar<T> =
2424
? T['foo'] extends string
2525
>T : Symbol(T, Decl(substitutionTypeForIndexedAccessType2.ts, 6, 9))
2626

27-
// Type 'T["foo"]' does not satisfy the constraint 'string'.
28-
// Type 'string | undefined' is not assignable to type 'string'.
29-
// Type 'undefined' is not assignable to type 'string'.(2344)
3027
? Str<T['foo']>
3128
>Str : Symbol(Str, Decl(substitutionTypeForIndexedAccessType2.ts, 2, 1))
3229
>T : Symbol(T, Decl(substitutionTypeForIndexedAccessType2.ts, 6, 9))

tests/baselines/reference/substitutionTypeForIndexedAccessType2.types

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ type Bar<T> =
1414

1515
T extends Foo
1616
? T['foo'] extends string
17-
// Type 'T["foo"]' does not satisfy the constraint 'string'.
18-
// Type 'string | undefined' is not assignable to type 'string'.
19-
// Type 'undefined' is not assignable to type 'string'.(2344)
2017
? Str<T['foo']>
2118
: never
2219
: never

tests/cases/compiler/keyRemappingKeyofResult.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function g<T>() {
6767
let x: Oops;
6868
x = sym;
6969
x = "str";
70+
x = "whatever"; // error
7071
}
7172

7273
export {};

tests/cases/compiler/substitutionTypeForIndexedAccessType2.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ type Str<T extends string> = T
77
type Bar<T> =
88
T extends Foo
99
? T['foo'] extends string
10-
// Type 'T["foo"]' does not satisfy the constraint 'string'.
11-
// Type 'string | undefined' is not assignable to type 'string'.
12-
// Type 'undefined' is not assignable to type 'string'.(2344)
1310
? Str<T['foo']>
1411
: never
1512
: never

0 commit comments

Comments
 (0)