Skip to content

Commit 44c475f

Browse files
authored
Merge pull request #11263 from Microsoft/controlFlowLetVar
Control flow analysis for implicit any variables
2 parents 4a5f56f + dd63c46 commit 44c475f

File tree

115 files changed

+2072
-431
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+2072
-431
lines changed

src/compiler/checker.ts

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ namespace ts {
120120
const resolvingSymbol = createSymbol(SymbolFlags.Transient, "__resolving__");
121121

122122
const anyType = createIntrinsicType(TypeFlags.Any, "any");
123+
const autoType = createIntrinsicType(TypeFlags.Any, "any");
123124
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
124125
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
125126
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined");
@@ -3056,6 +3057,11 @@ namespace ts {
30563057
return undefined;
30573058
}
30583059

3060+
function isAutoVariableInitializer(initializer: Expression) {
3061+
const expr = initializer && skipParentheses(initializer);
3062+
return !expr || expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>expr) === undefinedSymbol;
3063+
}
3064+
30593065
function addOptionality(type: Type, optional: boolean): Type {
30603066
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
30613067
}
@@ -3094,6 +3100,14 @@ namespace ts {
30943100
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
30953101
}
30963102

3103+
// Use control flow type inference for non-ambient, non-exported var or let variables with no initializer
3104+
// or a 'null' or 'undefined' initializer.
3105+
if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) &&
3106+
!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) &&
3107+
!isInAmbientContext(declaration) && isAutoVariableInitializer(declaration.initializer)) {
3108+
return autoType;
3109+
}
3110+
30973111
if (declaration.kind === SyntaxKind.Parameter) {
30983112
const func = <FunctionLikeDeclaration>declaration.parent;
30993113
// For a parameter of a set accessor, use the type of the get accessor if one is present
@@ -8460,7 +8474,9 @@ namespace ts {
84608474
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
84618475
return declaredType;
84628476
}
8463-
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
8477+
const initialType = assumeInitialized ? declaredType :
8478+
declaredType === autoType ? undefinedType :
8479+
includeFalsyTypes(declaredType, TypeFlags.Undefined);
84648480
const visitedFlowStart = visitedFlowCount;
84658481
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
84668482
visitedFlowCount = visitedFlowStart;
@@ -8534,9 +8550,12 @@ namespace ts {
85348550
// Assignments only narrow the computed type if the declared type is a union type. Thus, we
85358551
// only need to evaluate the assigned type if the declared type is a union type.
85368552
if (isMatchingReference(reference, node)) {
8537-
const isIncrementOrDecrement = node.parent.kind === SyntaxKind.PrefixUnaryExpression || node.parent.kind === SyntaxKind.PostfixUnaryExpression;
8538-
return declaredType.flags & TypeFlags.Union && !isIncrementOrDecrement ?
8539-
getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
8553+
if (node.parent.kind === SyntaxKind.PrefixUnaryExpression || node.parent.kind === SyntaxKind.PostfixUnaryExpression) {
8554+
const flowType = getTypeAtFlowNode(flow.antecedent);
8555+
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
8556+
}
8557+
return declaredType === autoType ? getBaseTypeOfLiteralType(getInitialOrAssignedType(node)) :
8558+
declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
85408559
declaredType;
85418560
}
85428561
// We didn't have a direct match. However, if the reference is a dotted name, this
@@ -8980,7 +8999,7 @@ namespace ts {
89808999
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
89819000
location = location.parent;
89829001
}
8983-
if (isExpression(location) && !isAssignmentTarget(location)) {
9002+
if (isPartOfExpression(location) && !isAssignmentTarget(location)) {
89849003
const type = checkExpression(<Expression>location);
89859004
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
89869005
return type;
@@ -9151,13 +9170,23 @@ namespace ts {
91519170
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
91529171
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
91539172
// declaration container are the same).
9154-
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter ||
9155-
isOuterVariable || isInAmbientContext(declaration);
9173+
const assumeInitialized = isParameter || isOuterVariable ||
9174+
type !== autoType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) ||
9175+
isInAmbientContext(declaration);
91569176
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer);
91579177
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
91589178
// from declaration to use, and when the variable's declared type doesn't include undefined but the
91599179
// control flow based type does include undefined.
9160-
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
9180+
if (type === autoType) {
9181+
if (flowType === autoType) {
9182+
if (compilerOptions.noImplicitAny) {
9183+
error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_any_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol));
9184+
error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(anyType));
9185+
}
9186+
return anyType;
9187+
}
9188+
}
9189+
else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
91619190
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
91629191
// Return the declared type to reduce follow-on errors
91639192
return type;
@@ -15906,6 +15935,10 @@ namespace ts {
1590615935
}
1590715936
}
1590815937

