@@ -531,7 +531,7 @@ namespace ts {
531
531
532
532
function getNodeLinks(node: Node): NodeLinks {
533
533
const nodeId = getNodeId(node);
534
- return nodeLinks[nodeId] || (nodeLinks[nodeId] = {});
534
+ return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 });
535
535
}
536
536
537
537
function isGlobalSourceFile(node: Node) {
@@ -8192,7 +8192,7 @@ namespace ts {
8192
8192
return incomplete ? { flags: 0, type } : type;
8193
8193
}
8194
8194
8195
- function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean ) {
8195
+ function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node ) {
8196
8196
let key: string;
8197
8197
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
8198
8198
return declaredType;
@@ -8244,7 +8244,7 @@ namespace ts {
8244
8244
else if (flow.flags & FlowFlags.Start) {
8245
8245
// Check if we should continue with the control flow of the containing function.
8246
8246
const container = (<FlowStart>flow).container;
8247
- if (container && includeOuterFunctions ) {
8247
+ if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression ) {
8248
8248
flow = container.flowNode;
8249
8249
continue;
8250
8250
}
@@ -8722,21 +8722,52 @@ namespace ts {
8722
8722
function getControlFlowContainer(node: Node): Node {
8723
8723
while (true) {
8724
8724
node = node.parent;
8725
- if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration) {
8725
+ if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) ||
8726
+ node.kind === SyntaxKind.ModuleBlock ||
8727
+ node.kind === SyntaxKind.SourceFile ||
8728
+ node.kind === SyntaxKind.PropertyDeclaration) {
8726
8729
return node;
8727
8730
}
8728
8731
}
8729
8732
}
8730
8733
8731
- function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
8732
- const declarationContainer = getControlFlowContainer(declaration);
8733
- let container = getControlFlowContainer(reference);
8734
- while (container !== declarationContainer &&
8735
- (container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
8736
- (includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
8737
- container = getControlFlowContainer(container);
8734
+ // Check if a parameter is assigned anywhere within its declaring function.
8735
+ function isParameterAssigned(symbol: Symbol) {
8736
+ const func = <FunctionLikeDeclaration>getRootDeclaration(symbol.valueDeclaration).parent;
8737
+ const links = getNodeLinks(func);
8738
+ if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
8739
+ links.flags |= NodeCheckFlags.AssignmentsMarked;
8740
+ if (!hasParentWithAssignmentsMarked(func)) {
8741
+ markParameterAssignments(func);
8742
+ }
8743
+ }
8744
+ return symbol.isAssigned || false;
8745
+ }
8746
+
8747
+ function hasParentWithAssignmentsMarked(node: Node) {
8748
+ while (true) {
8749
+ node = node.parent;
8750
+ if (!node) {
8751
+ return false;
8752
+ }
8753
+ if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) {
8754
+ return true;
8755
+ }
8756
+ }
8757
+ }
8758
+
8759
+ function markParameterAssignments(node: Node) {
8760
+ if (node.kind === SyntaxKind.Identifier) {
8761
+ if (isAssignmentTarget(node)) {
8762
+ const symbol = getResolvedSymbol(<Identifier>node);
8763
+ if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) {
8764
+ symbol.isAssigned = true;
8765
+ }
8766
+ }
8767
+ }
8768
+ else {
8769
+ forEachChild(node, markParameterAssignments);
8738
8770
}
8739
- return container === declarationContainer;
8740
8771
}
8741
8772
8742
8773
function checkIdentifier(node: Identifier): Type {
@@ -8791,15 +8822,35 @@ namespace ts {
8791
8822
checkNestedBlockScopedBinding(node, symbol);
8792
8823
8793
8824
const type = getTypeOfSymbol(localOrExportSymbol);
8794
- if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) {
8825
+ const declaration = localOrExportSymbol.valueDeclaration;
8826
+ // We only narrow variables and parameters occurring in a non-assignment position. For all other
8827
+ // entities we simply return the declared type.
8828
+ if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node) || !declaration) {
8795
8829
return type;
8796
8830
}
8797
- const declaration = localOrExportSymbol.valueDeclaration;
8798
- const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
8799
- const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
8800
- getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
8801
- !isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
8802
- const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
8831
+ // The declaration container is the innermost function that encloses the declaration of the variable
8832
+ // or parameter. The flow container is the innermost function starting with which we analyze the control
8833
+ // flow graph to determine the control flow based type.
8834
+ const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
8835
+ const declarationContainer = getControlFlowContainer(declaration);
8836
+ let flowContainer = getControlFlowContainer(node);
8837
+ // When the control flow originates in a function expression or arrow function and we are referencing
8838
+ // a const variable or parameter from an outer function, we extend the origin of the control flow
8839
+ // analysis to include the immediately enclosing function.
8840
+ while (flowContainer !== declarationContainer &&
8841
+ (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) &&
8842
+ (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) {
8843
+ flowContainer = getControlFlowContainer(flowContainer);
8844
+ }
8845
+ // We only look for uninitialized variables in strict null checking mode, and only when we can analyze
8846
+ // the entire control flow graph from the variable's declaration (i.e. when the flow container and
8847
+ // declaration container are the same).
8848
+ const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter ||
8849
+ flowContainer !== declarationContainer || isInAmbientContext(declaration);
8850
+ const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer);
8851
+ // A variable is considered uninitialized when it is possible to analyze the entire control flow graph
8852
+ // from declaration to use, and when the variable's declared type doesn't include undefined but the
8853
+ // control flow based type does include undefined.
8803
8854
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
8804
8855
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
8805
8856
// Return the declared type to reduce follow-on errors
@@ -9052,7 +9103,7 @@ namespace ts {
9052
9103
if (isClassLike(container.parent)) {
9053
9104
const symbol = getSymbolOfNode(container.parent);
9054
9105
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
9055
- return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions */ true );
9106
+ return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer */ undefined );
9056
9107
}
9057
9108
9058
9109
if (isInJavaScriptFile(node)) {
@@ -10717,7 +10768,7 @@ namespace ts {
10717
10768
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
10718
10769
return propType;
10719
10770
}
10720
- return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions */ false );
10771
+ return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer */ undefined );
10721
10772
}
10722
10773
10723
10774
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {
0 commit comments