Skip to content

Commit

Permalink
Check if switch statements are exhaustive when their expressions is g…
Browse files Browse the repository at this point in the history
…eneric with a literal type constraint (#60644)
  • Loading branch information
Andarist authored Dec 2, 2024
1 parent 9717772 commit 14c65b3
Show file tree
Hide file tree
Showing 5 changed files with 618 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38682,7 +38682,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// A missing not-equal flag indicates that the type wasn't handled by some case.
return !someType(operandConstraint, t => getTypeFacts(t, notEqualFacts) === notEqualFacts);
}
const type = checkExpressionCached(node.expression);
const type = getBaseConstraintOrType(checkExpressionCached(node.expression));
if (!isLiteralType(type)) {
return false;
}
Expand Down
71 changes: 71 additions & 0 deletions tests/baselines/reference/dependentReturnType9.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
dependentReturnType9.ts(57,4): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.


==== dependentReturnType9.ts (1 errors) ====
type Payload =
| { _tag: "auth"; username: string; password: string }
| { _tag: "cart"; items: Array<{ id: string; quantity: number }> }
| { _tag: "person"; name: string; age: number };

type PayloadContent = {
[P in Payload as P["_tag"]]: Omit<P, "_tag">;
};

// ok, exhaustive cases and default case with throw
function mockPayload<P_TAG extends Payload["_tag"]>(
str: P_TAG,
): PayloadContent[P_TAG] {
switch (str) {
case "auth":
return { username: "test", password: "admin" };
case "cart":
return { items: [{ id: "123", quantity: 123 }] };
case "person":
return { name: "andrea", age: 27 };
default:
throw new Error("unknown tag");
}
}

// ok, non-exhaustive cases but default case with throw
function mockPayload2<P_TAG extends Payload["_tag"]>(
str: P_TAG,
): PayloadContent[P_TAG] {
switch (str) {
case "auth":
return { username: "test", password: "admin" };
case "cart":
return { items: [{ id: "123", quantity: 123 }] };
default:
throw new Error("unhandled tag");
}
}

// ok, exhaustive cases
function mockPayload3<P_TAG extends Payload["_tag"]>(
str: P_TAG,
): PayloadContent[P_TAG] {
switch (str) {
case "auth":
return { username: "test", password: "admin" };
case "cart":
return { items: [{ id: "123", quantity: 123 }] };
case "person":
return { name: "andrea", age: 27 };
}
}

// error, non-exhaustive cases
function mockPayload4<P_TAG extends Payload["_tag"]>(
str: P_TAG,
): PayloadContent[P_TAG] {
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
switch (str) {
case "auth":
return { username: "test", password: "admin" };
case "cart":
return { items: [{ id: "123", quantity: 123 }] };
}
}

173 changes: 173 additions & 0 deletions tests/baselines/reference/dependentReturnType9.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//// [tests/cases/compiler/dependentReturnType9.ts] ////

=== dependentReturnType9.ts ===
type Payload =
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))

| { _tag: "auth"; username: string; password: string }
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 1, 5))
>username : Symbol(username, Decl(dependentReturnType9.ts, 1, 19))
>password : Symbol(password, Decl(dependentReturnType9.ts, 1, 37))

| { _tag: "cart"; items: Array<{ id: string; quantity: number }> }
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 2, 5))
>items : Symbol(items, Decl(dependentReturnType9.ts, 2, 19))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>id : Symbol(id, Decl(dependentReturnType9.ts, 2, 34))
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 2, 46))

| { _tag: "person"; name: string; age: number };
>_tag : Symbol(_tag, Decl(dependentReturnType9.ts, 3, 5))
>name : Symbol(name, Decl(dependentReturnType9.ts, 3, 21))
>age : Symbol(age, Decl(dependentReturnType9.ts, 3, 35))

type PayloadContent = {
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))

[P in Payload as P["_tag"]]: Omit<P, "_tag">;
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))
>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --))
>P : Symbol(P, Decl(dependentReturnType9.ts, 6, 3))

};

// ok, exhaustive cases and default case with throw
function mockPayload<P_TAG extends Payload["_tag"]>(
>mockPayload : Symbol(mockPayload, Decl(dependentReturnType9.ts, 7, 2))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))

str: P_TAG,
>str : Symbol(str, Decl(dependentReturnType9.ts, 10, 52))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))

): PayloadContent[P_TAG] {
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 10, 21))

