Skip to content

Commit 36169b4

Browse files
authored
Class fields w/esnext+[[Define]]:no shadow error (microsoft#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
1 parent 8da3eff commit 36169b4

File tree

6 files changed

+254
-2
lines changed

6 files changed

+254
-2
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,9 +1822,10 @@ namespace ts {
18221822

18231823
// Perform extra checks only if error reporting was requested
18241824
if (nameNotFoundMessage) {
1825-
if (propertyWithInvalidInitializer) {
1825+
if (propertyWithInvalidInitializer && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) {
18261826
// We have a match, but the reference occurred within a property initializer and the identifier also binds
1827-
// to a local variable in the constructor where the code will be emitted.
1827+
// to a local variable in the constructor where the code will be emitted. Note that this is actually allowed
1828+
// with ESNext+useDefineForClassFields because the scope semantics are different.
18281829
const propertyName = (<PropertyDeclaration>propertyWithInvalidInitializer).name;
18291830
error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor,
18301831
declarationNameToString(propertyName), diagnosticName(nameArg!));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts(28,9): error TS2304: Cannot find name 'z'.
2+
3+
4+
==== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts (1 errors) ====
5+
// With useDefineForClassFields: true and ESNext target, initializer
6+
// expressions for property declarations are evaluated in the scope of
7+
// the class body and are permitted to reference parameters or local
8+
// variables of the constructor. This is different from classic
9+
// Typescript behaviour, with useDefineForClassFields: false. There,
10+
// initialisers of property declarations are evaluated in the scope of
11+
// the constructor body.
12+
13+
// Note that when class fields are accepted in the ECMAScript
14+
// standard, the target will become that year's ES20xx
15+
16+
var x = 1;
17+
class C {
18+
b = x; // ok
19+
constructor(x: string) {
20+
}
21+
}
22+
23+
var y = 1;
24+
class D {
25+
b = y; // ok
26+
constructor(x: string) {
27+
var y = "";
28+
}
29+
}
30+
31+
class E {
32+
b = z; // not ok
33+
~
34+
!!! error TS2304: Cannot find name 'z'.
35+
constructor(z: string) {
36+
}
37+
}
38+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//// [constructorParameterShadowsOuterScopes2.ts]
2+
// With useDefineForClassFields: true and ESNext target, initializer
3+
// expressions for property declarations are evaluated in the scope of
4+
// the class body and are permitted to reference parameters or local
5+
// variables of the constructor. This is different from classic
6+
// Typescript behaviour, with useDefineForClassFields: false. There,
7+
// initialisers of property declarations are evaluated in the scope of
8+
// the constructor body.
9+
10+
// Note that when class fields are accepted in the ECMAScript
11+
// standard, the target will become that year's ES20xx
12+
13+
var x = 1;
14+
class C {
15+
b = x; // ok
16+
constructor(x: string) {
17+
}
18+
}
19+
20+
var y = 1;
21+
class D {
22+
b = y; // ok
23+
constructor(x: string) {
24+
var y = "";
25+
}
26+
}
27+
28+
class E {
29+
b = z; // not ok
30+
constructor(z: string) {
31+
}
32+
}
33+
34+
35+
//// [constructorParameterShadowsOuterScopes2.js]
36+
// With useDefineForClassFields: true and ESNext target, initializer
37+
// expressions for property declarations are evaluated in the scope of
38+
// the class body and are permitted to reference parameters or local
39+
// variables of the constructor. This is different from classic
40+
// Typescript behaviour, with useDefineForClassFields: false. There,
41+
// initialisers of property declarations are evaluated in the scope of
42+
// the constructor body.
43+
// Note that when class fields are accepted in the ECMAScript
44+
// standard, the target will become that year's ES20xx
45+
var x = 1;
46+
class C {
47+
b = x; // ok
48+
constructor(x) {
49+
}
50+
}
51+
var y = 1;
52+
class D {
53+
b = y; // ok
54+
constructor(x) {
55+
var y = "";
56+
}
57+
}
58+
class E {
59+
b = z; // not ok
60+
constructor(z) {
61+
}
62+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts ===
2+
// With useDefineForClassFields: true and ESNext target, initializer
3+
// expressions for property declarations are evaluated in the scope of
4+
// the class body and are permitted to reference parameters or local
5+
// variables of the constructor. This is different from classic
6+
// Typescript behaviour, with useDefineForClassFields: false. There,
7+
// initialisers of property declarations are evaluated in the scope of
8+
// the constructor body.
9+
10+
// Note that when class fields are accepted in the ECMAScript
11+
// standard, the target will become that year's ES20xx
12+
13+
var x = 1;
14+
>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 3))
15+
16+
class C {
17+
>C : Symbol(C, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 10))
18+
19+
b = x; // ok
20+
>b : Symbol(C.b, Decl(constructorParameterShadowsOuterScopes2.ts, 12, 9))
21+
>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 11, 3))
22+
23+
constructor(x: string) {
24+
>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 14, 16))
25+
}
26+
}
27+
28+
var y = 1;
29+
>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 3))
30+
31+
class D {
32+
>D : Symbol(D, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 10))
33+
34+
b = y; // ok
35+
>b : Symbol(D.b, Decl(constructorParameterShadowsOuterScopes2.ts, 19, 9))
36+
>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 18, 3))
37+
38+
constructor(x: string) {
39+
>x : Symbol(x, Decl(constructorParameterShadowsOuterScopes2.ts, 21, 16))
40+
41+
var y = "";
42+
>y : Symbol(y, Decl(constructorParameterShadowsOuterScopes2.ts, 22, 11))
43+
}
44+
}
45+
46+
class E {
47+
>E : Symbol(E, Decl(constructorParameterShadowsOuterScopes2.ts, 24, 1))
48+
49+
b = z; // not ok
50+
>b : Symbol(E.b, Decl(constructorParameterShadowsOuterScopes2.ts, 26, 9))
51+
52+
constructor(z: string) {
53+
>z : Symbol(z, Decl(constructorParameterShadowsOuterScopes2.ts, 28, 16))
54+
}
55+
}
56+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
=== tests/cases/conformance/classes/propertyMemberDeclarations/constructorParameterShadowsOuterScopes2.ts ===
2+
// With useDefineForClassFields: true and ESNext target, initializer
3+
// expressions for property declarations are evaluated in the scope of
4+
// the class body and are permitted to reference parameters or local
5+
// variables of the constructor. This is different from classic
6+
// Typescript behaviour, with useDefineForClassFields: false. There,
7+
// initialisers of property declarations are evaluated in the scope of
8+
// the constructor body.
9+
10+
// Note that when class fields are accepted in the ECMAScript
11+
// standard, the target will become that year's ES20xx
12+
13+
var x = 1;
14+
>x : number
15+
>1 : 1
16+
17+
class C {
18+
>C : C
19+
20+
b = x; // ok
21+
>b : number
22+
>x : number
23+
24+
constructor(x: string) {
25+
>x : string
26+
}
27+
}
28+
29+
var y = 1;
30+
>y : number
31+
>1 : 1
32+
33+
class D {
34+
>D : D
35+
36+
b = y; // ok
37+
>b : number
38+
>y : number
39+
40+
constructor(x: string) {
41+
>x : string
42+
43+
var y = "";
44+
>y : string
45+
>"" : ""
46+
}
47+
}
48+
49+
class E {
50+
>E : E
51+
52+
b = z; // not ok
53+
>b : any
54+
>z : any
55+
56+
constructor(z: string) {
57+
>z : string
58+
}
59+
}
60+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// @target: esnext
2+
// @useDefineForClassFields: true
3+
4+
5+
// With useDefineForClassFields: true and ESNext target, initializer
6+
// expressions for property declarations are evaluated in the scope of
7+
// the class body and are permitted to reference parameters or local
8+
// variables of the constructor. This is different from classic
9+
// Typescript behaviour, with useDefineForClassFields: false. There,
10+
// initialisers of property declarations are evaluated in the scope of
11+
// the constructor body.
12+
13+
// Note that when class fields are accepted in the ECMAScript
14+
// standard, the target will become that year's ES20xx
15+
16+
var x = 1;
17+
class C {
18+
b = x; // ok
19+
constructor(x: string) {
20+
}
21+
}
22+
23+
var y = 1;
24+
class D {
25+
b = y; // ok
26+
constructor(x: string) {
27+
var y = "";
28+
}
29+
}
30+
31+
class E {
32+
b = z; // not ok
33+
constructor(z: string) {
34+
}
35+
}

0 commit comments

Comments
 (0)