Use comparability for discriminant properties when narrowing types for a default switch clause (#61211)

This commit is contained in:
Mateusz Burzyński 2025-10-30 15:20:13 +01:00 committed by GitHub
parent 2a90a739c1
commit 6fd2874d0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 540 additions and 1 deletions

View File

@ -29739,7 +29739,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!hasDefaultClause) {
return caseType;
}
const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, t.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(t)))));
const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, t.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(t)), (t1, t2) => isUnitType(t1) && areTypesComparable(t1, t2))));
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
}

View File

@ -0,0 +1,180 @@
//// [tests/cases/conformance/types/union/discriminatedUnionTypes4.ts] ////
=== discriminatedUnionTypes4.ts ===
// https://github.com/microsoft/TypeScript/issues/61207
enum AnimalType {
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
cat = "cat",
>cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
dog = "dog",
>dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
}
type Animal =
>Animal : Symbol(Animal, Decl(discriminatedUnionTypes4.ts, 5, 1))
| {
type: `${AnimalType.cat}`;
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
meow: string;
>meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
}
| {
type: `${AnimalType.dog}`;
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 12, 5))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
bark: string;
>bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
};
function check(p: never) {
>check : Symbol(check, Decl(discriminatedUnionTypes4.ts, 15, 6))
>p : Symbol(p, Decl(discriminatedUnionTypes4.ts, 17, 15))
throw new Error("Error!");
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
}
function action1(animal: Animal) {
>action1 : Symbol(action1, Decl(discriminatedUnionTypes4.ts, 19, 1))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
>Animal : Symbol(Animal, Decl(discriminatedUnionTypes4.ts, 5, 1))
if (animal.type === AnimalType.cat) {
>animal.type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
>AnimalType.cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
console.log(animal.meow);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
>meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
} else if (animal.type === AnimalType.dog) {
>animal.type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 12, 5))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 12, 5))
>AnimalType.dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
console.log(animal.bark);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
>bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
} else {
check(animal);
>check : Symbol(check, Decl(discriminatedUnionTypes4.ts, 15, 6))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 21, 17))
}
}
function action2(animal: Animal) {
>action2 : Symbol(action2, Decl(discriminatedUnionTypes4.ts, 29, 1))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 31, 17))
>Animal : Symbol(Animal, Decl(discriminatedUnionTypes4.ts, 5, 1))
switch (animal.type) {
>animal.type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 31, 17))
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
case `${AnimalType.cat}`:
>AnimalType.cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
console.log(animal.meow);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 31, 17))
>meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
break;
case `${AnimalType.dog}`:
>AnimalType.dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
console.log(animal.bark);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 31, 17))
>bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
break;
default:
check(animal);
>check : Symbol(check, Decl(discriminatedUnionTypes4.ts, 15, 6))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 31, 17))
}
}
function action3(animal: Animal) {
>action3 : Symbol(action3, Decl(discriminatedUnionTypes4.ts, 42, 1))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 44, 17))
>Animal : Symbol(Animal, Decl(discriminatedUnionTypes4.ts, 5, 1))
switch (animal.type) {
>animal.type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 44, 17))
>type : Symbol(type, Decl(discriminatedUnionTypes4.ts, 8, 5), Decl(discriminatedUnionTypes4.ts, 12, 5))
case AnimalType.cat:
>AnimalType.cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>cat : Symbol(AnimalType.cat, Decl(discriminatedUnionTypes4.ts, 2, 17))
console.log(animal.meow);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 44, 17))
>meow : Symbol(meow, Decl(discriminatedUnionTypes4.ts, 9, 32))
break;
case AnimalType.dog:
>AnimalType.dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
>AnimalType : Symbol(AnimalType, Decl(discriminatedUnionTypes4.ts, 0, 0))
>dog : Symbol(AnimalType.dog, Decl(discriminatedUnionTypes4.ts, 3, 14))
console.log(animal.bark);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>animal.bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 44, 17))
>bark : Symbol(bark, Decl(discriminatedUnionTypes4.ts, 13, 32))
break;
default:
check(animal);
>check : Symbol(check, Decl(discriminatedUnionTypes4.ts, 15, 6))
>animal : Symbol(animal, Decl(discriminatedUnionTypes4.ts, 44, 17))
}
}

View File

