Skip to content

Commit b02b823

Browse files
authored
Track parameter references errors in resolve name rather than secondary pass (#30349)
1 parent 84b8ab8 commit b02b823

13 files changed

+428
-148
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,8 @@ namespace ts {
12921292
let lastLocation: Node | undefined;
12931293
let lastSelfReferenceLocation: Node | undefined;
12941294
let propertyWithInvalidInitializer: Node | undefined;
1295+
let associatedDeclarationForContainingInitializer: ParameterDeclaration | BindingElement | undefined;
1296+
let withinDeferredContext = false;
12951297
const errorLocation = location;
12961298
let grandparent: Node;
12971299
let isInExternalModule = false;
@@ -1352,6 +1354,7 @@ namespace ts {
13521354
}
13531355
}
13541356
}
1357+
withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation);
13551358
switch (location.kind) {
13561359
case SyntaxKind.SourceFile:
13571360
if (!isExternalOrCommonJsModule(<SourceFile>location)) break;
@@ -1547,6 +1550,19 @@ namespace ts {
15471550
// js type aliases do not resolve names from their host, so skip past it
15481551
location = getJSDocHost(location);
15491552
break;
1553+
case SyntaxKind.Parameter:
1554+
if (lastLocation && lastLocation === (location as ParameterDeclaration).initializer) {
1555+
associatedDeclarationForContainingInitializer = location as ParameterDeclaration;
1556+
}
1557+
break;
1558+
case SyntaxKind.BindingElement:
1559+
if (lastLocation && lastLocation === (location as BindingElement).initializer) {
1560+
const root = getRootDeclaration(location);
1561+
if (root.kind === SyntaxKind.Parameter) {
1562+
associatedDeclarationForContainingInitializer = location as BindingElement;
1563+
}
1564+
}
1565+
break;
15501566
}
15511567
if (isSelfReferenceLocation(location)) {
15521568
lastSelfReferenceLocation = location;
@@ -1651,10 +1667,42 @@ namespace ts {
16511667
errorOrSuggestion(!compilerOptions.allowUmdGlobalAccess, errorLocation!, Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead, unescapeLeadingUnderscores(name));
16521668
}
16531669
}
1670+
1671+
// If we're in a parameter initializer, we can't reference the values of the parameter whose initializer we're within or parameters to the right
1672+
if (result && associatedDeclarationForContainingInitializer && !withinDeferredContext && (meaning & SymbolFlags.Value) === SymbolFlags.Value) {
1673+
const candidate = getMergedSymbol(getLateBoundSymbol(result));
1674+
const root = (getRootDeclaration(associatedDeclarationForContainingInitializer) as ParameterDeclaration);
1675+
// A parameter initializer or binding pattern initializer within a parameter cannot refer to itself
1676+
if (candidate === getSymbolOfNode(associatedDeclarationForContainingInitializer)) {
1677+
error(errorLocation, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(associatedDeclarationForContainingInitializer.name));
1678+
}
1679+
// And it cannot refer to any declarations which come after it
1680+
else if (candidate.valueDeclaration && candidate.valueDeclaration.pos > associatedDeclarationForContainingInitializer.pos && root.parent.locals && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) {
1681+
error(errorLocation, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializer.name), declarationNameToString(<Identifier>errorLocation));
1682+
}
1683+
}
16541684
}
16551685
return result;
16561686
}
16571687

