Skip to content

Commit 905957d

Browse files
authored
Merge pull request #10357 from Microsoft/implicitConstParameters
Implicit const parameters
2 parents 01aaff7 + 5770157 commit 905957d

File tree

5 files changed

+311
-30
lines changed

5 files changed

+311
-30
lines changed

src/compiler/checker.ts

Lines changed: 72 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) {
@@ -8192,7 +8192,7 @@ namespace ts {
81928192
return incomplete ? { flags: 0, type } : type;
81938193
}
81948194

8195-
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
8195+
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) {
81968196
let key: string;
81978197
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
81988198
return declaredType;
@@ -8244,7 +8244,7 @@ namespace ts {
82448244
else if (flow.flags & FlowFlags.Start) {
82458245
// Check if we should continue with the control flow of the containing function.
82468246
const container = (<FlowStart>flow).container;
8247-
if (container && includeOuterFunctions) {
8247+
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression) {
82488248
flow = container.flowNode;
82498249
continue;
82508250
}
@@ -8722,21 +8722,52 @@ namespace ts {
87228722
function getControlFlowContainer(node: Node): Node {
87238723
while (true) {
87248724
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) {
87268729
return node;
87278730
}
87288731
}
87298732
}
87308733

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);
87388770
}
8739-
return container === declarationContainer;
87408771
}
87418772

87428773
function checkIdentifier(node: Identifier): Type {
@@ -8791,15 +8822,35 @@ namespace ts {
87918822
checkNestedBlockScopedBinding(node, symbol);
87928823

87938824
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) {
87958829
return type;
87968830
}
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.
88038854
if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
88048855
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
88058856
// Return the declared type to reduce follow-on errors
@@ -9052,7 +9103,7 @@ namespace ts {
90529103
if (isClassLike(container.parent)) {
90539104
const symbol = getSymbolOfNode(container.parent);
90549105
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);
90569107
}
90579108

90589109
if (isInJavaScriptFile(node)) {
@@ -10717,7 +10768,7 @@ namespace ts {
1071710768
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
1071810769
return propType;
1071910770
}
10720-
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*includeOuterFunctions*/ false);
10771+
return getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined);
1072110772
}
1072210773

1072310774
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 is a parameter with 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
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
tests/cases/compiler/implicitConstParameters.ts(39,27): error TS2532: Object is possibly 'undefined'.
2+
tests/cases/compiler/implicitConstParameters.ts(45,27): error TS2532: Object is possibly 'undefined'.
3+
4+
5+
==== tests/cases/compiler/implicitConstParameters.ts (2 errors) ====
6+
7+
function doSomething(cb: () => void) {
8+
cb();
9+
}
10+
11+
function fn(x: number | string) {
12+
if (typeof x === 'number') {
13+
doSomething(() => x.toFixed());
14+
}
15+
}
16+
17+
function f1(x: string | undefined) {
18+
if (!x) {
19+
return;
20+
}
21+
doSomething(() => x.length);
22+
}
23+
24+
function f2(x: string | undefined) {
25+
if (x) {
26+
doSomething(() => {
27+
doSomething(() => x.length);
28+
});
29+
}
30+
}
31+
32+
function f3(x: string | undefined) {
33+
inner();
34+
function inner() {
35+
if (x) {
36+
doSomething(() => x.length);
37+
}
38+
}
39+
}
40+
41+
function f4(x: string | undefined) {
42+
x = "abc"; // causes x to be considered non-const
43+
if (x) {
44+
doSomething(() => x.length);
45+
~
46+
!!! error TS2532: Object is possibly 'undefined'.
47+
}
48+
}
49+
50+
function f5(x: string | undefined) {
51+
if (x) {
52+
doSomething(() => x.length);
53+
~
54+
!!! error TS2532: Object is possibly 'undefined'.
55+
}
56+
x = "abc"; // causes x to be considered non-const
57+
}
58+
59+
60+
function f6(x: string | undefined) {
61+
const y = x || "";
62+
if (x) {
63+
doSomething(() => y.length);
64+
}
65+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//// [implicitConstParameters.ts]
2+
3+
function doSomething(cb: () => void) {
4+
cb();
5+
}
6+
7+
function fn(x: number | string) {
8+
if (typeof x === 'number') {
9+
doSomething(() => x.toFixed());
10+
}
11+
}
12+
13+
function f1(x: string | undefined) {
14+
if (!x) {
15+
return;
16+
}
17+
doSomething(() => x.length);
18+
}
19+
20+
function f2(x: string | undefined) {
21+
if (x) {
22+
doSomething(() => {
23+
doSomething(() => x.length);
24+
});
25+
}
26+
}
27+
28+
function f3(x: string | undefined) {
29+
inner();
30+
function inner() {
31+
if (x) {
32+
doSomething(() => x.length);
33+
}
34+
}
35+
}
36+
37+
function f4(x: string | undefined) {
38+
x = "abc"; // causes x to be considered non-const
39+
if (x) {
40+
doSomething(() => x.length);
41+
}
42+
}
43+
44+
function f5(x: string | undefined) {
45+
if (x) {
46+
doSomething(() => x.length);
47+
}
48+
x = "abc"; // causes x to be considered non-const
49+
}
50+
51+
52+
function f6(x: string | undefined) {
53+
const y = x || "";
54+
if (x) {
55+
doSomething(() => y.length);
56+
}
57+
}
58+
59+
//// [implicitConstParameters.js]
60+
function doSomething(cb) {
61+
cb();
62+
}
63+
function fn(x) {
64+
if (typeof x === 'number') {
65+
doSomething(function () { return x.toFixed(); });
66+
}
67+
}
68+
function f1(x) {
69+
if (!x) {
70+
return;
71+
}
72+
doSomething(function () { return x.length; });
73+
}
74+
function f2(x) {
75+
if (x) {
76+
doSomething(function () {
77+
doSomething(function () { return x.length; });
78+
});
79+
}
80+
}
81+
function f3(x) {
82+
inner();
83+
function inner() {
84+
if (x) {
85+
doSomething(function () { return x.length; });
86+
}
87+
}
88+
}
89+
function f4(x) {
90+
x = "abc"; // causes x to be considered non-const
91+
if (x) {
92+
doSomething(function () { return x.length; });
93+
}
94+
}
95+
function f5(x) {
96+
if (x) {
97+
doSomething(function () { return x.length; });
98+
}
99+
x = "abc"; // causes x to be considered non-const
100+
}
101+
function f6(x) {
102+
var y = x || "";
103+
if (x) {
104+
doSomething(function () { return y.length; });
105+
}
106+
}

0 commit comments

Comments
 (0)