@ -0,0 +1,300 @@
//// [tests/cases/conformance/types/union/discriminatedUnionTypes4.ts] ////
=== discriminatedUnionTypes4.ts ===
// https://github.com/microsoft/TypeScript/issues/61207
enum AnimalType {
>AnimalType : AnimalType
> : ^^^^^^^^^^
cat = "cat",
>cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
>"cat" : "cat"
> : ^^^^^
dog = "dog",
>dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
>"dog" : "dog"
> : ^^^^^
}
type Animal =
>Animal : Animal
> : ^^^^^^
| {
type: `${AnimalType.cat}`;
>type : "cat"
> : ^^^^^
>AnimalType : any
> : ^^^
meow: string;
>meow : string
> : ^^^^^^
}
| {
type: `${AnimalType.dog}`;
>type : "dog"
> : ^^^^^
>AnimalType : any
> : ^^^
bark: string;
>bark : string
> : ^^^^^^
};
function check(p: never) {
>check : (p: never) => void
> : ^ ^^ ^^^^^^^^^
>p : never
> : ^^^^^
throw new Error("Error!");
>new Error("Error!") : Error
> : ^^^^^
>Error : ErrorConstructor
> : ^^^^^^^^^^^^^^^^
>"Error!" : "Error!"
> : ^^^^^^^^
}
function action1(animal: Animal) {
>action1 : (animal: Animal) => void
> : ^ ^^ ^^^^^^^^^
>animal : Animal
> : ^^^^^^
if (animal.type === AnimalType.cat) {
>animal.type === AnimalType.cat : boolean
> : ^^^^^^^
>animal.type : "cat" | "dog"
> : ^^^^^^^^^^^^^
>animal : Animal
> : ^^^^^^
>type : "cat" | "dog"
> : ^^^^^^^^^^^^^
>AnimalType.cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
console.log(animal.meow);
>console.log(animal.meow) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.meow : string
> : ^^^^^^
>animal : { type: `${AnimalType.cat}`; meow: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>meow : string
> : ^^^^^^
} else if (animal.type === AnimalType.dog) {
>animal.type === AnimalType.dog : boolean
> : ^^^^^^^
>animal.type : "dog"
> : ^^^^^
>animal : { type: `${AnimalType.dog}`; bark: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>type : "dog"
> : ^^^^^
>AnimalType.dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
console.log(animal.bark);
>console.log(animal.bark) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.bark : string
> : ^^^^^^
>animal : { type: `${AnimalType.dog}`; bark: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>bark : string
> : ^^^^^^
} else {
check(animal);
>check(animal) : void
> : ^^^^
>check : (p: never) => void
> : ^ ^^ ^^^^^^^^^
>animal : never
> : ^^^^^
}
}
function action2(animal: Animal) {
>action2 : (animal: Animal) => void
> : ^ ^^ ^^^^^^^^^
>animal : Animal
> : ^^^^^^
switch (animal.type) {
>animal.type : "cat" | "dog"
> : ^^^^^^^^^^^^^
>animal : Animal
> : ^^^^^^
>type : "cat" | "dog"
> : ^^^^^^^^^^^^^
case `${AnimalType.cat}`:
>`${AnimalType.cat}` : "cat"
> : ^^^^^
>AnimalType.cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
console.log(animal.meow);
>console.log(animal.meow) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.meow : string
> : ^^^^^^
>animal : { type: `${AnimalType.cat}`; meow: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>meow : string
> : ^^^^^^
break;
case `${AnimalType.dog}`:
>`${AnimalType.dog}` : "dog"
> : ^^^^^
>AnimalType.dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
console.log(animal.bark);
>console.log(animal.bark) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.bark : string
> : ^^^^^^
>animal : { type: `${AnimalType.dog}`; bark: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>bark : string
> : ^^^^^^
break;
default:
check(animal);
>check(animal) : void
> : ^^^^
>check : (p: never) => void
> : ^ ^^ ^^^^^^^^^
>animal : never
> : ^^^^^
}
}
function action3(animal: Animal) {
>action3 : (animal: Animal) => void
> : ^ ^^ ^^^^^^^^^
>animal : Animal
> : ^^^^^^
switch (animal.type) {
>animal.type : "cat" | "dog"
> : ^^^^^^^^^^^^^
>animal : Animal
> : ^^^^^^
>type : "cat" | "dog"
> : ^^^^^^^^^^^^^
case AnimalType.cat:
>AnimalType.cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>cat : AnimalType.cat
> : ^^^^^^^^^^^^^^
console.log(animal.meow);
>console.log(animal.meow) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.meow : string
> : ^^^^^^
>animal : { type: `${AnimalType.cat}`; meow: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>meow : string
> : ^^^^^^
break;
case AnimalType.dog:
>AnimalType.dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
>AnimalType : typeof AnimalType
> : ^^^^^^^^^^^^^^^^^
>dog : AnimalType.dog
> : ^^^^^^^^^^^^^^
console.log(animal.bark);
>console.log(animal.bark) : void
> : ^^^^
>console.log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>console : Console
> : ^^^^^^^
>log : (...data: any[]) => void
> : ^^^^ ^^ ^^^^^
>animal.bark : string
> : ^^^^^^
>animal : { type: `${AnimalType.dog}`; bark: string; }
> : ^^^^^^^^ ^^^^^^^^ ^^^
>bark : string
> : ^^^^^^
break;
default:
check(animal);
>check(animal) : void
> : ^^^^
>check : (p: never) => void
> : ^ ^^ ^^^^^^^^^
>animal : never
> : ^^^^^
}
}

View File

@ -0,0 +1,59 @@
// @strict: true
// @noEmit: true
// https://github.com/microsoft/TypeScript/issues/61207
enum AnimalType {
cat = "cat",
dog = "dog",
}
type Animal =
| {
type: `${AnimalType.cat}`;
meow: string;
}
| {
type: `${AnimalType.dog}`;
bark: string;
};
function check(p: never) {
throw new Error("Error!");
}
function action1(animal: Animal) {
if (animal.type === AnimalType.cat) {
console.log(animal.meow);
} else if (animal.type === AnimalType.dog) {
console.log(animal.bark);
} else {
check(animal);
}
}
function action2(animal: Animal) {
switch (animal.type) {
case `${AnimalType.cat}`:
console.log(animal.meow);
break;
case `${AnimalType.dog}`:
console.log(animal.bark);
break;
default:
check(animal);
}
}
function action3(animal: Animal) {
switch (animal.type) {
case AnimalType.cat:
console.log(animal.meow);
break;
case AnimalType.dog:
console.log(animal.bark);
break;
default:
check(animal);
}
}