mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 03:09:39 -06:00
Fix exhaustiveness checking for single-member enums in switch statements
Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
parent
425a2c086d
commit
ca88611465
@ -29772,6 +29772,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return caseType;
|
||||
}
|
||||
const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, t.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(t)), (t1, t2) => isUnitType(t1) && areTypesComparable(t1, t2))));
|
||||
// Allow non-union types to narrow to never in the default case when all values are handled
|
||||
if (!(type.flags & TypeFlags.Union) && isUnitLikeType(type)) {
|
||||
const regularType = type.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(type));
|
||||
if (isUnitType(regularType) && contains(switchTypes, regularType, (t1, t2) => isUnitType(t1) && areTypesComparable(t1, t2))) {
|
||||
return neverType;
|
||||
}
|
||||
}
|
||||
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
|
||||
}
|
||||
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
// Test the issue: Exhaustiveness checking against an enum with 1 member
|
||||
|
||||
enum ActionTypes {
|
||||
INCREMENT = 'INCREMENT',
|
||||
}
|
||||
|
||||
interface IIncrement {
|
||||
payload: {};
|
||||
type: ActionTypes.INCREMENT;
|
||||
}
|
||||
|
||||
type AnyStringExcept<T extends string> = { [P in T]: never; };
|
||||
|
||||
type ValidAction = IIncrement;
|
||||
type UnhandledAction = { type: AnyStringExcept<ActionTypes>; };
|
||||
type PossibleAction = ValidAction | UnhandledAction;
|
||||
|
||||
function isUnhandled(x: PossibleAction): x is UnhandledAction {
|
||||
return !(x.type in ActionTypes);
|
||||
}
|
||||
|
||||
type CounterState = number;
|
||||
const initialState: CounterState = 0;
|
||||
|
||||
function receiveAction(state = initialState, action: PossibleAction) {
|
||||
if (isUnhandled(action)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
// typeof action === ValidAction
|
||||
switch (action.type) {
|
||||
case ActionTypes.INCREMENT:
|
||||
return state + 1;
|
||||
}
|
||||
|
||||
// This should not error - all cases are handled
|
||||
const n: never = action;
|
||||
return state;
|
||||
}
|
||||
|
||||
// Simpler test case from RyanCavanaugh's comment
|
||||
function fn(obj: { name: "bob" }) {
|
||||
if (obj.name == "bob") {
|
||||
// bob case
|
||||
} else {
|
||||
// Should not be an error
|
||||
const n: never = obj;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
exhaustiveSwitchSingleEnumMember.ts(78,9): error TS2322: Type 'MultiMemberEnum.B' is not assignable to type 'never'.
|
||||
|
||||
|
||||
==== exhaustiveSwitchSingleEnumMember.ts (1 errors) ====
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
|
||||
// Single enum member should narrow to never in default case
|
||||
enum SingleMemberEnum {
|
||||
VALUE = 'VALUE'
|
||||
}
|
||||
|
||||
function testSingleEnumExhaustive(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
const n: never = x;
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric enum
|
||||
enum NumericSingleMember {
|
||||
ONE = 1
|
||||
}
|
||||
|
||||
function testNumericSingleEnum(x: NumericSingleMember) {
|
||||
switch (x) {
|
||||
case NumericSingleMember.ONE:
|
||||
return 'one';
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test that non-enum single types also work
|
||||
type SingleLiteral = 'onlyValue';
|
||||
|
||||
function testSingleLiteral(x: SingleLiteral) {
|
||||
switch (x) {
|
||||
case 'onlyValue':
|
||||
return 1;
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
enum MultiMemberEnum {
|
||||
A = 'A',
|
||||
B = 'B'
|
||||
}
|
||||
|
||||
function testMultiEnum(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
case MultiMemberEnum.B:
|
||||
return 2;
|
||||
}
|
||||
// Should narrow to never
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
const n: never = x; // Error expected
|
||||
~
|
||||
!!! error TS2322: Type 'MultiMemberEnum.B' is not assignable to type 'never'.
|
||||
}
|
||||
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
|
||||
163
tests/baselines/reference/exhaustiveSwitchSingleEnumMember.js
Normal file
163
tests/baselines/reference/exhaustiveSwitchSingleEnumMember.js
Normal file
@ -0,0 +1,163 @@
|
||||
//// [tests/cases/compiler/exhaustiveSwitchSingleEnumMember.ts] ////
|
||||
|
||||
//// [exhaustiveSwitchSingleEnumMember.ts]
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
|
||||
// Single enum member should narrow to never in default case
|
||||
enum SingleMemberEnum {
|
||||
VALUE = 'VALUE'
|
||||
}
|
||||
|
||||
function testSingleEnumExhaustive(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
const n: never = x;
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric enum
|
||||
enum NumericSingleMember {
|
||||
ONE = 1
|
||||
}
|
||||
|
||||
function testNumericSingleEnum(x: NumericSingleMember) {
|
||||
switch (x) {
|
||||
case NumericSingleMember.ONE:
|
||||
return 'one';
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test that non-enum single types also work
|
||||
type SingleLiteral = 'onlyValue';
|
||||
|
||||
function testSingleLiteral(x: SingleLiteral) {
|
||||
switch (x) {
|
||||
case 'onlyValue':
|
||||
return 1;
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
enum MultiMemberEnum {
|
||||
A = 'A',
|
||||
B = 'B'
|
||||
}
|
||||
|
||||
function testMultiEnum(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
case MultiMemberEnum.B:
|
||||
return 2;
|
||||
}
|
||||
// Should narrow to never
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
const n: never = x; // Error expected
|
||||
}
|
||||
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
|
||||
|
||||
//// [exhaustiveSwitchSingleEnumMember.js]
|
||||
"use strict";
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
// Single enum member should narrow to never in default case
|
||||
var SingleMemberEnum;
|
||||
(function (SingleMemberEnum) {
|
||||
SingleMemberEnum["VALUE"] = "VALUE";
|
||||
})(SingleMemberEnum || (SingleMemberEnum = {}));
|
||||
function testSingleEnumExhaustive(x) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
var n = x;
|
||||
}
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
var n = x;
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
}
|
||||
// Numeric enum
|
||||
var NumericSingleMember;
|
||||
(function (NumericSingleMember) {
|
||||
NumericSingleMember[NumericSingleMember["ONE"] = 1] = "ONE";
|
||||
})(NumericSingleMember || (NumericSingleMember = {}));
|
||||
function testNumericSingleEnum(x) {
|
||||
switch (x) {
|
||||
case NumericSingleMember.ONE:
|
||||
return 'one';
|
||||
}
|
||||
var n = x;
|
||||
}
|
||||
function testSingleLiteral(x) {
|
||||
switch (x) {
|
||||
case 'onlyValue':
|
||||
return 1;
|
||||
}
|
||||
var n = x;
|
||||
}
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
var MultiMemberEnum;
|
||||
(function (MultiMemberEnum) {
|
||||
MultiMemberEnum["A"] = "A";
|
||||
MultiMemberEnum["B"] = "B";
|
||||
})(MultiMemberEnum || (MultiMemberEnum = {}));
|
||||
function testMultiEnum(x) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
case MultiMemberEnum.B:
|
||||
return 2;
|
||||
}
|
||||
// Should narrow to never
|
||||
var n = x;
|
||||
}
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
var n = x; // Error expected
|
||||
}
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
@ -0,0 +1,173 @@
|
||||
//// [tests/cases/compiler/exhaustiveSwitchSingleEnumMember.ts] ////
|
||||
|
||||
=== exhaustiveSwitchSingleEnumMember.ts ===
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
|
||||
// Single enum member should narrow to never in default case
|
||||
enum SingleMemberEnum {
|
||||
>SingleMemberEnum : Symbol(SingleMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 0, 0))
|
||||
|
||||
VALUE = 'VALUE'
|
||||
>VALUE : Symbol(SingleMemberEnum.VALUE, Decl(exhaustiveSwitchSingleEnumMember.ts, 4, 23))
|
||||
}
|
||||
|
||||
function testSingleEnumExhaustive(x: SingleMemberEnum) {
|
||||
>testSingleEnumExhaustive : Symbol(testSingleEnumExhaustive, Decl(exhaustiveSwitchSingleEnumMember.ts, 6, 1))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 8, 34))
|
||||
>SingleMemberEnum : Symbol(SingleMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 0, 0))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 8, 34))
|
||||
|
||||
case SingleMemberEnum.VALUE:
|
||||
>SingleMemberEnum.VALUE : Symbol(SingleMemberEnum.VALUE, Decl(exhaustiveSwitchSingleEnumMember.ts, 4, 23))
|
||||
>SingleMemberEnum : Symbol(SingleMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 0, 0))
|
||||
>VALUE : Symbol(SingleMemberEnum.VALUE, Decl(exhaustiveSwitchSingleEnumMember.ts, 4, 23))
|
||||
|
||||
return 1;
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
const n: never = x;
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 14, 7))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 8, 34))
|
||||
}
|
||||
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x: SingleMemberEnum) {
|
||||
>testSingleEnumWithDefault : Symbol(testSingleEnumWithDefault, Decl(exhaustiveSwitchSingleEnumMember.ts, 15, 1))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 18, 35))
|
||||
>SingleMemberEnum : Symbol(SingleMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 0, 0))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 18, 35))
|
||||
|
||||
case SingleMemberEnum.VALUE:
|
||||
>SingleMemberEnum.VALUE : Symbol(SingleMemberEnum.VALUE, Decl(exhaustiveSwitchSingleEnumMember.ts, 4, 23))
|
||||
>SingleMemberEnum : Symbol(SingleMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 0, 0))
|
||||
>VALUE : Symbol(SingleMemberEnum.VALUE, Decl(exhaustiveSwitchSingleEnumMember.ts, 4, 23))
|
||||
|
||||
return 1;
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
const n: never = x;
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 24, 11))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 18, 35))
|
||||
|
||||
throw new Error("unreachable");
|
||||
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric enum
|
||||
enum NumericSingleMember {
|
||||
>NumericSingleMember : Symbol(NumericSingleMember, Decl(exhaustiveSwitchSingleEnumMember.ts, 27, 1))
|
||||
|
||||
ONE = 1
|
||||
>ONE : Symbol(NumericSingleMember.ONE, Decl(exhaustiveSwitchSingleEnumMember.ts, 30, 26))
|
||||
}
|
||||
|
||||
function testNumericSingleEnum(x: NumericSingleMember) {
|
||||
>testNumericSingleEnum : Symbol(testNumericSingleEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 32, 1))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 34, 31))
|
||||
>NumericSingleMember : Symbol(NumericSingleMember, Decl(exhaustiveSwitchSingleEnumMember.ts, 27, 1))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 34, 31))
|
||||
|
||||
case NumericSingleMember.ONE:
|
||||
>NumericSingleMember.ONE : Symbol(NumericSingleMember.ONE, Decl(exhaustiveSwitchSingleEnumMember.ts, 30, 26))
|
||||
>NumericSingleMember : Symbol(NumericSingleMember, Decl(exhaustiveSwitchSingleEnumMember.ts, 27, 1))
|
||||
>ONE : Symbol(NumericSingleMember.ONE, Decl(exhaustiveSwitchSingleEnumMember.ts, 30, 26))
|
||||
|
||||
return 'one';
|
||||
}
|
||||
const n: never = x;
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 39, 7))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 34, 31))
|
||||
}
|
||||
|
||||
// Test that non-enum single types also work
|
||||
type SingleLiteral = 'onlyValue';
|
||||
>SingleLiteral : Symbol(SingleLiteral, Decl(exhaustiveSwitchSingleEnumMember.ts, 40, 1))
|
||||
|
||||
function testSingleLiteral(x: SingleLiteral) {
|
||||
>testSingleLiteral : Symbol(testSingleLiteral, Decl(exhaustiveSwitchSingleEnumMember.ts, 43, 33))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 45, 27))
|
||||
>SingleLiteral : Symbol(SingleLiteral, Decl(exhaustiveSwitchSingleEnumMember.ts, 40, 1))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 45, 27))
|
||||
|
||||
case 'onlyValue':
|
||||
return 1;
|
||||
}
|
||||
const n: never = x;
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 50, 7))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 45, 27))
|
||||
}
|
||||
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
enum MultiMemberEnum {
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
|
||||
A = 'A',
|
||||
>A : Symbol(MultiMemberEnum.A, Decl(exhaustiveSwitchSingleEnumMember.ts, 54, 22))
|
||||
|
||||
B = 'B'
|
||||
>B : Symbol(MultiMemberEnum.B, Decl(exhaustiveSwitchSingleEnumMember.ts, 55, 10))
|
||||
}
|
||||
|
||||
function testMultiEnum(x: MultiMemberEnum) {
|
||||
>testMultiEnum : Symbol(testMultiEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 57, 1))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 59, 23))
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 59, 23))
|
||||
|
||||
case MultiMemberEnum.A:
|
||||
>MultiMemberEnum.A : Symbol(MultiMemberEnum.A, Decl(exhaustiveSwitchSingleEnumMember.ts, 54, 22))
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
>A : Symbol(MultiMemberEnum.A, Decl(exhaustiveSwitchSingleEnumMember.ts, 54, 22))
|
||||
|
||||
return 1;
|
||||
case MultiMemberEnum.B:
|
||||
>MultiMemberEnum.B : Symbol(MultiMemberEnum.B, Decl(exhaustiveSwitchSingleEnumMember.ts, 55, 10))
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
>B : Symbol(MultiMemberEnum.B, Decl(exhaustiveSwitchSingleEnumMember.ts, 55, 10))
|
||||
|
||||
return 2;
|
||||
}
|
||||
// Should narrow to never
|
||||
const n: never = x;
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 67, 7))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 59, 23))
|
||||
}
|
||||
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x: MultiMemberEnum) {
|
||||
>testIncomplete : Symbol(testIncomplete, Decl(exhaustiveSwitchSingleEnumMember.ts, 68, 1))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 71, 24))
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
|
||||
switch (x) {
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 71, 24))
|
||||
|
||||
case MultiMemberEnum.A:
|
||||
>MultiMemberEnum.A : Symbol(MultiMemberEnum.A, Decl(exhaustiveSwitchSingleEnumMember.ts, 54, 22))
|
||||
>MultiMemberEnum : Symbol(MultiMemberEnum, Decl(exhaustiveSwitchSingleEnumMember.ts, 51, 1))
|
||||
>A : Symbol(MultiMemberEnum.A, Decl(exhaustiveSwitchSingleEnumMember.ts, 54, 22))
|
||||
|
||||
return 1;
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
const n: never = x; // Error expected
|
||||
>n : Symbol(n, Decl(exhaustiveSwitchSingleEnumMember.ts, 77, 7))
|
||||
>x : Symbol(x, Decl(exhaustiveSwitchSingleEnumMember.ts, 71, 24))
|
||||
}
|
||||
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
|
||||
255
tests/baselines/reference/exhaustiveSwitchSingleEnumMember.types
Normal file
255
tests/baselines/reference/exhaustiveSwitchSingleEnumMember.types
Normal file
@ -0,0 +1,255 @@
|
||||
//// [tests/cases/compiler/exhaustiveSwitchSingleEnumMember.ts] ////
|
||||
|
||||
=== exhaustiveSwitchSingleEnumMember.ts ===
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
|
||||
// Single enum member should narrow to never in default case
|
||||
enum SingleMemberEnum {
|
||||
>SingleMemberEnum : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
VALUE = 'VALUE'
|
||||
>VALUE : SingleMemberEnum.VALUE
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^
|
||||
>'VALUE' : "VALUE"
|
||||
> : ^^^^^^^
|
||||
}
|
||||
|
||||
function testSingleEnumExhaustive(x: SingleMemberEnum) {
|
||||
>testSingleEnumExhaustive : (x: SingleMemberEnum) => number
|
||||
> : ^ ^^ ^^^^^^^^^^^
|
||||
>x : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
case SingleMemberEnum.VALUE:
|
||||
>SingleMemberEnum.VALUE : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
>SingleMemberEnum : typeof SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>VALUE : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
return 1;
|
||||
>1 : 1
|
||||
> : ^
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
const n: never = x;
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : never
|
||||
> : ^^^^^
|
||||
}
|
||||
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x: SingleMemberEnum) {
|
||||
>testSingleEnumWithDefault : (x: SingleMemberEnum) => number
|
||||
> : ^ ^^ ^^^^^^^^^^^
|
||||
>x : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
case SingleMemberEnum.VALUE:
|
||||
>SingleMemberEnum.VALUE : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
>SingleMemberEnum : typeof SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>VALUE : SingleMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
|
||||
return 1;
|
||||
>1 : 1
|
||||
> : ^
|
||||
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
const n: never = x;
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : never
|
||||
> : ^^^^^
|
||||
|
||||
throw new Error("unreachable");
|
||||
>new Error("unreachable") : Error
|
||||
> : ^^^^^
|
||||
>Error : ErrorConstructor
|
||||
> : ^^^^^^^^^^^^^^^^
|
||||
>"unreachable" : "unreachable"
|
||||
> : ^^^^^^^^^^^^^
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric enum
|
||||
enum NumericSingleMember {
|
||||
>NumericSingleMember : NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ONE = 1
|
||||
>ONE : NumericSingleMember.ONE
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>1 : 1
|
||||
> : ^
|
||||
}
|
||||
|
||||
function testNumericSingleEnum(x: NumericSingleMember) {
|
||||
>testNumericSingleEnum : (x: NumericSingleMember) => string
|
||||
> : ^ ^^ ^^^^^^^^^^^
|
||||
>x : NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
case NumericSingleMember.ONE:
|
||||
>NumericSingleMember.ONE : NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
>NumericSingleMember : typeof NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
>ONE : NumericSingleMember
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
return 'one';
|
||||
>'one' : "one"
|
||||
> : ^^^^^
|
||||
}
|
||||
const n: never = x;
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : never
|
||||
> : ^^^^^
|
||||
}
|
||||
|
||||
// Test that non-enum single types also work
|
||||
type SingleLiteral = 'onlyValue';
|
||||
>SingleLiteral : "onlyValue"
|
||||
> : ^^^^^^^^^^^
|
||||
|
||||
function testSingleLiteral(x: SingleLiteral) {
|
||||
>testSingleLiteral : (x: SingleLiteral) => number
|
||||
> : ^ ^^ ^^^^^^^^^^^
|
||||
>x : "onlyValue"
|
||||
> : ^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : "onlyValue"
|
||||
> : ^^^^^^^^^^^
|
||||
|
||||
case 'onlyValue':
|
||||
>'onlyValue' : "onlyValue"
|
||||
> : ^^^^^^^^^^^
|
||||
|
||||
return 1;
|
||||
>1 : 1
|
||||
> : ^
|
||||
}
|
||||
const n: never = x;
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : never
|
||||
> : ^^^^^
|
||||
}
|
||||
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
enum MultiMemberEnum {
|
||||
>MultiMemberEnum : MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^
|
||||
|
||||
A = 'A',
|
||||
>A : MultiMemberEnum.A
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
>'A' : "A"
|
||||
> : ^^^
|
||||
|
||||
B = 'B'
|
||||
>B : MultiMemberEnum.B
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
>'B' : "B"
|
||||
> : ^^^
|
||||
}
|
||||
|
||||
function testMultiEnum(x: MultiMemberEnum) {
|
||||
>testMultiEnum : (x: MultiMemberEnum) => 1 | 2
|
||||
> : ^ ^^ ^^^^^^^^^^
|
||||
>x : MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^
|
||||
|
||||
case MultiMemberEnum.A:
|
||||
>MultiMemberEnum.A : MultiMemberEnum.A
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
>MultiMemberEnum : typeof MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^
|
||||
>A : MultiMemberEnum.A
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
|
||||
return 1;
|
||||
>1 : 1
|
||||
> : ^
|
||||
|
||||
case MultiMemberEnum.B:
|
||||
>MultiMemberEnum.B : MultiMemberEnum.B
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
>MultiMemberEnum : typeof MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^
|
||||
>B : MultiMemberEnum.B
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
|
||||
return 2;
|
||||
>2 : 2
|
||||
> : ^
|
||||
}
|
||||
// Should narrow to never
|
||||
const n: never = x;
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : never
|
||||
> : ^^^^^
|
||||
}
|
||||
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x: MultiMemberEnum) {
|
||||
>testIncomplete : (x: MultiMemberEnum) => 1 | undefined
|
||||
> : ^ ^^ ^^^^^^^^^^^^^^^^^^
|
||||
>x : MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^
|
||||
|
||||
switch (x) {
|
||||
>x : MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^
|
||||
|
||||
case MultiMemberEnum.A:
|
||||
>MultiMemberEnum.A : MultiMemberEnum.A
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
>MultiMemberEnum : typeof MultiMemberEnum
|
||||
> : ^^^^^^^^^^^^^^^^^^^^^^
|
||||
>A : MultiMemberEnum.A
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
|
||||
return 1;
|
||||
>1 : 1
|
||||
> : ^
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
const n: never = x; // Error expected
|
||||
>n : never
|
||||
> : ^^^^^
|
||||
>x : MultiMemberEnum.B
|
||||
> : ^^^^^^^^^^^^^^^^^
|
||||
}
|
||||
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
|
||||
84
tests/cases/compiler/exhaustiveSwitchSingleEnumMember.ts
Normal file
84
tests/cases/compiler/exhaustiveSwitchSingleEnumMember.ts
Normal file
@ -0,0 +1,84 @@
|
||||
// @strict: true
|
||||
// Test exhaustiveness checking for single-member enums
|
||||
// Repro for #23155
|
||||
|
||||
// Single enum member should narrow to never in default case
|
||||
enum SingleMemberEnum {
|
||||
VALUE = 'VALUE'
|
||||
}
|
||||
|
||||
function testSingleEnumExhaustive(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
}
|
||||
// x should be narrowed to never here
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// With explicit default clause
|
||||
function testSingleEnumWithDefault(x: SingleMemberEnum) {
|
||||
switch (x) {
|
||||
case SingleMemberEnum.VALUE:
|
||||
return 1;
|
||||
default:
|
||||
// x should be narrowed to never in default
|
||||
const n: never = x;
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
// Numeric enum
|
||||
enum NumericSingleMember {
|
||||
ONE = 1
|
||||
}
|
||||
|
||||
function testNumericSingleEnum(x: NumericSingleMember) {
|
||||
switch (x) {
|
||||
case NumericSingleMember.ONE:
|
||||
return 'one';
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test that non-enum single types also work
|
||||
type SingleLiteral = 'onlyValue';
|
||||
|
||||
function testSingleLiteral(x: SingleLiteral) {
|
||||
switch (x) {
|
||||
case 'onlyValue':
|
||||
return 1;
|
||||
}
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Ensure unions still work correctly (existing behavior)
|
||||
enum MultiMemberEnum {
|
||||
A = 'A',
|
||||
B = 'B'
|
||||
}
|
||||
|
||||
function testMultiEnum(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
case MultiMemberEnum.B:
|
||||
return 2;
|
||||
}
|
||||
// Should narrow to never
|
||||
const n: never = x;
|
||||
}
|
||||
|
||||
// Test incomplete coverage - should error
|
||||
function testIncomplete(x: MultiMemberEnum) {
|
||||
switch (x) {
|
||||
case MultiMemberEnum.A:
|
||||
return 1;
|
||||
}
|
||||
// Should NOT narrow to never - B is not handled
|
||||
const n: never = x; // Error expected
|
||||
}
|
||||
|
||||
// Note: Discriminated union narrowing for single-member types is a more complex case
|
||||
// that involves property access narrowing, not just direct value narrowing.
|
||||
// This test focuses on direct value narrowing.
|
||||
Loading…
x
Reference in New Issue
Block a user