diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1c88d04e2c..27dbb5905e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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, (n).text, SymbolFlags.Value); - if (!symbol || symbol === unknownSymbol) { + const symbol = resolveName(n, (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(n)); } - error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(n)); } else { return forEachChild(n, visit); diff --git a/tests/baselines/reference/parameterReferenceInInitializer1.js b/tests/baselines/reference/parameterReferenceInInitializer1.js new file mode 100644 index 0000000000..51f98603de --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer1.js @@ -0,0 +1,26 @@ +//// [parameterReferenceInInitializer1.ts] +function fn(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; +}()); diff --git a/tests/baselines/reference/parameterReferenceInInitializer1.symbols b/tests/baselines/reference/parameterReferenceInInitializer1.symbols new file mode 100644 index 0000000000..9981db39bb --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer1.symbols @@ -0,0 +1,41 @@ +=== tests/cases/compiler/parameterReferenceInInitializer1.ts === +function fn(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)) + + ) { + } +} diff --git a/tests/baselines/reference/parameterReferenceInInitializer1.types b/tests/baselines/reference/parameterReferenceInInitializer1.types new file mode 100644 index 0000000000..b73e3256a0 --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer1.types @@ -0,0 +1,44 @@ +=== tests/cases/compiler/parameterReferenceInInitializer1.ts === +function fn(y: Y, set: (y: Y, x: number) => void): a { +>fn : (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 : (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 + + ) { + } +} diff --git a/tests/baselines/reference/parameterReferenceInInitializer2.js b/tests/baselines/reference/parameterReferenceInInitializer2.js new file mode 100644 index 0000000000..c66a1d8cd5 --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer2.js @@ -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 +} diff --git a/tests/baselines/reference/parameterReferenceInInitializer2.symbols b/tests/baselines/reference/parameterReferenceInInitializer2.symbols new file mode 100644 index 0000000000..126745373c --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer2.symbols @@ -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 +} diff --git a/tests/baselines/reference/parameterReferenceInInitializer2.types b/tests/baselines/reference/parameterReferenceInInitializer2.types new file mode 100644 index 0000000000..b137bccf70 --- /dev/null +++ b/tests/baselines/reference/parameterReferenceInInitializer2.types @@ -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 +} diff --git a/tests/cases/compiler/parameterReferenceInInitializer1.ts b/tests/cases/compiler/parameterReferenceInInitializer1.ts new file mode 100644 index 0000000000..c2ca2f08db --- /dev/null +++ b/tests/cases/compiler/parameterReferenceInInitializer1.ts @@ -0,0 +1,12 @@ +function fn(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 + ) { + } +} \ No newline at end of file diff --git a/tests/cases/compiler/parameterReferenceInInitializer2.ts b/tests/cases/compiler/parameterReferenceInInitializer2.ts new file mode 100644 index 0000000000..f093f3f88f --- /dev/null +++ b/tests/cases/compiler/parameterReferenceInInitializer2.ts @@ -0,0 +1,3 @@ +function Example(x = function(x: any) { return x; }) { // Error: parameter 'x' cannot be + // referenced in its initializer +} \ No newline at end of file