1688+
function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean {
1689+
if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) {
1690+
// initializers in instance property declaration of class like entities are executed in constructor and thus deferred
1691+
return isTypeQueryNode(location) || ((
1692+
isFunctionLikeDeclaration(location) ||
1693+
(location.kind === SyntaxKind.PropertyDeclaration && !hasModifier(location, ModifierFlags.Static))
1694+
) && (!lastLocation || lastLocation !== (location as FunctionLike | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred
1695+
}
1696+
if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) {
1697+
return false;
1698+
}
1699+
// generator functions and async functions are not inlined in control flow when immediately invoked
1700+
if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasModifier(location, ModifierFlags.Async)) {
1701+
return true;
1702+
}
1703+
return !getImmediatelyInvokedFunctionExpression(location);
1704+
}
1705+
16581706
function isSelfReferenceLocation(node: Node): boolean {
16591707
switch (node.kind) {
16601708
case SyntaxKind.FunctionDeclaration:
@@ -26304,74 +26352,6 @@ namespace ts {
2630426352
}
2630526353
}
2630626354

26307-
// Check that a parameter initializer contains no references to parameters declared to the right of itself
26308-
function checkParameterInitializer(node: HasExpressionInitializer): void {
26309-
if (getRootDeclaration(node).kind !== SyntaxKind.Parameter) {
26310-
return;
26311-
}
26312-
26313-
const func = getContainingFunction(node);
26314-
visit(node.initializer!);
26315-
26316-
function visit(n: Node): void {
26317-
if (isTypeNode(n) || isDeclarationName(n)) {
26318-
// do not dive in types
26319-
// skip declaration names (i.e. in object literal expressions)
26320-
return;
26321-
}
26322-
if (n.kind === SyntaxKind.PropertyAccessExpression) {
26323-
// skip property names in property access expression
26324-
return visit((<PropertyAccessExpression>n).expression);
26325-
}
26326-
else if (n.kind === SyntaxKind.Identifier) {
26327-
// check FunctionLikeDeclaration.locals (stores parameters\function local variable)
26328-
// if it contains entry with a specified name
26329-
const symbol = resolveName(n, (<Identifier>n).escapedText, SymbolFlags.Value | SymbolFlags.Alias, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined, /*isUse*/ false);
26330-
if (!symbol || symbol === unknownSymbol || !symbol.valueDeclaration) {
26331-
return;
26332-
}
26333-
if (symbol.valueDeclaration === node) {
26334-
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
26335-
return;
26336-
}
26337-
// locals map for function contain both parameters and function locals
26338-
// so we need to do a bit of extra work to check if reference is legal
26339-
const enclosingContainer = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
26340-
if (enclosingContainer === func) {
26341-
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter ||
26342-
symbol.valueDeclaration.kind === SyntaxKind.BindingElement) {
26343-
// it is ok to reference parameter in initializer if either
26344-
// - parameter is located strictly on the left of current parameter declaration
26345-
if (symbol.valueDeclaration.pos < node.pos) {
26346-
return;
26347-
}
26348-
// - parameter is wrapped in function-like entity
26349-
if (findAncestor(
26350-
n,
26351-
current => {
26352-
if (current === node.initializer) {
26353-
return "quit";
26354-
}
26355-
return isFunctionLike(current.parent) ||
26356-
// computed property names/initializers in instance property declaration of class like entities
26357-
// are executed in constructor and thus deferred
26358-
(current.parent.kind === SyntaxKind.PropertyDeclaration &&
26359-
!(hasModifier(current.parent, ModifierFlags.Static)) &&
26360-
isClassLike(current.parent.parent));
26361-
})) {
26362-
return;
26363-
}
26364-
// fall through to report error
26365-
}
26366-
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
26367-
}
26368-
}
26369-
else {
26370-
return forEachChild(n, visit);
26371-
}
26372-
}
26373-
}
26374-
2637526355
function convertAutoToAny(type: Type) {
2637626356
return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type;
2637726357
}
@@ -26448,7 +26428,6 @@ namespace ts {
2644826428
else {
2644926429
checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer);
2645026430
}
26451-
checkParameterInitializer(node);
2645226431
}
2645326432
return;
2645426433
}
@@ -26465,7 +26444,6 @@ namespace ts {
2646526444
hasEntries(symbol.exports);
2646626445
if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) {
2646726446
checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined);
26468-
checkParameterInitializer(node);
2646926447
}
2647026448
}
2647126449
if (symbol.declarations.length > 1) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
tests/cases/compiler/capturedParametersInInitializers1.ts(18,20): error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
2+
tests/cases/compiler/capturedParametersInInitializers1.ts(22,26): error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
3+
tests/cases/compiler/capturedParametersInInitializers1.ts(38,21): error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
4+
5+
6+
==== tests/cases/compiler/capturedParametersInInitializers1.ts (3 errors) ====
7+
// ok - usage is deferred
8+
function foo1(y = class {c = x}, x = 1) {
9+
new y().c;
10+
}
11+
12+
// ok - used in file
13+
function foo2(y = function(x: typeof z) {}, z = 1) {
14+
15+
}
16+
17+
// ok -used in type
18+
let a;
19+
function foo3(y = { x: <typeof z>a }, z = 1) {
20+
21+
}
22+
23+
// error - used before declaration
24+
function foo4(y = {z}, z = 1) {
25+
~
26+
!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
27+
}
28+
29+
// error - used before declaration, IIFEs are inlined
30+
function foo5(y = (() => z)(), z = 1) {
31+
~
32+
!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
33+
}
34+
35+
// ok - IIFE inside another function
36+
function foo6(y = () => (() => z)(), z = 1) {
37+
}
38+
39+
// ok - used inside immediately invoked generator function
40+
function foo7(y = (function*() {yield z})(), z = 1) {
41+
}
42+
43+
// ok - used inside immediately invoked async function
44+
function foo8(y = (async () => z)(), z = 1) {
45+
}
46+
47+
// error - used as computed name of method
48+
function foo9(y = {[z]() { return z; }}, z = 1) {
49+
~
50+
!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'z' declared after it.
51+
}
52+