switch (str) {
>str : Symbol(str, Decl(dependentReturnType9.ts, 10, 52))

case "auth":
return { username: "test", password: "admin" };
>username : Symbol(username, Decl(dependentReturnType9.ts, 15, 14))
>password : Symbol(password, Decl(dependentReturnType9.ts, 15, 32))

case "cart":
return { items: [{ id: "123", quantity: 123 }] };
>items : Symbol(items, Decl(dependentReturnType9.ts, 17, 14))
>id : Symbol(id, Decl(dependentReturnType9.ts, 17, 24))
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 17, 35))

case "person":
return { name: "andrea", age: 27 };
>name : Symbol(name, Decl(dependentReturnType9.ts, 19, 14))
>age : Symbol(age, Decl(dependentReturnType9.ts, 19, 30))

default:
throw new Error("unknown tag");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

// ok, non-exhaustive cases but default case with throw
function mockPayload2<P_TAG extends Payload["_tag"]>(
>mockPayload2 : Symbol(mockPayload2, Decl(dependentReturnType9.ts, 23, 1))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))

str: P_TAG,
>str : Symbol(str, Decl(dependentReturnType9.ts, 26, 53))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))

): PayloadContent[P_TAG] {
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 26, 22))

switch (str) {
>str : Symbol(str, Decl(dependentReturnType9.ts, 26, 53))

case "auth":
return { username: "test", password: "admin" };
>username : Symbol(username, Decl(dependentReturnType9.ts, 31, 14))
>password : Symbol(password, Decl(dependentReturnType9.ts, 31, 32))

case "cart":
return { items: [{ id: "123", quantity: 123 }] };
>items : Symbol(items, Decl(dependentReturnType9.ts, 33, 14))
>id : Symbol(id, Decl(dependentReturnType9.ts, 33, 24))
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 33, 35))

default:
throw new Error("unhandled tag");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
}

// ok, exhaustive cases
function mockPayload3<P_TAG extends Payload["_tag"]>(
>mockPayload3 : Symbol(mockPayload3, Decl(dependentReturnType9.ts, 37, 1))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))

str: P_TAG,
>str : Symbol(str, Decl(dependentReturnType9.ts, 40, 53))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))

): PayloadContent[P_TAG] {
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 40, 22))

switch (str) {
>str : Symbol(str, Decl(dependentReturnType9.ts, 40, 53))

case "auth":
return { username: "test", password: "admin" };
>username : Symbol(username, Decl(dependentReturnType9.ts, 45, 14))
>password : Symbol(password, Decl(dependentReturnType9.ts, 45, 32))

case "cart":
return { items: [{ id: "123", quantity: 123 }] };
>items : Symbol(items, Decl(dependentReturnType9.ts, 47, 14))
>id : Symbol(id, Decl(dependentReturnType9.ts, 47, 24))
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 47, 35))

case "person":
return { name: "andrea", age: 27 };
>name : Symbol(name, Decl(dependentReturnType9.ts, 49, 14))
>age : Symbol(age, Decl(dependentReturnType9.ts, 49, 30))
}
}

// error, non-exhaustive cases
function mockPayload4<P_TAG extends Payload["_tag"]>(
>mockPayload4 : Symbol(mockPayload4, Decl(dependentReturnType9.ts, 51, 1))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))
>Payload : Symbol(Payload, Decl(dependentReturnType9.ts, 0, 0))

str: P_TAG,
>str : Symbol(str, Decl(dependentReturnType9.ts, 54, 53))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))

): PayloadContent[P_TAG] {
>PayloadContent : Symbol(PayloadContent, Decl(dependentReturnType9.ts, 3, 50))
>P_TAG : Symbol(P_TAG, Decl(dependentReturnType9.ts, 54, 22))

switch (str) {
>str : Symbol(str, Decl(dependentReturnType9.ts, 54, 53))

case "auth":
return { username: "test", password: "admin" };
>username : Symbol(username, Decl(dependentReturnType9.ts, 59, 14))
>password : Symbol(password, Decl(dependentReturnType9.ts, 59, 32))

case "cart":
return { items: [{ id: "123", quantity: 123 }] };
>items : Symbol(items, Decl(dependentReturnType9.ts, 61, 14))
>id : Symbol(id, Decl(dependentReturnType9.ts, 61, 24))
>quantity : Symbol(quantity, Decl(dependentReturnType9.ts, 61, 35))
}
}

Loading

0 comments on commit 14c65b3

Please sign in to comment.