diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ee22318fb20d..6176d1506dfbb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -134,6 +134,9 @@ namespace ts { EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, EmptyObjectFacts = All, + // Masks + OrFactsMask = TypeofEQFunction | TypeofEQObject | TypeofNEObject, + AndFactsMask = All & ~OrFactsMask, } const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ @@ -22985,11 +22988,24 @@ namespace ts { // 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 getIntersectionTypeFacts(type as IntersectionType, ignoreObjects); } return TypeFacts.All; } + function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts { + // When computing the type facts of an intersection type, certain type facts are computed as `and` + // and others are computed as `or`. + let oredFacts = TypeFacts.None; + let andedFacts = TypeFacts.All; + for (const t of type.types) { + const f = getTypeFacts(t, ignoreObjects); + oredFacts |= f; + andedFacts &= f; + } + return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask; + } + function getTypeWithFacts(type: Type, include: TypeFacts) { return filterType(type, t => (getTypeFacts(t) & include) !== 0); } diff --git a/tests/baselines/reference/narrowingTypeofFunction.js b/tests/baselines/reference/narrowingTypeofFunction.js new file mode 100644 index 0000000000000..fb38d9249cfa5 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.js @@ -0,0 +1,40 @@ +//// [narrowingTypeofFunction.ts] +type Meta = { foo: string } +interface F { (): string } + +function f1(a: (F & Meta) | string) { + if (typeof a === "function") { + a; + } + else { + a; + } +} + +function f2(x: (T & F) | T & string) { + if (typeof x === "function") { + x; + } + else { + x; + } +} + +//// [narrowingTypeofFunction.js] +"use strict"; +function f1(a) { + if (typeof a === "function") { + a; + } + else { + a; + } +} +function f2(x) { + if (typeof x === "function") { + x; + } + else { + x; + } +} diff --git a/tests/baselines/reference/narrowingTypeofFunction.symbols b/tests/baselines/reference/narrowingTypeofFunction.symbols new file mode 100644 index 0000000000000..3bad8d9a42ff5 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.symbols @@ -0,0 +1,45 @@ +=== 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)) + +function f1(a: (F & Meta) | string) { +>f1 : Symbol(f1, Decl(narrowingTypeofFunction.ts, 1, 26)) +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12)) +>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, 12)) + + a; +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12)) + } + else { + a; +>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12)) + } +} + +function f2(x: (T & F) | T & string) { +>f2 : Symbol(f2, Decl(narrowingTypeofFunction.ts, 10, 1)) +>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12)) +>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15)) +>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12)) +>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27)) +>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12)) + + if (typeof x === "function") { +>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15)) + + x; +>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15)) + } + else { + x; +>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15)) + } +} diff --git a/tests/baselines/reference/narrowingTypeofFunction.types b/tests/baselines/reference/narrowingTypeofFunction.types new file mode 100644 index 0000000000000..86fbf0571fee2 --- /dev/null +++ b/tests/baselines/reference/narrowingTypeofFunction.types @@ -0,0 +1,44 @@ +=== tests/cases/compiler/narrowingTypeofFunction.ts === +type Meta = { foo: string } +>Meta : Meta +>foo : string + +interface F { (): string } + +function f1(a: (F & Meta) | string) { +>f1 : (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" + + a; +>a : F & Meta + } + else { + a; +>a : string + } +} + +function f2(x: (T & F) | T & string) { +>f2 : (x: (T & F) | (T & string)) => void +>x : (T & F) | (T & string) + + if (typeof x === "function") { +>typeof x === "function" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : (T & F) | (T & string) +>"function" : "function" + + x; +>x : (T & F) | (T & string) + } + else { + x; +>x : T & string + } +} diff --git a/tests/cases/compiler/narrowingTypeofFunction.ts b/tests/cases/compiler/narrowingTypeofFunction.ts new file mode 100644 index 0000000000000..baceaae052222 --- /dev/null +++ b/tests/cases/compiler/narrowingTypeofFunction.ts @@ -0,0 +1,22 @@ +// @strict: true + +type Meta = { foo: string } +interface F { (): string } + +function f1(a: (F & Meta) | string) { + if (typeof a === "function") { + a; + } + else { + a; + } +} + +function f2(x: (T & F) | T & string) { + if (typeof x === "function") { + x; + } + else { + x; + } +} \ No newline at end of file