align ClassStaticBlockDeclaration with IIFE in CFA (#44969)

* align ClassStaticBlockDeclaration with IIFE in CFA

* isIIFELike => isImmediatelyInvoked

* fix unexpected used-before-assignment errors

* update baseline
This commit is contained in:
Zzzen 2022-03-24 06:47:29 +08:00 committed by GitHub
parent 4ec16b2a6b
commit 20c01cdd3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 350 additions and 5 deletions

View File

@ -657,11 +657,15 @@ namespace ts {
const saveExceptionTarget = currentExceptionTarget;
const saveActiveLabelList = activeLabelList;
const saveHasExplicitReturn = hasExplicitReturn;
const isIIFE = containerFlags & ContainerFlags.IsFunctionExpression && !hasSyntacticModifier(node, ModifierFlags.Async) &&
!(node as FunctionLikeDeclaration).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node);
const isImmediatelyInvoked =
(containerFlags & ContainerFlags.IsFunctionExpression &&
!hasSyntacticModifier(node, ModifierFlags.Async) &&
!(node as FunctionLikeDeclaration).asteriskToken &&
!!getImmediatelyInvokedFunctionExpression(node)) ||
node.kind === SyntaxKind.ClassStaticBlockDeclaration;
// A non-async, non-generator IIFE is considered part of the containing control flow. Return statements behave
// similarly to break statements that exit to a label just past the statement body.
if (!isIIFE) {
if (!isImmediatelyInvoked) {
currentFlow = initFlowNode({ flags: FlowFlags.Start });
if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) {
currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration;
@ -669,7 +673,7 @@ namespace ts {
}
// We create a return control flow graph for IIFEs and constructors. For constructors
// we use the return control flow graph in strict property initialization checks.
currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ClassStaticBlockDeclaration || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentReturnTarget = isImmediatelyInvoked || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined;
currentExceptionTarget = undefined;
currentBreakTarget = undefined;
currentContinueTarget = undefined;
@ -695,7 +699,7 @@ namespace ts {
(node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow;
}
}
if (!isIIFE) {
if (!isImmediatelyInvoked) {
currentFlow = saveCurrentFlow;
}
currentBreakTarget = saveBreakTarget;

View File

@ -28982,6 +28982,7 @@ namespace ts {
&& !isOptionalPropertyDeclaration(valueDeclaration)
&& !(isAccessExpression(node) && isAccessExpression(node.expression))
&& !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)
&& !(isMethodDeclaration(valueDeclaration) && getCombinedModifierFlags(valueDeclaration) & ModifierFlags.Static)
&& (compilerOptions.useDefineForClassFields || !isPropertyDeclaredInAncestorClass(prop))) {
diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName);
}

View File

@ -0,0 +1,23 @@
//// [classStaticBlock28.ts]
let foo: number;
class C {
static {
foo = 1
}
}
console.log(foo)
//// [classStaticBlock28.js]
"use strict";
var foo;
var C = /** @class */ (function () {
function C() {
}
return C;
}());
(function () {
foo = 1;
})();
console.log(foo);

View File

@ -0,0 +1,19 @@
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts ===
let foo: number;
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))
class C {
>C : Symbol(C, Decl(classStaticBlock28.ts, 0, 16))
static {
foo = 1
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))
}
}
console.log(foo)
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>foo : Symbol(foo, Decl(classStaticBlock28.ts, 0, 3))

View File

@ -0,0 +1,22 @@
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts ===
let foo: number;
>foo : number
class C {
>C : C
static {
foo = 1
>foo = 1 : 1
>foo : number
>1 : 1
}
}
console.log(foo)
>console.log(foo) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>foo : number

View File

@ -0,0 +1,53 @@
tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts(14,21): error TS2448: Block-scoped variable 'FOO' used before its declaration.
tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts(14,21): error TS2454: Variable 'FOO' is used before being assigned.
==== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts (2 errors) ====
class A {
static {
A.doSomething(); // should not error
}
static doSomething() {
console.log("gotcha!");
}
}
class Baz {
static {
console.log(FOO); // should error
~~~
!!! error TS2448: Block-scoped variable 'FOO' used before its declaration.
!!! related TS2728 tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts:18:7: 'FOO' is declared here.
~~~
!!! error TS2454: Variable 'FOO' is used before being assigned.
}
}
const FOO = "FOO";
class Bar {
static {
console.log(FOO); // should not error
}
}
let u = "FOO" as "FOO" | "BAR";
class CFA {
static {
u = "BAR";
u; // should be "BAR"
}
static t = 1;
static doSomething() {}
static {
u; // should be "BAR"
}
}
u; // should be "BAR"

