Merge pull request #8533 from Microsoft/parameterReferencesInInitalizer
guard agains cases when local value in initializer shadows function parameter with the same name
This commit is contained in:
commit
d17450d17b
|
@ -14215,38 +14215,43 @@ namespace ts {
|
|||
else if (n.kind === SyntaxKind.Identifier) {
|
||||
// check FunctionLikeDeclaration.locals (stores parameters\function local variable)
|
||||
// if it contains entry with a specified name
|
||||
const symbol = getSymbol(func.locals, (<Identifier>n).text, SymbolFlags.Value);
|
||||
if (!symbol || symbol === unknownSymbol) {
|
||||
const symbol = resolveName(n, (<Identifier>n).text, SymbolFlags.Value | SymbolFlags.Alias, /*nameNotFoundMessage*/undefined, /*nameArg*/undefined);
|
||||
if (!symbol || symbol === unknownSymbol || !symbol.valueDeclaration) {
|
||||
return;
|
||||
}
|
||||
if (symbol.valueDeclaration === node) {
|
||||
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
|
||||
return;
|
||||
}
|
||||
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
|
||||
// it is ok to reference parameter in initializer if either
|
||||
// - parameter is located strictly on the left of current parameter declaration
|
||||
if (symbol.valueDeclaration.pos < node.pos) {
|
||||
return;
|
||||
}
|
||||
// - parameter is wrapped in function-like entity
|
||||
let current = n;
|
||||
while (current !== node.initializer) {
|
||||
if (isFunctionLike(current.parent)) {
|
||||
// locals map for function contain both parameters and function locals
|
||||
// so we need to do a bit of extra work to check if reference is legal
|
||||
const enclosingContainer = getEnclosingBlockScopeContainer(symbol.valueDeclaration);
|
||||
if (enclosingContainer === func) {
|
||||
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
|
||||
// it is ok to reference parameter in initializer if either
|
||||
// - parameter is located strictly on the left of current parameter declaration
|
||||
if (symbol.valueDeclaration.pos < node.pos) {
|
||||
return;
|
||||
}
|
||||
// computed property names/initializers in instance property declaration of class like entities
|
||||
// are executed in constructor and thus deferred
|
||||
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
|
||||
!(current.parent.flags & NodeFlags.Static) &&
|
||||
isClassLike(current.parent.parent)) {
|
||||
return;
|
||||
// - parameter is wrapped in function-like entity
|
||||
let current = n;
|
||||
while (current !== node.initializer) {
|
||||
if (isFunctionLike(current.parent)) {
|
||||
return;
|
||||
}
|
||||
// computed property names/initializers in instance property declaration of class like entities
|
||||
// are executed in constructor and thus deferred
|
||||
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
|
||||
!(current.parent.flags & NodeFlags.Static) &&
|
||||
isClassLike(current.parent.parent)) {
|
||||
return;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
current = current.parent;
|
||||
// fall through to report error
|
||||
}
|
||||
// fall through to report error
|
||||
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
|
||||
}
|
||||
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
|
||||
}
|
||||
else {
|
||||
return forEachChild(n, visit);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//// [parameterReferenceInInitializer1.ts]
|
||||
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
|
||||
return undefined;
|
||||
}
|
||||
interface Y { x: number }
|
||||
|
||||
class C {
|
||||
constructor(
|
||||
y: Y,
|
||||
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
//// [parameterReferenceInInitializer1.js]
|
||||
function fn(y, set) {
|
||||
return undefined;
|
||||
}
|
||||
var C = (function () {
|
||||
function C(y, x // expected to work, but actually doesn't
|
||||
) {
|
||||
if (x === void 0) { x = fn(y, function (y, x) { return y.x = x; }); }
|
||||
this.x = x;
|
||||
}
|
||||
return C;
|
||||
}());
|
|
@ -0,0 +1,41 @@
|
|||
=== tests/cases/compiler/parameterReferenceInInitializer1.ts ===
|
||||
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
|
||||
>fn : Symbol(fn, Decl(parameterReferenceInInitializer1.ts, 0, 0))
|
||||
>a : Symbol(a, Decl(parameterReferenceInInitializer1.ts, 0, 12))
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 0, 15))
|
||||
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
|
||||
>set : Symbol(set, Decl(parameterReferenceInInitializer1.ts, 0, 20))
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 0, 27))
|
||||
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 0, 32))
|
||||
>a : Symbol(a, Decl(parameterReferenceInInitializer1.ts, 0, 12))
|
||||
|
||||
return undefined;
|
||||
>undefined : Symbol(undefined)
|
||||
}
|
||||
interface Y { x: number }
|
||||
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
|
||||
>x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
|
||||
|
||||
class C {
|
||||
>C : Symbol(C, Decl(parameterReferenceInInitializer1.ts, 3, 25))
|
||||
|
||||
constructor(
|
||||
y: Y,
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 6, 16))
|
||||
>Y : Symbol(Y, Decl(parameterReferenceInInitializer1.ts, 2, 1))
|
||||
|
||||
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
|
||||
>x : Symbol(C.x, Decl(parameterReferenceInInitializer1.ts, 7, 13))
|
||||
>fn : Symbol(fn, Decl(parameterReferenceInInitializer1.ts, 0, 0))
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 6, 16))
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 8, 26))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 8, 28))
|
||||
>y.x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
|
||||
>y : Symbol(y, Decl(parameterReferenceInInitializer1.ts, 8, 26))
|
||||
>x : Symbol(Y.x, Decl(parameterReferenceInInitializer1.ts, 3, 13))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer1.ts, 8, 28))
|
||||
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
=== tests/cases/compiler/parameterReferenceInInitializer1.ts ===
|
||||
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
|
||||
>fn : <a>(y: Y, set: (y: Y, x: number) => void) => a
|
||||
>a : a
|
||||
>y : Y
|
||||
>Y : Y
|
||||
>set : (y: Y, x: number) => void
|
||||
>y : Y
|
||||
>Y : Y
|
||||
>x : number
|
||||
>a : a
|
||||
|
||||
return undefined;
|
||||
>undefined : undefined
|
||||
}
|
||||
interface Y { x: number }
|
||||
>Y : Y
|
||||
>x : number
|
||||
|
||||
class C {
|
||||
>C : C
|
||||
|
||||
constructor(
|
||||
y: Y,
|
||||
>y : Y
|
||||
>Y : Y
|
||||
|
||||
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
|
||||
>x : {}
|
||||
>fn(y, (y, x) => y.x = x) : {}
|
||||
>fn : <a>(y: Y, set: (y: Y, x: number) => void) => a
|
||||
>y : Y
|
||||
>(y, x) => y.x = x : (y: Y, x: number) => number
|
||||
>y : Y
|
||||
>x : number
|
||||
>y.x = x : number
|
||||
>y.x : number
|
||||
>y : Y
|
||||
>x : number
|
||||
>x : number
|
||||
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
//// [parameterReferenceInInitializer2.ts]
|
||||
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
|
||||
// referenced in its initializer
|
||||
}
|
||||
|
||||
//// [parameterReferenceInInitializer2.js]
|
||||
function Example(x) {
|
||||
if (x === void 0) { x = function (x) { return x; }; }
|
||||
// referenced in its initializer
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
=== tests/cases/compiler/parameterReferenceInInitializer2.ts ===
|
||||
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
|
||||
>Example : Symbol(Example, Decl(parameterReferenceInInitializer2.ts, 0, 0))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 17))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 30))
|
||||
>x : Symbol(x, Decl(parameterReferenceInInitializer2.ts, 0, 30))
|
||||
|
||||
// referenced in its initializer
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
=== tests/cases/compiler/parameterReferenceInInitializer2.ts ===
|
||||
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
|
||||
>Example : (x?: (x: any) => any) => void
|
||||
>x : (x: any) => any
|
||||
>function(x: any) { return x; } : (x: any) => any
|
||||
>x : any
|
||||
>x : any
|
||||
|
||||
// referenced in its initializer
|
||||
}
|
12
tests/cases/compiler/parameterReferenceInInitializer1.ts
Normal file
12
tests/cases/compiler/parameterReferenceInInitializer1.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
function fn<a>(y: Y, set: (y: Y, x: number) => void): a {
|
||||
return undefined;
|
||||
}
|
||||
interface Y { x: number }
|
||||
|
||||
class C {
|
||||
constructor(
|
||||
y: Y,
|
||||
public x = fn(y, (y, x) => y.x = x) // expected to work, but actually doesn't
|
||||
) {
|
||||
}
|
||||
}
|
3
tests/cases/compiler/parameterReferenceInInitializer2.ts
Normal file
3
tests/cases/compiler/parameterReferenceInInitializer2.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
|
||||
// referenced in its initializer
|
||||
}
|
Loading…
Reference in a new issue