tests/baselines/reference/capturedParametersInInitializers1.js

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,72 @@ function foo2(y = function(x: typeof z) {}, z = 1) {
1313
let a;
1414
function foo3(y = { x: <typeof z>a }, z = 1) {
1515

16-
}
16+
}
17+
18+
// error - used before declaration
19+
function foo4(y = {z}, z = 1) {
20+
}
21+
22+
// error - used before declaration, IIFEs are inlined
23+
function foo5(y = (() => z)(), z = 1) {
24+
}
25+
26+
// ok - IIFE inside another function
27+
function foo6(y = () => (() => z)(), z = 1) {
28+
}
29+
30+
// ok - used inside immediately invoked generator function
31+
function foo7(y = (function*() {yield z})(), z = 1) {
32+
}
33+
34+
// ok - used inside immediately invoked async function
35+
function foo8(y = (async () => z)(), z = 1) {
36+
}
37+
38+
// error - used as computed name of method
39+
function foo9(y = {[z]() { return z; }}, z = 1) {
40+
}
41+
1742

1843
//// [capturedParametersInInitializers1.js]
44+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
45+
return new (P || (P = Promise))(function (resolve, reject) {
46+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
47+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
48+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
49+
step((generator = generator.apply(thisArg, _arguments || [])).next());
50+
});
51+
};
1952
// ok - usage is deferred
20-
function foo1(y, x) {
21-
if (y === void 0) { y = /** @class */ (function () {
22-
function class_1() {
23-
this.c = x;
24-
}
25-
return class_1;
26-
}()); }
27-
if (x === void 0) { x = 1; }
53+
function foo1(y = class {
54+
constructor() {
55+
this.c = x;
56+
}
57+
}, x = 1) {
2858
new y().c;
2959
}
3060
// ok - used in file
31-
function foo2(y, z) {
32-
if (y === void 0) { y = function (x) { }; }
33-
if (z === void 0) { z = 1; }
61+
function foo2(y = function (x) { }, z = 1) {
3462
}
3563
// ok -used in type
36-
var a;
37-
function foo3(y, z) {
38-
if (y === void 0) { y = { x: a }; }
39-
if (z === void 0) { z = 1; }
64+
let a;
65+
function foo3(y = { x: a }, z = 1) {
66+
}
67+
// error - used before declaration
68+
function foo4(y = { z }, z = 1) {
69+
}
70+
// error - used before declaration, IIFEs are inlined
71+
function foo5(y = (() => z)(), z = 1) {
72+
}
73+
// ok - IIFE inside another function
74+
function foo6(y = () => (() => z)(), z = 1) {
75+
}
76+
// ok - used inside immediately invoked generator function
77+
function foo7(y = (function* () { yield z; })(), z = 1) {
78+
}
79+
// ok - used inside immediately invoked async function
80+
function foo8(y = (() => __awaiter(this, void 0, void 0, function* () { return z; }))(), z = 1) {
81+
}
82+
// error - used as computed name of method
83+
function foo9(y = { [z]() { return z; } }, z = 1) {
4084
}

tests/baselines/reference/capturedParametersInInitializers1.symbols

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,54 @@ function foo3(y = { x: <typeof z>a }, z = 1) {
3636
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37))
3737

3838
}
39+
40+
// error - used before declaration
41+
function foo4(y = {z}, z = 1) {
42+
>foo4 : Symbol(foo4, Decl(capturedParametersInInitializers1.ts, 14, 1))
43+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 17, 14))
44+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 17, 19))
45+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 17, 22))
46+
}
47+
48+
// error - used before declaration, IIFEs are inlined
49+
function foo5(y = (() => z)(), z = 1) {
50+
>foo5 : Symbol(foo5, Decl(capturedParametersInInitializers1.ts, 18, 1))
51+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 21, 14))
52+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 21, 30))
53+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 21, 30))
54+
}
55+
56+
// ok - IIFE inside another function
57+
function foo6(y = () => (() => z)(), z = 1) {
58+
>foo6 : Symbol(foo6, Decl(capturedParametersInInitializers1.ts, 22, 1))
59+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 25, 14))
60+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 25, 36))
61+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 25, 36))
62+
}
63+
64+
// ok - used inside immediately invoked generator function
65+
function foo7(y = (function*() {yield z})(), z = 1) {
66+
>foo7 : Symbol(foo7, Decl(capturedParametersInInitializers1.ts, 26, 1))
67+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 29, 14))
68+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 29, 44))
69+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 29, 44))
70+
}
71+
72+
// ok - used inside immediately invoked async function
73+
function foo8(y = (async () => z)(), z = 1) {
74+
>foo8 : Symbol(foo8, Decl(capturedParametersInInitializers1.ts, 30, 1))
75+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 33, 14))
76+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 33, 36))
77+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 33, 36))
78+
}
79+
80+
// error - used as computed name of method
81+
function foo9(y = {[z]() { return z; }}, z = 1) {
82+
>foo9 : Symbol(foo9, Decl(capturedParametersInInitializers1.ts, 34, 1))
83+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 37, 14))
84+
>[z] : Symbol([z], Decl(capturedParametersInInitializers1.ts, 37, 19))
85+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 37, 40))
86+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 37, 40))
87+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 37, 40))
88+
}
89+

0 commit comments

Comments
 (0)