Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control flow analysis for implicit any variables #11263

Merged
merged 16 commits into from
Oct 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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 @@ -3050,6 +3051,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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this work for [] or {} as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but they each need specialized strategies and I'll cover them in separate PRs.

}

function addOptionality(type: Type, optional: boolean): Type {
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
}
Expand Down Expand Up @@ -3088,6 +3094,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 @@ -8432,7 +8446,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 @@ -8506,9 +8522,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 @@ -8952,7 +8971,7 @@ namespace ts {
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
location = location.parent;
}
if (isExpression(location) && !isAssignmentTarget(location)) {
if (isPartOfExpression(location) && !isAssignmentTarget(location)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change and why? I'm guessing that now we can get the type of a symbol given a child of the symbol's declaration, but I can't guess why.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bug that somehow got introduced during some refactoring for the new emitter. The old isExpression was renamed to isPartOfExpression and a new isExpression was introduced that doesn't look at the context (i.e. doesn't access the parent property).

const type = checkExpression(<Expression>location);
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
return type;
Expand Down Expand Up @@ -9123,13 +9142,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 @@ -15874,6 +15903,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 @@ -15924,7 +15957,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 @@ -15936,7 +15969,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 @@ -2953,6 +2953,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