diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a40d66280ea9e..6188cc1ace6f3 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -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; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eee32406b7790..ee00a28b5f1fd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28852,6 +28852,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); } diff --git a/tests/baselines/reference/classStaticBlock28.js b/tests/baselines/reference/classStaticBlock28.js new file mode 100644 index 0000000000000..955dc88d3cfd7 --- /dev/null +++ b/tests/baselines/reference/classStaticBlock28.js @@ -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); diff --git a/tests/baselines/reference/classStaticBlock28.symbols b/tests/baselines/reference/classStaticBlock28.symbols new file mode 100644 index 0000000000000..3a8dc99b9d194 --- /dev/null +++ b/tests/baselines/reference/classStaticBlock28.symbols @@ -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)) + diff --git a/tests/baselines/reference/classStaticBlock28.types b/tests/baselines/reference/classStaticBlock28.types new file mode 100644 index 0000000000000..8abbe617d9f5c --- /dev/null +++ b/tests/baselines/reference/classStaticBlock28.types @@ -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 + diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt new file mode 100644 index 0000000000000..7fed7444b57ef --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt @@ -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" + \ No newline at end of file diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols b/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols new file mode 100644 index 0000000000000..beb214dab0860 --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols @@ -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)) + diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.types b/tests/baselines/reference/classStaticBlockUseBeforeDef3.types new file mode 100644 index 0000000000000..f10c2ee0f67a1 --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.types @@ -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" + diff --git a/tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts b/tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts new file mode 100644 index 0000000000000..90cbf78bfdd59 --- /dev/null +++ b/tests/cases/conformance/classes/classStaticBlock/classStaticBlock28.ts @@ -0,0 +1,11 @@ +// @strict: true + +let foo: number; + +class C { + static { + foo = 1 + } +} + +console.log(foo) \ No newline at end of file diff --git a/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts new file mode 100644 index 0000000000000..bf59cb8a8c485 --- /dev/null +++ b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts @@ -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"