Skip to content

Commit b5d1e4c

Browse files
authored
Merge pull request #11432 from Microsoft/controlFlowArrays
Control flow analysis for array construction
2 parents cfbfe32 + c876d92 commit b5d1e4c

20 files changed

+2234
-102
lines changed

src/compiler/binder.ts

+21
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,15 @@ namespace ts {
785785
};
786786
}
787787

788+
function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
789+
setFlowNodeReferenced(antecedent);
790+
return <FlowArrayMutation>{
791+
flags: FlowFlags.ArrayMutation,
792+
antecedent,
793+
node
794+
};
795+
}
796+
788797
function finishFlowLabel(flow: FlowLabel): FlowNode {
789798
const antecedents = flow.antecedents;
790799
if (!antecedents) {
@@ -1165,6 +1174,12 @@ namespace ts {
11651174
forEachChild(node, bind);
11661175
if (operator === SyntaxKind.EqualsToken && !isAssignmentTarget(node)) {
11671176
bindAssignmentTargetFlow(node.left);
1177+
if (node.left.kind === SyntaxKind.ElementAccessExpression) {
1178+
const elementAccess = <ElementAccessExpression>node.left;
1179+
if (isNarrowableOperand(elementAccess.expression)) {
1180+
currentFlow = createFlowArrayMutation(currentFlow, node);
1181+
}
1182+
}
11681183
}
11691184
}
11701185
}
@@ -1225,6 +1240,12 @@ namespace ts {
12251240
else {
12261241
forEachChild(node, bind);
12271242
}
1243+
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
1244+
const propertyAccess = <PropertyAccessExpression>node.expression;
1245+
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
1246+
currentFlow = createFlowArrayMutation(currentFlow, node);
1247+
}
1248+
}
12281249
}
12291250

