Skip to content

Commit 8570a67

Browse files
authored
Merge pull request #29121 from Microsoft/mappedTypeConstraints
Improve constraints for non-homomorphic mapped types
2 parents fd3af78 + 194496f commit 8570a67

File tree

5 files changed

+390
-4
lines changed

5 files changed

+390
-4
lines changed

src/compiler/checker.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -7084,6 +7084,39 @@ namespace ts {
70847084
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
70857085
}
70867086

7087+
// Return the lower bound of the key type in a mapped type. Intuitively, the lower
7088+
// bound includes those keys that are known to always be present, for example because
7089+
// because of constraints on type parameters (e.g. 'keyof T' for a constrained T).
7090+
function getLowerBoundOfKeyType(type: Type): Type {
7091+
if (type.flags & (TypeFlags.Any | TypeFlags.Primitive)) {
7092+
return type;
7093+
}
7094+
if (type.flags & TypeFlags.Index) {
7095+
return getIndexType(getApparentType((<IndexType>type).type));
7096+
}
7097+
if (type.flags & TypeFlags.Conditional) {
7098+
return getLowerBoundOfConditionalType(<ConditionalType>type);
7099+
}
7100+
if (type.flags & TypeFlags.Union) {
7101+
return getUnionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
7102+
}
7103+
if (type.flags & TypeFlags.Intersection) {
7104+
return getIntersectionType(sameMap((<UnionType>type).types, getLowerBoundOfKeyType));
7105+
}
7106+
return neverType;
7107+
}
7108+
7109+
function getLowerBoundOfConditionalType(type: ConditionalType) {
7110+
if (type.root.isDistributive) {
7111+
const constraint = getLowerBoundOfKeyType(type.checkType);
7112+
if (constraint !== type.checkType) {
7113+
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
7114+
return getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
7115+
}
7116+
}
7117+
return type;
7118+
}
7119+
70877120
/** Resolve the members of a mapped type { [P in K]: T } */
70887121
function resolveMappedTypeMembers(type: MappedType) {
70897122
const members: SymbolTable = createSymbolTable();
@@ -7112,10 +7145,7 @@ namespace ts {
71127145
}
71137146
}
71147147
else {
7115-
// If the key type is a 'keyof X', obtain 'keyof C' where C is the base constraint of X.
7116-
// Then iterate over the constituents of the key type.
7117-
const iterationType = constraintType.flags & TypeFlags.Index ? getIndexType(getApparentType((<IndexType>constraintType).type)) : constraintType;
7118-
forEachType(iterationType, addMemberForKeyType);
7148+
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
71197149
}
71207150
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
71217151

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//// [mappedTypeConstraints.ts]
2+
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
3+
obj.b;
4+
}
5+
6+
function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
7+
obj.b;
8+
}
9+
10+
function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
11+
obj.b;
12+
}
13+
14+
function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
15+
obj.a;
16+
obj.b;
17+
obj.c;
18+
}
19+
20+
function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
21+
obj.a;
22+
obj.c;
23+
}
24+
25+
// Repro from #28821
26+
27+
type TargetProps = {
28+
foo: string,
29+
bar: string
30+
};
31+
32+
const modifier = <T extends TargetProps>(targetProps: T) => {
33+
let {bar, ...rest} = targetProps;
34+
rest.foo;
35+
};
36+
37+
38+
//// [mappedTypeConstraints.js]
39+
"use strict";
40+
var __rest = (this && this.__rest) || function (s, e) {
41+
var t = {};
42+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
43+
t[p] = s[p];
44+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
45+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
46+
t[p[i]] = s[p[i]];
47+
return t;
48+
};
49+
function f0(obj) {
50+
obj.b;
51+
}
52+
function f1(obj) {
53+
obj.b;
54+
}
55+
function f2(obj) {
56+
obj.b;
57+
}
58+
function f3(obj) {
59+
obj.a;
60+
obj.b;
61+
obj.c;
62+
}
63+
function f4(obj) {
64+
obj.a;
65+
obj.c;
66+
}
67+
var modifier = function (targetProps) {
68+
var bar = targetProps.bar, rest = __rest(targetProps, ["bar"]);
69+
rest.foo;
70+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts ===
2+
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
3+
>f0 : Symbol(f0, Decl(mappedTypeConstraints.ts, 0, 0))
4+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))
5+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 0, 23))
6+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
7+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 0, 48))
8+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
9+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))
10+
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))
11+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 0, 12))
12+
13+
obj.b;
14+
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
15+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 0, 48))
16+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 0, 34))
17+
}
18+
19+
function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
20+
>f1 : Symbol(f1, Decl(mappedTypeConstraints.ts, 2, 1))
21+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))
22+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 4, 23))
23+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
24+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 4, 48))
25+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
26+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))
27+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
28+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 4, 12))
29+
30+
obj.b;
31+
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
32+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 4, 48))
33+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 4, 34))
34+
}
35+
36+
function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
37+
>f2 : Symbol(f2, Decl(mappedTypeConstraints.ts, 6, 1))
38+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
39+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 8, 23))
40+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34))
41+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))
42+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 59))
43+
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 8, 70))
44+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 8, 84))
45+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
46+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
47+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))
48+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 8, 12))
49+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 8, 47))
50+
51+
obj.b;
52+
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34), Decl(mappedTypeConstraints.ts, 8, 59))
53+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 8, 84))
54+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 8, 34), Decl(mappedTypeConstraints.ts, 8, 59))
55+
}
56+
57+
function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
58+
>f3 : Symbol(f3, Decl(mappedTypeConstraints.ts, 10, 1))
59+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
60+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))
61+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34))
62+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))
63+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 59))
64+
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
65+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
66+
>Pick : Symbol(Pick, Decl(lib.es5.d.ts, --, --))
67+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
68+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))
69+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 12, 12))
70+
>U : Symbol(U, Decl(mappedTypeConstraints.ts, 12, 47))
71+
72+
obj.a;
73+
>obj.a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))
74+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
75+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 12, 23))
76+
77+
obj.b;
78+
>obj.b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34), Decl(mappedTypeConstraints.ts, 12, 59))
79+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
80+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 12, 34), Decl(mappedTypeConstraints.ts, 12, 59))
81+
82+
obj.c;
83+
>obj.c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
84+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 12, 84))
85+
>c : Symbol(c, Decl(mappedTypeConstraints.ts, 12, 70))
86+
}
87+
88+
function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
89+
>f4 : Symbol(f4, Decl(mappedTypeConstraints.ts, 16, 1))
90+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 18, 12))
91+
>a : Symbol(a, Decl(mappedTypeConstraints.ts, 18, 23))
92+
>b : Symbol(b, Decl(mappedTypeConstraints.ts, 18, 34))
93+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
94+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
95+
>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --))
96+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 18, 12))
97+
98+
obj.a;
99+
>obj.a : Symbol(a)
100+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
101+
>a : Symbol(a)
102+
103+
obj.c;
104+
>obj.c : Symbol(c)
105+
>obj : Symbol(obj, Decl(mappedTypeConstraints.ts, 18, 48))
106+
>c : Symbol(c)
107+
}
108+
109+
// Repro from #28821
110+
111+
type TargetProps = {
112+
>TargetProps : Symbol(TargetProps, Decl(mappedTypeConstraints.ts, 21, 1))
113+
114+
foo: string,
115+
>foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))
116+
117+
bar: string
118+
>bar : Symbol(bar, Decl(mappedTypeConstraints.ts, 26, 16))
119+
120+
};
121+
122+
const modifier = <T extends TargetProps>(targetProps: T) => {
123+
>modifier : Symbol(modifier, Decl(mappedTypeConstraints.ts, 30, 5))
124+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 30, 18))
125+
>TargetProps : Symbol(TargetProps, Decl(mappedTypeConstraints.ts, 21, 1))
126+
>targetProps : Symbol(targetProps, Decl(mappedTypeConstraints.ts, 30, 41))
127+
>T : Symbol(T, Decl(mappedTypeConstraints.ts, 30, 18))
128+
129+
let {bar, ...rest} = targetProps;
130+
>bar : Symbol(bar, Decl(mappedTypeConstraints.ts, 31, 9))
131+
>rest : Symbol(rest, Decl(mappedTypeConstraints.ts, 31, 13))
132+
>targetProps : Symbol(targetProps, Decl(mappedTypeConstraints.ts, 30, 41))
133+
134+
rest.foo;
135+
>rest.foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))
136+
>rest : Symbol(rest, Decl(mappedTypeConstraints.ts, 31, 13))
137+
>foo : Symbol(foo, Decl(mappedTypeConstraints.ts, 25, 20))
138+
139+
};
140+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
=== tests/cases/conformance/types/mapped/mappedTypeConstraints.ts ===
2+
function f0<T extends { a: string, b: string }>(obj: Pick<T, Extract<keyof T, 'b'>>) {
3+
>f0 : <T extends { a: string; b: string; }>(obj: Pick<T, Extract<keyof T, "b">>) => void
4+
>a : string
5+
>b : string
6+
>obj : Pick<T, Extract<keyof T, "b">>
7+
8+
obj.b;
9+
>obj.b : T["b"]
10+
>obj : Pick<T, Extract<keyof T, "b">>
11+
>b : T["b"]
12+
}
13+
14+
function f1<T extends { a: string, b: string }>(obj: Pick<T, Exclude<keyof T, 'a'>>) {
15+
>f1 : <T extends { a: string; b: string; }>(obj: Pick<T, Exclude<keyof T, "a">>) => void
16+
>a : string
17+
>b : string
18+
>obj : Pick<T, Exclude<keyof T, "a">>
19+
20+
obj.b;
21+
>obj.b : T["b"]
22+
>obj : Pick<T, Exclude<keyof T, "a">>
23+
>b : T["b"]
24+
}
25+
26+
function f2<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T | U, keyof (T | U)>) {
27+
>f2 : <T extends { a: string; b: string; }, U extends { b: string; c: string; }>(obj: Pick<T | U, keyof T & keyof U>) => void
28+
>a : string
29+
>b : string
30+
>b : string
31+
>c : string
32+
>obj : Pick<T | U, keyof T & keyof U>
33+
34+
obj.b;
35+
>obj.b : (T | U)["b"]
36+
>obj : Pick<T | U, keyof T & keyof U>
37+
>b : (T | U)["b"]
38+
}
39+
40+
function f3<T extends { a: string, b: string }, U extends { b: string, c: string }>(obj: Pick<T & U, keyof (T & U)>) {
41+
>f3 : <T extends { a: string; b: string; }, U extends { b: string; c: string; }>(obj: Pick<T & U, keyof T | keyof U>) => void
42+
>a : string
43+
>b : string
44+
>b : string
45+
>c : string
46+
>obj : Pick<T & U, keyof T | keyof U>
47+
48+
obj.a;
49+
>obj.a : (T & U)["a"]
50+
>obj : Pick<T & U, keyof T | keyof U>
51+
>a : (T & U)["a"]
52+
53+
obj.b;
54+
>obj.b : (T & U)["b"]
55+
>obj : Pick<T & U, keyof T | keyof U>
56+
>b : (T & U)["b"]
57+
58+
obj.c;
59+
>obj.c : (T & U)["c"]
60+
>obj : Pick<T & U, keyof T | keyof U>
61+
>c : (T & U)["c"]
62+
}
63+
64+
function f4<T extends { a: string, b: string }>(obj: Record<Exclude<keyof T, 'b'> | 'c', string>) {
65+
>f4 : <T extends { a: string; b: string; }>(obj: Record<"c" | Exclude<keyof T, "b">, string>) => void
66+
>a : string
67+
>b : string
68+
>obj : Record<"c" | Exclude<keyof T, "b">, string>
69+
70+
obj.a;
71+
>obj.a : string
72+
>obj : Record<"c" | Exclude<keyof T, "b">, string>
73+
>a : string
74+
75+
obj.c;
76+
>obj.c : string
77+
>obj : Record<"c" | Exclude<keyof T, "b">, string>
78+
>c : string
79+
}
80+
81+
// Repro from #28821
82+
83+
type TargetProps = {
84+
>TargetProps : TargetProps
85+
86+
foo: string,
87+
>foo : string
88+
89+
bar: string
90+
>bar : string
91+
92+
};
93+
94+
const modifier = <T extends TargetProps>(targetProps: T) => {
95+
>modifier : <T extends TargetProps>(targetProps: T) => void
96+
><T extends TargetProps>(targetProps: T) => { let {bar, ...rest} = targetProps; rest.foo;} : <T extends TargetProps>(targetProps: T) => void
97+
>targetProps : T
98+
99+
let {bar, ...rest} = targetProps;
100+
>bar : string
101+
>rest : Pick<T, Exclude<keyof T, "bar">>
102+
>targetProps : T
103+
104+
rest.foo;
105+
>rest.foo : T["foo"]
106+
>rest : Pick<T, Exclude<keyof T, "bar">>
107+
>foo : T["foo"]
108+
109+
};
110+

0 commit comments

Comments
 (0)