From 038d7c4c48b392b91e06379ac5a7bffa1300f79f Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sun, 6 Mar 2022 14:37:40 +0800 Subject: [PATCH 1/2] fix unexpected used-before-assignment errors --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 1 + .../classStaticBlockUseBeforeDef3.errors.txt | 50 +++++++++++ .../classStaticBlockUseBeforeDef3.symbols | 74 ++++++++++++++++ .../classStaticBlockUseBeforeDef3.types | 85 +++++++++++++++++++ .../classStaticBlockUseBeforeDef3.ts | 43 ++++++++++ 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt create mode 100644 tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols create mode 100644 tests/baselines/reference/classStaticBlockUseBeforeDef3.types create mode 100644 tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1f36ffff64448..3db58c5fe0177 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -661,7 +661,7 @@ namespace ts { !(node as FunctionLikeDeclaration).asteriskToken && !!getImmediatelyInvokedFunctionExpression(node); // 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 (!isIIFE && node.kind !== SyntaxKind.ClassStaticBlockDeclaration) { currentFlow = initFlowNode({ flags: FlowFlags.Start }); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9dfb4a7e112c2..385d8f582c5ab 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28761,6 +28761,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/classStaticBlockUseBeforeDef3.errors.txt b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt new file mode 100644 index 0000000000000..b389b3331aeba --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt @@ -0,0 +1,50 @@ +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" + } + } \ 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..d0e5fc8e1fef9 --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols @@ -0,0 +1,74 @@ +=== 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)) + } +} diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.types b/tests/baselines/reference/classStaticBlockUseBeforeDef3.types new file mode 100644 index 0000000000000..de110a3d12dad --- /dev/null +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.types @@ -0,0 +1,85 @@ +=== 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 : "FOO" | "BAR" + } +} diff --git a/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts new file mode 100644 index 0000000000000..56931f34e37ad --- /dev/null +++ b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts @@ -0,0 +1,43 @@ +// @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" + } +} \ No newline at end of file From bb57855f92b62c7f5bf99e2cb4e205b7eff8b919 Mon Sep 17 00:00:00 2001 From: Zzzen Date: Sun, 6 Mar 2022 15:19:36 +0800 Subject: [PATCH 2/2] fix CFA in class static block --- src/compiler/binder.ts | 10 +++++----- .../reference/classStaticBlockUseBeforeDef3.errors.txt | 4 +++- .../reference/classStaticBlockUseBeforeDef3.symbols | 4 ++++ .../reference/classStaticBlockUseBeforeDef3.types | 6 +++++- .../classStaticBlock/classStaticBlockUseBeforeDef3.ts | 4 +++- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 3db58c5fe0177..94718dc9454a6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -657,11 +657,11 @@ 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 isIIFELike = 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 && node.kind !== SyntaxKind.ClassStaticBlockDeclaration) { + if (!isIIFELike) { currentFlow = initFlowNode({ flags: FlowFlags.Start }); if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethodOrAccessor)) { currentFlow.node = node as FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; @@ -669,7 +669,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 = isIIFELike || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; currentExceptionTarget = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; @@ -695,7 +695,7 @@ namespace ts { (node as FunctionLikeDeclaration | ClassStaticBlockDeclaration).returnFlowNode = currentFlow; } } - if (!isIIFE) { + if (!isIIFELike) { currentFlow = saveCurrentFlow; } currentBreakTarget = saveBreakTarget; diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt index b389b3331aeba..c25e4888049a8 100644 --- a/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.errors.txt @@ -47,4 +47,6 @@ tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.t static { u; // should be "BAR" } - } \ No newline at end of file + } + + u; // should be "BAR" \ No newline at end of file diff --git a/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols b/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols index d0e5fc8e1fef9..2bed348d7a5af 100644 --- a/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.symbols @@ -72,3 +72,7 @@ class CFA { >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 index de110a3d12dad..2dd87a42c49f0 100644 --- a/tests/baselines/reference/classStaticBlockUseBeforeDef3.types +++ b/tests/baselines/reference/classStaticBlockUseBeforeDef3.types @@ -80,6 +80,10 @@ class CFA { static { u; // should be "BAR" ->u : "FOO" | "BAR" +>u : "BAR" } } + +u; // should be "BAR" +>u : "BAR" + diff --git a/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts index 56931f34e37ad..04f5c34394671 100644 --- a/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts +++ b/tests/cases/conformance/classes/classStaticBlock/classStaticBlockUseBeforeDef3.ts @@ -40,4 +40,6 @@ class CFA { static { u; // should be "BAR" } -} \ No newline at end of file +} + +u; // should be "BAR" \ No newline at end of file