mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 07:13:45 -05:00
Merge pull request #11263 from Microsoft/controlFlowLetVar
Control flow analysis for implicit any variables
This commit is contained in:
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user