Skip to content

Commit 20c01cd

Browse files
authored
align ClassStaticBlockDeclaration with IIFE in CFA (#44969)
* align ClassStaticBlockDeclaration with IIFE in CFA * isIIFELike => isImmediatelyInvoked * fix unexpected used-before-assignment errors * update baseline
1 parent 4ec16b2 commit 20c01cd

10 files changed

+350
-5
lines changed

src/compiler/binder.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -657,19 +657,23 @@ namespace ts {
657657
const saveExceptionTarget = currentExceptionTarget;
658658
const saveActiveLabelList = activeLabelList;
659659
const saveHasExplicitReturn = hasExplicitReturn;
660-
const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasSyntacticModifier(node, ModifierFlags.Async) &&
661-
!(node as FunctionLikeDeclaration).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node);
660+
const isImmediatelyInvoked =
661+
(containerFlags & ContainerFlags.IsFunctionExpression &&
662+
!hasSyntacticModifier(node, ModifierFlags.Async) &&
663+
!(node as FunctionLikeDeclaration).asteriskToken &&
664+
!!getImmediatelyInvokedFunctionExpression(node)) ||
665+
node.kind === SyntaxKind.ClassStaticBlockDeclaration;
662666
// A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave
663667
// similarly to break statements that exit to a label just past the statement body.
664-
if (!isIIFE) {
668+
if (!isImmediatelyInvoked) {
665669
currentFlow = initFlowNode({ flags: FlowFlags.Start });
666670
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) {
667671
currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration;
668672
}
669673
}
670674
// We create a return control flow graph for IIFEs and constructors. For constructors
671675
// we use the return control flow graph in strict property initialization checks.
672-
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
676+
currentReturnTarget = isImmediatelyInvoked || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
673677
currentExceptionTarget = undefined;
674678
currentBreakTarget = undefined;
675679
currentContinueTarget = undefined;
@@ -695,7 +699,7 @@ namespace ts {
695699
(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow;
696700
}
697701
}
698-
if (!isIIFE) {
702+
if (!isImmediatelyInvoked) {
699703
currentFlow = saveCurrentFlow;
700704
}
701705
currentBreakTarget = saveBreakTarget;

src/compiler/checker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28982,6 +28982,7 @@ namespace ts {
2898228982
&& !isOptionalPropertyDeclaration(valueDeclaration)
2898328983
&& !(isAccessExpression(node) && isAccessExpression(node.expression))
2898428984
&& !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
28985+
&& !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlags(valueDeclaration) & ModifierFlags.Static)
2898528986
&& (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) {
2898628987
diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName);
2898728988
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [classStaticBlock28.ts]
2+
let foo: number;
3+
4+
class C {
5+
static {
6+
foo = 1
7+
}
8+
}
9+
10+
console.log(foo)
11+
12+
//// [classStaticBlock28.js]
13+
"use strict";
14+
var foo;
15+
var C = /** @class */ (function () {
16+
function C() {
17+
}
18+
return C;
19+
}());
20+
(function () {
21+
foo = 1;
22+
})();
23+
console.log(foo);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts ===
2+
let foo: number;
3+
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))
4+
5+
class C {
6+
>C : Symbol(C, Decl(classStaticBlock28.ts, 0, 16))
7+
8+
static {
9+
foo = 1
10+
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))
11+
}
12+
}
13+
14+
console.log(foo)
15+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
16+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
17+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
18+
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts ===
2+
let foo: number;
3+
>foo : number
4+
5+
class C {
6+
>C : C
7+
8+
static {
9+
foo = 1
10+
>foo = 1 : 1
11+
>foo : number
12+
>1 : 1
13+
}
14+
}
15+
16+
console.log(foo)
17+
>console.log(foo) : void
18+
>console.log : (...data: any[]) => void
19+
>console : Console
20+
>log : (...data: any[]) => void
21+
>foo : number
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts(14,21): error TS2448: Block-scoped variable 'FOO' used before its declaration.
2+
tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts(14,21): error TS2454: Variable 'FOO' is used before being assigned.
3+
4+
5+
==== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts (2 errors) ====
6+
class A {
7+
static {
8+
A.doSomething(); // should not error
9+
}
10+
11+
static doSomething() {
12+
console.log("gotcha!");
13+
}
14+
}
15+
16+
17+
class Baz {
18+
static {
19+
console.log(FOO); // should error
20+
~~~
21+
!!! error TS2448: Block-scoped variable 'FOO' used before its declaration.
22+
!!! related TS2728 tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts:18:7: 'FOO' is declared here.
23+
~~~
24+
!!! error TS2454: Variable 'FOO' is used before being assigned.
25+
}
26+
}
27+
28+
const FOO = "FOO";
29+
class Bar {
30+
static {
31+
console.log(FOO); // should not error
32+
}
33+
}
34+
35+
let u = "FOO" as "FOO" | "BAR";
36+
37+
class CFA {
38+
static {
39+
u = "BAR";
40+
u; // should be "BAR"
41+
}
42+
43+
static t = 1;
44+
45+
static doSomething() {}
46+
47+
static {
48+
u; // should be "BAR"
49+
}
50+
}
51+
52+
u; // should be "BAR"
53+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts ===
2+
class A {
3+
>A : Symbol(A, Decl(classStaticBlockUseBeforeDef3.ts, 0, 0))
4+
5+
static {
6+
A.doSomething(); // should not error
7+
>A.doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
8+
>A : Symbol(A, Decl(classStaticBlockUseBeforeDef3.ts, 0, 0))
9+
>doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
10+
}
11+
12+
static doSomething() {
13+
>doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
14+
15+
console.log("gotcha!");
16+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
17+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
18+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
19+
}
20+
}
21+
22+
23+
class Baz {
24+
>Baz : Symbol(Baz, Decl(classStaticBlockUseBeforeDef3.ts, 8, 1))
25+
26+
static {
27+
console.log(FOO); // should error
28+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
29+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
30+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
31+
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
32+
}
33+
}
34+
35+
const FOO = "FOO";
36+
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
37+
38+
class Bar {
39+
>Bar : Symbol(Bar, Decl(classStaticBlockUseBeforeDef3.ts, 17, 18))
40+
41+
static {
42+
console.log(FOO); // should not error
43+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
44+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
45+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
46+
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
47+
}
48+
}
49+
50+
let u = "FOO" as "FOO" | "BAR";
51+
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
52+
53+
class CFA {
54+
>CFA : Symbol(CFA, Decl(classStaticBlockUseBeforeDef3.ts, 24, 31))
55+
56+
static {
57+
u = "BAR";
58+
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
59+
60+
u; // should be "BAR"
61+
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
62+
}
63+
64+
static t = 1;
65+
>t : Symbol(CFA.t, Decl(classStaticBlockUseBeforeDef3.ts, 30, 5))
66+
67+
static doSomething() {}
68+
>doSomething : Symbol(CFA.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 32, 17))
69+
70+
static {
71+
u; // should be "BAR"
72+
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
73+
}
74+
}
75+
76+
u; // should be "BAR"
77+
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
78+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts ===
2+
class A {
3+
>A : A
4+
5+
static {
6+
A.doSomething(); // should not error
7+
>A.doSomething() : void
8+
>A.doSomething : () => void
9+
>A : typeof A
10+
>doSomething : () => void
11+
}
12+
13+
static doSomething() {
14+
>doSomething : () => void
15+
16+
console.log("gotcha!");
17+
>console.log("gotcha!") : void
18+
>console.log : (...data: any[]) => void
19+
>console : Console
20+
>log : (...data: any[]) => void
21+
>"gotcha!" : "gotcha!"
22+
}
23+
}
24+
25+
26+
class Baz {
27+
>Baz : Baz
28+
29+
static {
30+
console.log(FOO); // should error
31+
>console.log(FOO) : void
32+
>console.log : (...data: any[]) => void
33+
>console : Console
34+
>log : (...data: any[]) => void
35+
>FOO : "FOO"
36+
}
37+
}
38+
39+
const FOO = "FOO";
40+
>FOO : "FOO"
41+
>"FOO" : "FOO"
42+
43+
class Bar {
44+
>Bar : Bar
45+
46+
static {
47+
console.log(FOO); // should not error
48+
>console.log(FOO) : void
49+
>console.log : (...data: any[]) => void
50+
>console : Console
51+
>log : (...data: any[]) => void
52+
>FOO : "FOO"
53+
}
54+
}
55+
56+
let u = "FOO" as "FOO" | "BAR";
57+
>u : "FOO" | "BAR"
58+
>"FOO" as "FOO" | "BAR" : "FOO" | "BAR"
59+
>"FOO" : "FOO"
60+
61+
class CFA {
62+
>CFA : CFA
63+
64+
static {
65+
u = "BAR";
66+
>u = "BAR" : "BAR"
67+
>u : "FOO" | "BAR"
68+
>"BAR" : "BAR"
69+
70+
u; // should be "BAR"
71+
>u : "BAR"
72+
}
73+
74+
static t = 1;
75+
>t : number
76+
>1 : 1
77+
78+
static doSomething() {}
79+
>doSomething : () => void
80+
81+
static {
82+
u; // should be "BAR"
83+
>u : "BAR"
84+
}
85+
}
86+
87+
u; // should be "BAR"
88+
>u : "BAR"
89+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @strict: true
2+
3+
let foo: number;
4+
5+
class C {
6+
static {
7+
foo = 1
8+
}
9+
}
10+
11+
console.log(foo)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @noEmit: true
2+
// @strict: true
3+
4+
class A {
5+
static {
6+
A.doSomething(); // should not error
7+
}
8+
9+
static doSomething() {
10+
console.log("gotcha!");
11+
}
12+
}
13+
14+
15+
class Baz {
16+
static {
17+
console.log(FOO); // should error
18+
}
19+
}
20+
21+
const FOO = "FOO";
22+
class Bar {
23+
static {
24+
console.log(FOO); // should not error
25+
}
26+
}
27+
28+
let u = "FOO" as "FOO" | "BAR";
29+
30+
class CFA {
31+
static {
32+
u = "BAR";
33+
u; // should be "BAR"
34+
}
35+
36+
static t = 1;
37+
38+
static doSomething() {}
39+
40+
static {
41+
u; // should be "BAR"
42+
}
43+
}
44+
45+
u; // should be "BAR"

0 commit comments

Comments
 (0)