Skip to content

Commit 368db99

Browse files
authored
ESNext+[[Define]]: reference to param props illegal (#36425)
* ESNext+[[Define]]: reference to param props illegal When target: "esnext" and useDefineForClassFields: true, property declaration initialisers should not be able to reference parameter properties; class fields are initialised before the constructor runs, but parameter properties are not: ```ts class C { foo = this.bar constructor(public bar: string) { } } ``` emits code that looks like this: ```js class C { bar foo = this.bar constructor(bar) { this.bar = bar } } new C('x').foo.length // crashes; foo is undefined ``` This PR adds an error on foo's declaration with ESNext+[[Define]]. * improve test
1 parent 43fc19c commit 368db99

7 files changed

+264
-1
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,11 @@ namespace ts {
13281328
// still might be illegal if a self-referencing property initializer (eg private x = this.x)
13291329
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage);
13301330
}
1331+
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) {
1332+
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property
1333+
return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields
1334+
&& isUsedInFunctionOrInstanceProperty(usage, declaration));
1335+
}
13311336
return true;
13321337
}
13331338

@@ -1336,6 +1341,7 @@ namespace ts {
13361341
// 1. inside an export specifier
13371342
// 2. inside a function
13381343
// 3. inside an instance property initializer, a reference to a non-instance property
1344+
// (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property)
13391345
// 4. inside a static property initializer, a reference to a static method in the same class
13401346
// 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ)
13411347
// or if usage is in a type context:
@@ -1351,7 +1357,10 @@ namespace ts {
13511357
}
13521358

13531359
const container = getEnclosingBlockScopeContainer(declaration);
1354-
return !!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container);
1360+
return !!(usage.flags & NodeFlags.JSDoc)
1361+
|| isInTypeQuery(usage)
1362+
|| isUsedInFunctionOrInstanceProperty(usage, declaration, container)
1363+
&& !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields);
13551364

