diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cbdb92933023e..44c93c893e6e7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1426,6 +1426,13 @@ const enum IntrinsicTypeKind { NoInfer, } +const enum SharedFlowNodeCacheFlags { + None = 0, + Read = 1 << 0, + Write = 1 << 1, + ReadWrite = Read | Write, +} + const intrinsicTypeKinds: ReadonlyMap = new Map(Object.entries({ Uppercase: IntrinsicTypeKind.Uppercase, Lowercase: IntrinsicTypeKind.Lowercase, @@ -28104,7 +28111,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isReachableFlowNode(flow: FlowNode) { - const result = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ false); + const result = isReachableFlowNodeWorker(flow, SharedFlowNodeCacheFlags.ReadWrite); lastFlowNode = flow; lastFlowNodeReachable = result; return result; @@ -28118,19 +28125,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { ); } - function isReachableFlowNodeWorker(flow: FlowNode, noCacheCheck: boolean): boolean { + function isReachableFlowNodeWorker(flow: FlowNode, cacheFlags: SharedFlowNodeCacheFlags): boolean { while (true) { if (flow === lastFlowNode) { return lastFlowNodeReachable; } const flags = flow.flags; if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { + if (cacheFlags & SharedFlowNodeCacheFlags.Read) { const id = getFlowNodeId(flow); - const reachable = flowNodeReachable[id]; - return reachable !== undefined ? reachable : (flowNodeReachable[id] = isReachableFlowNodeWorker(flow, /*noCacheCheck*/ true)); + let reachable = flowNodeReachable[id]; + if (reachable !== undefined) { + return reachable; + } + reachable = isReachableFlowNodeWorker(flow, cacheFlags & ~SharedFlowNodeCacheFlags.Read); + if (cacheFlags & SharedFlowNodeCacheFlags.Write) { + flowNodeReachable[id] = reachable; + } + return reachable; } - noCacheCheck = false; + cacheFlags |= SharedFlowNodeCacheFlags.Read; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation)) { flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation).antecedent; @@ -28153,7 +28167,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (flags & FlowFlags.BranchLabel) { // A branching point is reachable if any branch is reachable. - return some((flow as FlowLabel).antecedent, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); + return some((flow as FlowLabel).antecedent, f => isReachableFlowNodeWorker(f, cacheFlags)); } else if (flags & FlowFlags.LoopLabel) { const antecedents = (flow as FlowLabel).antecedent; @@ -28178,7 +28192,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const target = (flow as FlowReduceLabel).node.target; const saveAntecedents = target.antecedent; target.antecedent = (flow as FlowReduceLabel).node.antecedents; - const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + const result = isReachableFlowNodeWorker((flow as FlowReduceLabel).antecedent, SharedFlowNodeCacheFlags.None); target.antecedent = saveAntecedents; return result; } @@ -28190,16 +28204,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path // leading to the node. - function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { + function isPostSuperFlowNode(flow: FlowNode, cacheFlags: SharedFlowNodeCacheFlags): boolean { while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { - if (!noCacheCheck) { + if (cacheFlags & SharedFlowNodeCacheFlags.Read) { const id = getFlowNodeId(flow); - const postSuper = flowNodePostSuper[id]; - return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); + let postSuper = flowNodePostSuper[id]; + if (postSuper !== undefined) { + return postSuper; + } + postSuper = isPostSuperFlowNode(flow, cacheFlags & ~SharedFlowNodeCacheFlags.Read); + if (cacheFlags & SharedFlowNodeCacheFlags.Write) { + flowNodePostSuper[id] = postSuper; + } + return postSuper; } - noCacheCheck = false; + cacheFlags |= SharedFlowNodeCacheFlags.Read; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { flow = (flow as FlowAssignment | FlowCondition | FlowArrayMutation | FlowSwitchClause).antecedent; @@ -28212,7 +28233,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else if (flags & FlowFlags.BranchLabel) { // A branching point is post-super if every branch is post-super. - return every((flow as FlowLabel).antecedent, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); + return every((flow as FlowLabel).antecedent, f => isPostSuperFlowNode(f, cacheFlags)); } else if (flags & FlowFlags.LoopLabel) { // A loop is post-super if the control flow path that leads to the top is post-super. @@ -28222,7 +28243,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const target = (flow as FlowReduceLabel).node.target; const saveAntecedents = target.antecedent; target.antecedent = (flow as FlowReduceLabel).node.antecedents; - const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, /*noCacheCheck*/ false); + const result = isPostSuperFlowNode((flow as FlowReduceLabel).antecedent, SharedFlowNodeCacheFlags.None); target.antecedent = saveAntecedents; return result; } @@ -30659,7 +30680,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If a containing class does not have extends clause or the class extends null // skip checking whether super statement is called before "this" accessing. if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { - if (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { + if (canHaveFlowNode(node) && node.flowNode && !isPostSuperFlowNode(node.flowNode, SharedFlowNodeCacheFlags.ReadWrite)) { error(node, diagnosticMessage); } } diff --git a/tests/baselines/reference/reachabilityChecks9.symbols b/tests/baselines/reference/reachabilityChecks9.symbols new file mode 100644 index 0000000000000..7ccd67398fc8b --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.symbols @@ -0,0 +1,113 @@ +//// [tests/cases/compiler/reachabilityChecks9.ts] //// + +=== reachabilityChecks9.ts === +// https://github.com/microsoft/TypeScript/issues/61259 + +const a = (v: 1 | 2) => { +>a : Symbol(a, Decl(reachabilityChecks9.ts, 2, 5)) +>v : Symbol(v, Decl(reachabilityChecks9.ts, 2, 11)) + + try { + switch (v) { +>v : Symbol(v, Decl(reachabilityChecks9.ts, 2, 11)) + + case 1: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 2, 11)) + + case 2: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 2, 11)) + } + } finally { + console.log("exit"); +>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, --, --)) + } +}; + +const b = (v: number) => { +>b : Symbol(b, Decl(reachabilityChecks9.ts, 15, 5)) +>v : Symbol(v, Decl(reachabilityChecks9.ts, 15, 11)) + + try { + switch (v) { +>v : Symbol(v, Decl(reachabilityChecks9.ts, 15, 11)) + + case 1: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 15, 11)) + + default: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 15, 11)) + } + } finally { + console.log("exit"); +>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, --, --)) + } +}; + +const c = (v: 1 | 2) => { +>c : Symbol(c, Decl(reachabilityChecks9.ts, 28, 5)) +>v : Symbol(v, Decl(reachabilityChecks9.ts, 28, 11)) + + try { + switch (v) { +>v : Symbol(v, Decl(reachabilityChecks9.ts, 28, 11)) + + case 1: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 28, 11)) + + case 2: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 28, 11)) + } + } finally { + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + console.log("exit"); +>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, --, --)) + } + } +}; + +const d = (v: number) => { +>d : Symbol(d, Decl(reachabilityChecks9.ts, 43, 5)) +>v : Symbol(v, Decl(reachabilityChecks9.ts, 43, 11)) + + try { + switch (v) { +>v : Symbol(v, Decl(reachabilityChecks9.ts, 43, 11)) + + case 1: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 43, 11)) + + default: + return v; +>v : Symbol(v, Decl(reachabilityChecks9.ts, 43, 11)) + } + } finally { + if (Math.random()) { +>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --)) + + console.log("exit"); +>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, --, --)) + } + } +}; + diff --git a/tests/baselines/reference/reachabilityChecks9.types b/tests/baselines/reference/reachabilityChecks9.types new file mode 100644 index 0000000000000..102cba292500a --- /dev/null +++ b/tests/baselines/reference/reachabilityChecks9.types @@ -0,0 +1,197 @@ +//// [tests/cases/compiler/reachabilityChecks9.ts] //// + +=== reachabilityChecks9.ts === +// https://github.com/microsoft/TypeScript/issues/61259 + +const a = (v: 1 | 2) => { +>a : (v: 1 | 2) => 1 | 2 +> : ^ ^^ ^^^^^^^^^^ +>(v: 1 | 2) => { try { switch (v) { case 1: return v; case 2: return v; } } finally { console.log("exit"); }} : (v: 1 | 2) => 1 | 2 +> : ^ ^^ ^^^^^^^^^^ +>v : 1 | 2 +> : ^^^^^ + + try { + switch (v) { +>v : 1 | 2 +> : ^^^^^ + + case 1: +>1 : 1 +> : ^ + + return v; +>v : 1 +> : ^ + + case 2: +>2 : 2 +> : ^ + + return v; +>v : 2 +> : ^ + } + } finally { + console.log("exit"); +>console.log("exit") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"exit" : "exit" +> : ^^^^^^ + } +}; + +const b = (v: number) => { +>b : (v: number) => number +> : ^ ^^ ^^^^^^^^^^^ +>(v: number) => { try { switch (v) { case 1: return v; default: return v; } } finally { console.log("exit"); }} : (v: number) => number +> : ^ ^^ ^^^^^^^^^^^ +>v : number +> : ^^^^^^ + + try { + switch (v) { +>v : number +> : ^^^^^^ + + case 1: +>1 : 1 +> : ^ + + return v; +>v : 1 +> : ^ + + default: + return v; +>v : number +> : ^^^^^^ + } + } finally { + console.log("exit"); +>console.log("exit") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"exit" : "exit" +> : ^^^^^^ + } +}; + +const c = (v: 1 | 2) => { +>c : (v: 1 | 2) => 1 | 2 +> : ^ ^^ ^^^^^^^^^^ +>(v: 1 | 2) => { try { switch (v) { case 1: return v; case 2: return v; } } finally { if (Math.random()) { console.log("exit"); } }} : (v: 1 | 2) => 1 | 2 +> : ^ ^^ ^^^^^^^^^^ +>v : 1 | 2 +> : ^^^^^ + + try { + switch (v) { +>v : 1 | 2 +> : ^^^^^ + + case 1: +>1 : 1 +> : ^ + + return v; +>v : 1 +> : ^ + + case 2: +>2 : 2 +> : ^ + + return v; +>v : 2 +> : ^ + } + } finally { + if (Math.random()) { +>Math.random() : number +> : ^^^^^^ +>Math.random : () => number +> : ^^^^^^ +>Math : Math +> : ^^^^ +>random : () => number +> : ^^^^^^ + + console.log("exit"); +>console.log("exit") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"exit" : "exit" +> : ^^^^^^ + } + } +}; + +const d = (v: number) => { +>d : (v: number) => number +> : ^ ^^ ^^^^^^^^^^^ +>(v: number) => { try { switch (v) { case 1: return v; default: return v; } } finally { if (Math.random()) { console.log("exit"); } }} : (v: number) => number +> : ^ ^^ ^^^^^^^^^^^ +>v : number +> : ^^^^^^ + + try { + switch (v) { +>v : number +> : ^^^^^^ + + case 1: +>1 : 1 +> : ^ + + return v; +>v : 1 +> : ^ + + default: + return v; +>v : number +> : ^^^^^^ + } + } finally { + if (Math.random()) { +>Math.random() : number +> : ^^^^^^ +>Math.random : () => number +> : ^^^^^^ +>Math : Math +> : ^^^^ +>random : () => number +> : ^^^^^^ + + console.log("exit"); +>console.log("exit") : void +> : ^^^^ +>console.log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>console : Console +> : ^^^^^^^ +>log : (...data: any[]) => void +> : ^^^^ ^^ ^^^^^ +>"exit" : "exit" +> : ^^^^^^ + } + } +}; + diff --git a/tests/cases/compiler/reachabilityChecks9.ts b/tests/cases/compiler/reachabilityChecks9.ts new file mode 100644 index 0000000000000..b6f6da1bf649d --- /dev/null +++ b/tests/cases/compiler/reachabilityChecks9.ts @@ -0,0 +1,61 @@ +// @strict: true +// @allowUnreachableCode: false +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/61259 + +const a = (v: 1 | 2) => { + try { + switch (v) { + case 1: + return v; + case 2: + return v; + } + } finally { + console.log("exit"); + } +}; + +const b = (v: number) => { + try { + switch (v) { + case 1: + return v; + default: + return v; + } + } finally { + console.log("exit"); + } +}; + +const c = (v: 1 | 2) => { + try { + switch (v) { + case 1: + return v; + case 2: + return v; + } + } finally { + if (Math.random()) { + console.log("exit"); + } + } +}; + +const d = (v: number) => { + try { + switch (v) { + case 1: + return v; + default: + return v; + } + } finally { + if (Math.random()) { + console.log("exit"); + } + } +};