View File

@ -0,0 +1,78 @@
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts ===
class A {
>A : Symbol(A, Decl(classStaticBlockUseBeforeDef3.ts, 0, 0))
static {
A.doSomething(); // should not error
>A.doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
>A : Symbol(A, Decl(classStaticBlockUseBeforeDef3.ts, 0, 0))
>doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
}
static doSomething() {
>doSomething : Symbol(A.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 3, 5))
console.log("gotcha!");
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
}
}
class Baz {
>Baz : Symbol(Baz, Decl(classStaticBlockUseBeforeDef3.ts, 8, 1))
static {
console.log(FOO); // should error
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
}
}
const FOO = "FOO";
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
class Bar {
>Bar : Symbol(Bar, Decl(classStaticBlockUseBeforeDef3.ts, 17, 18))
static {
console.log(FOO); // should not error
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>FOO : Symbol(FOO, Decl(classStaticBlockUseBeforeDef3.ts, 17, 5))
}
}
let u = "FOO" as "FOO" | "BAR";
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
class CFA {
>CFA : Symbol(CFA, Decl(classStaticBlockUseBeforeDef3.ts, 24, 31))
static {
u = "BAR";
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
u; // should be "BAR"
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
}
static t = 1;
>t : Symbol(CFA.t, Decl(classStaticBlockUseBeforeDef3.ts, 30, 5))
static doSomething() {}
>doSomething : Symbol(CFA.doSomething, Decl(classStaticBlockUseBeforeDef3.ts, 32, 17))
static {
u; // should be "BAR"
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))
}
}
u; // should be "BAR"
>u : Symbol(u, Decl(classStaticBlockUseBeforeDef3.ts, 24, 3))

View File

@ -0,0 +1,89 @@
=== tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts ===
class A {
>A : A
static {
A.doSomething(); // should not error
>A.doSomething() : void
>A.doSomething : () => void
>A : typeof A
>doSomething : () => void
}
static doSomething() {
>doSomething : () => void
console.log("gotcha!");
>console.log("gotcha!") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"gotcha!" : "gotcha!"
}
}
class Baz {
>Baz : Baz
static {
console.log(FOO); // should error
>console.log(FOO) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>FOO : "FOO"
}
}
const FOO = "FOO";
>FOO : "FOO"
>"FOO" : "FOO"
class Bar {
>Bar : Bar
static {
console.log(FOO); // should not error
>console.log(FOO) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>FOO : "FOO"
}
}
let u = "FOO" as "FOO" | "BAR";
>u : "FOO" | "BAR"
>"FOO" as "FOO" | "BAR" : "FOO" | "BAR"
>"FOO" : "FOO"
class CFA {
>CFA : CFA
static {
u = "BAR";
>u = "BAR" : "BAR"
>u : "FOO" | "BAR"
>"BAR" : "BAR"
u; // should be "BAR"
>u : "BAR"
}
static t = 1;
>t : number
>1 : 1
static doSomething() {}
>doSomething : () => void
static {
u; // should be "BAR"
>u : "BAR"
}
}
u; // should be "BAR"
>u : "BAR"

View File

@ -0,0 +1,11 @@
// @strict: true
let foo: number;
class C {
static {
foo = 1
}
}
console.log(foo)

View File

@ -0,0 +1,45 @@
// @noEmit: true
// @strict: true
class A {
static {
A.doSomething(); // should not error
}
static doSomething() {
console.log("gotcha!");
}
}
class Baz {
static {
console.log(FOO); // should error
}
}
const FOO = "FOO";
class Bar {
static {
console.log(FOO); // should not error
}
}
let u = "FOO" as "FOO" | "BAR";
class CFA {
static {
u = "BAR";
u; // should be "BAR"
}
static t = 1;
static doSomething() {}
static {
u; // should be "BAR"
}
}
u; // should be "BAR"