Skip to content

Commit 4f569c4

Browse files
committed
Merge pull request #8502 from Microsoft/deferred-references-in-initializers
disallow references to local variables of the function from parameter…
2 parents de177d4 + c36c074 commit 4f569c4

11 files changed

+328
-13
lines changed

src/compiler/checker.ts

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14201,28 +14201,54 @@ namespace ts {
1420114201
const func = getContainingFunction(node);
1420214202
visit(node.initializer);
1420314203

14204-
function visit(n: Node) {
14205-
if (n.kind === SyntaxKind.Identifier) {
14206-
const referencedSymbol = getNodeLinks(n).resolvedSymbol;
14204+
function visit(n: Node): void {
14205+
if (isTypeNode(n) || isDeclarationName(n)) {
14206+
// do not dive in types
14207+
// skip declaration names (i.e. in object literal expressions)
14208+
return;
14209+
}
14210+
if (n.kind === SyntaxKind.PropertyAccessExpression) {
14211+
// skip property names in property access expression
14212+
return visit((<PropertyAccessExpression>n).expression);
14213+
}
14214+
else if (n.kind === SyntaxKind.Identifier) {
1420714215
// check FunctionLikeDeclaration.locals (stores parameters\function local variable)
14208-
// if it contains entry with a specified name and if this entry matches the resolved symbol
14209-
if (referencedSymbol && referencedSymbol !== unknownSymbol && getSymbol(func.locals, referencedSymbol.name, SymbolFlags.Value) === referencedSymbol) {
14210-
if (referencedSymbol.valueDeclaration.kind === SyntaxKind.Parameter) {
14211-
if (referencedSymbol.valueDeclaration === node) {
14212-
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
14216+
// if it contains entry with a specified name
14217+
const symbol = getSymbol(func.locals, (<Identifier>n).text, SymbolFlags.Value);
14218+
if (!symbol || symbol === unknownSymbol) {
14219+
return;
14220+
}
14221+
if (symbol.valueDeclaration === node) {
14222+
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
14223+
return;
14224+
}
14225+
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
14226+
// it is ok to reference parameter in initializer if either
14227+
// - parameter is located strictly on the left of current parameter declaration
14228+
if (symbol.valueDeclaration.pos < node.pos) {
14229+
return;
14230+
}
14231+
// - parameter is wrapped in function-like entity
14232+
let current = n;
14233+
while (current !== node.initializer) {
14234+
if (isFunctionLike(current.parent)) {
1421314235
return;
1421414236
}
14215-
if (referencedSymbol.valueDeclaration.pos < node.pos) {
14216-
// legal case - parameter initializer references some parameter strictly on left of current parameter declaration
14237+
// computed property names/initializers in instance property declaration of class like entities
14238+
// are executed in constructor and thus deferred
14239+
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
14240+
!(current.parent.flags & NodeFlags.Static) &&
14241+
isClassLike(current.parent.parent)) {
1421714242
return;
1421814243
}
14219-
// fall through to error reporting
14244+
current = current.parent;
1422014245
}
14221-
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
14246+
// fall through to report error
1422214247
}
14248+
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
1422314249
}
1422414250
else {
14225-
forEachChild(n, visit);
14251+
return forEachChild(n, visit);
1422614252
}
1422714253
}
1422814254
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [capturedParametersInInitializers1.ts]
2+
// ok - usage is deferred
3+
function foo1(y = class {c = x}, x = 1) {
4+
new y().c;
5+
}
6+
7+
// ok - used in file
8+
function foo2(y = function(x: typeof z) {}, z = 1) {
9+
10+
}
11+
12+
// ok -used in type
13+
let a;
14+
function foo3(y = { x: <typeof z>a }, z = 1) {
15+
16+
}
17+
18+
//// [capturedParametersInInitializers1.js]
19+
// ok - usage is deferred
20+
function foo1(y, x) {
21+
if (y === void 0) { y = (function () {
22+
function class_1() {
23+
this.c = x;
24+
}
25+
return class_1;
26+
}()); }
27+
if (x === void 0) { x = 1; }
28+
new y().c;
29+
}
30+
// ok - used in file
31+
function foo2(y, z) {
32+
if (y === void 0) { y = function (x) { }; }
33+
if (z === void 0) { z = 1; }
34+
}
35+
// 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; }
40+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
=== tests/cases/compiler/capturedParametersInInitializers1.ts ===
2+
// ok - usage is deferred
3+
function foo1(y = class {c = x}, x = 1) {
4+
>foo1 : Symbol(foo1, Decl(capturedParametersInInitializers1.ts, 0, 0))
5+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14))
6+
>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
7+
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32))
8+
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32))
9+
10+
new y().c;
11+
>new y().c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
12+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14))
13+
>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
14+
}
15+
16+
// ok - used in file
17+
function foo2(y = function(x: typeof z) {}, z = 1) {
18+
>foo2 : Symbol(foo2, Decl(capturedParametersInInitializers1.ts, 3, 1))
19+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 6, 14))
20+
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 6, 27))
21+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43))
22+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43))
23+
24+
}
25+
26+
// ok -used in type
27+
let a;
28+
>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3))
29+
30+
function foo3(y = { x: <typeof z>a }, z = 1) {
31+
>foo3 : Symbol(foo3, Decl(capturedParametersInInitializers1.ts, 11, 6))
32+
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 12, 14))
33+
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 12, 19))
34+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37))
35+
>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3))
36+
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37))
37+
38+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
=== tests/cases/compiler/capturedParametersInInitializers1.ts ===
2+
// ok - usage is deferred
3+
function foo1(y = class {c = x}, x = 1) {
4+
>foo1 : (y?: typeof (Anonymous class), x?: number) => void
5+
>y : typeof (Anonymous class)
6+
>class {c = x} : typeof (Anonymous class)
7+
>c : number
8+
>x : number
9+
>x : number
10+
>1 : number
11+
12+
new y().c;
13+
>new y().c : number
14+
>new y() : (Anonymous class)
15+
>y : typeof (Anonymous class)
16+
>c : number
17+
}
18+
19+
// ok - used in file
20+
function foo2(y = function(x: typeof z) {}, z = 1) {
21+
>foo2 : (y?: (x: number) => void, z?: number) => void
22+
>y : (x: number) => void
23+
>function(x: typeof z) {} : (x: number) => void
24+
>x : number
25+
>z : number
26+
>z : number
27+
>1 : number
28+
29+
}
30+
31+
// ok -used in type
32+
let a;
33+
>a : any
34+
35+
function foo3(y = { x: <typeof z>a }, z = 1) {
36+
>foo3 : (y?: { x: number; }, z?: number) => void
37+
>y : { x: number; }
38+
>{ x: <typeof z>a } : { x: number; }
39+
>x : number
40+
><typeof z>a : number
41+
>z : number
42+
>a : any
43+
>z : number
44+
>1 : number
45+
46+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
tests/cases/compiler/capturedParametersInInitializers2.ts(1,36): error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it.
2+
tests/cases/compiler/capturedParametersInInitializers2.ts(4,26): error TS1166: A computed property name in a class property declaration must directly refer to a built-in symbol.
3+
4+
5+
==== tests/cases/compiler/capturedParametersInInitializers2.ts (2 errors) ====
6+
function foo(y = class {static c = x}, x = 1) {
7+
~
8+
!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it.
9+
y.c
10+
}
11+
function foo2(y = class {[x] = x}, x = 1) {
12+
~~~
13+
!!! error TS1166: A computed property name in a class property declaration must directly refer to a built-in symbol.
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [capturedParametersInInitializers2.ts]
2+
function foo(y = class {static c = x}, x = 1) {
3+
y.c
4+
}
5+
function foo2(y = class {[x] = x}, x = 1) {
6+
}
7+
8+
//// [capturedParametersInInitializers2.js]
9+
function foo(y, x) {
10+
if (y === void 0) { y = (function () {
11+
function class_1() {
12+
}
13+
class_1.c = x;
14+
return class_1;
15+
}()); }
16+
if (x === void 0) { x = 1; }
17+
y.c;
18+
}
19+
function foo2(y, x) {
20+
if (y === void 0) { y = (function () {
21+
function class_2() {
22+
this[x] = x;
23+
}
24+
return class_2;
25+
}()); }
26+
if (x === void 0) { x = 1; }
27+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tests/cases/compiler/functionLikeInParameterInitializer.ts(2,34): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
2+
tests/cases/compiler/functionLikeInParameterInitializer.ts(6,44): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
3+
tests/cases/compiler/functionLikeInParameterInitializer.ts(11,50): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
4+
tests/cases/compiler/functionLikeInParameterInitializer.ts(16,41): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
5+
6+
7+
==== tests/cases/compiler/functionLikeInParameterInitializer.ts (4 errors) ====
8+
// error
9+
export function bar(func = () => foo) {
10+
~~~
11+
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
12+
let foo = "in";
13+
}
14+
// error
15+
export function baz1(func = { f() { return foo } }) {
16+
~~~
17+
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
18+
let foo = "in";
19+
}
20+
21+
// error
22+
export function baz2(func = function () { return foo }) {
23+
~~~
24+
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
25+
let foo = "in";
26+
}
27+
28+
// error
29+
export function baz3(func = class { x = foo }) {
30+
~~~
31+
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
32+
let foo = "in";
33+
}
34+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//// [functionLikeInParameterInitializer.ts]
2+
// error
3+
export function bar(func = () => foo) {
4+
let foo = "in";
5+
}
6+
// error
7+
export function baz1(func = { f() { return foo } }) {
8+
let foo = "in";
9+
}
10+
11+
// error
12+
export function baz2(func = function () { return foo }) {
13+
let foo = "in";
14+
}
15+
16+
// error
17+
export function baz3(func = class { x = foo }) {
18+
let foo = "in";
19+
}
20+
21+
22+
//// [functionLikeInParameterInitializer.js]
23+
"use strict";
24+
// error
25+
function bar(func) {
26+
if (func === void 0) { func = function () { return foo; }; }
27+
var foo = "in";
28+
}
29+
exports.bar = bar;
30+
// error
31+
function baz1(func) {
32+
if (func === void 0) { func = { f: function () { return foo; } }; }
33+
var foo = "in";
34+
}
35+
exports.baz1 = baz1;
36+
// error
37+
function baz2(func) {
38+
if (func === void 0) { func = function () { return foo; }; }
39+
var foo = "in";
40+
}
41+
exports.baz2 = baz2;
42+
// error
43+
function baz3(func) {
44+
if (func === void 0) { func = (function () {
45+
function class_1() {
46+
this.x = foo;
47+
}
48+
return class_1;
49+
}()); }
50+
var foo = "in";
51+
}
52+
exports.baz3 = baz3;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// ok - usage is deferred
2+
function foo1(y = class {c = x}, x = 1) {
3+
new y().c;
4+
}
5+
6+
// ok - used in file
7+
function foo2(y = function(x: typeof z) {}, z = 1) {
8+
9+
}
10+
11+
// ok -used in type
12+
let a;
13+
function foo3(y = { x: <typeof z>a }, z = 1) {
14+
15+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function foo(y = class {static c = x}, x = 1) {
2+
y.c
3+
}
4+
function foo2(y = class {[x] = x}, x = 1) {
5+
}

0 commit comments

Comments
 (0)