Skip to content

Commit 14c65b3

Browse files
authored
Check if switch statements are exhaustive when their expressions is generic with a literal type constraint (#60644)
1 parent 9717772 commit 14c65b3

File tree

5 files changed

+618
-1
lines changed

5 files changed

+618
-1
lines changed

src/compiler/checker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38682,7 +38682,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3868238682
// A missing not-equal flag indicates that the type wasn't handled by some case.
3868338683
return !someType(operandConstraint, t => getTypeFacts(t, notEqualFacts) === notEqualFacts);
3868438684
}
38685-
const type = checkExpressionCached(node.expression);
38685+
const type = getBaseConstraintOrType(checkExpressionCached(node.expression));
3868638686
if (!isLiteralType(type)) {
3868738687
return false;
3868838688
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
dependentReturnType9.ts(57,4): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
2+
3+
4+
==== dependentReturnType9.ts (1 errors) ====
5+
type Payload =
6+
| { _tag: "auth"; username: string; password: string }
7+
| { _tag: "cart"; items: Array<{ id: string; quantity: number }> }
8+
| { _tag: "person"; name: string; age: number };
9+
10+
type PayloadContent = {
11+
[P in Payload as P["_tag"]]: Omit<P, "_tag">;
12+
};
13+
14+
// ok, exhaustive cases and default case with throw
15+
function mockPayload<P_TAG extends Payload["_tag"]>(
16+
str: P_TAG,
17+
): PayloadContent[P_TAG] {
18+
switch (str) {
19+
case "auth":
20+
return { username: "test", password: "admin" };
21+
case "cart":
22+
return { items: [{ id: "123", quantity: 123 }] };
23+
case "person":
24+
return { name: "andrea", age: 27 };
25+
default:
26+
throw new Error("unknown tag");
27+
}
28+
}
29+
30+
// ok, non-exhaustive cases but default case with throw
31+
function mockPayload2<P_TAG extends Payload["_tag"]>(
32+
str: P_TAG,
33+
): PayloadContent[P_TAG] {
34+
switch (str) {
35+
case "auth":
36+
return { username: "test", password: "admin" };
37+
case "cart":
38+
return { items: [{ id: "123", quantity: 123 }] };
39+
default:
40+
throw new Error("unhandled tag");
41+
}
42+
}
43+
44+
// ok, exhaustive cases
45+
function mockPayload3<P_TAG extends Payload["_tag"]>(
46+
str: P_TAG,
47+
): PayloadContent[P_TAG] {
48+
switch (str) {
49+
case "auth":
50+
return { username: "test", password: "admin" };
51+
case "cart":
52+
return { items: [{ id: "123", quantity: 123 }] };
53+
case "person":
54+
return { name: "andrea", age: 27 };
55+
}
56+
}
57+
58+
// error, non-exhaustive cases
59+
function mockPayload4<P_TAG extends Payload["_tag"]>(
60+
str: P_TAG,
61+
): PayloadContent[P_TAG] {
62+
~~~~~~~~~~~~~~~~~~~~~
63+
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
64+
switch (str) {
65+
case "auth":
66+
return { username: "test", password: "admin" };
67+
case "cart":
68+
return { items: [{ id: "123", quantity: 123 }] };
69+
}
70+
}
71+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
//// [tests/cases/compiler/dependentReturnType9.ts] ////
2+
3+
=== dependentReturnType9.ts ===
4+
type Payload =
5+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
6+
7+
| { _tag: "auth"; username: string; password: string }
8+
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 1, 5))
9+
>username : Symbol(username, Decl(dependentReturnType9.ts, 1, 19))
10+
>password : Symbol(password, Decl(dependentReturnType9.ts, 1, 37))
11+
12+
| { _tag: "cart"; items: Array<{ id: string; quantity: number }> }
13+
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 2, 5))
14+
>items : Symbol(items, Decl(dependentReturnType9.ts, 2, 19))
15+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
16+
>id : Symbol(id, Decl(dependentReturnType9.ts, 2, 34))
17+
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 2, 46))
18+
19+
| { _tag: "person"; name: string; age: number };
20+
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 3, 5))
21+
>name : Symbol(name, Decl(dependentReturnType9.ts, 3, 21))
22+
>age : Symbol(age, Decl(dependentReturnType9.ts, 3, 35))
23+
24+
type PayloadContent = {
25+
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
26+
27+
[P in Payload as P["_tag"]]: Omit<P, "_tag">;
28+
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))
29+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
30+
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))
31+
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
32+
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))
33+
34+
};
35+
36+
// ok, exhaustive cases and default case with throw
37+
function mockPayload<P_TAG extends Payload["_tag"]>(
38+
>mockPayload : Symbol(mockPayload, Decl(dependentReturnType9.ts, 7, 2))
39+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))
40+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
41+
42+
str: P_TAG,
43+
>str : Symbol(str, Decl(dependentReturnType9.ts, 10, 52))
44+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))
45+
46+
): PayloadContent[P_TAG] {
47+
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
48+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))
49+
50+
switch (str) {
51+
>str : Symbol(str, Decl(dependentReturnType9.ts, 10, 52))
52+
53+
case "auth":
54+
return { username: "test", password: "admin" };
55+
>username : Symbol(username, Decl(dependentReturnType9.ts, 15, 14))
56+
>password : Symbol(password, Decl(dependentReturnType9.ts, 15, 32))
57+
58+
case "cart":
59+
return { items: [{ id: "123", quantity: 123 }] };
60+
>items : Symbol(items, Decl(dependentReturnType9.ts, 17, 14))
61+
>id : Symbol(id, Decl(dependentReturnType9.ts, 17, 24))
62+
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 17, 35))
63+
64+
case "person":
65+
return { name: "andrea", age: 27 };
66+
>name : Symbol(name, Decl(dependentReturnType9.ts, 19, 14))
67+
>age : Symbol(age, Decl(dependentReturnType9.ts, 19, 30))
68+
69+
default:
70+
throw new Error("unknown tag");
71+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
72+
}
73+
}
74+
75+
// ok, non-exhaustive cases but default case with throw
76+
function mockPayload2<P_TAG extends Payload["_tag"]>(
77+
>mockPayload2 : Symbol(mockPayload2, Decl(dependentReturnType9.ts, 23, 1))
78+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))
79+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
80+
81+
str: P_TAG,
82+
>str : Symbol(str, Decl(dependentReturnType9.ts, 26, 53))
83+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))
84+
85+
): PayloadContent[P_TAG] {
86+
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
87+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))
88+
89+
switch (str) {
90+
>str : Symbol(str, Decl(dependentReturnType9.ts, 26, 53))
91+
92+
case "auth":
93+
return { username: "test", password: "admin" };
94+
>username : Symbol(username, Decl(dependentReturnType9.ts, 31, 14))
95+
>password : Symbol(password, Decl(dependentReturnType9.ts, 31, 32))
96+
97+
case "cart":
98+
return { items: [{ id: "123", quantity: 123 }] };
99+
>items : Symbol(items, Decl(dependentReturnType9.ts, 33, 14))
100+
>id : Symbol(id, Decl(dependentReturnType9.ts, 33, 24))
101+
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 33, 35))
102+
103+
default:
104+
throw new Error("unhandled tag");
105+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
106+
}
107+
}
108+
109+
// ok, exhaustive cases
110+
function mockPayload3<P_TAG extends Payload["_tag"]>(
111+
>mockPayload3 : Symbol(mockPayload3, Decl(dependentReturnType9.ts, 37, 1))
112+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))
113+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
114+
115+
str: P_TAG,
116+
>str : Symbol(str, Decl(dependentReturnType9.ts, 40, 53))
117+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))
118+
119+
): PayloadContent[P_TAG] {
120+
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
121+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))
122+
123+
switch (str) {
124+
>str : Symbol(str, Decl(dependentReturnType9.ts, 40, 53))
125+
126+
case "auth":
127+
return { username: "test", password: "admin" };
128+
>username : Symbol(username, Decl(dependentReturnType9.ts, 45, 14))
129+
>password : Symbol(password, Decl(dependentReturnType9.ts, 45, 32))
130+
131+
case "cart":
132+
return { items: [{ id: "123", quantity: 123 }] };
133+
>items : Symbol(items, Decl(dependentReturnType9.ts, 47, 14))
134+
>id : Symbol(id, Decl(dependentReturnType9.ts, 47, 24))
135+
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 47, 35))
136+
137+
case "person":
138+
return { name: "andrea", age: 27 };
139+
>name : Symbol(name, Decl(dependentReturnType9.ts, 49, 14))
140+
>age : Symbol(age, Decl(dependentReturnType9.ts, 49, 30))
141+
}
142+
}
143+
144+
// error, non-exhaustive cases
145+
function mockPayload4<P_TAG extends Payload["_tag"]>(
146+
>mockPayload4 : Symbol(mockPayload4, Decl(dependentReturnType9.ts, 51, 1))
147+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))
148+
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
149+
150+
str: P_TAG,
151+
>str : Symbol(str, Decl(dependentReturnType9.ts, 54, 53))
152+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))
153+
154+
): PayloadContent[P_TAG] {
155+
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
156+
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))
157+
158+
switch (str) {
159+
>str : Symbol(str, Decl(dependentReturnType9.ts, 54, 53))
160+
161+
case "auth":
162+
return { username: "test", password: "admin" };
163+
>username : Symbol(username, Decl(dependentReturnType9.ts, 59, 14))
164+
>password : Symbol(password, Decl(dependentReturnType9.ts, 59, 32))
165+
166+
case "cart":
167+
return { items: [{ id: "123", quantity: 123 }] };
168+
>items : Symbol(items, Decl(dependentReturnType9.ts, 61, 14))
169+
>id : Symbol(id, Decl(dependentReturnType9.ts, 61, 24))
170+
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 61, 35))
171+
}
172+
}
173+

0 commit comments

Comments
 (0)