12301251
function getContainerFlags(node: Node): ContainerFlags {

src/compiler/checker.ts

+232-58
Large diffs are not rendered by default.

src/compiler/core.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,33 @@ namespace ts {
268268
if (array) {
269269
result = [];
270270
for (let i = 0; i < array.length; i++) {
271-
const v = array[i];
272-
result.push(f(v, i));
271+
result.push(f(array[i], i));
273272
}
274273
}
275274
return result;
276275
}
277276

277+
// Maps from T to T and avoids allocation if all elements map to themselves
278+
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[] {
279+
let result: T[];
280+
if (array) {
281+
for (let i = 0; i < array.length; i++) {
282+
if (result) {
283+
result.push(f(array[i], i));
284+
}
285+
else {
286+
const item = array[i];
287+
const mapped = f(item, i);
288+
if (item !== mapped) {
289+
result = array.slice(0, i);
290+
result.push(mapped);
291+
}
292+
}
293+
}
294+
}
295+
return result || array;
296+
}
297+
278298
/**
279299
* Flattens an array containing a mix of array or non-array elements.
280300
*

src/compiler/diagnosticMessages.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2961,7 +2961,7 @@
29612961
"category": "Error",
29622962
"code": 7033
29632963
},
2964-
"Variable '{0}' implicitly has type 'any' in some locations where its type cannot be determined.": {
2964+
"Variable '{0}' implicitly has type '{1}' in some locations where its type cannot be determined.": {
29652965
"category": "Error",
29662966
"code": 7034
29672967
},

src/compiler/types.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1907,8 +1907,9 @@ namespace ts {
19071907
TrueCondition = 1 << 5, // Condition known to be true
19081908
FalseCondition = 1 << 6, // Condition known to be false
19091909
SwitchClause = 1 << 7, // Switch statement clause
1910-
Referenced = 1 << 8, // Referenced as antecedent once
1911-
Shared = 1 << 9, // Referenced as antecedent more than once
1910+
ArrayMutation = 1 << 8, // Potential array mutation
1911+
Referenced = 1 << 9, // Referenced as antecedent once
1912+
Shared = 1 << 10, // Referenced as antecedent more than once
19121913
Label = BranchLabel | LoopLabel,
19131914
Condition = TrueCondition | FalseCondition
19141915
}
@@ -1951,6 +1952,13 @@ namespace ts {
19511952
antecedent: FlowNode;
19521953
}
19531954

1955+
// FlowArrayMutation represents a node potentially mutates an array, i.e. an
1956+
// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'.
1957+
export interface FlowArrayMutation extends FlowNode {
1958+
node: CallExpression | BinaryExpression;
1959+
antecedent: FlowNode;
1960+
}
1961+
19541962
export type FlowType = Type | IncompleteType;
19551963

19561964
// Incomplete types occur during control flow analysis of loops. An IncompleteType
@@ -2748,6 +2756,8 @@ namespace ts {
27482756
export interface AnonymousType extends ObjectType {
27492757
target?: AnonymousType; // Instantiation target
27502758
mapper?: TypeMapper; // Instantiation mapper
2759+
elementType?: Type; // Element expressions of evolving array type
2760+
finalArrayType?: Type; // Final array type of evolving array type
27512761
}
27522762

27532763
/* @internal */

src/compiler/utilities.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,10 @@ namespace ts {
19011901
return node.kind === SyntaxKind.Identifier && (<Identifier>node).text === "Symbol";
19021902
}
19031903

1904+
export function isPushOrUnshiftIdentifier(node: Identifier) {
1905+
return node.text === "push" || node.text === "unshift";
1906+
}
1907+
19041908
export function isModifierKind(token: SyntaxKind): boolean {
19051909
switch (token) {
19061910
case SyntaxKind.AbstractKeyword:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
tests/cases/compiler/controlFlowArrayErrors.ts(5,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
2+
tests/cases/compiler/controlFlowArrayErrors.ts(6,13): error TS7005: Variable 'x' implicitly has an 'any[]' type.
3+
tests/cases/compiler/controlFlowArrayErrors.ts(12,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
4+
tests/cases/compiler/controlFlowArrayErrors.ts(14,13): error TS7005: Variable 'x' implicitly has an 'any[]' type.
5+
tests/cases/compiler/controlFlowArrayErrors.ts(20,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
6+
tests/cases/compiler/controlFlowArrayErrors.ts(23,9): error TS7005: Variable 'x' implicitly has an 'any[]' type.
7+
tests/cases/compiler/controlFlowArrayErrors.ts(30,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
8+
tests/cases/compiler/controlFlowArrayErrors.ts(35,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
9+
tests/cases/compiler/controlFlowArrayErrors.ts(49,5): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures.
10+
tests/cases/compiler/controlFlowArrayErrors.ts(57,12): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'.
11+
tests/cases/compiler/controlFlowArrayErrors.ts(61,11): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
12+
tests/cases/compiler/controlFlowArrayErrors.ts(64,9): error TS7005: Variable 'x' implicitly has an 'any[]' type.
13+
14+
15+
==== tests/cases/compiler/controlFlowArrayErrors.ts (12 errors) ====
16+
17+
declare function cond(): boolean;
18+
19+
function f1() {
20+
let x = []; // Implicit any[] error in some locations
21+
~
22+
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
23+
let y = x; // Implicit any[] error
24+
~
25+
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
26+
x.push(5);
27+
let z = x;
28+
}
29+
30+
function f2() {
31+
let x; // Implicit any[] error in some locations
32+
~
33+
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
34+
x = [];
35+
let y = x; // Implicit any[] error
36+
~
37+
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
38+
x.push(5);
39+
let z = x;
40+
}
41+
42+
function f3() {
43+
let x = []; // Implicit any[] error in some locations
44+
~
45+
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
46+
x.push(5);
47+
function g() {
48+
x; // Implicit any[] error
49+
~
50+
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
51+
}
52+
}
53+
54+
function f4() {
55+
let x;
56+
x = [5, "hello"]; // Non-evolving array
57+
x.push(true); // Error
58+
~~~~
59+
!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
60+
}
61+
62+
function f5() {
63+
let x = [5, "hello"]; // Non-evolving array
64+
x.push(true); // Error
65+
~~~~
66+
!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
67+
}
68+
69+
function f6() {
70+
let x;
71+
if (cond()) {
72+
x = [];
73+
x.push(5);
74+
x.push("hello");
75+
}
76+
else {
77+
x = [true]; // Non-evolving array
78+
}
79+
x; // boolean[] | (string | number)[]
80+
x.push(99); // Error
81+
~~~~~~~~~~
82+
!!! error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '((...items: (string | number)[]) => number) | ((...items: boolean[]) => number)' has no compatible call signatures.
83+
}
84+
85+
function f7() {
86+
let x = []; // x has evolving array value
87+
x.push(5);
88+
let y = x; // y has non-evolving array value
89+
x.push("hello"); // Ok
90+
y.push("hello"); // Error
91+
~~~~~~~
92+
!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'.
93+
}
94+
95+
function f8() {
96+
const x = []; // Implicit any[] error in some locations
97+
~
98+
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
99+
x.push(5);
100+
function g() {
101+
x; // Implicit any[] error
102+
~
103+
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//// [controlFlowArrayErrors.ts]
2+
3+
declare function cond(): boolean;
4+
5+
function f1() {
6+
let x = []; // Implicit any[] error in some locations
7+
let y = x; // Implicit any[] error
8+
x.push(5);
9+
let z = x;
10+
}
11+
12+
function f2() {
13+
let x; // Implicit any[] error in some locations
14+
x = [];
15+
let y = x; // Implicit any[] error
16+
x.push(5);
17+
let z = x;
18+
}
19+
20+
function f3() {
21+
let x = []; // Implicit any[] error in some locations
22+
x.push(5);
23+
function g() {
24+
x; // Implicit any[] error
25+
}
26+
}
27+
28+
function f4() {
29+
let x;
30+
x = [5, "hello"]; // Non-evolving array
31+
x.push(true); // Error
32+
}
33+
34+
function f5() {
35+
let x = [5, "hello"]; // Non-evolving array
36+
x.push(true); // Error
37+
}
38+
39+
function f6() {
40+
let x;
41+
if (cond()) {
42+
x = [];
43+
x.push(5);
44+
x.push("hello");
45+
}
46+
else {
47+
x = [true]; // Non-evolving array
48+
}
49+
x; // boolean[] | (string | number)[]
50+
x.push(99); // Error
51+
}
52+
53+
function f7() {
54+
let x = []; // x has evolving array value
55+
x.push(5);
56+
let y = x; // y has non-evolving array value
57+
x.push("hello"); // Ok
58+
y.push("hello"); // Error
59+
}
60+
61+
function f8() {
62+
const x = []; // Implicit any[] error in some locations
63+
x.push(5);
64+
function g() {
65+
x; // Implicit any[] error
66+
}
67+
}
68+
69+
//// [controlFlowArrayErrors.js]
70+
function f1() {
71+
var x = []; // Implicit any[] error in some locations
72+
var y = x; // Implicit any[] error
73+
x.push(5);
74+
var z = x;
75+
}
76+
function f2() {
77+
var x; // Implicit any[] error in some locations
78+
x = [];
79+
var y = x; // Implicit any[] error
80+
x.push(5);
81+
var z = x;
82+
}
83+
function f3() {
84+
var x = []; // Implicit any[] error in some locations
85+
x.push(5);
86+
function g() {
87+
x; // Implicit any[] error
88+
}
89+
}
90+
function f4() {
91+
var x;
92+
x = [5, "hello"]; // Non-evolving array
93+
x.push(true); // Error
94+
}
95+
function f5() {
96+
var x = [5, "hello"]; // Non-evolving array
97+
x.push(true); // Error
98+
}
99+
function f6() {
100+
var x;
101+
if (cond()) {
102+
x = [];
103+
x.push(5);
104+
x.push("hello");
105+
}
106+
else {
107+
x = [true]; // Non-evolving array
108+
}
109+
x; // boolean[] | (string | number)[]
110+
x.push(99); // Error
111+
}
112+
function f7() {
113+
var x = []; // x has evolving array value
114+
x.push(5);
115+
var y = x; // y has non-evolving array value
116+
x.push("hello"); // Ok
117+
y.push("hello"); // Error
118+
}
119+
function f8() {
120+
var x = []; // Implicit any[] error in some locations
121+
x.push(5);
122+
function g() {
123+
x; // Implicit any[] error
124+
}
125+
}

0 commit comments

Comments
 (0)