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:
Vladimir Matveev 2016-05-09 09:47:12 -07:00
commit d17450d17b
9 changed files with 181 additions and 21 deletions

View file

@ -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);

View file

@ -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;
}());

View file

@ -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))
) {
}
}

View file

@ -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
) {
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View 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
) {
}
}

View file

@ -0,0 +1,3 @@
function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be
// referenced in its initializer
}