Skip to content

Commit 6501c85

Browse files
committed
Support template literals in element access discriminant
1 parent f07ecf8 commit 6501c85

File tree

6 files changed

+420
-2
lines changed

6 files changed

+420
-2
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ namespace ts {
777777
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
778778
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
779779
isElementAccessExpression(expr) && expr.argumentExpression &&
780-
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
780+
isStringOrNumericLiteralLike(expr.argumentExpression) &&
781781
isNarrowableReference(expr.expression);
782782
}
783783

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16248,7 +16248,7 @@ namespace ts {
1624816248

1624916249
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
1625016250
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
16251-
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
16251+
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
1625216252
undefined;
1625316253
}
1625416254

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//// [discriminantElementAccessCheck.ts]
2+
type U = TypeA | TypeB;
3+
4+
interface TypeA {
5+
kind: 'A';
6+
a: number;
7+
}
8+
interface TypeB {
9+
kind: 'B';
10+
b: string;
11+
}
12+
13+
function assertNever(x: never) {
14+
return x;
15+
}
16+
17+
function IfWithString(val: U) {
18+
if (val['kind'] === 'B') {
19+
return val.b;
20+
} else {
21+
return val.a;
22+
}
23+
}
24+
25+
function SwitchWithString(val: U) {
26+
switch (val['kind']) {
27+
case 'A':
28+
return val.a;
29+
case 'B':
30+
return val.b;
31+
default:
32+
return assertNever(val);
33+
}
34+
}
35+
36+
function IfWithTemplate(val: U) {
37+
if (val[`kind`] === 'B') {
38+
return val.b;
39+
} else {
40+
return val.a;
41+
}
42+
}
43+
44+
function SwitchWithTemplate(val: U) {
45+
switch (val[`kind`]) {
46+
case 'A':
47+
return val.a;
48+
case 'B':
49+
return val.b;
50+
default:
51+
return assertNever(val);
52+
}
53+
}
54+
55+
//// [discriminantElementAccessCheck.js]
56+
function assertNever(x) {
57+
return x;
58+
}
59+
function IfWithString(val) {
60+
if (val['kind'] === 'B') {
61+
return val.b;
62+
}
63+
else {
64+
return val.a;
65+
}
66+
}
67+
function SwitchWithString(val) {
68+
switch (val['kind']) {
69+
case 'A':
70+
return val.a;
71+
case 'B':
72+
return val.b;
73+
default:
74+
return assertNever(val);
75+
}
76+
}
77+
function IfWithTemplate(val) {
78+
if (val["kind"] === 'B') {
79+
return val.b;
80+
}
81+
else {
82+
return val.a;
83+
}
84+
}
85+
function SwitchWithTemplate(val) {
86+
switch (val["kind"]) {
87+
case 'A':
88+
return val.a;
89+
case 'B':
90+
return val.b;
91+
default:
92+
return assertNever(val);
93+
}
94+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
=== tests/cases/compiler/discriminantElementAccessCheck.ts ===
2+
type U = TypeA | TypeB;
3+
>U : Symbol(U, Decl(discriminantElementAccessCheck.ts, 0, 0))
4+
>TypeA : Symbol(TypeA, Decl(discriminantElementAccessCheck.ts, 0, 23))
5+
>TypeB : Symbol(TypeB, Decl(discriminantElementAccessCheck.ts, 5, 1))
6+
7+
interface TypeA {
8+
>TypeA : Symbol(TypeA, Decl(discriminantElementAccessCheck.ts, 0, 23))
9+
10+
kind: 'A';
11+
>kind : Symbol(TypeA.kind, Decl(discriminantElementAccessCheck.ts, 2, 17))
12+
13+
a: number;
14+
>a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
15+
}
16+
interface TypeB {
17+
>TypeB : Symbol(TypeB, Decl(discriminantElementAccessCheck.ts, 5, 1))
18+
19+
kind: 'B';
20+
>kind : Symbol(TypeB.kind, Decl(discriminantElementAccessCheck.ts, 6, 17))
21+
22+
b: string;
23+
>b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
24+
}
25+
26+
function assertNever(x: never) {
27+
>assertNever : Symbol(assertNever, Decl(discriminantElementAccessCheck.ts, 9, 1))
28+
>x : Symbol(x, Decl(discriminantElementAccessCheck.ts, 11, 21))
29+
30+
return x;
31+
>x : Symbol(x, Decl(discriminantElementAccessCheck.ts, 11, 21))
32+
}
33+
34+
function IfWithString(val: U) {
35+
>IfWithString : Symbol(IfWithString, Decl(discriminantElementAccessCheck.ts, 13, 1))
36+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 15, 22))
37+
>U : Symbol(U, Decl(discriminantElementAccessCheck.ts, 0, 0))
38+
39+
if (val['kind'] === 'B') {
40+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 15, 22))
41+
>'kind' : Symbol(kind, Decl(discriminantElementAccessCheck.ts, 2, 17), Decl(discriminantElementAccessCheck.ts, 6, 17))
42+
43+
return val.b;
44+
>val.b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
45+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 15, 22))
46+
>b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
47+
48+
} else {
49+
return val.a;
50+
>val.a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
51+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 15, 22))
52+
>a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
53+
}
54+
}
55+
56+
function SwitchWithString(val: U) {
57+
>SwitchWithString : Symbol(SwitchWithString, Decl(discriminantElementAccessCheck.ts, 21, 1))
58+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 23, 26))
59+
>U : Symbol(U, Decl(discriminantElementAccessCheck.ts, 0, 0))
60+
61+
switch (val['kind']) {
62+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 23, 26))
63+
>'kind' : Symbol(kind, Decl(discriminantElementAccessCheck.ts, 2, 17), Decl(discriminantElementAccessCheck.ts, 6, 17))
64+
65+
case 'A':
66+
return val.a;
67+
>val.a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
68+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 23, 26))
69+
>a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
70+
71+
case 'B':
72+
return val.b;
73+
>val.b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
74+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 23, 26))
75+
>b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
76+
77+
default:
78+
return assertNever(val);
79+
>assertNever : Symbol(assertNever, Decl(discriminantElementAccessCheck.ts, 9, 1))
80+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 23, 26))
81+
}
82+
}
83+
84+
function IfWithTemplate(val: U) {
85+
>IfWithTemplate : Symbol(IfWithTemplate, Decl(discriminantElementAccessCheck.ts, 32, 1))
86+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 34, 24))
87+
>U : Symbol(U, Decl(discriminantElementAccessCheck.ts, 0, 0))
88+
89+
if (val[`kind`] === 'B') {
90+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 34, 24))
91+
>`kind` : Symbol(kind, Decl(discriminantElementAccessCheck.ts, 2, 17), Decl(discriminantElementAccessCheck.ts, 6, 17))
92+
93+
return val.b;
94+
>val.b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
95+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 34, 24))
96+
>b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
97+
98+
} else {
99+
return val.a;
100+
>val.a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
101+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 34, 24))
102+
>a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
103+
}
104+
}
105+
106+
function SwitchWithTemplate(val: U) {
107+
>SwitchWithTemplate : Symbol(SwitchWithTemplate, Decl(discriminantElementAccessCheck.ts, 40, 1))
108+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 42, 28))
109+
>U : Symbol(U, Decl(discriminantElementAccessCheck.ts, 0, 0))
110+
111+
switch (val[`kind`]) {
112+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 42, 28))
113+
>`kind` : Symbol(kind, Decl(discriminantElementAccessCheck.ts, 2, 17), Decl(discriminantElementAccessCheck.ts, 6, 17))
114+
115+
case 'A':
116+
return val.a;
117+
>val.a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
118+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 42, 28))
119+
>a : Symbol(TypeA.a, Decl(discriminantElementAccessCheck.ts, 3, 14))
120+
121+
case 'B':
122+
return val.b;
123+
>val.b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
124+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 42, 28))
125+
>b : Symbol(TypeB.b, Decl(discriminantElementAccessCheck.ts, 7, 14))
126+
127+
default:
128+
return assertNever(val);
129+
>assertNever : Symbol(assertNever, Decl(discriminantElementAccessCheck.ts, 9, 1))
130+
>val : Symbol(val, Decl(discriminantElementAccessCheck.ts, 42, 28))
131+
}
132+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
=== tests/cases/compiler/discriminantElementAccessCheck.ts ===
2+
type U = TypeA | TypeB;
3+
>U : U
4+
5+
interface TypeA {
6+
kind: 'A';
7+
>kind : "A"
8+
9+
a: number;
10+
>a : number
11+
}
12+
interface TypeB {
13+
kind: 'B';
14+
>kind : "B"
15+
16+
b: string;
17+
>b : string
18+
}
19+
20+
function assertNever(x: never) {
21+
>assertNever : (x: never) => never
22+
>x : never
23+
24+
return x;
25+
>x : never
26+
}
27+
28+
function IfWithString(val: U) {
29+
>IfWithString : (val: U) => string | number
30+
>val : U
31+
32+
if (val['kind'] === 'B') {
33+
>val['kind'] === 'B' : boolean
34+
>val['kind'] : "A" | "B"
35+
>val : U
36+
>'kind' : "kind"
37+
>'B' : "B"
38+
39+
return val.b;
40+
>val.b : string
41+
>val : TypeB
42+
>b : string
43+
44+
} else {
45+
return val.a;
46+
>val.a : number
47+
>val : TypeA
48+
>a : number
49+
}
50+
}
51+
52+
function SwitchWithString(val: U) {
53+
>SwitchWithString : (val: U) => string | number
54+
>val : U
55+
56+
switch (val['kind']) {
57+
>val['kind'] : "A" | "B"
58+
>val : U
59+
>'kind' : "kind"
60+
61+
case 'A':
62+
>'A' : "A"
63+
64+
return val.a;
65+
>val.a : number
66+
>val : TypeA
67+
>a : number
68+
69+
case 'B':
70+
>'B' : "B"
71+
72+
return val.b;
73+
>val.b : string
74+
>val : TypeB
75+
>b : string
76+
77+
default:
78+
return assertNever(val);
79+
>assertNever(val) : never
80+
>assertNever : (x: never) => never
81+
>val : never
82+
}
83+
}
84+
85+
function IfWithTemplate(val: U) {
86+
>IfWithTemplate : (val: U) => string | number
87+
>val : U
88+
89+
if (val[`kind`] === 'B') {
90+
>val[`kind`] === 'B' : boolean
91+
>val[`kind`] : "A" | "B"
92+
>val : U
93+
>`kind` : "kind"
94+
>'B' : "B"
95+
96+
return val.b;
97+
>val.b : string
98+
>val : TypeB
99+
>b : string
100+
101+
} else {
102+
return val.a;
103+
>val.a : number
104+
>val : TypeA
105+
>a : number
106+
}
107+
}
108+
109+
function SwitchWithTemplate(val: U) {
110+
>SwitchWithTemplate : (val: U) => string | number
111+
>val : U
112+
113+
switch (val[`kind`]) {
114+
>val[`kind`] : "A" | "B"
115+
>val : U
116+
>`kind` : "kind"
117+
118+
case 'A':
119+
>'A' : "A"
120+
121+
return val.a;
122+
>val.a : number
123+
>val : TypeA
124+
>a : number
125+
126+
case 'B':
127+
>'B' : "B"
128+
129+
return val.b;
130+
>val.b : string
131+
>val : TypeB
132+
>b : string
133+
134+
default:
135+
return assertNever(val);
136+
>assertNever(val) : never
137+
>assertNever : (x: never) => never
138+
>val : never
139+
}
140+
}

0 commit comments

Comments
 (0)