Skip to content

Commit 23eabea

Browse files
authored
fix use-before-init error when targeting ES2022 (#55028)
1 parent 6037cf5 commit 23eabea

File tree

25 files changed

+824
-19
lines changed

25 files changed

+824
-19
lines changed

src/compiler/checker.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ import {
274274
getEmitModuleKind,
275275
getEmitModuleResolutionKind,
276276
getEmitScriptTarget,
277+
getEmitStandardClassFields,
277278
getEnclosingBlockScopeContainer,
278279
getEnclosingContainer,
279280
getEntityNameFromTypeNode,
@@ -1437,6 +1438,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
14371438
var moduleKind = getEmitModuleKind(compilerOptions);
14381439
var legacyDecorators = !!compilerOptions.experimentalDecorators;
14391440
var useDefineForClassFields = getUseDefineForClassFields(compilerOptions);
1441+
var emitStandardClassFields = getEmitStandardClassFields(compilerOptions);
14401442
var allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions);
14411443
var strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");
14421444
var strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes");
@@ -2785,8 +2787,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
27852787
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ false);
27862788
}
27872789
else if (isParameterPropertyDeclaration(declaration, declaration.parent)) {
2788-
// foo = this.bar is illegal in esnext+useDefineForClassFields when bar is a parameter property
2789-
return !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields
2790+
// foo = this.bar is illegal in emitStandardClassFields when bar is a parameter property
2791+
return !(emitStandardClassFields
27902792
&& getContainingClass(declaration) === getContainingClass(usage)
27912793
&& isUsedInFunctionOrInstanceProperty(usage, declaration));
27922794
}
@@ -2798,7 +2800,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
27982800
// 1. inside an export specifier
27992801
// 2. inside a function
28002802
// 3. inside an instance property initializer, a reference to a non-instance property
2801-
// (except when target: "esnext" and useDefineForClassFields: true and the reference is to a parameter property)
2803+
// (except when emitStandardClassFields: true and the reference is to a parameter property)
28022804
// 4. inside a static property initializer, a reference to a static method in the same class
28032805
// 5. inside a TS export= declaration (since we will move the export statement during emit to avoid TDZ)
28042806
// or if usage is in a type context:
@@ -2817,7 +2819,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
28172819
return true;
28182820
}
28192821
if (isUsedInFunctionOrInstanceProperty(usage, declaration)) {
2820-
if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022 && useDefineForClassFields
2822+
if (emitStandardClassFields
28212823
&& getContainingClass(declaration)
28222824
&& (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) {
28232825
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true);
@@ -2974,7 +2976,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
29742976
case SyntaxKind.PropertyDeclaration:
29752977
// static properties in classes introduce temporary variables
29762978
if (hasStaticModifier(node)) {
2977-
return target < ScriptTarget.ESNext || !useDefineForClassFields;
2979+
return !emitStandardClassFields;
29782980
}
29792981
return requiresScopeChangeWorker((node as PropertyDeclaration).name);
29802982
default:
@@ -3392,10 +3394,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
33923394
// 1. When result is undefined, after checking for a missing "this."
33933395
// 2. When result is defined
33943396
function checkAndReportErrorForInvalidInitializer() {
3395-
if (propertyWithInvalidInitializer && !(useDefineForClassFields && getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022)) {
3397+
if (propertyWithInvalidInitializer && !emitStandardClassFields) {
33963398
// We have a match, but the reference occurred within a property initializer and the identifier also binds
33973399
// to a local variable in the constructor where the code will be emitted. Note that this is actually allowed
3398-
// with ESNext+useDefineForClassFields because the scope semantics are different.
3400+
// with emitStandardClassFields because the scope semantics are different.
33993401
error(errorLocation,
34003402
errorLocation && propertyWithInvalidInitializer.type && textRangeContainsPositionInclusive(propertyWithInvalidInitializer.type, errorLocation.pos)
34013403
? Diagnostics.Type_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor
@@ -31739,7 +31741,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3173931741
&& !(isAccessExpression(node) && isAccessExpression(node.expression))
3174031742
&& !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
3174131743
&& !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlagsCached(valueDeclaration) & ModifierFlags.Static)
31742-
&& (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) {
31744+
&& (useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) {
3174331745
diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName);
3174431746
}
3174531747
else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration &&
@@ -38453,7 +38455,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3845338455
case "length":
3845438456
case "caller":
3845538457
case "arguments":
38456-
if (compilerOptions.useDefineForClassFields) {
38458+
if (useDefineForClassFields) {
3845738459
break;
3845838460
}
3845938461
// fall through
@@ -38658,7 +38660,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3865838660
// or the containing class declares instance member variables with initializers.
3865938661

3866038662
const superCallShouldBeRootLevel =
38661-
(getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) &&
38663+
!emitStandardClassFields &&
3866238664
(some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) ||
3866338665
some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier)));
3866438666

@@ -42935,7 +42937,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4293542937
!legacyDecorators && languageVersion < ScriptTarget.ESNext &&
4293642938
classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node);
4293742939
const willTransformPrivateElementsOrClassStaticBlocks = languageVersion <= ScriptTarget.ES2022;
42938-
const willTransformInitializers = !useDefineForClassFields || languageVersion < ScriptTarget.ES2022;
42940+
const willTransformInitializers = !emitStandardClassFields;
4293942941
if (willTransformStaticElementsOfDecoratedClass || willTransformPrivateElementsOrClassStaticBlocks) {
4294042942
for (const member of node.members) {
4294142943
if (willTransformStaticElementsOfDecoratedClass && classElementOrClassElementParameterIsDecorated(/*useLegacyDecorators*/ false, member, node)) {

src/compiler/utilities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8666,6 +8666,11 @@ export function getUseDefineForClassFields(compilerOptions: CompilerOptions): bo
86668666
return compilerOptions.useDefineForClassFields === undefined ? getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022 : compilerOptions.useDefineForClassFields;
86678667
}
86688668

8669+
/** @internal */
8670+
export function getEmitStandardClassFields(compilerOptions: CompilerOptions) {
8671+
return compilerOptions.useDefineForClassFields !== false && getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022;
8672+
}
8673+
86698674
/** @internal */
86708675
export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions): boolean {
86718676
return optionsHaveChanges(oldOptions, newOptions, semanticDiagnosticsOptionDeclarations);

tests/baselines/reference/assignParameterPropertyToPropertyDeclarationES2022.errors.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ assignParameterPropertyToPropertyDeclarationES2022.ts(2,16): error TS2729: Prope
22
assignParameterPropertyToPropertyDeclarationES2022.ts(3,16): error TS2729: Property 'foo' is used before its initialization.
33
assignParameterPropertyToPropertyDeclarationES2022.ts(6,19): error TS2729: Property 'm3' is used before its initialization.
44
assignParameterPropertyToPropertyDeclarationES2022.ts(12,17): error TS2729: Property 'baz' is used before its initialization.
5+
assignParameterPropertyToPropertyDeclarationES2022.ts(13,16): error TS2729: Property 'foo' is used before its initialization.
56

67

7-
==== assignParameterPropertyToPropertyDeclarationES2022.ts (4 errors) ====
8+
==== assignParameterPropertyToPropertyDeclarationES2022.ts (5 errors) ====
89
class C {
910
qux = this.bar // should error
1011
~~~
@@ -30,6 +31,9 @@ assignParameterPropertyToPropertyDeclarationES2022.ts(12,17): error TS2729: Prop
3031
!!! error TS2729: Property 'baz' is used before its initialization.
3132
!!! related TS2728 assignParameterPropertyToPropertyDeclarationES2022.ts:13:5: 'baz' is declared here.
3233
baz = this.foo; // should error
34+
~~~
35+
!!! error TS2729: Property 'foo' is used before its initialization.
36+
!!! related TS2728 assignParameterPropertyToPropertyDeclarationES2022.ts:11:17: 'foo' is declared here.
3337
quid = this.baz // ok
3438
m2() {
3539
this.foo // ok
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
//// [initializationOrdering1.ts]
4+
class Helper {
5+
create(): boolean {
6+
return true
7+
}
8+
}
9+
10+
export class Broken {
11+
constructor(readonly facade: Helper) {
12+
console.log(this.bug)
13+
}
14+
bug = this.facade.create()
15+
16+
}
17+
18+
new Broken(new Helper)
19+
20+
//// [initializationOrdering1.js]
21+
class Helper {
22+
create() {
23+
return true;
24+
}
25+
}
26+
export class Broken {
27+
constructor(facade) {
28+
this.facade = facade;
29+
this.bug = this.facade.create();
30+
console.log(this.bug);
31+
}
32+
}
33+
new Broken(new Helper);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
=== initializationOrdering1.ts ===
4+
class Helper {
5+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
6+
7+
create(): boolean {
8+
>create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
9+
10+
return true
11+
}
12+
}
13+
14+
export class Broken {
15+
>Broken : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
16+
17+
constructor(readonly facade: Helper) {
18+
>facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
19+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
20+
21+
console.log(this.bug)
22+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
23+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
24+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
25+
>this.bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
26+
>this : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
27+
>bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
28+
}
29+
bug = this.facade.create()
30+
>bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
31+
>this.facade.create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
32+
>this.facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
33+
>this : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
34+
>facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
35+
>create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
36+
37+
}
38+
39+
new Broken(new Helper)
40+
>Broken : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
41+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
42+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
=== initializationOrdering1.ts ===
4+
class Helper {
5+
>Helper : Helper
6+
7+
create(): boolean {
8+
>create : () => boolean
9+
10+
return true
11+
>true : true
12+
}
13+
}
14+
15+
export class Broken {
16+
>Broken : Broken
17+
18+
constructor(readonly facade: Helper) {
19+
>facade : Helper
20+
21+
console.log(this.bug)
22+
>console.log(this.bug) : void
23+
>console.log : (...data: any[]) => void
24+
>console : Console
25+
>log : (...data: any[]) => void
26+
>this.bug : boolean
27+
>this : this
28+
>bug : boolean
29+
}
30+
bug = this.facade.create()
31+
>bug : boolean
32+
>this.facade.create() : boolean
33+
>this.facade.create : () => boolean
34+
>this.facade : Helper
35+
>this : this
36+
>facade : Helper
37+
>create : () => boolean
38+
39+
}
40+
41+
new Broken(new Helper)
42+
>new Broken(new Helper) : Broken
43+
>Broken : typeof Broken
44+
>new Helper : Helper
45+
>Helper : typeof Helper
46+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
//// [initializationOrdering1.ts]
4+
class Helper {
5+
create(): boolean {
6+
return true
7+
}
8+
}
9+
10+
export class Broken {
11+
constructor(readonly facade: Helper) {
12+
console.log(this.bug)
13+
}
14+
bug = this.facade.create()
15+
16+
}
17+
18+
new Broken(new Helper)
19+
20+
//// [initializationOrdering1.js]
21+
class Helper {
22+
create() {
23+
return true;
24+
}
25+
}
26+
export class Broken {
27+
constructor(facade) {
28+
Object.defineProperty(this, "facade", {
29+
enumerable: true,
30+
configurable: true,
31+
writable: true,
32+
value: facade
33+
});
34+
Object.defineProperty(this, "bug", {
35+
enumerable: true,
36+
configurable: true,
37+
writable: true,
38+
value: this.facade.create()
39+
});
40+
console.log(this.bug);
41+
}
42+
}
43+
new Broken(new Helper);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
=== initializationOrdering1.ts ===
4+
class Helper {
5+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
6+
7+
create(): boolean {
8+
>create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
9+
10+
return true
11+
}
12+
}
13+
14+
export class Broken {
15+
>Broken : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
16+
17+
constructor(readonly facade: Helper) {
18+
>facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
19+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
20+
21+
console.log(this.bug)
22+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
23+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
24+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
25+
>this.bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
26+
>this : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
27+
>bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
28+
}
29+
bug = this.facade.create()
30+
>bug : Symbol(Broken.bug, Decl(initializationOrdering1.ts, 9, 5))
31+
>this.facade.create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
32+
>this.facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
33+
>this : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
34+
>facade : Symbol(Broken.facade, Decl(initializationOrdering1.ts, 7, 16))
35+
>create : Symbol(Helper.create, Decl(initializationOrdering1.ts, 0, 14))
36+
37+
}
38+
39+
new Broken(new Helper)
40+
>Broken : Symbol(Broken, Decl(initializationOrdering1.ts, 4, 1))
41+
>Helper : Symbol(Helper, Decl(initializationOrdering1.ts, 0, 0))
42+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [tests/cases/conformance/classes/propertyMemberDeclarations/initializationOrdering1.ts] ////
2+
3+
=== initializationOrdering1.ts ===
4+
class Helper {
5+
>Helper : Helper
6+
7+
create(): boolean {
8+
>create : () => boolean
9+
10+
return true
11+
>true : true
12+
}
13+
}
14+
15+
export class Broken {
16+
>Broken : Broken
17+
18+
constructor(readonly facade: Helper) {
19+
>facade : Helper
20+
21+
console.log(this.bug)
22+
>console.log(this.bug) : void
23+
>console.log : (...data: any[]) => void
24+
>console : Console
25+
>log : (...data: any[]) => void
26+
>this.bug : boolean
27+
>this : this
28+
>bug : boolean
29+
}
30+
bug = this.facade.create()
31+
>bug : boolean
32+
>this.facade.create() : boolean
33+
>this.facade.create : () => boolean
34+
>this.facade : Helper
35+
>this : this
36+
>facade : Helper
37+
>create : () => boolean
38+
39+
}
40+
41+
new Broken(new Helper)
42+
>new Broken(new Helper) : Broken
43+
>Broken : typeof Broken
44+
>new Helper : Helper
45+
>Helper : typeof Helper
46+

0 commit comments

Comments
 (0)