Add narrowing for non-union types in narrowTypeByDiscriminant

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-16 19:29:12 +00:00
parent ca88611465
commit 90c2555488
6 changed files with 30 additions and 20 deletions

View File

@ -29459,6 +29459,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
propType = removeNullable && optionalChain ? getOptionalType(propType) : propType;
const narrowedPropType = narrowType(propType);
// If the narrowed property type is never and the type is not a union, return never
// This handles cases where a non-union type has a single discriminant value that's been exhausted
if (narrowedPropType.flags & TypeFlags.Never && !(type.flags & TypeFlags.Union)) {
return neverType;
}
return filterType(type, t => {
const discriminantType = getTypeOfPropertyOrIndexSignatureOfType(t, propName) || unknownType;
return !(discriminantType.flags & TypeFlags.Never) && !(narrowedPropType.flags & TypeFlags.Never) && areTypesComparable(narrowedPropType, discriminantType);
@ -29772,13 +29777,24 @@ 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)) {
// Allow non-union types to narrow to never when all values are handled
// This applies when caseType is never (meaning we're in a default-like position)
if (caseType.flags & TypeFlags.Never && !(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;
}
}
// Also handle single-member unions
if (caseType.flags & TypeFlags.Never && type.flags & TypeFlags.Union && (type as UnionType).types.length === 1) {
const singleType = (type as UnionType).types[0];
if (isUnitLikeType(singleType)) {
const regularType = singleType.flags & TypeFlags.Undefined ? undefinedType : getRegularTypeOfLiteralType(extractUnitType(singleType));
if (isUnitType(regularType) && contains(switchTypes, regularType, (t1, t2) => isUnitType(t1) && areTypesComparable(t1, t2))) {
return neverType;
}
}
}
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
}

View File

@ -84,7 +84,6 @@ exhaustiveSwitchSingleEnumMember.ts(78,9): error TS2322: Type 'MultiMemberEnum.B
!!! 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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.

View File

@ -81,9 +81,8 @@ function testIncomplete(x: MultiMemberEnum) {
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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.
//// [exhaustiveSwitchSingleEnumMember.js]
@ -158,6 +157,5 @@ function testIncomplete(x) {
// 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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.

View File

@ -167,7 +167,6 @@ function testIncomplete(x: MultiMemberEnum) {
>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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.

View File

@ -249,7 +249,6 @@ function testIncomplete(x: MultiMemberEnum) {
> : ^^^^^^^^^^^^^^^^^
}
// 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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.

View File

@ -79,6 +79,5 @@ function testIncomplete(x: MultiMemberEnum) {
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.
// Note: Discriminated union narrowing for single-member types requires
// narrowing through property access, which is more complex and not yet implemented.