Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,13 @@ const enum IntrinsicTypeKind {
NoInfer,
}

const enum SharedFlowNodeCacheFlags {
None = 0,
Read = 1 << 0,
Write = 1 << 1,
ReadWrite = Read | Write,
}

const intrinsicTypeKinds: ReadonlyMap<string, IntrinsicTypeKind> = new Map(Object.entries({
Uppercase: IntrinsicTypeKind.Uppercase,
Lowercase: IntrinsicTypeKind.Lowercase,
Expand Down Expand Up @@ -28761,7 +28768,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;
Expand All @@ -28775,19 +28782,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;
Expand All @@ -28810,7 +28824,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;
Expand All @@ -28835,7 +28849,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;
}
Expand All @@ -28847,16 +28861,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;
Expand All @@ -28869,7 +28890,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.
Expand All @@ -28879,7 +28900,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;
}
Expand Down Expand Up @@ -31315,7 +31336,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);
}
}
Expand Down
151 changes: 151 additions & 0 deletions tests/baselines/reference/reachabilityChecks12.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//// [tests/cases/compiler/reachabilityChecks12.ts] ////

=== reachabilityChecks12.ts ===
// https://github.com/microsoft/TypeScript/issues/61259

const a = (v: 1 | 2) => {
>a : Symbol(a, Decl(reachabilityChecks12.ts, 2, 5))
>v : Symbol(v, Decl(reachabilityChecks12.ts, 2, 11))

try {
switch (v) {
>v : Symbol(v, Decl(reachabilityChecks12.ts, 2, 11))

case 1:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.ts, 2, 11))

case 2:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.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(reachabilityChecks12.ts, 15, 5))
>v : Symbol(v, Decl(reachabilityChecks12.ts, 15, 11))

try {
switch (v) {
>v : Symbol(v, Decl(reachabilityChecks12.ts, 15, 11))

case 1:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.ts, 15, 11))

default:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.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(reachabilityChecks12.ts, 28, 5))
>v : Symbol(v, Decl(reachabilityChecks12.ts, 28, 11))

try {
switch (v) {
>v : Symbol(v, Decl(reachabilityChecks12.ts, 28, 11))

case 1:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.ts, 28, 11))

case 2:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.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, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.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(reachabilityChecks12.ts, 43, 5))
>v : Symbol(v, Decl(reachabilityChecks12.ts, 43, 11))

try {
switch (v) {
>v : Symbol(v, Decl(reachabilityChecks12.ts, 43, 11))

case 1:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.ts, 43, 11))

default:
return v;
>v : Symbol(v, Decl(reachabilityChecks12.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, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.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, --, --))
}
}
};

// https://github.com/microsoft/TypeScript/issues/63004
const report: (e: any) => never = (e): never => {
>report : Symbol(report, Decl(reachabilityChecks12.ts, 59, 5))
>e : Symbol(e, Decl(reachabilityChecks12.ts, 59, 15))
>e : Symbol(e, Decl(reachabilityChecks12.ts, 59, 35))

throw e;
>e : Symbol(e, Decl(reachabilityChecks12.ts, 59, 35))

};

const foo = async (): Promise<number> => {
>foo : Symbol(foo, Decl(reachabilityChecks12.ts, 63, 5))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

try {
return 3;
} catch (e) {
>e : Symbol(e, Decl(reachabilityChecks12.ts, 66, 11))

report(e);
>report : Symbol(report, Decl(reachabilityChecks12.ts, 59, 5))
>e : Symbol(e, Decl(reachabilityChecks12.ts, 66, 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, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

console.log("heh");
>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, --, --))
}
}
};

Loading