Skip to content

Commit 8c060ee

Browse files
authored
Exclude mapped types with 'as' clauses from certain checks (#47889)
* Exclude mapped types with 'as' clauses from certain checks * Add tests
1 parent c981c9b commit 8c060ee

File tree

6 files changed

+316
-2
lines changed

6 files changed

+316
-2
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12220,6 +12220,7 @@ namespace ts {
1222012220

1222112221
function isMappedTypeGenericIndexedAccess(type: Type) {
1222212222
return type.flags & TypeFlags.IndexedAccess && getObjectFlags((type as IndexedAccessType).objectType) & ObjectFlags.Mapped &&
12223+
!((type as IndexedAccessType).objectType as MappedType).declaration.nameType &&
1222312224
!isGenericMappedType((type as IndexedAccessType).objectType) && isGenericIndexType((type as IndexedAccessType).indexType);
1222412225
}
1222512226

@@ -15656,7 +15657,7 @@ namespace ts {
1565615657
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
1565715658
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
1565815659
// construct the type Box<T[X]>.
15659-
if (isGenericMappedType(objectType)) {
15660+
if (isGenericMappedType(objectType) && !objectType.declaration.nameType) {
1566015661
return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing));
1566115662
}
1566215663
return type[cache] = type;
@@ -26650,7 +26651,7 @@ namespace ts {
2665026651

2665126652
function getTypeOfPropertyOfContextualType(type: Type, name: __String, nameType?: Type) {
2665226653
return mapType(type, t => {
26653-
if (isGenericMappedType(t)) {
26654+
if (isGenericMappedType(t) && !t.declaration.nameType) {
2665426655
const constraint = getConstraintTypeFromMappedType(t);
2665526656
const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint;
2665626657
const propertyNameType = nameType || getStringLiteralType(unescapeLeadingUnderscores(name));
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(10,11): error TS2322: Type 'Mapped2<K>[`get${K}`]' is not assignable to type '{ a: K; }'.
2+
Type 'Mapped2<K>[`get${string}`]' is not assignable to type '{ a: K; }'.
3+
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(16,11): error TS2322: Type 'Mapped3<K>[Uppercase<K>]' is not assignable to type '{ a: K; }'.
4+
Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
5+
tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts(25,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
6+
'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'.
7+
8+
9+
==== tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts (3 errors) ====
10+
type Mapped1<K extends string> = { [P in K]: { a: P } };
11+
12+
function f1<K extends string>(obj: Mapped1<K>, key: K) {
13+
const x: { a: K } = obj[key];
14+
}
15+
16+
type Mapped2<K extends string> = { [P in K as `get${P}`]: { a: P } };
17+
18+
function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`) {
19+
const x: { a: K } = obj[key]; // Error
20+
~
21+
!!! error TS2322: Type 'Mapped2<K>[`get${K}`]' is not assignable to type '{ a: K; }'.
22+
!!! error TS2322: Type 'Mapped2<K>[`get${string}`]' is not assignable to type '{ a: K; }'.
23+
}
24+
25+
type Mapped3<K extends string> = { [P in K as Uppercase<P>]: { a: P } };
26+
27+
function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>) {
28+
const x: { a: K } = obj[key]; // Error
29+
~
30+
!!! error TS2322: Type 'Mapped3<K>[Uppercase<K>]' is not assignable to type '{ a: K; }'.
31+
!!! error TS2322: Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
32+
}
33+
34+
// Repro from #47794
35+
36+
type Foo<T extends string> = {
37+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
38+
};
39+
40+
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
41+
~~~~~~~~~~~~~~
42+
!!! error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
43+
!!! error TS2322: 'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'.
44+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//// [mappedTypeConstraints2.ts]
2+
type Mapped1<K extends string> = { [P in K]: { a: P } };
3+
4+
function f1<K extends string>(obj: Mapped1<K>, key: K) {
5+
const x: { a: K } = obj[key];
6+
}
7+
8+
type Mapped2<K extends string> = { [P in K as `get${P}`]: { a: P } };
9+
10+
function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`) {
11+
const x: { a: K } = obj[key]; // Error
12+
}
13+
14+
type Mapped3<K extends string> = { [P in K as Uppercase<P>]: { a: P } };
15+
16+
function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>) {
17+
const x: { a: K } = obj[key]; // Error
18+
}
19+
20+
// Repro from #47794
21+
22+
type Foo<T extends string> = {
23+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
24+
};
25+
26+
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
27+
28+
29+
//// [mappedTypeConstraints2.js]
30+
"use strict";
31+
function f1(obj, key) {
32+
var x = obj[key];
33+
}
34+
function f2(obj, key) {
35+
var x = obj[key]; // Error
36+
}
37+
function f3(obj, key) {
38+
var x = obj[key]; // Error
39+
}
40+
var get = function (t, foo) { return foo["get".concat(t)]; }; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
41+
42+
43+
//// [mappedTypeConstraints2.d.ts]
44+
declare type Mapped1<K extends string> = {
45+
[P in K]: {
46+
a: P;
47+
};
48+
};
49+
declare function f1<K extends string>(obj: Mapped1<K>, key: K): void;
50+
declare type Mapped2<K extends string> = {
51+
[P in K as `get${P}`]: {
52+
a: P;
53+
};
54+
};
55+
declare function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`): void;
56+
declare type Mapped3<K extends string> = {
57+
[P in K as Uppercase<P>]: {
58+
a: P;
59+
};
60+
};
61+
declare function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>): void;
62+
declare type Foo<T extends string> = {
63+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
64+
};
65+
declare const get: <T extends string>(t: T, foo: Foo<T>) => T;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
=== tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts ===
2+
type Mapped1<K extends string> = { [P in K]: { a: P } };
3+
>Mapped1 : Symbol(Mapped1, Decl(mappedTypeConstraints2.ts, 0, 0))
4+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 0, 13))
5+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 0, 36))
6+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 0, 13))
7+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 0, 46))
8+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 0, 36))
9+
10+
function f1<K extends string>(obj: Mapped1<K>, key: K) {
11+
>f1 : Symbol(f1, Decl(mappedTypeConstraints2.ts, 0, 56))
12+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 2, 12))
13+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 2, 30))
14+
>Mapped1 : Symbol(Mapped1, Decl(mappedTypeConstraints2.ts, 0, 0))
15+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 2, 12))
16+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 2, 46))
17+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 2, 12))
18+
19+
const x: { a: K } = obj[key];
20+
>x : Symbol(x, Decl(mappedTypeConstraints2.ts, 3, 9))
21+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 3, 14))
22+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 2, 12))
23+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 2, 30))
24+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 2, 46))
25+
}
26+
27+
type Mapped2<K extends string> = { [P in K as `get${P}`]: { a: P } };
28+
>Mapped2 : Symbol(Mapped2, Decl(mappedTypeConstraints2.ts, 4, 1))
29+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 6, 13))
30+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 6, 36))
31+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 6, 13))
32+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 6, 36))
33+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 6, 59))
34+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 6, 36))
35+
36+
function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`) {
37+
>f2 : Symbol(f2, Decl(mappedTypeConstraints2.ts, 6, 69))
38+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 8, 12))
39+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 8, 30))
40+
>Mapped2 : Symbol(Mapped2, Decl(mappedTypeConstraints2.ts, 4, 1))
41+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 8, 12))
42+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 8, 46))
43+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 8, 12))
44+
45+
const x: { a: K } = obj[key]; // Error
46+
>x : Symbol(x, Decl(mappedTypeConstraints2.ts, 9, 9))
47+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 9, 14))
48+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 8, 12))
49+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 8, 30))
50+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 8, 46))
51+
}
52+
53+
type Mapped3<K extends string> = { [P in K as Uppercase<P>]: { a: P } };
54+
>Mapped3 : Symbol(Mapped3, Decl(mappedTypeConstraints2.ts, 10, 1))
55+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 12, 13))
56+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 12, 36))
57+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 12, 13))
58+
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
59+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 12, 36))
60+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 12, 62))
61+
>P : Symbol(P, Decl(mappedTypeConstraints2.ts, 12, 36))
62+
63+
function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>) {
64+
>f3 : Symbol(f3, Decl(mappedTypeConstraints2.ts, 12, 72))
65+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 14, 12))
66+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 14, 30))
67+
>Mapped3 : Symbol(Mapped3, Decl(mappedTypeConstraints2.ts, 10, 1))
68+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 14, 12))
69+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 14, 46))
70+
>Uppercase : Symbol(Uppercase, Decl(lib.es5.d.ts, --, --))
71+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 14, 12))
72+
73+
const x: { a: K } = obj[key]; // Error
74+
>x : Symbol(x, Decl(mappedTypeConstraints2.ts, 15, 9))
75+
>a : Symbol(a, Decl(mappedTypeConstraints2.ts, 15, 14))
76+
>K : Symbol(K, Decl(mappedTypeConstraints2.ts, 14, 12))
77+
>obj : Symbol(obj, Decl(mappedTypeConstraints2.ts, 14, 30))
78+
>key : Symbol(key, Decl(mappedTypeConstraints2.ts, 14, 46))
79+
}
80+
81+
// Repro from #47794
82+
83+
type Foo<T extends string> = {
84+
>Foo : Symbol(Foo, Decl(mappedTypeConstraints2.ts, 16, 1))
85+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 20, 9))
86+
87+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
88+
>RemappedT : Symbol(RemappedT, Decl(mappedTypeConstraints2.ts, 21, 5))
89+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 20, 9))
90+
>RemappedT : Symbol(RemappedT, Decl(mappedTypeConstraints2.ts, 21, 5))
91+
>RemappedT : Symbol(RemappedT, Decl(mappedTypeConstraints2.ts, 21, 5))
92+
93+
};
94+
95+
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
96+
>get : Symbol(get, Decl(mappedTypeConstraints2.ts, 24, 5))
97+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 24, 13))
98+
>t : Symbol(t, Decl(mappedTypeConstraints2.ts, 24, 31))
99+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 24, 13))
100+
>foo : Symbol(foo, Decl(mappedTypeConstraints2.ts, 24, 36))
101+
>Foo : Symbol(Foo, Decl(mappedTypeConstraints2.ts, 16, 1))
102+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 24, 13))
103+
>T : Symbol(T, Decl(mappedTypeConstraints2.ts, 24, 13))
104+
>foo : Symbol(foo, Decl(mappedTypeConstraints2.ts, 24, 36))
105+
>t : Symbol(t, Decl(mappedTypeConstraints2.ts, 24, 31))
106+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
=== tests/cases/conformance/types/mapped/mappedTypeConstraints2.ts ===
2+
type Mapped1<K extends string> = { [P in K]: { a: P } };
3+
>Mapped1 : Mapped1<K>
4+
>a : P
5+
6+
function f1<K extends string>(obj: Mapped1<K>, key: K) {
7+
>f1 : <K extends string>(obj: Mapped1<K>, key: K) => void
8+
>obj : Mapped1<K>
9+
>key : K
10+
11+
const x: { a: K } = obj[key];
12+
>x : { a: K; }
13+
>a : K
14+
>obj[key] : Mapped1<K>[K]
15+
>obj : Mapped1<K>
16+
>key : K
17+
}
18+
19+
type Mapped2<K extends string> = { [P in K as `get${P}`]: { a: P } };
20+
>Mapped2 : Mapped2<K>
21+
>a : P
22+
23+
function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`) {
24+
>f2 : <K extends string>(obj: Mapped2<K>, key: `get${K}`) => void
25+
>obj : Mapped2<K>
26+
>key : `get${K}`
27+
28+
const x: { a: K } = obj[key]; // Error
29+
>x : { a: K; }
30+
>a : K
31+
>obj[key] : Mapped2<K>[`get${K}`]
32+
>obj : Mapped2<K>
33+
>key : `get${K}`
34+
}
35+
36+
type Mapped3<K extends string> = { [P in K as Uppercase<P>]: { a: P } };
37+
>Mapped3 : Mapped3<K>
38+
>a : P
39+
40+
function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>) {
41+
>f3 : <K extends string>(obj: Mapped3<K>, key: Uppercase<K>) => void
42+
>obj : Mapped3<K>
43+
>key : Uppercase<K>
44+
45+
const x: { a: K } = obj[key]; // Error
46+
>x : { a: K; }
47+
>a : K
48+
>obj[key] : Mapped3<K>[Uppercase<K>]
49+
>obj : Mapped3<K>
50+
>key : Uppercase<K>
51+
}
52+
53+
// Repro from #47794
54+
55+
type Foo<T extends string> = {
56+
>Foo : Foo<T>
57+
58+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
59+
};
60+
61+
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
62+
>get : <T extends string>(t: T, foo: Foo<T>) => T
63+
><T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`] : <T extends string>(t: T, foo: Foo<T>) => T
64+
>t : T
65+
>foo : Foo<T>
66+
>foo[`get${t}`] : Foo<T>[`get${T}`]
67+
>foo : Foo<T>
68+
>`get${t}` : `get${T}`
69+
>t : T
70+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @strict: true
2+
// @declaration: true
3+
4+
type Mapped1<K extends string> = { [P in K]: { a: P } };
5+
6+
function f1<K extends string>(obj: Mapped1<K>, key: K) {
7+
const x: { a: K } = obj[key];
8+
}
9+
10+
type Mapped2<K extends string> = { [P in K as `get${P}`]: { a: P } };
11+
12+
function f2<K extends string>(obj: Mapped2<K>, key: `get${K}`) {
13+
const x: { a: K } = obj[key]; // Error
14+
}
15+
16+
type Mapped3<K extends string> = { [P in K as Uppercase<P>]: { a: P } };
17+
18+
function f3<K extends string>(obj: Mapped3<K>, key: Uppercase<K>) {
19+
const x: { a: K } = obj[key]; // Error
20+
}
21+
22+
// Repro from #47794
23+
24+
type Foo<T extends string> = {
25+
[RemappedT in T as `get${RemappedT}`]: RemappedT;
26+
};
27+
28+
const get = <T extends string>(t: T, foo: Foo<T>): T => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'

0 commit comments

Comments
 (0)