From 36169b4d13fa000c69ee01d4e80a60335b6fab94 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 29 Jan 2020 14:47:44 -0800 Subject: [PATCH] Class fields w/esnext+[[Define]]:no shadow error (#36405) * Class fields w/esnext+[[Define]]:no shadow error With useDefineForClassFields: true and ESNext target, initializer expressions for property declarations are evaluated in the scope of the class body and are permitted to reference parameters or local variables of the constructor. This is different from classic Typescript behaviour, with useDefineForClassFields: false. There, initialisers of property declarations are evaluated in the scope of the constructor body. Note that when class fields are accepted in the ECMAScript standard, the target will become that year's ES20xx * add negative test case * Add explanatory comment --- src/compiler/checker.ts | 5 +- ...torParameterShadowsOuterScopes2.errors.txt | 38 ++++++++++++ ...constructorParameterShadowsOuterScopes2.js | 62 +++++++++++++++++++ ...ructorParameterShadowsOuterScopes2.symbols | 56 +++++++++++++++++ ...structorParameterShadowsOuterScopes2.types | 60 ++++++++++++++++++ ...constructorParameterShadowsOuterScopes2.ts | 35 +++++++++++ 6 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/constructorParameterShadowsOuterScopes2.errors.txt create mode 100644 tests/baselines/reference/constructorParameterShadowsOuterScopes2.js create mode 100644 tests/baselines/reference/constructorParameterShadowsOuterScopes2.symbols create mode 100644 tests/baselines/reference/constructorParameterShadowsOuterScopes2.types create mode 100644 tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2217f2f430b..db0b3a6db91 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1822,9 +1822,10 @@ namespace ts { // Perform extra checks only if error reporting was requested if (nameNotFoundMessage) { - if (propertyWithInvalidInitializer) { + if (propertyWithInvalidInitializer && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) { // We have a match, but the reference occurred within a property initializer and the identifier also binds - // to a local variable in the constructor where the code will be emitted. + // to a local variable in the constructor where the code will be emitted. Note that this is actually allowed + // with ESNext+useDefineForClassFields because the scope semantics are different. const propertyName = (propertyWithInvalidInitializer).name; error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, declarationNameToString(propertyName), diagnosticName(nameArg!)); diff --git a/tests/baselines/reference/constructorParameterShadowsOuterScopes2.errors.txt b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.errors.txt new file mode 100644 index 00000000000..7b2f3c6121a --- /dev/null +++ b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.errors.txt @@ -0,0 +1,38 @@ +tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts(28,9): error TS2304: Cannot find name 'z'. + + +==== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts (1 errors) ==== + // With useDefineForClassFields: true and ESNext target, initializer + // expressions for property declarations are evaluated in the scope of + // the class body and are permitted to reference parameters or local + // variables of the constructor. This is different from classic + // Typescript behaviour, with useDefineForClassFields: false. There, + // initialisers of property declarations are evaluated in the scope of + // the constructor body. + + // Note that when class fields are accepted in the ECMAScript + // standard, the target will become that year's ES20xx + + var x = 1; + class C { + b = x; // ok + constructor(x: string) { + } + } + + var y = 1; + class D { + b = y; // ok + constructor(x: string) { + var y = ""; + } + } + + class E { + b = z; // not ok + ~ +!!! error TS2304: Cannot find name 'z'. + constructor(z: string) { + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/constructorParameterShadowsOuterScopes2.js b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.js new file mode 100644 index 00000000000..a266bc5b0fb --- /dev/null +++ b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.js @@ -0,0 +1,62 @@ +//// [constructorParameterShadowsOuterScopes2.ts] +// With useDefineForClassFields: true and ESNext target, initializer +// expressions for property declarations are evaluated in the scope of +// the class body and are permitted to reference parameters or local +// variables of the constructor. This is different from classic +// Typescript behaviour, with useDefineForClassFields: false. There, +// initialisers of property declarations are evaluated in the scope of +// the constructor body. + +// Note that when class fields are accepted in the ECMAScript +// standard, the target will become that year's ES20xx + +var x = 1; +class C { + b = x; // ok + constructor(x: string) { + } +} + +var y = 1; +class D { + b = y; // ok + constructor(x: string) { + var y = ""; + } +} + +class E { + b = z; // not ok + constructor(z: string) { + } +} + + +//// [constructorParameterShadowsOuterScopes2.js] +// With useDefineForClassFields: true and ESNext target, initializer +// expressions for property declarations are evaluated in the scope of +// the class body and are permitted to reference parameters or local +// variables of the constructor. This is different from classic +// Typescript behaviour, with useDefineForClassFields: false. There, +// initialisers of property declarations are evaluated in the scope of +// the constructor body. +// Note that when class fields are accepted in the ECMAScript +// standard, the target will become that year's ES20xx +var x = 1; +class C { + b = x; // ok + constructor(x) { + } +} +var y = 1; +class D { + b = y; // ok + constructor(x) { + var y = ""; + } +} +class E { + b = z; // not ok + constructor(z) { + } +} diff --git a/tests/baselines/reference/constructorParameterShadowsOuterScopes2.symbols b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.symbols new file mode 100644 index 00000000000..01bb0bb7613 --- /dev/null +++ b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.symbols @@ -0,0 +1,56 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts === +// With useDefineForClassFields: true and ESNext target, initializer +// expressions for property declarations are evaluated in the scope of +// the class body and are permitted to reference parameters or local +// variables of the constructor. This is different from classic +// Typescript behaviour, with useDefineForClassFields: false. There, +// initialisers of property declarations are evaluated in the scope of +// the constructor body. + +// Note that when class fields are accepted in the ECMAScript +// standard, the target will become that year's ES20xx + +var x = 1; +>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 3)) + +class C { +>C : Symbol(C, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 10)) + + b = x; // ok +>b : Symbol(C.b, Decl(constructorParameterShadowsOuterScopes2.ts, 12, 9)) +>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 3)) + + constructor(x: string) { +>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 14, 16)) + } +} + +var y = 1; +>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 3)) + +class D { +>D : Symbol(D, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 10)) + + b = y; // ok +>b : Symbol(D.b, Decl(constructorParameterShadowsOuterScopes2.ts, 19, 9)) +>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 3)) + + constructor(x: string) { +>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 21, 16)) + + var y = ""; +>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 22, 11)) + } +} + +class E { +>E : Symbol(E, Decl(constructorParameterShadowsOuterScopes2.ts, 24, 1)) + + b = z; // not ok +>b : Symbol(E.b, Decl(constructorParameterShadowsOuterScopes2.ts, 26, 9)) + + constructor(z: string) { +>z : Symbol(z, Decl(constructorParameterShadowsOuterScopes2.ts, 28, 16)) + } +} + diff --git a/tests/baselines/reference/constructorParameterShadowsOuterScopes2.types b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.types new file mode 100644 index 00000000000..c9c24a45e5e --- /dev/null +++ b/tests/baselines/reference/constructorParameterShadowsOuterScopes2.types @@ -0,0 +1,60 @@ +=== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts === +// With useDefineForClassFields: true and ESNext target, initializer +// expressions for property declarations are evaluated in the scope of +// the class body and are permitted to reference parameters or local +// variables of the constructor. This is different from classic +// Typescript behaviour, with useDefineForClassFields: false. There, +// initialisers of property declarations are evaluated in the scope of +// the constructor body. + +// Note that when class fields are accepted in the ECMAScript +// standard, the target will become that year's ES20xx + +var x = 1; +>x : number +>1 : 1 + +class C { +>C : C + + b = x; // ok +>b : number +>x : number + + constructor(x: string) { +>x : string + } +} + +var y = 1; +>y : number +>1 : 1 + +class D { +>D : D + + b = y; // ok +>b : number +>y : number + + constructor(x: string) { +>x : string + + var y = ""; +>y : string +>"" : "" + } +} + +class E { +>E : E + + b = z; // not ok +>b : any +>z : any + + constructor(z: string) { +>z : string + } +} + diff --git a/tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts b/tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts new file mode 100644 index 00000000000..d6c5be3bd79 --- /dev/null +++ b/tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts @@ -0,0 +1,35 @@ +// @target: esnext +// @useDefineForClassFields: true + + +// With useDefineForClassFields: true and ESNext target, initializer +// expressions for property declarations are evaluated in the scope of +// the class body and are permitted to reference parameters or local +// variables of the constructor. This is different from classic +// Typescript behaviour, with useDefineForClassFields: false. There, +// initialisers of property declarations are evaluated in the scope of +// the constructor body. + +// Note that when class fields are accepted in the ECMAScript +// standard, the target will become that year's ES20xx + +var x = 1; +class C { + b = x; // ok + constructor(x: string) { + } +} + +var y = 1; +class D { + b = y; // ok + constructor(x: string) { + var y = ""; + } +} + +class E { + b = z; // not ok + constructor(z: string) { + } +}