Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upControl flow analysis for array construction #11432
Conversation
This comment has been minimized.
This comment has been minimized.
zenmumbler
commented
Oct 11, 2016
|
Will this affect a problem (#11082) I submitted earlier? This sounds like its different enough from my problem but wanted to check if there is any overlap, thanks! |
| } | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| // Maps from T to T and avoids allocation of all elements map to themselves |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@zenmumbler No, this PR doesn't affect the issue in #11082. |
This comment has been minimized.
This comment has been minimized.
|
@mhegazy Want to take a look before I merge this? |
|
Besides a few small changes, I also want to make sure we're OK with less localised errors in a series of functions with no return type annotations. In the example below, the error moves from the line with 'oops' on it to the last line. function f() {
let x = [];
x.push(12);
x.push('oops');
return x;
}
function g() {
let a = g();
a.push('oops 2'); // shouldn't be allowed
return a; // g now has type (string | number)[]
}
let sum = 0;
for (const n of g()) {
sum += n; // error, can't add (string | number) to number
}Right now, I think I'm on the side of fewer annotations, but I think we should know about the pitfalls of the new behaviour. |
|
|
||
|
|
||
| ==== tests/cases/compiler/implicitAnyWidenToAny.ts (2 errors) ==== | ||
| ==== tests/cases/compiler/implicitAnyWidenToAny.ts (1 errors) ==== | ||
| // these should be errors | ||
| var x = null; // error at "x" | ||
| var x1 = undefined; // error at "x1" | ||
| var widenArray = [null, undefined]; // error at "widenArray" | ||
| ~~~~~~~~~~ | ||
| !!! error TS7005: Variable 'widenArray' implicitly has an 'any[]' type. | ||
| var emptyArray = []; // error at "emptyArray" |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
|
||
| declare function cond(): boolean; | ||
|
|
||
| function f1() { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| x[1] = "hello"; | ||
| x[2] = true; | ||
| return x; // (string | number | boolean)[] | ||
| } |
This comment has been minimized.
This comment has been minimized.
sandersn
Oct 12, 2016
Member
what about a test case that case that does x[0] = 5; x[0] = "hello"; x[0] = true? It would still return (string | number | boolean)[], right?
This comment has been minimized.
This comment has been minimized.
ahejlsberg
Oct 13, 2016
Author
Member
Yes, you'd get the same type. We don't look at the value of the index expression, so nothing gained by an additional test.
| @@ -1200,6 +1215,12 @@ namespace ts { | |||
| else { | |||
| forEachChild(node, bind); | |||
| } | |||
| if (node.expression.kind === SyntaxKind.PropertyAccessExpression) { | |||
| const propertyAccess = <PropertyAccessExpression>node.expression; | |||
| if (isNarrowableOperand(propertyAccess.expression) && propertyAccess.name.text === "push") { | |||
This comment has been minimized.
This comment has been minimized.
sandersn
Oct 12, 2016
Member
what about unshift? I used unshift briefly when writing spread types. :)
This comment has been minimized.
This comment has been minimized.
| @@ -8391,6 +8404,11 @@ namespace ts { | |||
| getAssignedType(<Expression>node); | |||
| } | |||
|
|
|||
| function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { | |||
| return node.kind === SyntaxKind.VariableDeclaration && (<VariableDeclaration>node).initializer && isEmptyArrayLiteral((<VariableDeclaration>node).initializer) || | |||
This comment has been minimized.
This comment has been minimized.
sandersn
Oct 12, 2016
Member
can you break these lines up a bit more? They are really hard to read on github.
This comment has been minimized.
This comment has been minimized.
| @@ -8469,21 +8495,115 @@ namespace ts { | |||
| return incomplete ? { flags: 0, type } : type; | |||
| } | |||
|
|
|||
| // An evolving array type tracks the element types that have so far been seen in an | |||
This comment has been minimized.
This comment has been minimized.
| @@ -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 | |||
This comment has been minimized.
This comment has been minimized.
sandersn
Oct 12, 2016
•
Member
why isn't AutoArrayType a new subtype of AnonymousType? I think it would make it easier to track the property that auto array types don't escape the dynamic scope of getFlowTypeOfReference.
This comment has been minimized.
This comment has been minimized.
ahejlsberg
Oct 13, 2016
Author
Member
I don't follow. autoArrayType is the declared type of an auto-inferred any[] and it's already used outside of getFlowTypeOfReference.
This comment has been minimized.
This comment has been minimized.
sandersn
Oct 13, 2016
Member
It's separate from autoArrayType, whose type is actually TypeReference.
I'm talking about the comment at the top of the new code that says
Evolving array types are ultimately converted into manifest array types
and never escape the getFlowTypeOfReference function."
I was suggesting something like:
export interface AutoArrayType extends AnonymousType {
elementType?: Type;
finalArrayType?: Type;
}And then having the new code that returns AnonymousType today return AutoArrayType instead.
This comment has been minimized.
This comment has been minimized.
ahejlsberg
Oct 13, 2016
Author
Member
Oh, I see. The issue with doing it that way is that we don't have a TypeFlags.EvolvingArrayType that would indicate an evolving array type (because we're out of flag bits). Instead, we distinguish by looking for the elementType property on AnonymousType, so it has to be part of AnonymousType.
This comment has been minimized.
This comment has been minimized.
| getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); | ||
| } | ||
|
|
||
| // Return true if the given node is 'x' in an 'x.push(value)' operation. |
This comment has been minimized.
This comment has been minimized.
| visitedFlowCount = visitedFlowStart; | ||
| if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) { | ||
| // When the reference is 'x' in an 'x.push(value)' or 'x[n] = value' operation, we give type | ||
| // 'any[]' to 'x' instead of using the type determed by control flow analysis such that new |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
With the latest commits, when an evolving array has no function f1() {
let a = [];
if (cond()) {
a.push("hello");
}
a; // string[]
}
function f2() {
let a = [];
a; // any[], error with --noImplicitAny
}Note that references to the function f3() {
let a = [];
while (a.length < 10) {
a.push("test");
}
return a; // string[]
} |
This comment has been minimized.
This comment has been minimized.
|
@mhegazy Latest commits should solve the issue we discussed yesterday. |
# Conflicts: # src/compiler/checker.ts
ahejlsberg commentedOct 6, 2016
•
edited
This PR introduces control flow analysis for array construction patterns that originate in an empty array literal (
x = []) followed by some number ofx.push(value),x.unshift(value)orx[n] = valueoperations.A
let,const, orvarvariable declared with no type annotation and an initial value of[]is considered an implicitany[]variable. When an implicitanyvariable (see #11263) or implicitany[]variable is assigned an empty array literal[], each followingx.push(value),x.unshift(value)orx[n] = valueoperation evolves the type of the variable in accordance with what elements are added to it.An array type is evolved only by operations on the variable in which the evolving array type originated. When an evolving array type variable is referenced in an expression, its type is "fixed" and cannot be further evolved.
Similar to implicit
anyvariables, control flow analysis is unable to determine the actual types of implicitany[]variables when they are referenced in nested functions. In such cases, the variables will appear to have typeany[]in the nested functions and errors will be reported for the references if --noImplicitAny is enabled.