Skip to content

Commit

Permalink
Merge pull request #11432 from Microsoft/controlFlowArrays
Browse files Browse the repository at this point in the history
Control flow analysis for array construction
  • Loading branch information
ahejlsberg committed Oct 14, 2016
2 parents cfbfe32 + c876d92 commit b5d1e4c
Show file tree
Hide file tree
Showing 20 changed files with 2,234 additions and 102 deletions.
21 changes: 21 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,15 @@ namespace ts {
};
}

function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
setFlowNodeReferenced(antecedent);
return <FlowArrayMutation>{
flags: FlowFlags.ArrayMutation,
antecedent,
node
};
}

function finishFlowLabel(flow: FlowLabel): FlowNode {
const antecedents = flow.antecedents;
if (!antecedents) {
Expand Down Expand Up @@ -1165,6 +1174,12 @@ namespace ts {
forEachChild(node, bind);
if (operator === SyntaxKind.EqualsToken && !isAssignmentTarget(node)) {
bindAssignmentTargetFlow(node.left);
if (node.left.kind === SyntaxKind.ElementAccessExpression) {
const elementAccess = <ElementAccessExpression>node.left;
if (isNarrowableOperand(elementAccess.expression)) {
currentFlow = createFlowArrayMutation(currentFlow, node);
}
}
}
}
}
Expand Down Expand Up @@ -1225,6 +1240,12 @@ namespace ts {
else {
forEachChild(node, bind);
}
if (node.expression.kind === SyntaxKind.PropertyAccessExpression) {
const propertyAccess = <PropertyAccessExpression>node.expression;
if (isNarrowableOperand(propertyAccess.expression) && isPushOrUnshiftIdentifier(propertyAccess.name)) {
currentFlow = createFlowArrayMutation(currentFlow, node);
}
}
}

function getContainerFlags(node: Node): ContainerFlags {
Expand Down
290 changes: 232 additions & 58 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

24 changes: 22 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,33 @@ namespace ts {
if (array) {
result = [];
for (let i = 0; i < array.length; i++) {
const v = array[i];
result.push(f(v, i));
result.push(f(array[i], i));
}
}
return result;
}

// Maps from T to T and avoids allocation if all elements map to themselves
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[] {
let result: T[];
if (array) {
for (let i = 0; i < array.length; i++) {
if (result) {
result.push(f(array[i], i));
}
else {
const item = array[i];
const mapped = f(item, i);
if (item !== mapped) {
result = array.slice(0, i);
result.push(mapped);
}
}
}
}
return result || array;
}

/**
* Flattens an array containing a mix of array or non-array elements.
*
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2961,7 +2961,7 @@
"category": "Error",
"code": 7033
},
"Variable '{0}' implicitly has type 'any' in some locations where its type cannot be determined.": {
"Variable '{0}' implicitly has type '{1}' in some locations where its type cannot be determined.": {
"category": "Error",
"code": 7034
},
Expand Down
14 changes: 12 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1907,8 +1907,9 @@ namespace ts {
TrueCondition = 1 << 5, // Condition known to be true
FalseCondition = 1 << 6, // Condition known to be false
SwitchClause = 1 << 7, // Switch statement clause
Referenced = 1 << 8, // Referenced as antecedent once
Shared = 1 << 9, // Referenced as antecedent more than once
ArrayMutation = 1 << 8, // Potential array mutation
Referenced = 1 << 9, // Referenced as antecedent once
Shared = 1 << 10, // Referenced as antecedent more than once
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
}
Expand Down Expand Up @@ -1951,6 +1952,13 @@ namespace ts {
antecedent: FlowNode;
}

// FlowArrayMutation represents a node potentially mutates an array, i.e. an
// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'.
export interface FlowArrayMutation extends FlowNode {
node: CallExpression | BinaryExpression;
antecedent: FlowNode;
}

export type FlowType = Type | IncompleteType;

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

/* @internal */
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,10 @@ namespace ts {
return node.kind === SyntaxKind.Identifier && (<Identifier>node).text === "Symbol";
}

export function isPushOrUnshiftIdentifier(node: Identifier) {
return node.text === "push" || node.text === "unshift";
}

export function isModifierKind(token: SyntaxKind): boolean {
switch (token) {
case SyntaxKind.AbstractKeyword:
Expand Down
105 changes: 105 additions & 0 deletions tests/baselines/reference/controlFlowArrayErrors.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
tests/cases/compiler/controlFlowArrayErrors.ts(5,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
tests/cases/compiler/controlFlowArrayErrors.ts(6,13): error TS7005: Variable 'x' implicitly has an 'any[]' type.
tests/cases/compiler/controlFlowArrayErrors.ts(12,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
tests/cases/compiler/controlFlowArrayErrors.ts(14,13): error TS7005: Variable 'x' implicitly has an 'any[]' type.
tests/cases/compiler/controlFlowArrayErrors.ts(20,9): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
tests/cases/compiler/controlFlowArrayErrors.ts(23,9): error TS7005: Variable 'x' implicitly has an 'any[]' type.
tests/cases/compiler/controlFlowArrayErrors.ts(30,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
tests/cases/compiler/controlFlowArrayErrors.ts(35,12): error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
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.
tests/cases/compiler/controlFlowArrayErrors.ts(57,12): error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'.
tests/cases/compiler/controlFlowArrayErrors.ts(61,11): error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
tests/cases/compiler/controlFlowArrayErrors.ts(64,9): error TS7005: Variable 'x' implicitly has an 'any[]' type.


==== tests/cases/compiler/controlFlowArrayErrors.ts (12 errors) ====

declare function cond(): boolean;

function f1() {
let x = []; // Implicit any[] error in some locations
~
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
let y = x; // Implicit any[] error
~
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
x.push(5);
let z = x;
}

function f2() {
let x; // Implicit any[] error in some locations
~
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
x = [];
let y = x; // Implicit any[] error
~
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
x.push(5);
let z = x;
}

function f3() {
let x = []; // Implicit any[] error in some locations
~
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
x.push(5);
function g() {
x; // Implicit any[] error
~
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
}
}

function f4() {
let x;
x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
~~~~
!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
}

function f5() {
let x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
~~~~
!!! error TS2345: Argument of type 'true' is not assignable to parameter of type 'string | number'.
}

function f6() {
let x;
if (cond()) {
x = [];
x.push(5);
x.push("hello");
}
else {
x = [true]; // Non-evolving array
}
x; // boolean[] | (string | number)[]
x.push(99); // Error
~~~~~~~~~~
!!! 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.
}

function f7() {
let x = []; // x has evolving array value
x.push(5);
let y = x; // y has non-evolving array value
x.push("hello"); // Ok
y.push("hello"); // Error
~~~~~~~
!!! error TS2345: Argument of type '"hello"' is not assignable to parameter of type 'number'.
}

function f8() {
const x = []; // Implicit any[] error in some locations
~
!!! error TS7034: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.
x.push(5);
function g() {
x; // Implicit any[] error
~
!!! error TS7005: Variable 'x' implicitly has an 'any[]' type.
}
}
125 changes: 125 additions & 0 deletions tests/baselines/reference/controlFlowArrayErrors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//// [controlFlowArrayErrors.ts]

declare function cond(): boolean;

function f1() {
let x = []; // Implicit any[] error in some locations
let y = x; // Implicit any[] error
x.push(5);
let z = x;
}

function f2() {
let x; // Implicit any[] error in some locations
x = [];
let y = x; // Implicit any[] error
x.push(5);
let z = x;
}

function f3() {
let x = []; // Implicit any[] error in some locations
x.push(5);
function g() {
x; // Implicit any[] error
}
}

function f4() {
let x;
x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
}

function f5() {
let x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
}

function f6() {
let x;
if (cond()) {
x = [];
x.push(5);
x.push("hello");
}
else {
x = [true]; // Non-evolving array
}
x; // boolean[] | (string | number)[]
x.push(99); // Error
}

function f7() {
let x = []; // x has evolving array value
x.push(5);
let y = x; // y has non-evolving array value
x.push("hello"); // Ok
y.push("hello"); // Error
}

function f8() {
const x = []; // Implicit any[] error in some locations
x.push(5);
function g() {
x; // Implicit any[] error
}
}

//// [controlFlowArrayErrors.js]
function f1() {
var x = []; // Implicit any[] error in some locations
var y = x; // Implicit any[] error
x.push(5);
var z = x;
}
function f2() {
var x; // Implicit any[] error in some locations
x = [];
var y = x; // Implicit any[] error
x.push(5);
var z = x;
}
function f3() {
var x = []; // Implicit any[] error in some locations
x.push(5);
function g() {
x; // Implicit any[] error
}
}
function f4() {
var x;
x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
}
function f5() {
var x = [5, "hello"]; // Non-evolving array
x.push(true); // Error
}
function f6() {
var x;
if (cond()) {
x = [];
x.push(5);
x.push("hello");
}
else {
x = [true]; // Non-evolving array
}
x; // boolean[] | (string | number)[]
x.push(99); // Error
}
function f7() {
var x = []; // x has evolving array value
x.push(5);
var y = x; // y has non-evolving array value
x.push("hello"); // Ok
y.push("hello"); // Error
}
function f8() {
var x = []; // Implicit any[] error in some locations
x.push(5);
function g() {
x; // Implicit any[] error
}
}
Loading

0 comments on commit b5d1e4c

Please sign in to comment.