Skip to content

Conversation

@Andarist
Copy link
Contributor

fixes #63015
caused by https://github.com/microsoft/TypeScript/pull/62243/files#r2264931395

With the change above, some of the function-like type-level nodes lost ContainerFlags.IsControlFlowContainer. It turns out that, despite them being type-level nodes, it was still important for them to be treated as flow containers. Or rather, it was important for them to get their own detached~ flow node as currentFlow in the bindContainer's branch handling ContainerFlags.IsControlFlowContainer.

Without that flag on them the nodes received .flowNode that was attached to the flow graph of the containing control flow container. Where with the flag they received FlowFlags.Start node that usually no other flow nodes would be attached to, unless one would write an incorrect program with narrowing expressions within type level nodes (those would attach to that FlowFlags.Start as that was set as currentFlow) in situations akin to this one:

type SomeTypeAlias = { [Math.random() ? foo : bar]: string } // error

So all of that caused typeof x within those function-like type-level nodes to get narrowed by the containing container's flow and the code managed to infinitely recurse in the reported code:

unionOfDifferentReturnType1(true);
const unionOfDifferentReturnType1: { (a: any): number; } | { (a: number): typeof Date; };

In here, the Date in the typeof query was being narrowed by FlowFlags.Call node but that depends on the annotated type of unionOfDifferentReturnType1 (it depends on getEffectsSignature and that depends on explicitly annotated return types).

It seemed to me it would be possible to create a similar crash in 5.9 and I tried weird things (I added them as tests) but couldn't make it happen. The closest thing I got was this somewhat surprising error:

function test4(arg: string | number, whatever: any) {
  if (typeof arg === "string") {
    b()
    type First = typeof arg // Type alias 'First' circularly references itself.(2456)
    type Test = () => First
    const b: Test = whatever
    return b
  }
  return undefined;
}

I think this means that type aliases have reentrancy protection already implemented and the reported code crashes because it manages to slip through codepaths that don't have it (anonymous type nodes don't implement pushTypeResolution+popTypeResolution etc).

All of that led me to exploring the inconsistencies around typeof queries, like here:

function test1(a: number | string) {
  if (typeof a === "number") {
    const fn = (arg: typeof a) => true;
    return fn;
  }
  return;
}

test1(0)?.(100); // ok
test1(0)?.(""); // error

function test2(a: number | string) {
  if (typeof a === "number") {
    const fn: { (arg: typeof a): boolean; } = () => true;
    return fn;
  }
  return;
}

test2(0)?.(100); // ok
test2(0)?.(""); // ok ?

So I figured out, I would prefer to keep their behavior change accidentally implemented in #62243 . It would be nice if they would behave in the same way in the example above. It seemed the easiest to just eagerly set a "zero value" links.effectsSignature before resolving the true value. That's a pretty common pattern used across the codebase.

FWIW, I have also briefly explored disabling control flow narrowing within type-only nodes but that broke a couple of existing explicit tests.

Alternative fixes I have considered:

  • reverting the ContainerFlags.IsControlFlowContainer changes + fine-tuning seenThisKeyword tracking
  • adding an extra codepath to bindContainer, smth like:
if (ContainerFlags.IsFunctionLike) {
  const saveCurrentFlow = currentFlow;
  currentFlow = createFlowNode(FlowFlags.Start, /*node*/ undefined, /*antecedent*/ undefined);
  bindChildren(node);
  currentFlow = saveCurrentFlow;
}
  • using links.effectsSignature = resolvingEffectsSignature strategy

cc @gabritto

@github-project-automation github-project-automation bot moved this to Not started in PR Backlog Jan 21, 2026
@typescript-bot typescript-bot added the For Milestone Bug PRs that fix a bug with a specific milestone label Jan 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Milestone Bug PRs that fix a bug with a specific milestone

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Regression Crash: RangeError: Maximum call stack size exceeded in isThisInTypeQuery on Nightly

3 participants