-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Changes from 14 commits
38d16bb
7df7bb2
864ee1c
dda8aac
b14b24a
c37b790
bd19f1d
0621f7e
52d2aa5
4479013
1c39cdc
d8ec857
0739611
6ae5337
6c2344e
dd63c46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"); | ||
|
@@ -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; | ||
} | ||
|
||
function addOptionality(type: Type, optional: boolean): Type { | ||
return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type; | ||
} | ||
|
@@ -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 | ||
|
@@ -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; | ||
|
@@ -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 | ||
|
@@ -8952,7 +8971,7 @@ namespace ts { | |
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { | ||
location = location.parent; | ||
} | ||
if (isExpression(location) && !isAssignmentTarget(location)) { | ||
if (isPartOfExpression(location) && !isAssignmentTarget(location)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
const type = checkExpression(<Expression>location); | ||
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { | ||
return type; | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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 | ||
|
@@ -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)); | ||
} | ||
|
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]() { } | ||
|
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]() { } | ||
|
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; | ||
|
||
|
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"; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,7 +39,7 @@ for (let x = 0; x < 1; ++x) { | |
} | ||
|
||
switch (x) { | ||
>x : any | ||
>x : undefined | ||
|
||
case 1: | ||
>1 : 1 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,7 @@ for (let x = 0; x < 1; ++x) { | |
} | ||
|
||
switch (x) { | ||
>x : any | ||
>x : undefined | ||
|
||
case 1: | ||
>1 : 1 | ||
|
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.