From 2be4cf408cdbc10770c5ddb00269ee2292a8de8a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 28 Dec 2021 17:19:24 -0800 Subject: [PATCH 1/4] more precise type facts for intersection --- src/compiler/checker.ts | 258 +++++++++++++++++- .../reference/narrowingTypeofFunction.js | 27 ++ .../reference/narrowingTypeofFunction.symbols | 27 ++ .../reference/narrowingTypeofFunction.types | 28 ++ .../cases/compiler/narrowingTypeofFunction.ts | 15 + 5 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/narrowingTypeofFunction.js create mode 100644 tests/baselines/reference/narrowingTypeofFunction.symbols create mode 100644 tests/baselines/reference/narrowingTypeofFunction.types create mode 100644 tests/cases/compiler/narrowingTypeofFunction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae7b16234fead..ec60c4f21b3d0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -134,6 +134,94 @@ namespace ts { EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, EmptyObjectFacts = All, + // Facts that are always true or always false (i.e. for all values of that type), to be used in `getIntersectionTypeFacts`. + // String + StringAlwaysTrue = TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, + StringStrictAlwaysTrue = StringAlwaysTrue | TypeofEQString | NEUndefined | NENull | NEUndefinedOrNull, + StringAlwaysFalse = TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, + StringStrictAlwaysFalse = StringAlwaysFalse | TypeofNEString | EQUndefined | EQNull | EQUndefinedOrNull, + // Empty string + EmptyStringAlwaysTrue = StringAlwaysTrue, + EmptyStringStrictAlwaysTrue = StringStrictAlwaysTrue | Falsy, + EmptyStringAlwaysFalse = StringAlwaysFalse, + EmptyStringStrictAlwaysFalse = StringStrictAlwaysFalse | Truthy, + // Non-empty string + NonEmptyStringAlwaysTrue = StringAlwaysTrue, + NonEmptyStringStrictAlwaysTrue = StringStrictAlwaysTrue | Truthy, + NonEmptyStringAlwaysFalse = StringAlwaysFalse, + NonEmptyStringStrictAlwaysFalse = StringStrictAlwaysFalse | Falsy, + // Number + NumberAlwaysTrue = TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, + NumberStrictAlwaysTrue = NumberAlwaysTrue | TypeofEQNumber | NEUndefined | NENull | NEUndefinedOrNull, + NumberAlwaysFalse = TypeofEQString | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, + NumberStrictAlwaysFalse = NumberAlwaysFalse | TypeofNENumber | EQUndefined | EQNull | EQUndefinedOrNull, + // Zero number + ZeroNumberAlwaysTrue = NumberAlwaysTrue, + ZeroNumberStrictAlwaysTrue = NumberStrictAlwaysTrue | Falsy, + ZeroNumberAlwaysFalse = NumberAlwaysFalse, + ZeroNumberStrictAlwaysFalse = NumberStrictAlwaysFalse | Truthy, + // Non-zero number + NonZeroNumberAlwaysTrue = NumberAlwaysTrue, + NonZeroNumberStrictAlwaysTrue = NumberStrictAlwaysTrue | Truthy, + NonZeroNumberAlwaysFalse = NumberAlwaysFalse, + NonZeroNumberStrictAlwaysFalse = NumberStrictAlwaysFalse | Falsy, + // Big int + BigIntAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, + BigIntStrictAlwaysTrue = BigIntAlwaysTrue | TypeofEQBigInt | NEUndefined | NENull | NEUndefinedOrNull, + BigIntAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, + BigIntStrictAlwaysFalse = BigIntAlwaysFalse | TypeofNEBigInt | EQUndefined | EQNull | EQUndefinedOrNull, + // Zero big int + ZeroBigIntAlwaysTrue = BigIntAlwaysTrue, + ZeroBigIntStrictAlwaysTrue = BigIntStrictAlwaysTrue | Falsy, + ZeroBigIntAlwaysFalse = BigIntAlwaysFalse, + ZeroBigIntStrictAlwaysFalse = BigIntStrictAlwaysFalse | Truthy, + // Non-zero big int + NonZeroBigIntAlwaysTrue = BigIntAlwaysTrue, + NonZeroBigIntStrictAlwaysTrue = BigIntStrictAlwaysTrue | Truthy, + NonZeroBigIntAlwaysFalse = BigIntAlwaysFalse, + NonZeroBigIntStrictAlwaysFalse = BigIntStrictAlwaysFalse | Falsy, + // Boolean + BooleanAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, + BooleanStrictAlwaysTrue = BooleanAlwaysTrue | TypeofEQBoolean | NEUndefined | NENull | NEUndefinedOrNull, + BooleanAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, + BooleanStrictAlwaysFalse = BooleanAlwaysFalse | TypeofNEBoolean | EQUndefined | EQNull | EQUndefinedOrNull, + // Boolean-like + // False + FalseAlwaysTrue = BooleanAlwaysTrue, + FalseStrictAlwaysTrue = BooleanStrictAlwaysTrue | Falsy, + FalseAlwaysFalse = BooleanAlwaysFalse, + FalseStrictAlwaysFalse = BooleanStrictAlwaysFalse | Truthy, + // True + TrueAlwaysTrue = BooleanAlwaysTrue, + TrueStrictAlwaysTrue = BooleanStrictAlwaysTrue | Truthy, + TrueAlwaysFalse = BooleanAlwaysFalse, + TrueStrictAlwaysFalse = BooleanStrictAlwaysFalse | Falsy, + // Undefined + UndefinedAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, + UndefinedAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject | NEUndefined | NEUndefinedOrNull | EQNull | Truthy, + // Null + NullAlwaysTrue = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, + NullAlwaysFalse = TypeofNEObject | TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQFunction | TypeofEQHostObject | NENull | NEUndefinedOrNull | EQUndefined | Truthy, + // Symbol + SymbolAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, + SymbolStrictAlwaysTrue = SymbolAlwaysTrue | TypeofEQSymbol | NEUndefined | NENull | NEUndefinedOrNull | Truthy, + SymbolAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, + SymbolStrictAlwaysFalse = SymbolAlwaysFalse | TypeofNESymbol | EQUndefined | EQNull | EQUndefinedOrNull, + // Function + FunctionAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject, + FunctionStrictAlwaysTrue = FunctionAlwaysTrue | TypeofEQFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, // >> TODO: what about `TypeofEQHostObject`? + FunctionAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject, + FunctionStrictAlwaysFalse = FunctionAlwaysFalse | TypeofNEFunction | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + // Object + ObjectAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol, // could it still be e.g. a symbol? + ObjectStrictAlwaysTrue = ObjectAlwaysTrue | NEUndefined | NENull | NEUndefinedOrNull | Truthy, // >> TODO: what about `TypeofEQHostObject` + ObjectAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol, + ObjectStrictAlwaysFalse = ObjectAlwaysFalse | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, + // Empty object + EmptyObjectAlwaysTrue = None, + EmptyObjectStrictAlwaysTrue = NEUndefined | NENull | NEUndefinedOrNull, + EmptyObjectAlwaysFalse = None, + EmptyObjectStrictAlwaysFalse = EQUndefined | EQNull | EQUndefinedOrNull, } const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ @@ -22964,13 +23052,179 @@ namespace ts { if (flags & TypeFlags.Union) { return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None); } + if (flags & TypeFlags.Intersection) { + // // When an intersection contains a primitive type we ignore object type constituents as they are + // // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. + // const hasPrimitive = maybeTypeOfKind(type, TypeFlags.Primitive); + // ignoreObjects ||= hasPrimitive; + // if (hasPrimitive) { + // return reduceLeft((type as IntersectionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); + // } + // else { + // return reduceLeft((type as IntersectionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None); + // } + return getIntersectionTypeFacts(type as IntersectionType, ignoreObjects); + } + return TypeFacts.All; + } + + function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts { + ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); + let alwaysTrue = TypeFacts.None; + let alwaysFalse = TypeFacts.None; + let facts = TypeFacts.None; + for (const t of type.types) { + facts |= getTypeFacts(t, ignoreObjects); + const { alwaysTrue: tTrue, alwaysFalse: tFalse } = getAlwaysTypeFacts(t, ignoreObjects); + alwaysTrue |= tTrue; + alwaysFalse |= tFalse; + } + + if (alwaysTrue & alwaysFalse) { + // Contradiction + return TypeFacts.None; + } + + return (facts | alwaysTrue) & (~alwaysFalse); + } + + function getAlwaysTypeFacts(type: Type, ignoreObjects: boolean): { alwaysTrue: TypeFacts, alwaysFalse: TypeFacts } { + const flags = type.flags; + if (flags & TypeFlags.String) { + return { + alwaysTrue: strictNullChecks ? TypeFacts.StringStrictAlwaysTrue : TypeFacts.StringAlwaysTrue, + alwaysFalse: strictNullChecks ? + TypeFacts.StringStrictAlwaysFalse : TypeFacts.StringAlwaysFalse, + }; + } + if (flags & TypeFlags.StringLiteral) { + const isEmpty = (type as StringLiteralType).value === ""; + return strictNullChecks + ? isEmpty + ? { alwaysTrue: TypeFacts.EmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.EmptyStringStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.NonEmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringStrictAlwaysFalse } + : isEmpty + ? { alwaysTrue: TypeFacts.EmptyStringAlwaysTrue, alwaysFalse: TypeFacts.EmptyStringAlwaysFalse } + : { alwaysTrue: TypeFacts.NonEmptyStringAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringAlwaysFalse }; + } + if (flags & (TypeFlags.Number | TypeFlags.Enum)) { + return { + alwaysTrue: strictNullChecks ? TypeFacts.NumberStrictAlwaysTrue : TypeFacts.NumberAlwaysTrue, + alwaysFalse: strictNullChecks ? TypeFacts.NumberStrictAlwaysFalse : TypeFacts.NumberAlwaysFalse, + }; + } + if (flags & TypeFlags.NumberLiteral) { + const isZero = (type as NumberLiteralType).value === 0; + return strictNullChecks + ? isZero + ? { alwaysTrue: TypeFacts.ZeroNumberStrictAlwaysTrue, alwaysFalse: TypeFacts.ZeroNumberStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.NonZeroNumberStrictAlwaysTrue, alwaysFalse: TypeFacts.NonZeroNumberStrictAlwaysFalse } + : isZero + ? { alwaysTrue: TypeFacts.ZeroNumberAlwaysTrue, alwaysFalse: TypeFacts.ZeroNumberAlwaysFalse } + : { alwaysTrue: TypeFacts.NonZeroNumberAlwaysTrue, alwaysFalse: TypeFacts.NonZeroNumberAlwaysFalse }; + } + if (flags & TypeFlags.BigInt) { + return { + alwaysTrue: strictNullChecks ? TypeFacts.BigIntStrictAlwaysTrue : TypeFacts.BigIntAlwaysTrue, + alwaysFalse: strictNullChecks ? TypeFacts.BigIntStrictAlwaysFalse : TypeFacts.BigIntAlwaysFalse, + }; + } + if (flags & TypeFlags.BigIntLiteral) { + const isZero = isZeroBigInt(type as BigIntLiteralType); + return strictNullChecks + ? isZero + ? { alwaysTrue: TypeFacts.ZeroBigIntStrictAlwaysTrue, alwaysFalse: TypeFacts.ZeroBigIntStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.NonZeroBigIntStrictAlwaysTrue, alwaysFalse: TypeFacts.NonZeroBigIntStrictAlwaysFalse } + : isZero + ? { alwaysTrue: TypeFacts.ZeroBigIntAlwaysTrue, alwaysFalse: TypeFacts.ZeroBigIntAlwaysFalse } + : { alwaysTrue: TypeFacts.NonZeroBigIntAlwaysTrue, alwaysFalse: TypeFacts.NonZeroBigIntAlwaysFalse }; + } + if (flags & TypeFlags.Boolean) { + return { + alwaysTrue: strictNullChecks ? TypeFacts.BooleanStrictAlwaysTrue : TypeFacts.BooleanAlwaysTrue, + alwaysFalse: strictNullChecks ? + TypeFacts.BooleanStrictAlwaysFalse : TypeFacts.BooleanAlwaysFalse, + }; + } + if (flags & TypeFlags.BooleanLike) { + return strictNullChecks + ? (type === falseType || type === regularFalseType) + ? { alwaysTrue: TypeFacts.FalseStrictAlwaysTrue, alwaysFalse: TypeFacts.FalseStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.TrueStrictAlwaysTrue, alwaysFalse: TypeFacts.TrueStrictAlwaysFalse } + : (type === falseType || type === regularFalseType) + ? { alwaysTrue: TypeFacts.FalseAlwaysTrue, alwaysFalse: TypeFacts.FalseAlwaysFalse } + : { alwaysTrue: TypeFacts.TrueAlwaysTrue, alwaysFalse: TypeFacts.TrueAlwaysFalse }; + } + if (flags & TypeFlags.Object && !ignoreObjects) { + return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) + ? strictNullChecks + ? { alwaysTrue: TypeFacts.EmptyObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.EmptyObjectStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.EmptyObjectAlwaysTrue, alwaysFalse: TypeFacts.EmptyObjectAlwaysFalse } + : isFunctionObjectType(type as ObjectType) + ? strictNullChecks + ? { alwaysTrue: TypeFacts.FunctionStrictAlwaysTrue, alwaysFalse: TypeFacts.FunctionStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.FunctionAlwaysTrue, alwaysFalse: TypeFacts.FunctionAlwaysFalse } + : strictNullChecks + ? { alwaysTrue: TypeFacts.ObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.ObjectStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.ObjectAlwaysTrue, alwaysFalse: TypeFacts.ObjectAlwaysFalse }; + } + if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { + return { alwaysTrue: TypeFacts.UndefinedAlwaysTrue, alwaysFalse: TypeFacts.UndefinedAlwaysFalse }; + } + if (flags & TypeFlags.Null) { + return { alwaysTrue: TypeFacts.NullAlwaysTrue, alwaysFalse: TypeFacts.NullAlwaysFalse }; + } + if (flags & TypeFlags.ESSymbolLike) { + return { + alwaysTrue: strictNullChecks ? TypeFacts.SymbolStrictAlwaysTrue : TypeFacts.SymbolAlwaysTrue, + alwaysFalse: strictNullChecks ? TypeFacts.SymbolStrictAlwaysFalse : TypeFacts.SymbolAlwaysFalse, + }; + } + if (flags & TypeFlags.NonPrimitive) { + return strictNullChecks ? + { alwaysTrue: TypeFacts.ObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.ObjectStrictAlwaysFalse } : + { alwaysTrue: TypeFacts.ObjectAlwaysTrue, alwaysFalse: TypeFacts.ObjectAlwaysFalse }; + } + if (flags & TypeFlags.Never) { + return { + alwaysTrue: TypeFacts.None, + alwaysFalse: TypeFacts.None, + }; + } + if (flags & TypeFlags.Instantiable) { + return !isPatternLiteralType(type) + ? getAlwaysTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) + : strictNullChecks + ? { alwaysTrue: TypeFacts.NonEmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringStrictAlwaysFalse } + : { alwaysTrue: TypeFacts.NonEmptyStringAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringAlwaysFalse }; + } + if (flags & TypeFlags.Union) { + const types = (type as UnionType).types; + if (types.length) { + return reduceLeft(types, (facts, t) => { + const { alwaysTrue, alwaysFalse } = getAlwaysTypeFacts(t, ignoreObjects); + return { alwaysTrue: facts.alwaysTrue & alwaysTrue, alwaysFalse: facts.alwaysFalse & alwaysFalse }; + }, { alwaysTrue: TypeFacts.All, alwaysFalse: TypeFacts.All }); // >> TODO: that's assuming union will never be empty; check that + } + return { + alwaysTrue: TypeFacts.None, + alwaysFalse: TypeFacts.None, + }; + } if (flags & TypeFlags.Intersection) { // When an intersection contains a primitive type we ignore object type constituents as they are // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); - return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); + return reduceLeft((type as IntersectionType).types, (facts, t) => { + const { alwaysTrue, alwaysFalse } = getAlwaysTypeFacts(t, ignoreObjects); + return { alwaysTrue: facts.alwaysTrue | alwaysTrue, alwaysFalse: facts.alwaysFalse | alwaysFalse }; + }, { alwaysTrue: TypeFacts.None, alwaysFalse: TypeFacts.None }); } - return TypeFacts.All; + + return { + alwaysTrue: TypeFacts.None, + alwaysFalse: TypeFacts.None, + }; } function getTypeWithFacts(type: Type, include: TypeFacts) { diff --git a/tests/baselines/reference/narrowingTypeofFunction.js b/tests/baselines/reference/narrowingTypeofFunction.js new file mode 100644 index 0000000000000..f4a1114d4b735 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.js @@ -0,0 +1,27 @@ +//// [narrowingTypeofFunction.ts] +type Meta = { foo: string } +interface F { (): string } + +const x = (a: (F & Meta) | string) => { + if (typeof a === "function") { + // ts.version >= 4.3.5: never -- unexpected + // ts.version <= 4.2.3: F & Meta -- expected + a; + } + else { + a; + } +} + +//// [narrowingTypeofFunction.js] +"use strict"; +var x = function (a) { + if (typeof a === "function") { + // ts.version >= 4.3.5: never -- unexpected + // ts.version <= 4.2.3: F & Meta -- expected + a; + } + else { + a; + } +}; diff --git a/tests/baselines/reference/narrowingTypeofFunction.symbols b/tests/baselines/reference/narrowingTypeofFunction.symbols new file mode 100644 index 0000000000000..4d9eddf002099 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.symbols @@ -0,0 +1,27 @@ +=== tests/cases/compiler/narrowingTypeofFunction.ts === +type Meta = { foo: string } +>Meta : Symbol(Meta, Decl(narrowingTypeofFunction.ts, 0, 0)) +>foo : Symbol(foo, Decl(narrowingTypeofFunction.ts, 0, 13)) + +interface F { (): string } +>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27)) + +const x = (a: (F & Meta) | string) => { +>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 3, 5)) +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 11)) +>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27)) +>Meta : Symbol(Meta, Decl(narrowingTypeofFunction.ts, 0, 0)) + + if (typeof a === "function") { +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 11)) + + // ts.version >= 4.3.5: never -- unexpected + // ts.version <= 4.2.3: F & Meta -- expected + a; +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 11)) + } + else { + a; +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 11)) + } +} diff --git a/tests/baselines/reference/narrowingTypeofFunction.types b/tests/baselines/reference/narrowingTypeofFunction.types new file mode 100644 index 0000000000000..2a417d435af6a --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.types @@ -0,0 +1,28 @@ +=== tests/cases/compiler/narrowingTypeofFunction.ts === +type Meta = { foo: string } +>Meta : Meta +>foo : string + +interface F { (): string } + +const x = (a: (F & Meta) | string) => { +>x : (a: (F & Meta) | string) => void +>(a: (F & Meta) | string) => { if (typeof a === "function") { // ts.version >= 4.3.5: never -- unexpected // ts.version <= 4.2.3: F & Meta -- expected a; } else { a; }} : (a: (F & Meta) | string) => void +>a : string | (F & Meta) + + if (typeof a === "function") { +>typeof a === "function" : boolean +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a : string | (F & Meta) +>"function" : "function" + + // ts.version >= 4.3.5: never -- unexpected + // ts.version <= 4.2.3: F & Meta -- expected + a; +>a : F & Meta + } + else { + a; +>a : string + } +} diff --git a/tests/cases/compiler/narrowingTypeofFunction.ts b/tests/cases/compiler/narrowingTypeofFunction.ts new file mode 100644 index 0000000000000..4cd5eaab2efff --- /dev/null +++ b/tests/cases/compiler/narrowingTypeofFunction.ts @@ -0,0 +1,15 @@ +// @strict: true + +type Meta = { foo: string } +interface F { (): string } + +const x = (a: (F & Meta) | string) => { + if (typeof a === "function") { + // ts.version >= 4.3.5: never -- unexpected + // ts.version <= 4.2.3: F & Meta -- expected + a; + } + else { + a; + } +} \ No newline at end of file From 248991688359e2dc072f092345c0fde6cff747e5 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 31 Dec 2021 17:01:52 -0800 Subject: [PATCH 2/4] fix ignoreObject behavior --- src/compiler/checker.ts | 23 ++++++++------- tests/baselines/reference/narrowingTypeof.js | 15 ++++++++++ .../reference/narrowingTypeof.symbols | 26 +++++++++++++++++ .../baselines/reference/narrowingTypeof.types | 29 +++++++++++++++++++ tests/cases/compiler/narrowingTypeof.ts | 10 +++++++ 5 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/narrowingTypeof.js create mode 100644 tests/baselines/reference/narrowingTypeof.symbols create mode 100644 tests/baselines/reference/narrowingTypeof.types create mode 100644 tests/cases/compiler/narrowingTypeof.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ec60c4f21b3d0..1f1dad89b0b15 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23023,7 +23023,10 @@ namespace ts { (type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts : (type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts; } - if (flags & TypeFlags.Object && !ignoreObjects) { + if (flags & TypeFlags.Object) { + if (ignoreObjects) { + return TypeFacts.None; + } return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : isFunctionObjectType(type as ObjectType) ? @@ -23055,21 +23058,13 @@ namespace ts { if (flags & TypeFlags.Intersection) { // // When an intersection contains a primitive type we ignore object type constituents as they are // // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. - // const hasPrimitive = maybeTypeOfKind(type, TypeFlags.Primitive); - // ignoreObjects ||= hasPrimitive; - // if (hasPrimitive) { - // return reduceLeft((type as IntersectionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All); - // } - // else { - // return reduceLeft((type as IntersectionType).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None); - // } + ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); return getIntersectionTypeFacts(type as IntersectionType, ignoreObjects); } return TypeFacts.All; } function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts { - ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); let alwaysTrue = TypeFacts.None; let alwaysFalse = TypeFacts.None; let facts = TypeFacts.None; @@ -23155,7 +23150,13 @@ namespace ts { ? { alwaysTrue: TypeFacts.FalseAlwaysTrue, alwaysFalse: TypeFacts.FalseAlwaysFalse } : { alwaysTrue: TypeFacts.TrueAlwaysTrue, alwaysFalse: TypeFacts.TrueAlwaysFalse }; } - if (flags & TypeFlags.Object && !ignoreObjects) { + if (flags & TypeFlags.Object) { + if (ignoreObjects) { + return { + alwaysTrue: TypeFacts.None, + alwaysFalse: TypeFacts.None, + }; + } return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks ? { alwaysTrue: TypeFacts.EmptyObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.EmptyObjectStrictAlwaysFalse } diff --git a/tests/baselines/reference/narrowingTypeof.js b/tests/baselines/reference/narrowingTypeof.js new file mode 100644 index 0000000000000..25d7e2d9ec59b --- /dev/null +++ b/tests/baselines/reference/narrowingTypeof.js @@ -0,0 +1,15 @@ +//// [narrowingTypeof.ts] +type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }); + +declare const s: __String; +declare let t: string | number | undefined; + +declare function assert(e: unknown): asserts e; + +assert(typeof s === "string"); +t = s; + + +//// [narrowingTypeof.js] +assert(typeof s === "string"); +t = s; diff --git a/tests/baselines/reference/narrowingTypeof.symbols b/tests/baselines/reference/narrowingTypeof.symbols new file mode 100644 index 0000000000000..51343b592c44c --- /dev/null +++ b/tests/baselines/reference/narrowingTypeof.symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/narrowingTypeof.ts === +type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }); +>__String : Symbol(__String, Decl(narrowingTypeof.ts, 0, 0)) +>__escapedIdentifier : Symbol(__escapedIdentifier, Decl(narrowingTypeof.ts, 0, 27)) +>__escapedIdentifier : Symbol(__escapedIdentifier, Decl(narrowingTypeof.ts, 0, 68)) + +declare const s: __String; +>s : Symbol(s, Decl(narrowingTypeof.ts, 2, 13)) +>__String : Symbol(__String, Decl(narrowingTypeof.ts, 0, 0)) + +declare let t: string | number | undefined; +>t : Symbol(t, Decl(narrowingTypeof.ts, 3, 11)) + +declare function assert(e: unknown): asserts e; +>assert : Symbol(assert, Decl(narrowingTypeof.ts, 3, 43)) +>e : Symbol(e, Decl(narrowingTypeof.ts, 5, 24)) +>e : Symbol(e, Decl(narrowingTypeof.ts, 5, 24)) + +assert(typeof s === "string"); +>assert : Symbol(assert, Decl(narrowingTypeof.ts, 3, 43)) +>s : Symbol(s, Decl(narrowingTypeof.ts, 2, 13)) + +t = s; +>t : Symbol(t, Decl(narrowingTypeof.ts, 3, 11)) +>s : Symbol(s, Decl(narrowingTypeof.ts, 2, 13)) + diff --git a/tests/baselines/reference/narrowingTypeof.types b/tests/baselines/reference/narrowingTypeof.types new file mode 100644 index 0000000000000..a9035c4bfd8c9 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeof.types @@ -0,0 +1,29 @@ +=== tests/cases/compiler/narrowingTypeof.ts === +type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }); +>__String : __String +>__escapedIdentifier : void +>__escapedIdentifier : void + +declare const s: __String; +>s : __String + +declare let t: string | number | undefined; +>t : string | number + +declare function assert(e: unknown): asserts e; +>assert : (e: unknown) => asserts e +>e : unknown + +assert(typeof s === "string"); +>assert(typeof s === "string") : void +>assert : (e: unknown) => asserts e +>typeof s === "string" : boolean +>typeof s : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>s : __String +>"string" : "string" + +t = s; +>t = s : string & { __escapedIdentifier: void; } +>t : string | number +>s : string & { __escapedIdentifier: void; } + diff --git a/tests/cases/compiler/narrowingTypeof.ts b/tests/cases/compiler/narrowingTypeof.ts new file mode 100644 index 0000000000000..461ab6780cba5 --- /dev/null +++ b/tests/cases/compiler/narrowingTypeof.ts @@ -0,0 +1,10 @@ + +type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }); + +declare const s: __String; +declare let t: string | number | undefined; + +declare function assert(e: unknown): asserts e; + +assert(typeof s === "string"); +t = s; From 01e404763aefb88dcc2a800d761e69a9966cfe87 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 5 Jan 2022 13:22:57 -0800 Subject: [PATCH 3/4] get rid of always true facts, keep only always false facts --- src/compiler/checker.ts | 167 +++++++++++----------------------------- 1 file changed, 44 insertions(+), 123 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1f1dad89b0b15..bfe7f2bf02788 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -74,7 +74,7 @@ namespace ts { TypeofNEString = 1 << 8, // typeof x !== "string" TypeofNENumber = 1 << 9, // typeof x !== "number" TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" - TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" + TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" TypeofNESymbol = 1 << 12, // typeof x !== "symbol" TypeofNEObject = 1 << 13, // typeof x !== "object" TypeofNEFunction = 1 << 14, // typeof x !== "function" @@ -134,92 +134,58 @@ namespace ts { EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, EmptyObjectFacts = All, - // Facts that are always true or always false (i.e. for all values of that type), to be used in `getIntersectionTypeFacts`. + // Facts that are always false (i.e. false for all values of that type), to be used in `getIntersectionTypeFacts`. // String - StringAlwaysTrue = TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, - StringStrictAlwaysTrue = StringAlwaysTrue | TypeofEQString | NEUndefined | NENull | NEUndefinedOrNull, StringAlwaysFalse = TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, StringStrictAlwaysFalse = StringAlwaysFalse | TypeofNEString | EQUndefined | EQNull | EQUndefinedOrNull, // Empty string - EmptyStringAlwaysTrue = StringAlwaysTrue, - EmptyStringStrictAlwaysTrue = StringStrictAlwaysTrue | Falsy, EmptyStringAlwaysFalse = StringAlwaysFalse, EmptyStringStrictAlwaysFalse = StringStrictAlwaysFalse | Truthy, // Non-empty string - NonEmptyStringAlwaysTrue = StringAlwaysTrue, - NonEmptyStringStrictAlwaysTrue = StringStrictAlwaysTrue | Truthy, NonEmptyStringAlwaysFalse = StringAlwaysFalse, NonEmptyStringStrictAlwaysFalse = StringStrictAlwaysFalse | Falsy, // Number - NumberAlwaysTrue = TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, - NumberStrictAlwaysTrue = NumberAlwaysTrue | TypeofEQNumber | NEUndefined | NENull | NEUndefinedOrNull, NumberAlwaysFalse = TypeofEQString | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, NumberStrictAlwaysFalse = NumberAlwaysFalse | TypeofNENumber | EQUndefined | EQNull | EQUndefinedOrNull, // Zero number - ZeroNumberAlwaysTrue = NumberAlwaysTrue, - ZeroNumberStrictAlwaysTrue = NumberStrictAlwaysTrue | Falsy, ZeroNumberAlwaysFalse = NumberAlwaysFalse, ZeroNumberStrictAlwaysFalse = NumberStrictAlwaysFalse | Truthy, // Non-zero number - NonZeroNumberAlwaysTrue = NumberAlwaysTrue, - NonZeroNumberStrictAlwaysTrue = NumberStrictAlwaysTrue | Truthy, NonZeroNumberAlwaysFalse = NumberAlwaysFalse, NonZeroNumberStrictAlwaysFalse = NumberStrictAlwaysFalse | Falsy, // Big int - BigIntAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, - BigIntStrictAlwaysTrue = BigIntAlwaysTrue | TypeofEQBigInt | NEUndefined | NENull | NEUndefinedOrNull, BigIntAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, BigIntStrictAlwaysFalse = BigIntAlwaysFalse | TypeofNEBigInt | EQUndefined | EQNull | EQUndefinedOrNull, // Zero big int - ZeroBigIntAlwaysTrue = BigIntAlwaysTrue, - ZeroBigIntStrictAlwaysTrue = BigIntStrictAlwaysTrue | Falsy, ZeroBigIntAlwaysFalse = BigIntAlwaysFalse, ZeroBigIntStrictAlwaysFalse = BigIntStrictAlwaysFalse | Truthy, // Non-zero big int - NonZeroBigIntAlwaysTrue = BigIntAlwaysTrue, - NonZeroBigIntStrictAlwaysTrue = BigIntStrictAlwaysTrue | Truthy, NonZeroBigIntAlwaysFalse = BigIntAlwaysFalse, NonZeroBigIntStrictAlwaysFalse = BigIntStrictAlwaysFalse | Falsy, // Boolean - BooleanAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, - BooleanStrictAlwaysTrue = BooleanAlwaysTrue | TypeofEQBoolean | NEUndefined | NENull | NEUndefinedOrNull, BooleanAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, BooleanStrictAlwaysFalse = BooleanAlwaysFalse | TypeofNEBoolean | EQUndefined | EQNull | EQUndefinedOrNull, // Boolean-like // False - FalseAlwaysTrue = BooleanAlwaysTrue, - FalseStrictAlwaysTrue = BooleanStrictAlwaysTrue | Falsy, FalseAlwaysFalse = BooleanAlwaysFalse, FalseStrictAlwaysFalse = BooleanStrictAlwaysFalse | Truthy, // True - TrueAlwaysTrue = BooleanAlwaysTrue, - TrueStrictAlwaysTrue = BooleanStrictAlwaysTrue | Truthy, TrueAlwaysFalse = BooleanAlwaysFalse, TrueStrictAlwaysFalse = BooleanStrictAlwaysFalse | Falsy, // Undefined - UndefinedAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, UndefinedAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject | NEUndefined | NEUndefinedOrNull | EQNull | Truthy, // Null - NullAlwaysTrue = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, NullAlwaysFalse = TypeofNEObject | TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQFunction | TypeofEQHostObject | NENull | NEUndefinedOrNull | EQUndefined | Truthy, // Symbol - SymbolAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject, - SymbolStrictAlwaysTrue = SymbolAlwaysTrue | TypeofEQSymbol | NEUndefined | NENull | NEUndefinedOrNull | Truthy, SymbolAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, SymbolStrictAlwaysFalse = SymbolAlwaysFalse | TypeofNESymbol | EQUndefined | EQNull | EQUndefinedOrNull, // Function - FunctionAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject, - FunctionStrictAlwaysTrue = FunctionAlwaysTrue | TypeofEQFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, // >> TODO: what about `TypeofEQHostObject`? FunctionAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject, FunctionStrictAlwaysFalse = FunctionAlwaysFalse | TypeofNEFunction | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, // Object - ObjectAlwaysTrue = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol, // could it still be e.g. a symbol? - ObjectStrictAlwaysTrue = ObjectAlwaysTrue | NEUndefined | NENull | NEUndefinedOrNull | Truthy, // >> TODO: what about `TypeofEQHostObject` ObjectAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol, ObjectStrictAlwaysFalse = ObjectAlwaysFalse | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, // Empty object - EmptyObjectAlwaysTrue = None, - EmptyObjectStrictAlwaysTrue = NEUndefined | NENull | NEUndefinedOrNull, EmptyObjectAlwaysFalse = None, EmptyObjectStrictAlwaysFalse = EQUndefined | EQNull | EQUndefinedOrNull, } @@ -23065,167 +23031,122 @@ namespace ts { } function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts { - let alwaysTrue = TypeFacts.None; let alwaysFalse = TypeFacts.None; let facts = TypeFacts.None; for (const t of type.types) { facts |= getTypeFacts(t, ignoreObjects); - const { alwaysTrue: tTrue, alwaysFalse: tFalse } = getAlwaysTypeFacts(t, ignoreObjects); - alwaysTrue |= tTrue; - alwaysFalse |= tFalse; + alwaysFalse |= getAlwaysFalseTypeFacts(t, ignoreObjects); } - if (alwaysTrue & alwaysFalse) { - // Contradiction - return TypeFacts.None; - } - - return (facts | alwaysTrue) & (~alwaysFalse); + return facts & ~alwaysFalse; } - function getAlwaysTypeFacts(type: Type, ignoreObjects: boolean): { alwaysTrue: TypeFacts, alwaysFalse: TypeFacts } { + function getAlwaysFalseTypeFacts(type: Type, ignoreObjects: boolean): TypeFacts { const flags = type.flags; if (flags & TypeFlags.String) { - return { - alwaysTrue: strictNullChecks ? TypeFacts.StringStrictAlwaysTrue : TypeFacts.StringAlwaysTrue, - alwaysFalse: strictNullChecks ? - TypeFacts.StringStrictAlwaysFalse : TypeFacts.StringAlwaysFalse, - }; + return strictNullChecks ? TypeFacts.StringStrictAlwaysFalse : TypeFacts.StringAlwaysFalse; } if (flags & TypeFlags.StringLiteral) { const isEmpty = (type as StringLiteralType).value === ""; return strictNullChecks ? isEmpty - ? { alwaysTrue: TypeFacts.EmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.EmptyStringStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.NonEmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringStrictAlwaysFalse } + ? TypeFacts.EmptyStringStrictAlwaysFalse + : TypeFacts.NonEmptyStringStrictAlwaysFalse : isEmpty - ? { alwaysTrue: TypeFacts.EmptyStringAlwaysTrue, alwaysFalse: TypeFacts.EmptyStringAlwaysFalse } - : { alwaysTrue: TypeFacts.NonEmptyStringAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringAlwaysFalse }; + ? TypeFacts.EmptyStringAlwaysFalse + : TypeFacts.NonEmptyStringAlwaysFalse; } if (flags & (TypeFlags.Number | TypeFlags.Enum)) { - return { - alwaysTrue: strictNullChecks ? TypeFacts.NumberStrictAlwaysTrue : TypeFacts.NumberAlwaysTrue, - alwaysFalse: strictNullChecks ? TypeFacts.NumberStrictAlwaysFalse : TypeFacts.NumberAlwaysFalse, - }; + return strictNullChecks ? TypeFacts.NumberStrictAlwaysFalse : TypeFacts.NumberAlwaysFalse; } if (flags & TypeFlags.NumberLiteral) { const isZero = (type as NumberLiteralType).value === 0; return strictNullChecks ? isZero - ? { alwaysTrue: TypeFacts.ZeroNumberStrictAlwaysTrue, alwaysFalse: TypeFacts.ZeroNumberStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.NonZeroNumberStrictAlwaysTrue, alwaysFalse: TypeFacts.NonZeroNumberStrictAlwaysFalse } + ? TypeFacts.ZeroNumberStrictAlwaysFalse + : TypeFacts.NonZeroNumberStrictAlwaysFalse : isZero - ? { alwaysTrue: TypeFacts.ZeroNumberAlwaysTrue, alwaysFalse: TypeFacts.ZeroNumberAlwaysFalse } - : { alwaysTrue: TypeFacts.NonZeroNumberAlwaysTrue, alwaysFalse: TypeFacts.NonZeroNumberAlwaysFalse }; + ? TypeFacts.ZeroNumberAlwaysFalse + : TypeFacts.NonZeroNumberAlwaysFalse; } if (flags & TypeFlags.BigInt) { - return { - alwaysTrue: strictNullChecks ? TypeFacts.BigIntStrictAlwaysTrue : TypeFacts.BigIntAlwaysTrue, - alwaysFalse: strictNullChecks ? TypeFacts.BigIntStrictAlwaysFalse : TypeFacts.BigIntAlwaysFalse, - }; + return strictNullChecks ? TypeFacts.BigIntStrictAlwaysFalse : TypeFacts.BigIntAlwaysFalse; } if (flags & TypeFlags.BigIntLiteral) { const isZero = isZeroBigInt(type as BigIntLiteralType); return strictNullChecks ? isZero - ? { alwaysTrue: TypeFacts.ZeroBigIntStrictAlwaysTrue, alwaysFalse: TypeFacts.ZeroBigIntStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.NonZeroBigIntStrictAlwaysTrue, alwaysFalse: TypeFacts.NonZeroBigIntStrictAlwaysFalse } + ? TypeFacts.ZeroBigIntStrictAlwaysFalse + : TypeFacts.NonZeroBigIntStrictAlwaysFalse : isZero - ? { alwaysTrue: TypeFacts.ZeroBigIntAlwaysTrue, alwaysFalse: TypeFacts.ZeroBigIntAlwaysFalse } - : { alwaysTrue: TypeFacts.NonZeroBigIntAlwaysTrue, alwaysFalse: TypeFacts.NonZeroBigIntAlwaysFalse }; + ? TypeFacts.ZeroBigIntAlwaysFalse + : TypeFacts.NonZeroBigIntAlwaysFalse; } if (flags & TypeFlags.Boolean) { - return { - alwaysTrue: strictNullChecks ? TypeFacts.BooleanStrictAlwaysTrue : TypeFacts.BooleanAlwaysTrue, - alwaysFalse: strictNullChecks ? - TypeFacts.BooleanStrictAlwaysFalse : TypeFacts.BooleanAlwaysFalse, - }; + return strictNullChecks ? TypeFacts.BooleanStrictAlwaysFalse : TypeFacts.BooleanAlwaysFalse; } if (flags & TypeFlags.BooleanLike) { return strictNullChecks ? (type === falseType || type === regularFalseType) - ? { alwaysTrue: TypeFacts.FalseStrictAlwaysTrue, alwaysFalse: TypeFacts.FalseStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.TrueStrictAlwaysTrue, alwaysFalse: TypeFacts.TrueStrictAlwaysFalse } + ? TypeFacts.FalseStrictAlwaysFalse + : TypeFacts.TrueStrictAlwaysFalse : (type === falseType || type === regularFalseType) - ? { alwaysTrue: TypeFacts.FalseAlwaysTrue, alwaysFalse: TypeFacts.FalseAlwaysFalse } - : { alwaysTrue: TypeFacts.TrueAlwaysTrue, alwaysFalse: TypeFacts.TrueAlwaysFalse }; + ? TypeFacts.FalseAlwaysFalse + : TypeFacts.TrueAlwaysFalse; } if (flags & TypeFlags.Object) { if (ignoreObjects) { - return { - alwaysTrue: TypeFacts.None, - alwaysFalse: TypeFacts.None, - }; + return TypeFacts.None; } return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type as ObjectType) ? strictNullChecks - ? { alwaysTrue: TypeFacts.EmptyObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.EmptyObjectStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.EmptyObjectAlwaysTrue, alwaysFalse: TypeFacts.EmptyObjectAlwaysFalse } + ? TypeFacts.EmptyObjectStrictAlwaysFalse + : TypeFacts.EmptyObjectAlwaysFalse : isFunctionObjectType(type as ObjectType) ? strictNullChecks - ? { alwaysTrue: TypeFacts.FunctionStrictAlwaysTrue, alwaysFalse: TypeFacts.FunctionStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.FunctionAlwaysTrue, alwaysFalse: TypeFacts.FunctionAlwaysFalse } + ? TypeFacts.FunctionStrictAlwaysFalse + : TypeFacts.FunctionAlwaysFalse : strictNullChecks - ? { alwaysTrue: TypeFacts.ObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.ObjectStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.ObjectAlwaysTrue, alwaysFalse: TypeFacts.ObjectAlwaysFalse }; + ? TypeFacts.ObjectStrictAlwaysFalse + : TypeFacts.ObjectAlwaysFalse; } if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { - return { alwaysTrue: TypeFacts.UndefinedAlwaysTrue, alwaysFalse: TypeFacts.UndefinedAlwaysFalse }; + return TypeFacts.UndefinedAlwaysFalse; } if (flags & TypeFlags.Null) { - return { alwaysTrue: TypeFacts.NullAlwaysTrue, alwaysFalse: TypeFacts.NullAlwaysFalse }; + return TypeFacts.NullAlwaysFalse; } if (flags & TypeFlags.ESSymbolLike) { - return { - alwaysTrue: strictNullChecks ? TypeFacts.SymbolStrictAlwaysTrue : TypeFacts.SymbolAlwaysTrue, - alwaysFalse: strictNullChecks ? TypeFacts.SymbolStrictAlwaysFalse : TypeFacts.SymbolAlwaysFalse, - }; + return strictNullChecks ? TypeFacts.SymbolStrictAlwaysFalse : TypeFacts.SymbolAlwaysFalse; } if (flags & TypeFlags.NonPrimitive) { - return strictNullChecks ? - { alwaysTrue: TypeFacts.ObjectStrictAlwaysTrue, alwaysFalse: TypeFacts.ObjectStrictAlwaysFalse } : - { alwaysTrue: TypeFacts.ObjectAlwaysTrue, alwaysFalse: TypeFacts.ObjectAlwaysFalse }; + return strictNullChecks ? TypeFacts.ObjectStrictAlwaysFalse : TypeFacts.ObjectAlwaysFalse; } if (flags & TypeFlags.Never) { - return { - alwaysTrue: TypeFacts.None, - alwaysFalse: TypeFacts.None, - }; + return TypeFacts.None; } if (flags & TypeFlags.Instantiable) { return !isPatternLiteralType(type) - ? getAlwaysTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) + ? getAlwaysFalseTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) : strictNullChecks - ? { alwaysTrue: TypeFacts.NonEmptyStringStrictAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringStrictAlwaysFalse } - : { alwaysTrue: TypeFacts.NonEmptyStringAlwaysTrue, alwaysFalse: TypeFacts.NonEmptyStringAlwaysFalse }; + ? TypeFacts.NonEmptyStringStrictAlwaysFalse + : TypeFacts.NonEmptyStringAlwaysFalse; } if (flags & TypeFlags.Union) { const types = (type as UnionType).types; if (types.length) { - return reduceLeft(types, (facts, t) => { - const { alwaysTrue, alwaysFalse } = getAlwaysTypeFacts(t, ignoreObjects); - return { alwaysTrue: facts.alwaysTrue & alwaysTrue, alwaysFalse: facts.alwaysFalse & alwaysFalse }; - }, { alwaysTrue: TypeFacts.All, alwaysFalse: TypeFacts.All }); // >> TODO: that's assuming union will never be empty; check that + return reduceLeft(types, (facts, t) => facts & getAlwaysFalseTypeFacts(t, ignoreObjects), TypeFacts.All); } - return { - alwaysTrue: TypeFacts.None, - alwaysFalse: TypeFacts.None, - }; + return TypeFacts.None; } if (flags & TypeFlags.Intersection) { // When an intersection contains a primitive type we ignore object type constituents as they are // presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type. ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive); - return reduceLeft((type as IntersectionType).types, (facts, t) => { - const { alwaysTrue, alwaysFalse } = getAlwaysTypeFacts(t, ignoreObjects); - return { alwaysTrue: facts.alwaysTrue | alwaysTrue, alwaysFalse: facts.alwaysFalse | alwaysFalse }; - }, { alwaysTrue: TypeFacts.None, alwaysFalse: TypeFacts.None }); + return reduceLeft((type as IntersectionType).types, (facts, t) => facts | getAlwaysFalseTypeFacts(t, ignoreObjects), TypeFacts.None); } - return { - alwaysTrue: TypeFacts.None, - alwaysFalse: TypeFacts.None, - }; + return TypeFacts.None; } function getTypeWithFacts(type: Type, include: TypeFacts) { From dd36ec062ff88d49714a3e030861cee992bb130f Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 5 Jan 2022 14:07:10 -0800 Subject: [PATCH 4/4] add typeofEQhostobject to always false function facts --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bfe7f2bf02788..747ea825e8628 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -180,7 +180,7 @@ namespace ts { SymbolAlwaysFalse = TypeofEQString | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQObject | TypeofEQFunction | TypeofEQHostObject, SymbolStrictAlwaysFalse = SymbolAlwaysFalse | TypeofNESymbol | EQUndefined | EQNull | EQUndefinedOrNull, // Function - FunctionAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject, + FunctionAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol | TypeofEQObject | TypeofEQHostObject, FunctionStrictAlwaysFalse = FunctionAlwaysFalse | TypeofNEFunction | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, // Object ObjectAlwaysFalse = TypeofEQSymbol | TypeofEQNumber | TypeofEQBigInt | TypeofEQBoolean | TypeofEQSymbol,