From c36c074f378ae66c7f6e31e2bdafa0da8892c21e Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 6 May 2016 12:01:38 -0700 Subject: [PATCH] disallow references to local variables of the function from parameter initializers --- src/compiler/checker.ts | 62 +++++++++++++------ .../capturedParametersInInitializers1.js | 40 ++++++++++++ .../capturedParametersInInitializers1.symbols | 38 ++++++++++++ .../capturedParametersInInitializers1.types | 46 ++++++++++++++ ...pturedParametersInInitializers2.errors.txt | 14 +++++ .../capturedParametersInInitializers2.js | 27 ++++++++ ...ctionLikeInParameterInitializer.errors.txt | 34 ++++++++++ .../functionLikeInParameterInitializer.js | 52 ++++++++++++++++ .../capturedParametersInInitializers1.ts | 15 +++++ .../capturedParametersInInitializers2.ts | 5 ++ .../functionLikeInParameterInitializer.ts | 18 ++++++ 11 files changed, 333 insertions(+), 18 deletions(-) create mode 100644 tests/baselines/reference/capturedParametersInInitializers1.js create mode 100644 tests/baselines/reference/capturedParametersInInitializers1.symbols create mode 100644 tests/baselines/reference/capturedParametersInInitializers1.types create mode 100644 tests/baselines/reference/capturedParametersInInitializers2.errors.txt create mode 100644 tests/baselines/reference/capturedParametersInInitializers2.js create mode 100644 tests/baselines/reference/functionLikeInParameterInitializer.errors.txt create mode 100644 tests/baselines/reference/functionLikeInParameterInitializer.js create mode 100644 tests/cases/compiler/capturedParametersInInitializers1.ts create mode 100644 tests/cases/compiler/capturedParametersInInitializers2.ts create mode 100644 tests/cases/compiler/functionLikeInParameterInitializer.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c4ec087c25..2210c8b22d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14174,28 +14174,54 @@ namespace ts { const func = getContainingFunction(node); visit(node.initializer); - function visit(n: Node) { - if (n.kind === SyntaxKind.Identifier) { - const referencedSymbol = getNodeLinks(n).resolvedSymbol; + function visit(n: Node): void { + if (isTypeNode(n) || isDeclarationName(n)) { + // do not dive in types + // skip declaration names (i.e. in object literal expressions) + return; + } + if (n.kind === SyntaxKind.PropertyAccessExpression) { + // skip property names in property access expression + return visit((n).expression); + } + else if (n.kind === SyntaxKind.Identifier) { // check FunctionLikeDeclaration.locals (stores parameters\function local variable) - // if it contains entry with a specified name and if this entry matches the resolved symbol - if (referencedSymbol && referencedSymbol !== unknownSymbol && getSymbol(func.locals, referencedSymbol.name, SymbolFlags.Value) === referencedSymbol) { - if (referencedSymbol.valueDeclaration.kind === SyntaxKind.Parameter) { - if (referencedSymbol.valueDeclaration === node) { - error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name)); - return; - } - if (referencedSymbol.valueDeclaration.pos < node.pos) { - // legal case - parameter initializer references some parameter strictly on left of current parameter declaration - return; - } - // fall through to error reporting - } - error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(n)); + // if it contains entry with a specified name + const symbol = getSymbol(func.locals, (n).text, SymbolFlags.Value); + if (!symbol || symbol === unknownSymbol) { + 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)) { + 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; + } + // fall through to report error + } + error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(n)); } else { - forEachChild(n, visit); + return forEachChild(n, visit); } } } diff --git a/tests/baselines/reference/capturedParametersInInitializers1.js b/tests/baselines/reference/capturedParametersInInitializers1.js new file mode 100644 index 0000000000..bc46acecad --- /dev/null +++ b/tests/baselines/reference/capturedParametersInInitializers1.js @@ -0,0 +1,40 @@ +//// [capturedParametersInInitializers1.ts] +// ok - usage is deferred +function foo1(y = class {c = x}, x = 1) { + new y().c; +} + +// ok - used in file +function foo2(y = function(x: typeof z) {}, z = 1) { + +} + +// ok -used in type +let a; +function foo3(y = { x: a }, z = 1) { + +} + +//// [capturedParametersInInitializers1.js] +// ok - usage is deferred +function foo1(y, x) { + if (y === void 0) { y = (function () { + function class_1() { + this.c = x; + } + return class_1; + }()); } + if (x === void 0) { x = 1; } + new y().c; +} +// ok - used in file +function foo2(y, z) { + if (y === void 0) { y = function (x) { }; } + if (z === void 0) { z = 1; } +} +// ok -used in type +var a; +function foo3(y, z) { + if (y === void 0) { y = { x: a }; } + if (z === void 0) { z = 1; } +} diff --git a/tests/baselines/reference/capturedParametersInInitializers1.symbols b/tests/baselines/reference/capturedParametersInInitializers1.symbols new file mode 100644 index 0000000000..b72a2c75ef --- /dev/null +++ b/tests/baselines/reference/capturedParametersInInitializers1.symbols @@ -0,0 +1,38 @@ +=== tests/cases/compiler/capturedParametersInInitializers1.ts === +// ok - usage is deferred +function foo1(y = class {c = x}, x = 1) { +>foo1 : Symbol(foo1, Decl(capturedParametersInInitializers1.ts, 0, 0)) +>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14)) +>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25)) +>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32)) +>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32)) + + new y().c; +>new y().c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25)) +>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14)) +>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25)) +} + +// ok - used in file +function foo2(y = function(x: typeof z) {}, z = 1) { +>foo2 : Symbol(foo2, Decl(capturedParametersInInitializers1.ts, 3, 1)) +>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 6, 14)) +>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 6, 27)) +>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43)) +>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43)) + +} + +// ok -used in type +let a; +>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3)) + +function foo3(y = { x: a }, z = 1) { +>foo3 : Symbol(foo3, Decl(capturedParametersInInitializers1.ts, 11, 6)) +>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 12, 14)) +>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 12, 19)) +>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37)) +>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3)) +>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37)) + +} diff --git a/tests/baselines/reference/capturedParametersInInitializers1.types b/tests/baselines/reference/capturedParametersInInitializers1.types new file mode 100644 index 0000000000..493c608c14 --- /dev/null +++ b/tests/baselines/reference/capturedParametersInInitializers1.types @@ -0,0 +1,46 @@ +=== tests/cases/compiler/capturedParametersInInitializers1.ts === +// ok - usage is deferred +function foo1(y = class {c = x}, x = 1) { +>foo1 : (y?: typeof (Anonymous class), x?: number) => void +>y : typeof (Anonymous class) +>class {c = x} : typeof (Anonymous class) +>c : number +>x : number +>x : number +>1 : number + + new y().c; +>new y().c : number +>new y() : (Anonymous class) +>y : typeof (Anonymous class) +>c : number +} + +// ok - used in file +function foo2(y = function(x: typeof z) {}, z = 1) { +>foo2 : (y?: (x: number) => void, z?: number) => void +>y : (x: number) => void +>function(x: typeof z) {} : (x: number) => void +>x : number +>z : number +>z : number +>1 : number + +} + +// ok -used in type +let a; +>a : any + +function foo3(y = { x: a }, z = 1) { +>foo3 : (y?: { x: number; }, z?: number) => void +>y : { x: number; } +>{ x: a } : { x: number; } +>x : number +>a : number +>z : number +>a : any +>z : number +>1 : number + +} diff --git a/tests/baselines/reference/capturedParametersInInitializers2.errors.txt b/tests/baselines/reference/capturedParametersInInitializers2.errors.txt new file mode 100644 index 0000000000..9e9f48e539 --- /dev/null +++ b/tests/baselines/reference/capturedParametersInInitializers2.errors.txt @@ -0,0 +1,14 @@ +tests/cases/compiler/capturedParametersInInitializers2.ts(1,36): error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it. +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. + + +==== tests/cases/compiler/capturedParametersInInitializers2.ts (2 errors) ==== + function foo(y = class {static c = x}, x = 1) { + ~ +!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it. + y.c + } + function foo2(y = class {[x] = x}, x = 1) { + ~~~ +!!! error TS1166: A computed property name in a class property declaration must directly refer to a built-in symbol. + } \ No newline at end of file diff --git a/tests/baselines/reference/capturedParametersInInitializers2.js b/tests/baselines/reference/capturedParametersInInitializers2.js new file mode 100644 index 0000000000..8d2cd4d58c --- /dev/null +++ b/tests/baselines/reference/capturedParametersInInitializers2.js @@ -0,0 +1,27 @@ +//// [capturedParametersInInitializers2.ts] +function foo(y = class {static c = x}, x = 1) { + y.c +} +function foo2(y = class {[x] = x}, x = 1) { +} + +//// [capturedParametersInInitializers2.js] +function foo(y, x) { + if (y === void 0) { y = (function () { + function class_1() { + } + class_1.c = x; + return class_1; + }()); } + if (x === void 0) { x = 1; } + y.c; +} +function foo2(y, x) { + if (y === void 0) { y = (function () { + function class_2() { + this[x] = x; + } + return class_2; + }()); } + if (x === void 0) { x = 1; } +} diff --git a/tests/baselines/reference/functionLikeInParameterInitializer.errors.txt b/tests/baselines/reference/functionLikeInParameterInitializer.errors.txt new file mode 100644 index 0000000000..b6ae78623b --- /dev/null +++ b/tests/baselines/reference/functionLikeInParameterInitializer.errors.txt @@ -0,0 +1,34 @@ +tests/cases/compiler/functionLikeInParameterInitializer.ts(2,34): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. +tests/cases/compiler/functionLikeInParameterInitializer.ts(6,44): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. +tests/cases/compiler/functionLikeInParameterInitializer.ts(11,50): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. +tests/cases/compiler/functionLikeInParameterInitializer.ts(16,41): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. + + +==== tests/cases/compiler/functionLikeInParameterInitializer.ts (4 errors) ==== + // error + export function bar(func = () => foo) { + ~~~ +!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. + let foo = "in"; + } + // error + export function baz1(func = { f() { return foo } }) { + ~~~ +!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. + let foo = "in"; + } + + // error + export function baz2(func = function () { return foo }) { + ~~~ +!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. + let foo = "in"; + } + + // error + export function baz3(func = class { x = foo }) { + ~~~ +!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it. + let foo = "in"; + } + \ No newline at end of file diff --git a/tests/baselines/reference/functionLikeInParameterInitializer.js b/tests/baselines/reference/functionLikeInParameterInitializer.js new file mode 100644 index 0000000000..bf1d887b43 --- /dev/null +++ b/tests/baselines/reference/functionLikeInParameterInitializer.js @@ -0,0 +1,52 @@ +//// [functionLikeInParameterInitializer.ts] +// error +export function bar(func = () => foo) { + let foo = "in"; +} +// error +export function baz1(func = { f() { return foo } }) { + let foo = "in"; +} + +// error +export function baz2(func = function () { return foo }) { + let foo = "in"; +} + +// error +export function baz3(func = class { x = foo }) { + let foo = "in"; +} + + +//// [functionLikeInParameterInitializer.js] +"use strict"; +// error +function bar(func) { + if (func === void 0) { func = function () { return foo; }; } + var foo = "in"; +} +exports.bar = bar; +// error +function baz1(func) { + if (func === void 0) { func = { f: function () { return foo; } }; } + var foo = "in"; +} +exports.baz1 = baz1; +// error +function baz2(func) { + if (func === void 0) { func = function () { return foo; }; } + var foo = "in"; +} +exports.baz2 = baz2; +// error +function baz3(func) { + if (func === void 0) { func = (function () { + function class_1() { + this.x = foo; + } + return class_1; + }()); } + var foo = "in"; +} +exports.baz3 = baz3; diff --git a/tests/cases/compiler/capturedParametersInInitializers1.ts b/tests/cases/compiler/capturedParametersInInitializers1.ts new file mode 100644 index 0000000000..b4150f6b62 --- /dev/null +++ b/tests/cases/compiler/capturedParametersInInitializers1.ts @@ -0,0 +1,15 @@ +// ok - usage is deferred +function foo1(y = class {c = x}, x = 1) { + new y().c; +} + +// ok - used in file +function foo2(y = function(x: typeof z) {}, z = 1) { + +} + +// ok -used in type +let a; +function foo3(y = { x: a }, z = 1) { + +} \ No newline at end of file diff --git a/tests/cases/compiler/capturedParametersInInitializers2.ts b/tests/cases/compiler/capturedParametersInInitializers2.ts new file mode 100644 index 0000000000..718c2db713 --- /dev/null +++ b/tests/cases/compiler/capturedParametersInInitializers2.ts @@ -0,0 +1,5 @@ +function foo(y = class {static c = x}, x = 1) { + y.c +} +function foo2(y = class {[x] = x}, x = 1) { +} \ No newline at end of file diff --git a/tests/cases/compiler/functionLikeInParameterInitializer.ts b/tests/cases/compiler/functionLikeInParameterInitializer.ts new file mode 100644 index 0000000000..2040a65efa --- /dev/null +++ b/tests/cases/compiler/functionLikeInParameterInitializer.ts @@ -0,0 +1,18 @@ +// error +export function bar(func = () => foo) { + let foo = "in"; +} +// error +export function baz1(func = { f() { return foo } }) { + let foo = "in"; +} + +// error +export function baz2(func = function () { return foo }) { + let foo = "in"; +} + +// error +export function baz3(func = class { x = foo }) { + let foo = "in"; +}