Skip to content

Conversation

@mlechu
Copy link
Member

@mlechu mlechu commented Dec 4, 2025

Fixes

Soft scope

We currently treat flisp's '(block (softscope true)) as a scope that "is
soft", but this isn't correct. It should behave more like a toggle, where if the
scope surrounding (softscope true) is the top-level thunk, neutral scopes not
protected by a hard scope become permeable to already-defined globals. I've
added K"use_softscope_if_toplevel" for this.

Fixes JuliaLang/JuliaLowering.jl#101.
JuliaLowering.activate!() should be much more usable in the REPL now, as
globals won't be accidentally eaten by the "soft scope."

Shadowing behaviour

Found while cleaning up the lhs-resolution step with the change above. This PR
allows static parameters to be shadowed by globals and locals as long as they're
explicit (and not in the same scope). flisp allows this with globals, and the
explicit locals that desugar to local-def forms (which JuliaLowering doesn't
have).

This change is more permissive than flisp in the local case, since after looking
into why shadowing was disallowed I realized it was just was just to prevent
assignment to the static parameter (#32623). The flisp fix leads to some funny
behaviour:

julia> function f(x::T) where T
     let
         global T # remove this T and the other two will fight
         let; local T; end
     end
     end
f (generic function with 1 method)

Bindings/LambdaBindings

In figuring out how to use the variable bookkeeping system, I ran into inaccuracies.

LambdaBindings is currently a per-lambda map from unique variable to four
flags: captured, read, assigned, and called. I think (but correct me if
I'm wrong @c42f) this was a misinterpretation of the holy text: in flisp
lowering, a local variable captured by some other lambda does show up in both
lambdas' variable lists, but is the same underlying object, and flag mutations
on one variable are seen by all lambdas.

I tried to think of other reasons for tracking vinfo per lambda within lowering,
but if we're doing something about the capture-boxing issue, we need something
more complex anyway.

This PR moves all vinfo to BindingInfo and deletes the incorrect bookkeeping
in LambdaBindings. We still need to have a per-lambda flag for capturedness
(different from the variable-level capt vinfo flag). With the added flags,
I've just made BindingInfo mutable since our previous workflow (BindingId is an
index into a vector; mutate the vector) doesn't give us the benefits of
immutability anyway.

Enhancements

  • Scopes are retained until the end of the pass, so consumers like JETLS can
    answer questions like "what names are available at my cursor?" Recreating
    this previously-discarded information was a bit hacky! [1] [2]
  • Hopefully enough tests and explanatory comments to make up for the large diff

TODO

  • Our use of K"local" in desugaring is dubious in some places
  • I've added expr_compat_mode to the scope analysis context, but we still need
    to implement flisp hygiene exemptions for globals (see note in test/scopes.jl).
  • Reviewing the IR changes, the #self# argument still has extra flags set in
    some cases. This is an existing desugaring bug with a comment that took me
    too long to find: we shouldnn't be using the same #self# binding for
    multiple methods defined by one function body

"get" is clearer to me that the binding is the output rather than the input,
     that it definitely exists, and that the ID resolves uniquely
@mlechu mlechu requested review from c42f and topolarity December 4, 2025 20:24
@mlechu mlechu added the compiler:lowering Syntax lowering (compiler front end, 2nd stage) label Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:lowering Syntax lowering (compiler front end, 2nd stage)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

REPL hook-related issues

1 participant