Skip to content

Commit 864ee1c

Browse files
committed
Use control flow analysis for let/var with no type annotation or initializer
1 parent 7df7bb2 commit 864ee1c

File tree

2 files changed

+29
-6
lines changed

2 files changed

+29
-6
lines changed

src/compiler/checker.ts

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

121121
const anyType = createIntrinsicType(TypeFlags.Any, "any");
122+
const autoType = createIntrinsicType(TypeFlags.Any, "any");
122123
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
123124
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
124125
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsWideningType, "undefined");
@@ -3086,6 +3087,13 @@ namespace ts {
30863087
return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality);
30873088
}
30883089

3090+
// Use control flow type inference for non-ambient, non-exported var or let variables with no initializer
3091+
if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) &&
3092+
!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) &&
3093+
!declaration.initializer && !isInAmbientContext(declaration)) {
3094+
return autoType;
3095+
}
3096+
30893097
if (declaration.kind === SyntaxKind.Parameter) {
30903098
const func = <FunctionLikeDeclaration>declaration.parent;
30913099
// For a parameter of a set accessor, use the type of the get accessor if one is present
@@ -8352,7 +8360,9 @@ namespace ts {
83528360
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
83538361
return declaredType;
83548362
}
8355-
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
8363+
const initialType = assumeInitialized ? declaredType :
8364+
declaredType === autoType ? undefinedType :
8365+
includeFalsyTypes(declaredType, TypeFlags.Undefined);
83568366
const visitedFlowStart = visitedFlowCount;
83578367
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
83588368
visitedFlowCount = visitedFlowStart;
@@ -8430,8 +8440,8 @@ namespace ts {
84308440
const flowType = getTypeAtFlowNode(flow.antecedent);
84318441
return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType));
84328442
}
8433-
return declaredType.flags & TypeFlags.Union ?
8434-
getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
8443+
return declaredType === autoType ? getBaseTypeOfLiteralType(getInitialOrAssignedType(node)) :
8444+
declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(<UnionType>declaredType, getInitialOrAssignedType(node)) :
84358445
declaredType;
84368446
}
84378447
// We didn't have a direct match. However, if the reference is a dotted name, this
@@ -9042,13 +9052,22 @@ namespace ts {
90429052
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
90439053
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
90449054
// declaration container are the same).
9045-
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter ||
9046-
isOuterVariable || isInAmbientContext(declaration);
9055+
const assumeInitialized = isParameter || isOuterVariable ||
9056+
type !== autoType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0) ||
9057+
isInAmbientContext(declaration);
90479058
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer);
90489059
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
90499060
// from declaration to use, and when the variable's declared type doesn't include undefined but the
90509061
// control flow based type does include undefined.
9051-
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
9062+
if (type === autoType) {
9063+
if (flowType === autoType) {
9064+
if (compilerOptions.noImplicitAny) {
9065+
error(declaration.name, Diagnostics.Variable_0_implicitly_has_type_any_and_is_referenced_in_a_nested_function, symbolToString(symbol));
9066+
}
9067+
return anyType;
9068+
}
9069+
}
9070+
else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
90529071
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
90539072
// Return the declared type to reduce follow-on errors
90549073
return type;

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2945,6 +2945,10 @@
29452945
"category": "Error",
29462946
"code": 7033
29472947
},
2948+
"Variable '{0}' implicitly has type 'any' and is referenced in a nested function.": {
2949+
"category": "Error",
2950+
"code": 7034
2951+
},
29482952
"You cannot rename this element.": {
29492953
"category": "Error",
29502954
"code": 8000

0 commit comments

Comments
 (0)