15938+
function convertAutoToAny(type: Type) {
15939+
return type === autoType ? anyType : type;
15940+
}
15941+
1590915942
// Check variable, parameter, or property declaration
1591015943
function checkVariableLikeDeclaration(node: VariableLikeDeclaration) {
1591115944
checkDecorators(node);
@@ -15956,7 +15989,7 @@ namespace ts {
1595615989
return;
1595715990
}
1595815991
const symbol = getSymbolOfNode(node);
15959-
const type = getTypeOfVariableOrParameterOrProperty(symbol);
15992+
const type = convertAutoToAny(getTypeOfVariableOrParameterOrProperty(symbol));
1596015993
if (node === symbol.valueDeclaration) {
1596115994
// Node is the primary declaration of the symbol, just validate the initializer
1596215995
// Don't validate for-in initializer as it is already an error
@@ -15968,7 +16001,7 @@ namespace ts {
1596816001
else {
1596916002
// Node is a secondary declaration, check that type is identical to primary declaration and check that
1597016003
// initializer is consistent with type associated with the node
15971-
const declarationType = getWidenedTypeForVariableLikeDeclaration(node);
16004+
const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node));
1597216005
if (type !== unknownType && declarationType !== unknownType && !isTypeIdenticalTo(type, declarationType)) {
1597316006
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));
1597416007
}

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,6 +2957,10 @@
29572957
"category": "Error",
29582958
"code": 7033
29592959
},
2960+
"Variable '{0}' implicitly has type 'any' in some locations where its type cannot be determined.": {
2961+
"category": "Error",
2962+
"code": 7034
2963+
},
29602964
"You cannot rename this element.": {
29612965
"category": "Error",
29622966
"code": 8000

tests/baselines/reference/ES5SymbolProperty2.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ tests/cases/conformance/Symbols/ES5SymbolProperty2.ts(10,11): error TS2304: Cann
44

55
==== tests/cases/conformance/Symbols/ES5SymbolProperty2.ts (2 errors) ====
66
module M {
7-
var Symbol;
7+
var Symbol: any;
88

99
export class C {
1010
[Symbol.iterator]() { }

tests/baselines/reference/ES5SymbolProperty2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//// [ES5SymbolProperty2.ts]
22
module M {
3-
var Symbol;
3+
var Symbol: any;
44

55
export class C {
66
[Symbol.iterator]() { }

tests/baselines/reference/ES5SymbolProperty3.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ tests/cases/conformance/Symbols/ES5SymbolProperty3.ts(4,6): error TS2471: A comp
22

33

44
==== tests/cases/conformance/Symbols/ES5SymbolProperty3.ts (1 errors) ====
5-
var Symbol;
5+
var Symbol: any;
66

77
class C {
88
[Symbol.iterator]() { }

tests/baselines/reference/ES5SymbolProperty3.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//// [ES5SymbolProperty3.ts]
2-
var Symbol;
2+
var Symbol: any;
33

44
class C {
55
[Symbol.iterator]() { }

tests/baselines/reference/anyPlusAny1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//// [anyPlusAny1.ts]
2-
var x;
2+
var x: any;
33
x.name = "hello";
44
var z = x + x;
55

tests/baselines/reference/anyPlusAny1.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/compiler/anyPlusAny1.ts ===
2-
var x;
2+
var x: any;
33
>x : Symbol(x, Decl(anyPlusAny1.ts, 0, 3))
44

55
x.name = "hello";

tests/baselines/reference/anyPlusAny1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/compiler/anyPlusAny1.ts ===
2-
var x;
2+
var x: any;
33
>x : any
44

55
x.name = "hello";

tests/baselines/reference/asiPreventsParsingAsTypeAlias01.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ var Foo;
1010
>Foo : any
1111

1212
type
13-
>type : any
13+
>type : undefined
1414

1515
Foo = string;
16-
>Foo = string : any
16+
>Foo = string : undefined
1717
>Foo : any
18-
>string : any
18+
>string : undefined
1919

tests/baselines/reference/assignEveryTypeToAny.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ var e = undefined;
5959
>undefined : undefined
6060

6161
x = e;
62-
>x = e : any
62+
>x = e : undefined
6363
>x : any
64-
>e : any
64+
>e : undefined
6565

6666
var e2: typeof undefined;
6767
>e2 : any

tests/baselines/reference/assignmentLHSIsReference.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//// [assignmentLHSIsReference.ts]
2-
var value;
2+
var value: any;
33

44
// identifiers: variable and parameter
55
var x1: number;

tests/baselines/reference/assignmentLHSIsReference.symbols

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsReference.ts ===
2-
var value;
2+
var value: any;
33
>value : Symbol(value, Decl(assignmentLHSIsReference.ts, 0, 3))
44

55
// identifiers: variable and parameter

tests/baselines/reference/assignmentLHSIsReference.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsReference.ts ===
2-
var value;
2+
var value: any;
33
>value : any
44

55
// identifiers: variable and parameter

tests/baselines/reference/assignmentLHSIsValue.errors.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ tests/cases/conformance/expressions/assignmentOperator/assignmentLHSIsValue.ts(7
4141

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

4646
// this
4747
class C {

tests/baselines/reference/assignmentLHSIsValue.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//// [assignmentLHSIsValue.ts]
22
// expected error for all the LHS of assignments
3-
var value;
3+
var value: any;
44

55
// this
66
class C {

tests/baselines/reference/capturedLetConstInLoop9.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ for (let x = 0; x < 1; ++x) {
3939
}
4040

4141
switch (x) {
42-
>x : any
42+
>x : undefined
4343

4444
case 1:
4545
>1 : 1

tests/baselines/reference/capturedLetConstInLoop9_ES6.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ for (let x = 0; x < 1; ++x) {
4040
}
4141

4242
switch (x) {
43-
>x : any
43+
>x : undefined
4444

4545
case 1:
4646
>1 : 1

tests/baselines/reference/commentsArgumentsOfCallExpression2.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ foo(/*c2*/ 1, /*d2*/ 1 + 2, /*e1*/ a + b);
1717
>1 : 1
1818
>2 : 2
1919
>a + b : any
20-
>a : any
20+
>a : undefined
2121
>b : any
2222

2323
foo(/*c3*/ function () { }, /*d2*/() => { }, /*e2*/ a + /*e3*/ b);
@@ -26,7 +26,7 @@ foo(/*c3*/ function () { }, /*d2*/() => { }, /*e2*/ a + /*e3*/ b);
2626
>function () { } : () => void
2727
>() => { } : () => void
2828
>a + /*e3*/ b : any
29-
>a : any
29+
>a : undefined
3030
>b : any
3131

3232
foo(/*c3*/ function () { }, /*d3*/() => { }, /*e3*/(a + b));
@@ -36,7 +36,7 @@ foo(/*c3*/ function () { }, /*d3*/() => { }, /*e3*/(a + b));
3636
>() => { } : () => void
3737
>(a + b) : any
3838
>a + b : any
39-
>a : any
39+
>a : undefined
4040
>b : any
4141

4242
foo(

0 commit comments

Comments
 (0)