[JuliaLowering] Syntax versioning and macro-expansion improvements (#62221)
Attach a new SyntaxContext to all syntax, which is shared between all
nodes of the same macro expansion (with some exceptions for
`escape`, etc.).
## New stuff
Macros can now be syntax-version-aware, so the following is now
possible:
```julia
macro some_versioned_macro(arg)
if syntax_version(__context__.macrocall) < v"1.14"
# ...
elseif syntax_version(arg) < v"1.14"
# ...
else
# ...
end
end
```
## Design
TL;DR: Racket does it right. The following should be equivalent:
| JL | Racket |
| :----------- | :------- |
| `SyntaxTree` | syntax object |
| `Expr`/`Symbol`/etc | plain data |
| `K"inert"`/`QuoteNode` | `'()` (quote) |
| `K"quote"`/`:()` | `` `() `` (quasiquote) |
| `K"$"` | `` ,() `` (unquote) |
| `syntaxinert` (formerly inert_syntaxtree) | `` #'() `` (syntax) |
| `syntaxquote` (new, no surface syntax yet) | `` #`() `` (quasisyntax)
|
| `syntaxunquote`(new, no surface syntax yet) | `` #,() `` (unsyntax) |
This PR doesn't implement the full sets-of-scopes resolution, though it
does make it easier to implement if we wanted that. No local macros
makes it less important. We miss out on some cross-toplevel-thunk
resolution, which I think is OK or desirable for now.
After each macro expands, we want to apply a new scope layer to all new
syntax (that wasn't passed as a macro argument at some point). Racket
applies the new scope to all argument syntax, expands, then inverts that
scope throughout the expansion. This PR expands, then applies the new
layer to all syntax whose existing layer is not known to be "under" that
new layer.