Skip to content

Keep the closure when a cast instantiates a capturing untyped arrow - #708

Merged
ASDAlexander77 merged 1 commit into
mainfrom
fix-capture-untyped-arrow-new
Oct 28, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
fix-capture-untyped-arrow-new

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

This program crashed with a stack dump under every memory model (gc, rc, none, own):

class W {
    constructor(private f: (v: number) => void) {}
    run() { this.f(1); }
}
function main() {
    let seen = 0;
    const w = new W((v) => { seen += 1; });
    w.run();
    print(seen);
}

The arrow (v) => ... has no parameter types, so it stays generic until the cast to the constructor parameter's type instantiates it. verifyFunctionCastRules did that through instantiateSpecializedFunction, but it kept only the LogicalResult. When the arrow captures something, the result is a new value: the closure bound over the captured variables. The cast carried on with the bare function, so the constructor stored a function whose captures were a null pointer.

The same arrow worked as an argument to an ordinary call, where the delayed instantiation already uses the returned value. It also worked with typed parameters, or when it captured nothing.

Fix

verifyFunctionCastRules now takes value and valueType by reference. It finishes the cast with the returned closure, the same way instantiateGenericsForBinaryOp already did, and refreshes valueType after instantiation.

Results

  • gc, rc, none: the program prints 1.
  • -mm=own: the program now gets the same "captured by a closure that escapes" diagnostic that the explicitly typed form already got.
  • New test 00capture_in_new_untyped_arrow.ts covers a parameter property, a stored field, a value-returning arrow and a non-capturing control. It runs as gc compile and JIT, and as rc and none through the corpus.
  • Full ctest -C Release: 100% passed, 0 failed out of 6026.

🤖 Generated with Claude Code

`new W((v) => { seen += 1; })` crashed in every memory model. The arrow has
no parameter types, so it stays generic until the cast to the constructor
parameter's type instantiates it. verifyFunctionCastRules did that through
instantiateSpecializedFunction but kept only its LogicalResult: when the
arrow captures, the result is a new value (the closure bound over the
captured variables), and the cast went on with the bare function, whose
captures were then read through a null pointer.

verifyFunctionCastRules now takes value and valueType by reference and
continues the cast with the returned closure, as instantiateGenericsForBinaryOp
already does. Under -mm=own the program now gets the escaping-closure
diagnostic the explicitly typed form already got.

Regression test 00capture_in_new_untyped_arrow.ts (gc compile/jit, rc and
none through the corpus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 9e01159 into main Oct 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the fix-capture-untyped-arrow-new branch October 28, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant