Skip to content

Commit

Permalink
Merge pull request #11263 from Microsoft/controlFlowLetVar
Browse files Browse the repository at this point in the history
Control flow analysis for implicit any variables
  • Loading branch information
ahejlsberg authored Oct 6, 2016
2 parents 4a5f56f + dd63c46 commit 44c475f
Show file tree
Hide file tree
Showing 115 changed files with 2,072 additions and 431 deletions.
53 changes: 43 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ namespace ts {
const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__");

const anyType = createIntrinsicType(TypeFlags.Any, "any");
const autoType = createIntrinsicType(TypeFlags.Any, "any");
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined");
Expand Down Expand Up @@ -3056,6 +3057,11 @@ namespace ts {
return undefined;
}

function isAutoVariableInitializer(initializer: Expression) {
const expr = initializer && skipParentheses(initializer);
return !expr || expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>expr) === undefinedSymbol;
}

function addOptionality(type: Type, optional: boolean): Type {
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
}
Expand Down Expand Up @@ -3094,6 +3100,14 @@ namespace ts {
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
}

// Use control flow type inference for non-ambient, non-exported var or let variables with no initializer
// or a 'null' or 'undefined' initializer.
if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) &&
!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) &&
!isInAmbientContext(declaration) && isAutoVariableInitializer(declaration.initializer)) {
return autoType;
}

if (declaration.kind === SyntaxKind.Parameter) {
const func = <FunctionLikeDeclaration>declaration.parent;
// For a parameter of a set accessor, use the type of the get accessor if one is present
Expand Down Expand Up @@ -8460,7 +8474,9 @@ namespace ts {
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
const initialType = assumeInitialized ? declaredType :
declaredType === autoType ? undefinedType :
includeFalsyTypes(declaredType, TypeFlags.Undefined);
const visitedFlowStart = visitedFlowCount;
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
visitedFlowCount = visitedFlowStart;
Expand Down Expand Up @@ -8534,9 +8550,12 @@ namespace ts {
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
// only need to evaluate the assigned type if the declared type is a union type.
if (isMatchingReference(reference, node)) {
const isIncrementOrDecrement = node.parent.kind === SyntaxKind.PrefixUnaryExpression || node.parent.kind === SyntaxKind.PostfixUnaryExpression;
return declaredType.flags & TypeFlags.Union && !isIncrementOrDecrement ?
getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
if (node.parent.kind === SyntaxKind.PrefixUnaryExpression || node.parent.kind === SyntaxKind.PostfixUnaryExpression) {
const flowType = getTypeAtFlowNode(flow.antecedent);
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
}
return declaredType === autoType ? getBaseTypeOfLiteralType(getInitialOrAssignedType(node)) :
declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
declaredType;
}
// We didn't have a direct match. However, if the reference is a dotted name, this
Expand Down Expand Up @@ -8980,7 +8999,7 @@ namespace ts {
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
location = location.parent;
}
if (isExpression(location) && !isAssignmentTarget(location)) {
if (isPartOfExpression(location) && !isAssignmentTarget(location)) {
const type = checkExpression(<Expression>location);
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
return type;
Expand Down Expand Up @@ -9151,13 +9170,23 @@ namespace ts {
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter ||
isOuterVariable || isInAmbientContext(declaration);
const assumeInitialized = isParameter || isOuterVariable ||
type !== autoType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) ||
isInAmbientContext(declaration);
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer);
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
// from declaration to use, and when the variable's declared type doesn't include undefined but the
// control flow based type does include undefined.
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
if (type === autoType) {
if (flowType === autoType) {
if (compilerOptions.noImplicitAny) {
error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_any_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol));
error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(anyType));
}
return anyType;
}
}
else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
// Return the declared type to reduce follow-on errors
return type;
Expand Down Expand Up @@ -15906,6 +15935,10 @@ namespace ts {
}
}

function convertAutoToAny(type: Type) {
return type === autoType ? anyType : type;
}

// Check variable, parameter, or property declaration
function checkVariableLikeDeclaration(node: VariableLikeDeclaration) {
checkDecorators(node);
Expand Down Expand Up @@ -15956,7 +15989,7 @@ namespace ts {
return;
}
const symbol = getSymbolOfNode(node);
const type = getTypeOfVariableOrParameterOrProperty(symbol);
const type = convertAutoToAny(getTypeOfVariableOrParameterOrProperty(symbol));
if (node === symbol.valueDeclaration) {
// Node is the primary declaration of the symbol, just validate the initializer
// Don't validate for-in initializer as it is already an error
Expand All @@ -15968,7 +16001,7 @@ namespace ts {
else {
// Node is a secondary declaration, check that type is identical to primary declaration and check that
// initializer is consistent with type associated with the node
const declarationType = getWidenedTypeForVariableLikeDeclaration(node);
const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node));
if (type !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(type, declarationType)) {
error(node.name, Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2, declarationNameToString(node.name), typeToString(type), typeToString(declarationType));
}
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2957,6 +2957,10 @@
"category": "Error",
"code": 7033
},
"Variable '{0}' implicitly has type 'any' in some locations where its type cannot be determined.": {
"category": "Error",
"code": 7034
},
"You cannot rename this element.": {
"category": "Error",
"code": 8000
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5SymbolProperty2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ tests/cases/conformance/Symbols/ES5SymbolProperty2.ts(10,11): error TS2304: Cann

==== tests/cases/conformance/Symbols/ES5SymbolProperty2.ts (2 errors) ====
module M {
var Symbol;
var Symbol: any;

export class C {
[Symbol.iterator]() { }
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5SymbolProperty2.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//// [ES5SymbolProperty2.ts]
module M {
var Symbol;
var Symbol: any;

export class C {
[Symbol.iterator]() { }
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5SymbolProperty3.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ tests/cases/conformance/Symbols/ES5SymbolProperty3.ts(4,6): error TS2471: A comp


==== tests/cases/conformance/Symbols/ES5SymbolProperty3.ts (1 errors) ====
var Symbol;
var Symbol: any;

class C {
[Symbol.iterator]() { }
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/ES5SymbolProperty3.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// [ES5SymbolProperty3.ts]
var Symbol;
var Symbol: any;

class C {
[Symbol.iterator]() { }
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/anyPlusAny1.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// [anyPlusAny1.ts]
var x;
var x: any;
x.name = "hello";
var z = x + x;

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/anyPlusAny1.symbols
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=== tests/cases/compiler/anyPlusAny1.ts ===
var x;
var x: any;
>x : Symbol(x, Decl(anyPlusAny1.ts, 0, 3))

x.name = "hello";
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/anyPlusAny1.types
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=== tests/cases/compiler/anyPlusAny1.ts ===
var x;
var x: any;
>x : any

x.name = "hello";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ var Foo;
>Foo : any

type
>type : any
>type : undefined

Foo = string;
>Foo = string : any
>Foo = string : undefined
>Foo : any
>string : any
>string : undefined

4 changes: 2 additions & 2 deletions tests/baselines/reference/assignEveryTypeToAny.types
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ var e = undefined;
>undefined : undefined

x = e;
>x = e : any
>x = e : undefined
>x : any
>e : any
>e : undefined

var e2: typeof undefined;
>e2 : any
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/assignmentLHSIsReference.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// [assignmentLHSIsReference.ts]
var value;
var value: any;

// identifiers: variable and parameter
var x1: number;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/assignmentLHSIsReference.symbols
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsReference.ts ===
var value;
var value: any;
>value : Symbol(value, Decl(assignmentLHSIsReference.ts, 0, 3))

// identifiers: variable and parameter
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/assignmentLHSIsReference.types
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
=== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsReference.ts ===
var value;
var value: any;
>value : any

// identifiers: variable and parameter
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/assignmentLHSIsValue.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(7

==== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts (39 errors) ====
// expected error for all the LHS of assignments
var value;
var value: any;

// this
class C {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/assignmentLHSIsValue.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//// [assignmentLHSIsValue.ts]
// expected error for all the LHS of assignments
var value;
var value: any;

// this
class C {
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/capturedLetConstInLoop9.types
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ for (let x = 0; x < 1; ++x) {
}

switch (x) {
>x : any
>x : undefined

case 1:
>1 : 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ for (let x = 0; x < 1; ++x) {
}

switch (x) {
>x : any
>x : undefined

case 1:
>1 : 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ foo(/*c2*/ 1, /*d2*/ 1 + 2, /*e1*/ a + b);
>1 : 1
>2 : 2
>a + b : any
>a : any
>a : undefined
>b : any

foo(/*c3*/ function () { }, /*d2*/() => { }, /*e2*/ a + /*e3*/ b);
Expand All @@ -26,7 +26,7 @@ foo(/*c3*/ function () { }, /*d2*/() => { }, /*e2*/ a + /*e3*/ b);
>function () { } : () => void
>() => { } : () => void
>a + /*e3*/ b : any
>a : any
>a : undefined
>b : any

foo(/*c3*/ function () { }, /*d3*/() => { }, /*e3*/(a + b));
Expand All @@ -36,7 +36,7 @@ foo(/*c3*/ function () { }, /*d3*/() => { }, /*e3*/(a + b));
>() => { } : () => void
>(a + b) : any
>a + b : any
>a : any
>a : undefined
>b : any

foo(
Expand Down
Loading

0 comments on commit 44c475f

Please sign in to comment.