Sitelet https://github.com/starkware-libs/cairo/pull/10086
Skip to content

bugfix(semantic): Include captured_types in closure occurs-check. - #10086

Merged
orizi merged 1 commit into
mainfrom
orizi/06-11-bugfix_semantic_include_captured_types_in_closure_occurs-check
Jun 14, 2026
Merged

orizi merged 1 commit into
mainfrom
orizi/06-11-bugfix_semantic_include_captured_types_in_closure_occurs-check

Conversation

@orizi

@orizi orizi commented Jun 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes a stack-overflow (occurs-check infinite loop) that occurred when a closure capturing a variable of inferred type was unified with that same variable. The internal_ty_contains_var check for TypeLongId::Closure was not including captured_types in its occurs-check traversal, meaning the inference engine could not detect the cyclic type constraint and would recurse indefinitely.

The fix extends the occurs-check to also iterate over closure.captured_types alongside param_tys and ret_ty, using itertools::chain! to unify the three into a single iterator.


Type of change

Please check one:

  • Bug fix (fixes incorrect behavior)
  • New feature
  • Performance improvement
  • Documentation change with concrete technical impact
  • Style, wording, formatting, or typo-only change

Why is this change needed?

When a closure captured a variable whose type was still being inferred, and that closure was then passed to a function expecting the same inferred type, the occurs-check (internal_ty_contains_var) failed to detect the cycle because it did not inspect captured_types. This caused the compiler to stack-overflow instead of emitting a proper type mismatch diagnostic.


What was the behavior or documentation before?

Compiling code like:

fn unify<T, +Drop<T>>(_a: T, _b: T) {}
fn foo() {
    let x = Default::default();
    let f = || { let _s = @x; };
    unify(x, f);
}

would cause a stack overflow in the compiler.


What is the behavior or documentation after?

The compiler now correctly detects the cyclic type constraint and emits a proper diagnostic:

error[E2041]: Unexpected argument type. Expected: "?0", found: "{closure@lib.cairo:4:13: 4:15}".

Related issue or discussion (if any)

Occurs-check regression for closures capturing inferred-type variables.


Additional context

A regression test has been added to crates/cairo-lang-semantic/src/expr/test_data/closure covering this exact scenario.

  internal_ty_contains_var scanned only a closure type's param_tys and
  ret_ty, omitting captured_types — unlike every other closure-type walk
  (conform_ty, the solver's can_conform check, and the canonical sub-type
  enumeration), which all include it.

  As a result, an inference variable appearing solely in captured_types
  passed the occurs-check, so it could be bound to a closure type that
  transitively contains itself. The resulting infinite type overflows the
  stack and crashes the compiler:

      fn unify<T, +Drop<T>>(_a: T, _b: T) {}
      fn foo() {
          let x = Default::default();   // x: ?T
          let f = || { let _s = @x; };  // captured_types = [@?T]
          unify(x, f);                  // binds ?T := closure containing @?T
      }

  Scan captured_types too, so the cyclic binding is rejected and the
  program reports a normal type mismatch (E2041) instead of crashing.
  Adds a regression test exercising the capture-only path.
@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

orizi commented Jun 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@orizi
orizi marked this pull request as ready for review June 11, 2026 14:59
@cursor

cursor Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

PR Summary

Low Risk
Narrow change to inference cycle detection for closure types; aligns occurs-check with existing closure conformance that already handles captured_types.

Overview
Fixes a compiler stack overflow when unifying an inferred variable with a closure that captures that variable: closure occurs-check traversal in internal_ty_contains_var now walks captured_types (via itertools::chain!) in addition to parameter and return types, so cyclic assignments are rejected instead of recursing forever.

Adds a semantic regression test in closure diagnostics: unify(x, f) with f capturing @x must report E2041 (type mismatch) rather than crashing.

Reviewed by Cursor Bugbot for commit d15963e. Bugbot is set up for automated code reviews on this repo. Configure here.

@eytan-starkware eytan-starkware left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does pub fn type_dependencies<'db> reveal a similar problem?

@eytan-starkware reviewed 2 files and all commit messages, and made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on TomerStarkware).

@orizi orizi left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no - as type_dependencies is used for finding the dependencies of a type for sierra-gen purposes - so there - it is about the types the closure actually holds (so basically the closure itself as a struct) - the params and return types are completely irrelevant there.

@orizi made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on TomerStarkware).

@orizi
orizi enabled auto-merge June 11, 2026 15:18

@TomerStarkware TomerStarkware left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:lgtm:

@TomerStarkware reviewed all commit messages and made 1 comment.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on orizi).

@orizi
orizi added this pull request to the merge queue Jun 14, 2026
Merged via the queue into main with commit 1587011 Jun 14, 2026
54 checks passed
@orizi
orizi deleted the orizi/06-11-bugfix_semantic_include_captured_types_in_closure_occurs-check branch June 14, 2026 11:54
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.

4 participants