13561365
function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean {
13571366
const container = getEnclosingBlockScopeContainer(declaration);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(2,16): error TS2729: Property 'bar' is used before its initialization.
2+
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(3,16): error TS2729: Property 'foo' is used before its initialization.
3+
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(9,17): error TS2729: Property 'baz' is used before its initialization.
4+
tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts(10,16): error TS2729: Property 'foo' is used before its initialization.
5+
6+
7+
==== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts (4 errors) ====
8+
class C {
9+
qux = this.bar // should error
10+
~~~
11+
!!! error TS2729: Property 'bar' is used before its initialization.
12+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:3:5: 'bar' is declared here.
13+
bar = this.foo // should error
14+
~~~
15+
!!! error TS2729: Property 'foo' is used before its initialization.
16+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:8:17: 'foo' is declared here.
17+
quiz = this.bar // ok
18+
m1() {
19+
this.foo // ok
20+
}
21+
constructor(private foo: string) {}
22+
quim = this.baz // should error
23+
~~~
24+
!!! error TS2729: Property 'baz' is used before its initialization.
25+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:10:5: 'baz' is declared here.
26+
baz = this.foo; // should error
27+
~~~
28+
!!! error TS2729: Property 'foo' is used before its initialization.
29+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts:8:17: 'foo' is declared here.
30+
quid = this.baz // ok
31+
m2() {
32+
this.foo // ok
33+
}
34+
}
35+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [assignParameterPropertyToPropertyDeclarationESNext.ts]
2+
class C {
3+
qux = this.bar // should error
4+
bar = this.foo // should error
5+
quiz = this.bar // ok
6+
m1() {
7+
this.foo // ok
8+
}
9+
constructor(private foo: string) {}
10+
quim = this.baz // should error
11+
baz = this.foo; // should error
12+
quid = this.baz // ok
13+
m2() {
14+
this.foo // ok
15+
}
16+
}
17+
18+
19+
//// [assignParameterPropertyToPropertyDeclarationESNext.js]
20+
class C {
21+
foo;
22+
qux = this.bar; // should error
23+
bar = this.foo; // should error
24+
quiz = this.bar; // ok
25+
m1() {
26+
this.foo; // ok
27+
}
28+
constructor(foo) {
29+
this.foo = foo;
30+
}
31+
quim = this.baz; // should error
32+
baz = this.foo; // should error
33+
quid = this.baz; // ok
34+
m2() {
35+
this.foo; // ok
36+
}
37+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts ===
2+
class C {
3+
>C : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
4+
5+
qux = this.bar // should error
6+
>qux : Symbol(C.qux, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 9))
7+
>this.bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
8+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
9+
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
10+
11+
bar = this.foo // should error
12+
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
13+
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
14+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
15+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
16+
17+
quiz = this.bar // ok
18+
>quiz : Symbol(C.quiz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 2, 18))
19+
>this.bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
20+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
21+
>bar : Symbol(C.bar, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 1, 18))
22+
23+
m1() {
24+
>m1 : Symbol(C.m1, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 3, 19))
25+
26+
this.foo // ok
27+
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
28+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
29+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
30+
}
31+
constructor(private foo: string) {}
32+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
33+
34+
quim = this.baz // should error
35+
>quim : Symbol(C.quim, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 39))
36+
>this.baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
37+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
38+
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
39+
40+
baz = this.foo; // should error
41+
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
42+
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
43+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
44+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
45+
46+
quid = this.baz // ok
47+
>quid : Symbol(C.quid, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 9, 19))
48+
>this.baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
49+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
50+
>baz : Symbol(C.baz, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 8, 19))
51+
52+
m2() {
53+
>m2 : Symbol(C.m2, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 10, 19))
54+
55+
this.foo // ok
56+
>this.foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
57+
>this : Symbol(C, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 0, 0))
58+
>foo : Symbol(C.foo, Decl(assignParameterPropertyToPropertyDeclarationESNext.ts, 7, 16))
59+
}
60+
}
61+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/conformance/classes/propertyMemberDeclarations/assignParameterPropertyToPropertyDeclarationESNext.ts ===
2+
class C {
3+
>C : C
4+
5+
qux = this.bar // should error
6+
>qux : string
7+
>this.bar : string
8+
>this : this
9+
>bar : string
10+
11+
bar = this.foo // should error
12+
>bar : string
13+
>this.foo : string
14+
>this : this
15+
>foo : string
16+
17+
quiz = this.bar // ok
18+
>quiz : string
19+
>this.bar : string
20+
>this : this
21+
>bar : string
22+
23+
m1() {
24+
>m1 : () => void
25+
26+
this.foo // ok
27+
>this.foo : string
28+
>this : this
29+
>foo : string
30+
}
31+
constructor(private foo: string) {}
32+
>foo : string
33+
34+
quim = this.baz // should error
35+
>quim : string
36+
>this.baz : string
37+
>this : this
38+
>baz : string
39+
40+
baz = this.foo; // should error
41+
>baz : string
42+
>this.foo : string
43+
>this : this
44+
>foo : string
45+
46+
quid = this.baz // ok
47+
>quid : string
48+
>this.baz : string
49+
>this : this
50+
>baz : string
51+
52+
m2() {
53+
>m2 : () => void
54+
55+
this.foo // ok
56+
>this.foo : string
57+
>this : this
58+
>foo : string
59+
}
60+
}
61+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(3,14): error TS2729: Property 'y' is used before its initialization.
2+
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(10,14): error TS2729: Property 'y' is used before its initialization.
3+
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(18,14): error TS2729: Property 'ka' is used before its initialization.
4+
tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts(22,15): error TS2729: Property 'ka' is used before its initialization.
5+
6+
7+
==== tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts (4 errors) ====
8+
var x: "p" = "p"
9+
class A {
10+
a = this.y
11+
~
12+
!!! error TS2729: Property 'y' is used before its initialization.
13+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:9:17: 'y' is declared here.
14+
b
15+
public c;
16+
["computed"] = 13
17+
;[x] = 14
18+
m() { }
19+
constructor(public readonly y: number) { }
20+
z = this.y
21+
~
22+
!!! error TS2729: Property 'y' is used before its initialization.
23+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:9:17: 'y' is declared here.
24+
declare notEmitted;
25+
}
26+
class B {
27+
public a;
28+
}
29+
class C extends B {
30+
declare public a;
31+
z = this.ka
32+
~~
33+
!!! error TS2729: Property 'ka' is used before its initialization.
34+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:19:17: 'ka' is declared here.
35+
constructor(public ka: number) {
36+
super()
37+
}
38+
ki = this.ka
39+
~~
40+
!!! error TS2729: Property 'ka' is used before its initialization.
41+
!!! related TS2728 tests/cases/conformance/classes/propertyMemberDeclarations/defineProperty.ts:19:17: 'ka' is declared here.
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// @useDefineForClassFields: true
2+
// @target: esnext
3+
class C {
4+
qux = this.bar // should error
5+
bar = this.foo // should error
6+
quiz = this.bar // ok
7+
m1() {
8+
this.foo // ok
9+
}
10+
constructor(private foo: string) {}
11+
quim = this.baz // should error
12+
baz = this.foo; // should error
13+
quid = this.baz // ok
14+
m2() {
15+
this.foo // ok
16+
}
17+
}

0 commit comments

Comments
 (0)