Skip to content

Commit 4f5bbd0

Browse files
authored
Allow type comparison when target is generic mapped type with key remapping (#45700)
* Add case for type related to for generic mapped type with as clause target * Clean up code and add baselines * cleanup
1 parent 617251f commit 4f5bbd0

6 files changed

+350
-15
lines changed

Diff for: src/compiler/checker.ts

+36-15
Original file line numberDiff line numberDiff line change
@@ -18625,39 +18625,60 @@ namespace ts {
1862518625
originalErrorInfo = undefined;
1862618626
}
1862718627
}
18628-
else if (isGenericMappedType(target) && !target.declaration.nameType) {
18629-
// A source type T is related to a target type { [P in X]: T[P] }
18630-
const template = getTemplateTypeFromMappedType(target);
18628+
else if (isGenericMappedType(target)) {
18629+
// Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`.
18630+
const keysRemapped = !!target.declaration.nameType;
18631+
const templateType = getTemplateTypeFromMappedType(target);
1863118632
const modifiers = getMappedTypeModifiers(target);
1863218633
if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) {
18633-
if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source &&
18634-
(template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
18634+
// If the mapped type has shape `{ [P in Q]: T[P] }`,
18635+
// source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`.
18636+
if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source &&
18637+
(templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) {
1863518638
return Ternary.True;
1863618639
}
1863718640
if (!isGenericMappedType(source)) {
18638-
const targetConstraint = getConstraintTypeFromMappedType(target);
18641+
// If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`.
18642+
// If target has shape `{ [P in Q]: T }`, then its keys have type `Q`.
18643+
const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target);
18644+
// Type of the keys of source type `S`, i.e. `keyof S`.
1863918645
const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true);
1864018646
const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional;
18641-
const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined;
18642-
// A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X.
18643-
// A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X.
18647+
const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined;
18648+
// A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`.
18649+
// A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T.
18650+
// A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`.
18651+
// A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`.
1864418652
if (includeOptional
1864518653
? !(filteredByApplicability!.flags & TypeFlags.Never)
18646-
: isRelatedTo(targetConstraint, sourceKeys)) {
18647-
const templateType = getTemplateTypeFromMappedType(target);
18654+
: isRelatedTo(targetKeys, sourceKeys)) {
1864818655
const typeParameter = getTypeParameterFromMappedType(target);
1864918656

18650-
// Fastpath: When the template has the form Obj[P] where P is the mapped type parameter, directly compare `source` with `Obj`
18651-
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `source[P]`
18657+
// Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj`
18658+
// to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`.
1865218659
const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable);
18653-
if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
18660+
if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) {
1865418661
if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) {
1865518662
return result;
1865618663
}
1865718664
}
1865818665
else {
18659-
const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter;
18666+
// We need to compare the type of a property on the source type `S` to the type of the same property on the target type,
18667+
// so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison.
18668+
18669+
// If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`.
18670+
// If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`,
18671+
// but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`.
18672+
// If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`.
18673+
// If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`,
18674+
// but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`.
18675+
const indexingType = keysRemapped
18676+
? (filteredByApplicability || targetKeys)
18677+
: filteredByApplicability
18678+
? getIntersectionType([filteredByApplicability, typeParameter])
18679+
: typeParameter;
1866018680
const indexedAccessType = getIndexedAccessType(source, indexingType);
18681+
// Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type.
1866118682
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
1866218683
return result;
1866318684
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(12,9): error TS2322: Type 'T' is not assignable to type 'Modify<T>'.
2+
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(23,9): error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
3+
tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt<T>'.
4+
5+
6+
==== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts (3 errors) ====
7+
// From original issue #45212:
8+
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
9+
type H<T> = T[keyof Methods<T>]; // Ok
10+
11+
// `Filter<T>` only filters out some keys of `T`.
12+
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
13+
// `Modify<T>` might modify some keys of `T`.
14+
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };
15+
16+
function fun<T>(val: T) {
17+
let x: Filter<T> = val; // Ok
18+
let y: Modify<T> = val; // Error
19+
~
20+
!!! error TS2322: Type 'T' is not assignable to type 'Modify<T>'.
21+
}
22+
23+
type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
24+
type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
25+
type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
26+
type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };
27+
28+
function fun2<T>(val: T) {
29+
let x: FilterInclOpt<T> = val; // Ok
30+
let y: ModifyInclOpt<T> = val; // Ok
31+
let z: FilterExclOpt<T> = val; // Error
32+
~
33+
!!! error TS2322: Type 'T' is not assignable to type 'FilterExclOpt<T>'.
34+
let w: ModifyExclOpt<T> = val; // Error
35+
~
36+
!!! error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt<T>'.
37+
}
38+
39+
40+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [mappedTypeAsClauseRelationships.ts]
2+
// From original issue #45212:
3+
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
4+
type H<T> = T[keyof Methods<T>]; // Ok
5+
6+
// `Filter<T>` only filters out some keys of `T`.
7+
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
8+
// `Modify<T>` might modify some keys of `T`.
9+
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };
10+
11+
function fun<T>(val: T) {
12+
let x: Filter<T> = val; // Ok
13+
let y: Modify<T> = val; // Error
14+
}
15+
16+
type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
17+
type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
18+
type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
19+
type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };
20+
21+
function fun2<T>(val: T) {
22+
let x: FilterInclOpt<T> = val; // Ok
23+
let y: ModifyInclOpt<T> = val; // Ok
24+
let z: FilterExclOpt<T> = val; // Error
25+
let w: ModifyExclOpt<T> = val; // Error
26+
}
27+
28+
29+
30+
31+
//// [mappedTypeAsClauseRelationships.js]
32+
function fun(val) {
33+
var x = val; // Ok
34+
var y = val; // Error
35+
}
36+
function fun2(val) {
37+
var x = val; // Ok
38+
var y = val; // Ok
39+
var z = val; // Error
40+
var w = val; // Error
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
=== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts ===
2+
// From original issue #45212:
3+
type Methods<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
4+
>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0))
5+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
6+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
7+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
8+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
9+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
10+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
11+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
12+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13))
13+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21))
14+
15+
type H<T> = T[keyof Methods<T>]; // Ok
16+
>H : Symbol(H, Decl(mappedTypeAsClauseRelationships.ts, 1, 80))
17+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))
18+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))
19+
>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0))
20+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7))
21+
22+
// `Filter<T>` only filters out some keys of `T`.
23+
type Filter<T> = { [P in keyof T as T[P] extends Function ? P : never]: T[P] };
24+
>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32))
25+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
26+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
27+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
28+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
29+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
30+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
31+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
32+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12))
33+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20))
34+
35+
// `Modify<T>` might modify some keys of `T`.
36+
type Modify<T> = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] };
37+
>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79))
38+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
39+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
40+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
41+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
42+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
43+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
44+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12))
45+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20))
46+
47+
function fun<T>(val: T) {
48+
>fun : Symbol(fun, Decl(mappedTypeAsClauseRelationships.ts, 7, 77))
49+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
50+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))
51+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
52+
53+
let x: Filter<T> = val; // Ok
54+
>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 10, 7))
55+
>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32))
56+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
57+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))
58+
59+
let y: Modify<T> = val; // Error
60+
>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 11, 7))
61+
>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79))
62+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13))
63+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16))
64+
}
65+
66+
type FilterInclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] };
67+
>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1))
68+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
69+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
70+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
71+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
72+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
73+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
74+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
75+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19))
76+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27))
77+
78+
type ModifyInclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] };
79+
>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88))
80+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
81+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
82+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
83+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
84+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
85+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19))
86+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27))
87+
88+
type FilterExclOpt<T> = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] };
89+
>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91))
90+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
91+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
92+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
93+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
94+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
95+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
96+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
97+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19))
98+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27))
99+
100+
type ModifyExclOpt<T> = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] };
101+
>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88))
102+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
103+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
104+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
105+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
106+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
107+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19))
108+
>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27))
109+
110+
function fun2<T>(val: T) {
111+
>fun2 : Symbol(fun2, Decl(mappedTypeAsClauseRelationships.ts, 17, 91))
112+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
113+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
114+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
115+
116+
let x: FilterInclOpt<T> = val; // Ok
117+
>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 20, 7))
118+
>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1))
119+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
120+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
121+
122+
let y: ModifyInclOpt<T> = val; // Ok
123+
>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 21, 7))
124+
>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88))
125+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
126+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
127+
128+
let z: FilterExclOpt<T> = val; // Error
129+
>z : Symbol(z, Decl(mappedTypeAsClauseRelationships.ts, 22, 7))
130+
>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91))
131+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
132+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
133+
134+
let w: ModifyExclOpt<T> = val; // Error
135+
>w : Symbol(w, Decl(mappedTypeAsClauseRelationships.ts, 23, 7))
136+
>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88))
137+
>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14))
138+
>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17))
139+
}
140+
141+
142+

0 commit comments

Comments
 (0)