From 9458f8acabedd00b034d0bc25da0121b3885a89b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 29 Jun 2020 15:25:00 -0700 Subject: [PATCH] Consistent narrowing to 'never' in conditional and switch statements (#39191) * Allow unions and unit types to narrow to 'never' * Remove odd check for TypeFlags.NotUnionOrUnit * Accept new baselines * Accept new API baselines --- src/compiler/checker.ts | 12 +++-------- src/compiler/types.ts | 1 - .../reference/api/tsserverlibrary.d.ts | 1 - tests/baselines/reference/api/typescript.d.ts | 1 - .../reference/discriminatedUnionTypes1.types | 2 +- ...equalityWithIntersectionTypes01.errors.txt | 17 +--------------- .../equalityWithIntersectionTypes01.types | 20 +++++++++---------- 7 files changed, 15 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f702e84f735..3ad58313c4c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20580,7 +20580,7 @@ namespace ts { } function createFlowType(type: Type, incomplete: boolean): FlowType { - return incomplete ? { flags: 0, type } : type; + return incomplete ? { flags: 0, type: type.flags & TypeFlags.Never ? silentNeverType : type } : type; } // An evolving array type tracks the element types that have so far been seen in an @@ -21168,9 +21168,7 @@ namespace ts { if (narrowedType === nonEvolvingType) { return flowType; } - const incomplete = isIncomplete(flowType); - const resultType = incomplete && narrowedType.flags & TypeFlags.Never ? silentNeverType : narrowedType; - return createFlowType(resultType, incomplete); + return createFlowType(narrowedType, isIncomplete(flowType)); } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { @@ -21508,15 +21506,11 @@ namespace ts { assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; return getTypeWithFacts(type, facts); } - if (type.flags & TypeFlags.NotUnionOrUnit) { - return type; - } if (assumeTrue) { const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? (t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType)) : t => areTypesComparable(t, valueType); - const narrowedType = filterType(type, filterFn); - return narrowedType.flags & TypeFlags.Never ? type : replacePrimitivesWithLiterals(narrowedType, valueType); + return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType); } if (isUnitType(valueType)) { const regularType = getRegularTypeOfLiteralType(valueType); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 05ad94c7599..ce1162933e4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4861,7 +4861,6 @@ namespace ts { // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, - NotUnionOrUnit = Any | Unknown | ESSymbol | Object | NonPrimitive, /* @internal */ NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | StructuredOrInstantiable, // The following flags are aggregated during union and intersection type construction diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 72100cfc289..e19dcd382c2 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2459,7 +2459,6 @@ declare namespace ts { Instantiable = 63176704, StructuredOrInstantiable = 66846720, Narrowable = 133970943, - NotUnionOrUnit = 67637251, } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; export interface Type { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8ed116b6b1d..aa014fb4584 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2459,7 +2459,6 @@ declare namespace ts { Instantiable = 63176704, StructuredOrInstantiable = 66846720, Narrowable = 133970943, - NotUnionOrUnit = 67637251, } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; export interface Type { diff --git a/tests/baselines/reference/discriminatedUnionTypes1.types b/tests/baselines/reference/discriminatedUnionTypes1.types index 462d8a23432..7c8c6c48171 100644 --- a/tests/baselines/reference/discriminatedUnionTypes1.types +++ b/tests/baselines/reference/discriminatedUnionTypes1.types @@ -320,7 +320,7 @@ function f3(m: Message) { >"X" : "X" m; // never ->m : Message +>m : never } } diff --git a/tests/baselines/reference/equalityWithIntersectionTypes01.errors.txt b/tests/baselines/reference/equalityWithIntersectionTypes01.errors.txt index d9cbc5c767d..243c85819f5 100644 --- a/tests/baselines/reference/equalityWithIntersectionTypes01.errors.txt +++ b/tests/baselines/reference/equalityWithIntersectionTypes01.errors.txt @@ -1,14 +1,9 @@ tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(17,5): error TS2367: This condition will always return 'false' since the types 'I1 & I3' and 'I2' have no overlap. tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(17,16): error TS2367: This condition will always return 'false' since the types 'I2' and 'I1 & I3' have no overlap. tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(19,10): error TS2367: This condition will always return 'true' since the types 'I1 & I3' and 'I2' have no overlap. -tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(19,21): error TS2367: This condition will always return 'true' since the types 'I2' and 'I1 & I3' have no overlap. -tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(21,10): error TS2367: This condition will always return 'false' since the types 'I1 & I3' and 'I2' have no overlap. -tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(21,20): error TS2367: This condition will always return 'false' since the types 'I2' and 'I1 & I3' have no overlap. -tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(23,10): error TS2367: This condition will always return 'true' since the types 'I1 & I3' and 'I2' have no overlap. -tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts(23,20): error TS2367: This condition will always return 'true' since the types 'I2' and 'I1 & I3' have no overlap. -==== tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts (8 errors) ==== +==== tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersectionTypes01.ts (3 errors) ==== interface I1 { p1: number } @@ -34,18 +29,8 @@ tests/cases/conformance/types/typeRelationships/comparable/equalityWithIntersect else if (y !== z || z !== y) { ~~~~~~~ !!! error TS2367: This condition will always return 'true' since the types 'I1 & I3' and 'I2' have no overlap. - ~~~~~~~ -!!! error TS2367: This condition will always return 'true' since the types 'I2' and 'I1 & I3' have no overlap. } else if (y == z || z == y) { - ~~~~~~ -!!! error TS2367: This condition will always return 'false' since the types 'I1 & I3' and 'I2' have no overlap. - ~~~~~~ -!!! error TS2367: This condition will always return 'false' since the types 'I2' and 'I1 & I3' have no overlap. } else if (y != z || z != y) { - ~~~~~~ -!!! error TS2367: This condition will always return 'true' since the types 'I1 & I3' and 'I2' have no overlap. - ~~~~~~ -!!! error TS2367: This condition will always return 'true' since the types 'I2' and 'I1 & I3' have no overlap. } \ No newline at end of file diff --git a/tests/baselines/reference/equalityWithIntersectionTypes01.types b/tests/baselines/reference/equalityWithIntersectionTypes01.types index e2d3f986183..4736f662ca5 100644 --- a/tests/baselines/reference/equalityWithIntersectionTypes01.types +++ b/tests/baselines/reference/equalityWithIntersectionTypes01.types @@ -47,24 +47,24 @@ else if (y !== z || z !== y) { >y : I1 & I3 >z : I2 >z !== y : boolean ->z : I2 ->y : I1 & I3 +>z : never +>y : never } else if (y == z || z == y) { >y == z || z == y : boolean >y == z : boolean ->y : I1 & I3 ->z : I2 +>y : never +>z : never >z == y : boolean ->z : I2 ->y : I1 & I3 +>z : never +>y : never } else if (y != z || z != y) { >y != z || z != y : boolean >y != z : boolean ->y : I1 & I3 ->z : I2 +>y : never +>z : never >z != y : boolean ->z : I2 ->y : I1 & I3 +>z : never +>y : never }