From 864ee1cfc550d3887554461c1d1c4059e9927faa Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 21 Sep 2016 14:17:09 -0700 Subject: [PATCH] Use control flow analysis for let/var with no type annotation or initializer --- src/compiler/checker.ts | 31 ++++++++++++++++++++++------ src/compiler/diagnosticMessages.json | 4 ++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aaa800a391e..ea1b98d2eb6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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"); @@ -3086,6 +3087,13 @@ 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 + if (declaration.kind === SyntaxKind.VariableDeclaration && !isBindingPattern(declaration.name) && + !(getCombinedNodeFlags(declaration) & NodeFlags.Const) && !(getCombinedModifierFlags(declaration) & ModifierFlags.Export) && + !declaration.initializer && !isInAmbientContext(declaration)) { + return autoType; + } + if (declaration.kind === SyntaxKind.Parameter) { const func = declaration.parent; // For a parameter of a set accessor, use the type of the get accessor if one is present @@ -8352,7 +8360,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; @@ -8430,8 +8440,8 @@ namespace ts { const flowType = getTypeAtFlowNode(flow.antecedent); return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } - return declaredType.flags & TypeFlags.Union ? - getAssignmentReducedType(declaredType, getInitialOrAssignedType(node)) : + return declaredType === autoType ? getBaseTypeOfLiteralType(getInitialOrAssignedType(node)) : + declaredType.flags & TypeFlags.Union ? getAssignmentReducedType(declaredType, getInitialOrAssignedType(node)) : declaredType; } // We didn't have a direct match. However, if the reference is a dotted name, this @@ -9042,13 +9052,22 @@ 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_and_is_referenced_in_a_nested_function, symbolToString(symbol)); + } + 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; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 0f3e90405a6..9aa2cb9a6d3 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -2945,6 +2945,10 @@ "category": "Error", "code": 7033 }, + "Variable '{0}' implicitly has type 'any' and is referenced in a nested function.": { + "category": "Error", + "code": 7034 + }, "You cannot rename this element.": { "category": "Error", "code": 8000