Skip to content

Commit a817435

Browse files
committed
Only check for undefined or null directly or in unions
1 parent 1b96117 commit a817435

5 files changed

+333
-9
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21227,17 +21227,22 @@ namespace ts {
2122721227
}
2122821228

2122921229
const supertypeOrUnion = getSupertypeOrUnion(types);
21230-
const supertypeOrUnionFacts = getTypeFacts(supertypeOrUnion);
21231-
const allFacts = getAllTypeFacts(types);
21230+
21231+
const shouldHaveNull = some(types, t => someHas(t, TypeFlags.Null));
21232+
const shouldHaveUndefined = some(types, t => someHas(t, TypeFlags.Undefined));
2123221233

2123321234
let missingNullableFlags: TypeFlags = 0;
21234-
if (allFacts & TypeFacts.IsNull && !(supertypeOrUnionFacts & TypeFacts.IsNull)) {
21235+
if (shouldHaveNull && !someHas(supertypeOrUnion, TypeFlags.Null)) {
2123521236
missingNullableFlags |= TypeFlags.Null;
2123621237
}
21237-
if (allFacts & TypeFacts.IsUndefined && !(supertypeOrUnionFacts & TypeFacts.IsUndefined)) {
21238+
if (shouldHaveUndefined && !someHas(supertypeOrUnion, TypeFlags.Undefined)) {
2123821239
missingNullableFlags |= TypeFlags.Undefined;
2123921240
}
2124021241
return getNullableType(supertypeOrUnion, missingNullableFlags);
21242+
21243+
function someHas(type: Type, flags: TypeFlags): boolean {
21244+
return someType(type, t => !!(t.flags & flags));
21245+
}
2124121246
}
2124221247

2124321248
// Return the leftmost type for which no type to the right is a subtype.
@@ -23724,18 +23729,14 @@ namespace ts {
2372423729
return TypeFacts.None;
2372523730
}
2372623731
if (flags & TypeFlags.Union) {
23727-
return getAllTypeFacts((type as UnionType).types);
23732+
return reduceLeft((type as UnionType).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None);
2372823733
}
2372923734
if (flags & TypeFlags.Intersection) {
2373023735
return getIntersectionTypeFacts(type as IntersectionType);
2373123736
}
2373223737
return TypeFacts.UnknownFacts;
2373323738
}
2373423739

