Skip to content

Commit 3cbc8d2

Browse files
authored
Fix narrowing of intersection with function type (#47483)
* add and and or mask to typefacts * add comment
1 parent 9b6c179 commit 3cbc8d2

File tree

5 files changed

+168
-1
lines changed

5 files changed

+168
-1
lines changed

src/compiler/checker.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ namespace ts {
134134
EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull),
135135
AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined,
136136
EmptyObjectFacts = All,
137+
// Masks
138+
OrFactsMask = TypeofEQFunction | TypeofEQObject | TypeofNEObject,
139+
AndFactsMask = All & ~OrFactsMask,
137140
}
138141

139142
const typeofEQFacts: ReadonlyESMap<string, TypeFacts> = new Map(getEntries({
@@ -22992,11 +22995,24 @@ namespace ts {
2299222995
// When an intersection contains a primitive type we ignore object type constituents as they are
2299322996
// presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type.
2299422997
ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive);
22995-
return reduceLeft((type as UnionType).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All);
22998+
return getIntersectionTypeFacts(type as IntersectionType, ignoreObjects);
2299622999
}
2299723000
return TypeFacts.All;
2299823001
}
2299923002

23003+
function getIntersectionTypeFacts(type: IntersectionType, ignoreObjects: boolean): TypeFacts {
23004+
// When computing the type facts of an intersection type, certain type facts are computed as `and`
23005+
// and others are computed as `or`.
23006+
let oredFacts = TypeFacts.None;
23007+
let andedFacts = TypeFacts.All;
23008+
for (const t of type.types) {
23009+
const f = getTypeFacts(t, ignoreObjects);
23010+
oredFacts |= f;
23011+
andedFacts &= f;
23012+
}
23013+
return oredFacts & TypeFacts.OrFactsMask | andedFacts & TypeFacts.AndFactsMask;
23014+
}
23015+
2300023016
function getTypeWithFacts(type: Type, include: TypeFacts) {
2300123017
return filterType(type, t => (getTypeFacts(t) & include) !== 0);
2300223018
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [narrowingTypeofFunction.ts]
2+
type Meta = { foo: string }
3+
interface F { (): string }
4+
5+
function f1(a: (F & Meta) | string) {
6+
if (typeof a === "function") {
7+
a;
8+
}
9+
else {
10+
a;
11+
}
12+
}
13+
14+
function f2<T>(x: (T & F) | T & string) {
15+
if (typeof x === "function") {
16+
x;
17+
}
18+
else {
19+
x;
20+
}
21+
}
22+
23+
//// [narrowingTypeofFunction.js]
24+
"use strict";
25+
function f1(a) {
26+
if (typeof a === "function") {
27+
a;
28+
}
29+
else {
30+
a;
31+
}
32+
}
33+
function f2(x) {
34+
if (typeof x === "function") {
35+
x;
36+
}
37+
else {
38+
x;
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
=== tests/cases/compiler/narrowingTypeofFunction.ts ===
2+
type Meta = { foo: string }
3+
>Meta : Symbol(Meta, Decl(narrowingTypeofFunction.ts, 0, 0))
4+
>foo : Symbol(foo, Decl(narrowingTypeofFunction.ts, 0, 13))
5+
6+
interface F { (): string }
7+
>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27))
8+
9+
function f1(a: (F & Meta) | string) {
10+
>f1 : Symbol(f1, Decl(narrowingTypeofFunction.ts, 1, 26))
11+
>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12))
12+
>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27))
13+
>Meta : Symbol(Meta, Decl(narrowingTypeofFunction.ts, 0, 0))
14+
15+
if (typeof a === "function") {
16+
>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12))
17+
18+
a;
19+
>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12))
20+
}
21+
else {
22+
a;
23+
>a : Symbol(a, Decl(narrowingTypeofFunction.ts, 3, 12))
24+
}
25+
}
26+
27+
function f2<T>(x: (T & F) | T & string) {
28+
>f2 : Symbol(f2, Decl(narrowingTypeofFunction.ts, 10, 1))
29+
>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12))
30+
>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15))
31+
>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12))
32+
>F : Symbol(F, Decl(narrowingTypeofFunction.ts, 0, 27))
33+
>T : Symbol(T, Decl(narrowingTypeofFunction.ts, 12, 12))
34+
35+
if (typeof x === "function") {
36+
>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15))
37+
38+
x;
39+
>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15))
40+
}
41+
else {
42+
x;
43+
>x : Symbol(x, Decl(narrowingTypeofFunction.ts, 12, 15))
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/compiler/narrowingTypeofFunction.ts ===
2+
type Meta = { foo: string }
3+
>Meta : Meta
4+
>foo : string
5+
6+
interface F { (): string }
7+
8+
function f1(a: (F & Meta) | string) {
9+
>f1 : (a: (F & Meta) | string) => void
10+
>a : string | (F & Meta)
11+
12+
if (typeof a === "function") {
13+
>typeof a === "function" : boolean
14+
>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
15+
>a : string | (F & Meta)
16+
>"function" : "function"
17+
18+
a;
19+
>a : F & Meta
20+
}
21+
else {
22+
a;
23+
>a : string
24+
}
25+
}
26+
27+
function f2<T>(x: (T & F) | T & string) {
28+
>f2 : <T>(x: (T & F) | (T & string)) => void
29+
>x : (T & F) | (T & string)
30+
31+
if (typeof x === "function") {
32+
>typeof x === "function" : boolean
33+
>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
34+
>x : (T & F) | (T & string)
35+
>"function" : "function"
36+
37+
x;
38+
>x : (T & F) | (T & string)
39+
}
40+
else {
41+
x;
42+
>x : T & string
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @strict: true
2+
3+
type Meta = { foo: string }
4+
interface F { (): string }
5+
6+
function f1(a: (F & Meta) | string) {
7+
if (typeof a === "function") {
8+
a;
9+
}
10+
else {
11+
a;
12+
}
13+
}
14+
15+
function f2<T>(x: (T & F) | T & string) {
16+
if (typeof x === "function") {
17+
x;
18+
}
19+
else {
20+
x;
21+
}
22+
}

0 commit comments

Comments
 (0)