Skip to content

Commit 8f847c5

Browse files
committed
Parameters with no assignments implicitly considered const
1 parent 5bdde3b commit 8f847c5

File tree

2 files changed

+70
-30
lines changed

2 files changed

+70
-30
lines changed

src/compiler/checker.ts

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ namespace ts {
531531

532532
function getNodeLinks(node: Node): NodeLinks {
533533
const nodeId = getNodeId(node);
534-
return nodeLinks[nodeId] || (nodeLinks[nodeId] = {});
534+
return nodeLinks[nodeId] || (nodeLinks[nodeId] = { flags: 0 });
535535
}
536536

537537
function isGlobalSourceFile(node: Node) {
@@ -8185,7 +8185,7 @@ namespace ts {
81858185
return incomplete ? { flags: 0, type } : type;
81868186
}
81878187

8188-
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
8188+
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) {
81898189
let key: string;
81908190
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
81918191
return declaredType;
@@ -8237,7 +8237,7 @@ namespace ts {
82378237
else if (flow.flags & FlowFlags.Start) {
82388238
// Check if we should continue with the control flow of the containing function.
82398239
const container = (<FlowStart>flow).container;
8240-
if (container && includeOuterFunctions) {
8240+
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression) {
82418241
flow = container.flowNode;
82428242
continue;
82438243
}
@@ -8708,21 +8708,52 @@ namespace ts {
87088708
function getControlFlowContainer(node: Node): Node {
87098709
while (true) {
87108710
node = node.parent;
8711-
if (isFunctionLike(node) || node.kind === SyntaxKind.ModuleBlock || node.kind === SyntaxKind.SourceFile || node.kind === SyntaxKind.PropertyDeclaration) {
8711+
if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) ||
8712+
node.kind === SyntaxKind.ModuleBlock ||
8713+
node.kind === SyntaxKind.SourceFile ||
8714+
node.kind === SyntaxKind.PropertyDeclaration) {
87128715
return node;
87138716
}
87148717
}
87158718
}
87168719

8717-
function isDeclarationIncludedInFlow(reference: Node, declaration: Declaration, includeOuterFunctions: boolean) {
8718-
const declarationContainer = getControlFlowContainer(declaration);
8719-
let container = getControlFlowContainer(reference);
8720-
while (container !== declarationContainer &&
8721-
(container.kind === SyntaxKind.FunctionExpression || container.kind === SyntaxKind.ArrowFunction) &&
8722-
(includeOuterFunctions || getImmediatelyInvokedFunctionExpression(<FunctionExpression>container))) {
8723-
container = getControlFlowContainer(container);
8720+
// Check if a parameter is assigned anywhere within its declaring function.
8721+
function isParameterAssigned(symbol: Symbol) {
8722+
const func = <FunctionLikeDeclaration>getRootDeclaration(symbol.valueDeclaration).parent;
8723+
const links = getNodeLinks(func);
8724+
if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) {
8725+
links.flags |= NodeCheckFlags.AssignmentsMarked;
8726+
if (!hasParentWithAssignmentsMarked(func)) {
8727+
markParameterAssignments(func);
8728+
}
8729+
}
8730+
return symbol.isAssigned || false;
8731+
}
8732+
8733+
function hasParentWithAssignmentsMarked(node: Node) {
8734+
while (true) {
8735+
node = node.parent;
8736+
if (!node) {
8737+
return false;
8738+
}
8739+
if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) {
8740+
return true;
8741+
}
8742+
}
8743+
}
8744+
8745+
function markParameterAssignments(node: Node) {
8746+
if (node.kind === SyntaxKind.Identifier) {
8747+
if (isAssignmentTarget(node)) {
8748+
const symbol = getResolvedSymbol(<Identifier>node);
8749+
if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) {
8750+
symbol.isAssigned = true;
8751+
}
8752+
}
8753+
}
8754+
else {
8755+
forEachChild(node, markParameterAssignments);
87248756
}
8725-
return container === declarationContainer;
87268757
}
87278758

87288759
function checkIdentifier(node: Identifier): Type {
@@ -8777,15 +8808,22 @@ namespace ts {
87778808
checkNestedBlockScopedBinding(node, symbol);
87788809

87798810
const type = getTypeOfSymbol(localOrExportSymbol);
8780-
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node)) {
8811+
const declaration = localOrExportSymbol.valueDeclaration;
8812+
if (!(localOrExportSymbol.flags & SymbolFlags.Variable) || isAssignmentTarget(node) || !declaration) {
87818813
return type;
87828814
}
8783-
const declaration = localOrExportSymbol.valueDeclaration;
8784-
const includeOuterFunctions = isReadonlySymbol(localOrExportSymbol);
8785-
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || !declaration ||
8786-
getRootDeclaration(declaration).kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
8787-
!isDeclarationIncludedInFlow(node, declaration, includeOuterFunctions);
8788-
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, includeOuterFunctions);
8815+
8816+
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
8817+
const declarationContainer = getControlFlowContainer(declaration);
8818+
let flowContainer = getControlFlowContainer(node);
8819+
while (flowContainer !== declarationContainer &&
8820+
(flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) &&
8821+
(isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) {
8822+
flowContainer = getControlFlowContainer(flowContainer);
8823+
}
8824+
const assumeInitialized = !strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isParameter ||
8825+
flowContainer !== declarationContainer || isInAmbientContext(declaration);
8826+
const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer);
87898827
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
87908828
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
87918829
// Return the declared type to reduce follow-on errors
@@ -9038,7 +9076,7 @@ namespace ts {
90389076
if (isClassLike(container.parent)) {
90399077
const symbol = getSymbolOfNode(container.parent);
90409078
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
9041-
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*includeOuterFunctions*/ true);
9079+
return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer*/ undefined);
90429080
}
90439081

90449082
if (isInJavaScriptFile(node)) {
@@ -10699,7 +10737,7 @@ namespace ts {
1069910737
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
1070010738
return propType;
1070110739
}
10702-
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
10740+
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined);
1070310741
}
1070410742

1070510743
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {

src/compiler/types.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2157,6 +2157,7 @@ namespace ts {
21572157
/* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol
21582158
/* @internal */ constEnumOnlyModule?: boolean; // True if module contains only const enums or other modules with only const enums
21592159
/* @internal */ isReferenced?: boolean; // True if the symbol is referenced elsewhere
2160+
/* @internal */ isAssigned?: boolean; // True if the symbol has assignments
21602161
}
21612162

21622163
/* @internal */
@@ -2209,23 +2210,24 @@ namespace ts {
22092210
AsyncMethodWithSuper = 0x00000800, // An async method that reads a value from a member of 'super'.
22102211
AsyncMethodWithSuperBinding = 0x00001000, // An async method that assigns a value to a member of 'super'.
22112212
CaptureArguments = 0x00002000, // Lexical 'arguments' used in body (for async functions)
2212-
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
2213-
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
2214-
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
2215-
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
2216-
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
2217-
ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body.
2218-
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body.
2219-
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
2213+
EnumValuesComputed = 0x00004000, // Values for enum members have been computed, and any errors have been reported for them.
2214+
LexicalModuleMergesWithClass = 0x00008000, // Instantiated lexical module declaration is merged with a previous class declaration.
2215+
LoopWithCapturedBlockScopedBinding = 0x00010000, // Loop that contains block scoped variable captured in closure
2216+
CapturedBlockScopedBinding = 0x00020000, // Block-scoped binding that is captured in some function
2217+
BlockScopedBindingInLoop = 0x00040000, // Block-scoped binding with declaration nested inside iteration statement
2218+
ClassWithBodyScopedClassBinding = 0x00080000, // Decorated class that contains a binding to itself inside of the class body.
2219+
BodyScopedClassBinding = 0x00100000, // Binding to a decorated class inside of the class's body.
2220+
NeedsLoopOutParameter = 0x00200000, // Block scoped binding whose value should be explicitly copied outside of the converted loop
2221+
AssignmentsMarked = 0x00400000, // Parameter assignments have been marked
22202222
}
22212223

22222224
/* @internal */
22232225
export interface NodeLinks {
2226+
flags?: NodeCheckFlags; // Set of flags specific to Node
22242227
resolvedType?: Type; // Cached type of type node
22252228
resolvedSignature?: Signature; // Cached signature of signature node or call expression
22262229
resolvedSymbol?: Symbol; // Cached name resolution result
22272230
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
2228-
flags?: NodeCheckFlags; // Set of flags specific to Node
22292231
enumMemberValue?: number; // Constant value of enum member
22302232
isVisible?: boolean; // Is this node visible
22312233
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context

0 commit comments

Comments
 (0)