23735-
function getAllTypeFacts(types: Type[]): TypeFacts {
23736-
return reduceLeft(types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None);
23737-
}
23738-
2373923740
function getIntersectionTypeFacts(type: IntersectionType): TypeFacts {
2374023741
// When an intersection contains a primitive type we ignore object type constituents as they are
2374123742
// presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//// [inferenceDoesNotAddUndefinedOrNull.ts]
2+
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
3+
4+
interface Node {
5+
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
6+
}
7+
8+
declare function toArray<T>(value: T | T[]): T[];
9+
declare function toArray<T>(value: T | readonly T[]): readonly T[];
10+
11+
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
12+
const result: T[] = [];
13+
node.forEachChild(child => {
14+
const value = cb(child);
15+
if (value !== undefined) {
16+
result.push(...toArray(value));
17+
}
18+
});
19+
return result;
20+
}
21+
22+
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
23+
const result: T[] = [];
24+
node.forEachChild(child => {
25+
const value = cb(child);
26+
if (value !== null) {
27+
result.push(...toArray(value));
28+
}
29+
});
30+
return result;
31+
}
32+
33+
34+
//// [inferenceDoesNotAddUndefinedOrNull.js]
35+
"use strict";
36+
function flatMapChildren(node, cb) {
37+
var result = [];
38+
node.forEachChild(function (child) {
39+
var value = cb(child);
40+
if (value !== undefined) {
41+
result.push.apply(result, toArray(value));
42+
}
43+
});
44+
return result;
45+
}
46+
function flatMapChildren2(node, cb) {
47+
var result = [];
48+
node.forEachChild(function (child) {
49+
var value = cb(child);
50+
if (value !== null) {
51+
result.push.apply(result, toArray(value));
52+
}
53+
});
54+
return result;
55+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
=== tests/cases/compiler/inferenceDoesNotAddUndefinedOrNull.ts ===
2+
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
3+
>NodeArray : Symbol(NodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 0))
4+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 20))
5+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
6+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.es5.d.ts, --, --))
7+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 20))
8+
9+
interface Node {
10+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
11+
12+
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
13+
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
14+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
15+
>cbNode : Symbol(cbNode, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 20))
16+
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 29))
17+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
18+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
19+
>cbNodeArray : Symbol(cbNodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 58))
20+
>nodes : Symbol(nodes, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 74))
21+
>NodeArray : Symbol(NodeArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 0))
22+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
23+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
24+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 3, 17))
25+
}
26+
27+
declare function toArray<T>(value: T | T[]): T[];
28+
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
29+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
30+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 28))
31+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
32+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
33+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 25))
34+
35+
declare function toArray<T>(value: T | readonly T[]): readonly T[];
36+
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
37+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
38+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 28))
39+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
40+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
41+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 25))
42+
43+
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
44+
>flatMapChildren : Symbol(flatMapChildren, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 7, 67))
45+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
46+
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 28))
47+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
48+
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 39))
49+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 45))
50+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
51+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
52+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
53+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
54+
55+
const result: T[] = [];
56+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
57+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 25))
58+
59+
node.forEachChild(child => {
60+
>node.forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
61+
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 28))
62+
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
63+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 11, 22))
64+
65+
const value = cb(child);
66+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
67+
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 9, 39))
68+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 11, 22))
69+
70+
if (value !== undefined) {
71+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
72+
>undefined : Symbol(undefined)
73+
74+
result.push(...toArray(value));
75+
>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
76+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
77+
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
78+
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
79+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 12, 13))
80+
}
81+
});
82+
return result;
83+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 10, 9))
84+
}
85+
86+
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
87+
>flatMapChildren2 : Symbol(flatMapChildren2, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 18, 1))
88+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
89+
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 29))
90+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
91+
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 40))
92+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 46))
93+
>Node : Symbol(Node, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 0, 63))
94+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
95+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
96+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
97+
98+
const result: T[] = [];
99+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
100+
>T : Symbol(T, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 26))
101+
102+
node.forEachChild(child => {
103+
>node.forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
104+
>node : Symbol(node, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 29))
105+
>forEachChild : Symbol(Node.forEachChild, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 2, 16))
106+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 22, 22))
107+
108+
const value = cb(child);
109+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
110+
>cb : Symbol(cb, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 20, 40))
111+
>child : Symbol(child, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 22, 22))
112+
113+
if (value !== null) {
114+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
115+
116+
result.push(...toArray(value));
117+
>result.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
118+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
119+
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
120+
>toArray : Symbol(toArray, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 4, 1), Decl(inferenceDoesNotAddUndefinedOrNull.ts, 6, 49))
121+
>value : Symbol(value, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 23, 13))
122+
}
123+
});
124+
return result;
125+
>result : Symbol(result, Decl(inferenceDoesNotAddUndefinedOrNull.ts, 21, 9))
126+
}
127+
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
=== tests/cases/compiler/inferenceDoesNotAddUndefinedOrNull.ts ===
2+
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
3+
4+
interface Node {
5+
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
6+
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
7+
>cbNode : (node: Node) => T | undefined
8+
>node : Node
9+
>cbNodeArray : ((nodes: NodeArray<Node>) => T | undefined) | undefined
10+
>nodes : NodeArray<Node>
11+
}
12+
13+
declare function toArray<T>(value: T | T[]): T[];
14+
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
15+
>value : T | T[]
16+
17+
declare function toArray<T>(value: T | readonly T[]): readonly T[];
18+
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
19+
>value : T | readonly T[]
20+
21+
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
22+
>flatMapChildren : <T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined) => readonly T[]
23+
>node : Node
24+
>cb : (child: Node) => readonly T[] | T | undefined
25+
>child : Node
26+
27+
const result: T[] = [];
28+
>result : T[]
29+
>[] : never[]
30+
31+
node.forEachChild(child => {
32+
>node.forEachChild(child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } }) : void | undefined
33+
>node.forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
34+
>node : Node
35+
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
36+
>child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } } : (child: Node) => void
37+
>child : Node
38+
39+
const value = cb(child);
40+
>value : T | readonly T[] | undefined
41+
>cb(child) : T | readonly T[] | undefined
42+
>cb : (child: Node) => T | readonly T[] | undefined
43+
>child : Node
44+
45+
if (value !== undefined) {
46+
>value !== undefined : boolean
47+
>value : T | readonly T[] | undefined
48+
>undefined : undefined
49+
50+
result.push(...toArray(value));
51+
>result.push(...toArray(value)) : number
52+
>result.push : (...items: T[]) => number
53+
>result : T[]
54+
>push : (...items: T[]) => number
55+
>...toArray(value) : T
56+
>toArray(value) : readonly T[]
57+
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
58+
>value : readonly T[] | (T & ({} | null))
59+
}
60+
});
61+
return result;
62+
>result : T[]
63+
}
64+
65+
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
66+
>flatMapChildren2 : <T>(node: Node, cb: (child: Node) => readonly T[] | T | null) => readonly T[]
67+
>node : Node
68+
>cb : (child: Node) => readonly T[] | T | null
69+
>child : Node
70+
>null : null
71+
72+
const result: T[] = [];
73+
>result : T[]
74+
>[] : never[]
75+
76+
node.forEachChild(child => {
77+
>node.forEachChild(child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } }) : void | undefined
78+
>node.forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
79+
>node : Node
80+
>forEachChild : <T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: ((nodes: NodeArray<Node>) => T | undefined) | undefined) => T | undefined
81+
>child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } } : (child: Node) => void
82+
>child : Node
83+
84+
const value = cb(child);
85+
>value : T | readonly T[] | null
86+
>cb(child) : T | readonly T[] | null
87+
>cb : (child: Node) => T | readonly T[] | null
88+
>child : Node
89+
90+
if (value !== null) {
91+
>value !== null : boolean
92+
>value : T | readonly T[] | null
93+
>null : null
94+
95+
result.push(...toArray(value));
96+
>result.push(...toArray(value)) : number
97+
>result.push : (...items: T[]) => number
98+
>result : T[]
99+
>push : (...items: T[]) => number
100+
>...toArray(value) : T
101+
>toArray(value) : readonly T[]
102+
>toArray : { <T>(value: T | T[]): T[]; <T>(value: T | readonly T[]): readonly T[]; }
103+
>value : readonly T[] | (T & ({} | undefined))
104+
}
105+
});
106+
return result;
107+
>result : T[]
108+
}
109+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @strict: true
2+
3+
interface NodeArray<T extends Node> extends ReadonlyArray<T> {}
4+
5+
interface Node {
6+
forEachChild<T>(cbNode: (node: Node) => T | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T | undefined): T | undefined;
7+
}
8+
9+
declare function toArray<T>(value: T | T[]): T[];
10+
declare function toArray<T>(value: T | readonly T[]): readonly T[];
11+
12+
function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] {
13+
const result: T[] = [];
14+
node.forEachChild(child => {
15+
const value = cb(child);
16+
if (value !== undefined) {
17+
result.push(...toArray(value));
18+
}
19+
});
20+
return result;
21+
}
22+
23+
function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T | null): readonly T[] {
24+
const result: T[] = [];
25+
node.forEachChild(child => {
26+
const value = cb(child);
27+
if (value !== null) {
28+
result.push(...toArray(value));
29+
}
30+
});
31+
return result;
32+
}

0 commit comments

Comments
 (0)