Skip to content

Commit d17450d

Browse files
committed
Merge pull request #8533 from Microsoft/parameterReferencesInInitalizer
guard agains cases when local value in initializer shadows function parameter with the same name
2 parents 72c19ec + a2948e1 commit d17450d

9 files changed

+181
-21
lines changed

src/compiler/checker.ts

+26-21
Original file line numberDiff line numberDiff line change
@@ -14215,38 +14215,43 @@ namespace ts {
1421514215
else if (n.kind === SyntaxKind.Identifier) {
1421614216
// check FunctionLikeDeclaration.locals (stores parameters\function local variable)
1421714217
// if it contains entry with a specified name
14218-
const symbol = getSymbol(func.locals, (<Identifier>n).text, SymbolFlags.Value);
14219-
if (!symbol || symbol === unknownSymbol) {
14218+
const symbol = resolveName(n, (<Identifier>n).text, SymbolFlags.Value | SymbolFlags.Alias, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined);
14219+
if (!symbol || symbol === unknownSymbol || !symbol.valueDeclaration) {
1422014220
return;
1422114221
}
1422214222
if (symbol.valueDeclaration === node) {
1422314223
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
1422414224
return;
1422514225
}
14226-
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
14227-
// it is ok to reference parameter in initializer if either
14228-
// - parameter is located strictly on the left of current parameter declaration
14229-
if (symbol.valueDeclaration.pos < node.pos) {
14230-
return;
14231-
}
14232-
// - parameter is wrapped in function-like entity
14233-
let current = n;
14234-
while (current !== node.initializer) {
14235-
if (isFunctionLike(current.parent)) {
14226+
// locals map for function contain both parameters and function locals
14227+
// so we need to do a bit of extra work to check if reference is legal
14228+
const enclosingContainer = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
14229+
if (enclosingContainer === func) {
14230+
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
14231+
// it is ok to reference parameter in initializer if either
14232+
// - parameter is located strictly on the left of current parameter declaration
14233+
if (symbol.valueDeclaration.pos < node.pos) {
1423614234
return;
1423714235
}
14238-
// computed property names/initializers in instance property declaration of class like entities
14239-
// are executed in constructor and thus deferred
14240-
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
14241-
!(current.parent.flags & NodeFlags.Static) &&
14242-
isClassLike(current.parent.parent)) {
14243-
return;
14236+
// - parameter is wrapped in function-like entity
14237+
let current = n;
14238+
while (current !== node.initializer) {
14239+
if (isFunctionLike(current.parent)) {
14240+
return;
14241+
}
14242+
// computed property names/initializers in instance property declaration of class like entities
14243+
// are executed in constructor and thus deferred
14244+
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
14245+
!(current.parent.flags & NodeFlags.Static) &&
14246+
isClassLike(current.parent.parent)) {
14247+
return;
14248+
}
14249+
current = current.parent;
1424414250
}
14245-
current = current.parent;
14251+
// fall through to report error
1424614252
}
14247-
// fall through to report error
14253+
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
1424814254
}
14249-
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
1425014255
}
1425114256
else {
1425214257
return forEachChild(n, visit);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [parameterReferenceInInitializer1.ts]
2+
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
3+
return undefined;
4+
}
5+
interface Y { x: number }
6+
7+
class C {
8+
constructor(
9+
y: Y,
10+
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
11+
) {
12+
}
13+
}
14+
15+
//// [parameterReferenceInInitializer1.js]
16+
function fn(y, set) {
17+
return undefined;
18+
}
19+
var C = (function () {
20+
function C(y, x // expected to work, but actually doesn't
21+
) {
22+
if (x === void 0) { x = fn(y, function (y, x) { return y.x = x; }); }
23+
this.x = x;
24+
}
25+
return C;
26+
}());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/compiler/parameterReferenceInInitializer1.ts ===
2+
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
3+
>fn : Symbol(fn, Decl(parameterReferenceInInitializer1.ts, 0, 0))
4+
>a : Symbol(a, Decl(parameterReferenceInInitializer1.ts, 0, 12))
5+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 0, 15))
6+
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
7+
>set : Symbol(set, Decl(parameterReferenceInInitializer1.ts, 0, 20))
8+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 0, 27))
9+
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
10+
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 0, 32))
11+
>a : Symbol(a, Decl(parameterReferenceInInitializer1.ts, 0, 12))
12+
13+
return undefined;
14+
>undefined : Symbol(undefined)
15+
}
16+
interface Y { x: number }
17+
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
18+
>x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
19+
20+
class C {
21+
>C : Symbol(C, Decl(parameterReferenceInInitializer1.ts, 3, 25))
22+
23+
constructor(
24+
y: Y,
25+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 6, 16))
26+
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
27+
28+
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
29+
>x : Symbol(C.x, Decl(parameterReferenceInInitializer1.ts, 7, 13))
30+
>fn : Symbol(fn, Decl(parameterReferenceInInitializer1.ts, 0, 0))
31+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 6, 16))
32+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 8, 26))
33+
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 8, 28))
34+
>y.x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
35+
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 8, 26))
36+
>x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
37+
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 8, 28))
38+
39+
) {
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
=== tests/cases/compiler/parameterReferenceInInitializer1.ts ===
2+
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
3+
>fn : <a>(y: Y, set: (y: Y, x: number) => void) => a
4+
>a : a
5+
>y : Y
6+
>Y : Y
7+
>set : (y: Y, x: number) => void
8+
>y : Y
9+
>Y : Y
10+
>x : number
11+
>a : a
12+
13+
return undefined;
14+
>undefined : undefined
15+
}
16+
interface Y { x: number }
17+
>Y : Y
18+
>x : number
19+
20+
class C {
21+
>C : C
22+
23+
constructor(
24+
y: Y,
25+
>y : Y
26+
>Y : Y
27+
28+
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
29+
>x : {}
30+
>fn(y, (y, x) => y.x = x) : {}
31+
>fn : <a>(y: Y, set: (y: Y, x: number) => void) => a
32+
>y : Y
33+
>(y, x) => y.x = x : (y: Y, x: number) => number
34+
>y : Y
35+
>x : number
36+
>y.x = x : number
37+
>y.x : number
38+
>y : Y
39+
>x : number
40+
>x : number
41+
42+
) {
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [parameterReferenceInInitializer2.ts]
2+
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
3+
// referenced in its initializer
4+
}
5+
6+
//// [parameterReferenceInInitializer2.js]
7+
function Example(x) {
8+
if (x === void 0) { x = function (x) { return x; }; }
9+
// referenced in its initializer
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/parameterReferenceInInitializer2.ts ===
2+
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
3+
>Example : Symbol(Example, Decl(parameterReferenceInInitializer2.ts, 0, 0))
4+
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 17))
5+
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 30))
6+
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 30))
7+
8+
// referenced in its initializer
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== tests/cases/compiler/parameterReferenceInInitializer2.ts ===
2+
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
3+
>Example : (x?: (x: any) => any) => void
4+
>x : (x: any) => any
5+
>function(x: any) { return x; } : (x: any) => any
6+
>x : any
7+
>x : any
8+
9+
// referenced in its initializer
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
2+
return undefined;
3+
}
4+
interface Y { x: number }
5+
6+
class C {
7+
constructor(
8+
y: Y,
9+
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
10+
) {
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
2+
// referenced in its initializer
3+
}

0 commit comments

Comments
 (0)