Skip to content

[JuliaLowering] Fix is_ambiguous_local in permeable scopes#61473

Open
aviatesk wants to merge 1 commit intomasterfrom
avi/JL-fix-soft-scope
Open

[JuliaLowering] Fix is_ambiguous_local in permeable scopes#61473
aviatesk wants to merge 1 commit intomasterfrom
avi/JL-fix-soft-scope

Conversation

@aviatesk
Copy link
Copy Markdown
Member

@aviatesk aviatesk commented Apr 1, 2026

The b.kind === :global fallback in enter_scope! was creating
a plain local without setting is_ambiguous_local when a
toplevel global was reassigned from within a permeable scope
(for/while/try). This affected two cases:

  1. When a pre-existing global (defined in the module before
    lowering) is reassigned in a while loop where the
    condition also references the variable — e.g.
    while x < 5; x += 1; end. The condition's read resolves
    x as :global via is_defined_and_owned_global, then
    the body's assignment enters the b.kind === :global path
    instead of the b === nothing path.

  2. When a toplevel assignment precedes a permeable scope within
    the same toplevel thunk — e.g. begin; x = 0; for ...; x = 1; end; end. The toplevel x = 0 declares x as :global,
    so the inner assignment also enters b.kind === :global.

The fix sets is_ambiguous_local based solely on
scope.is_permeable: any assignment to a toplevel global from
within a permeable scope is ambiguous regardless of how the
global was resolved.

@topolarity topolarity requested a review from mlechu April 1, 2026 19:52
@aviatesk aviatesk force-pushed the avi/JL-fix-soft-scope branch 2 times, most recently from 728f90c to 8948d4d Compare April 2, 2026 09:12
@aviatesk aviatesk changed the title JuliaLowering: Set is_ambiguous_local for toplevel-assigned globals in begin blocks [JuliaLowering] Fix is_ambiguous_local in permeable scopes Apr 2, 2026
The `b.kind === :global` fallback in `enter_scope!` was creating
a plain local without setting `is_ambiguous_local` when a
toplevel global was reassigned from within a permeable scope
(`for`/`while`/`try`). This affected two cases:

1. When a pre-existing global (defined in the module before
   lowering) is reassigned in a `while` loop where the
   condition also references the variable — e.g.
   `while x < 5; x += 1; end`. The condition's read resolves
   `x` as `:global` via `is_defined_and_owned_global`, then
   the body's assignment enters the `b.kind === :global` path
   instead of the `b === nothing` path.

2. When a toplevel assignment precedes a permeable scope within
   the same toplevel thunk — e.g. `begin; x = 0; for ...; x = 1;
   end; end`. The toplevel `x = 0` declares `x` as `:global`,
   so the inner assignment also enters `b.kind === :global`.

The fix sets `is_ambiguous_local` based solely on
`scope.is_permeable`: any assignment to a toplevel global from
within a permeable scope is ambiguous regardless of how the
global was resolved.
est = JuliaLowering.expr_to_est(ex)
ctx1, ex1 = JuliaLowering.expand_forms_1(mod, est, false, world)
ctx2, ex2 = JuliaLowering.expand_forms_2(ctx1, ex1)
ctx3, _ex3 = JuliaLowering.resolve_scopes(ctx2, ex2; soft_scope)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ctx3, _ex3 = JuliaLowering.resolve_scopes(ctx2, ex2; soft_scope)
ctx3, _ex3 = JuliaLowering.resolve_scopes(ctx2, ex2)

Assuming from #61476

return ctx3.bindings.info
end

# When a toplevel assignment precedes a permeable scope (for/while/try) within
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think "precedes" is necessary for this to happen; an assignment "below" the ambiguous local's scope should be the same. Maybe worth writing a test for.


# Block containing global assignment
let bindings = resolve_and_get_bindings(Module(), quote
x = 0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Noting this doesn't match flisp when this is global x = 0, which somehow makes the local unambiguous to flisp, but still ambiguous to us. I don't think it's worth changing unless we can describe the behaviour we want, but if you would write a comment or a test_broken, that would be helpful.

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.

2 participants