-
Notifications
You must be signed in to change notification settings - Fork 9
expr_to_syntaxtree: don't fail when incoming expr is invalid
#103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
337f356 to
2c8a478
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixing the problem of unknown heads seems good and necessary, thanks!
I do find it surprising that this gives us mixtures of SyntaxTree and Expr in the intermediate forms and I'm a little concerned this mixed representation will lead to bugs.
How about we slightly change the representation here such that unknown heads still turn into K"expr_syntax" (or maybe K"unknown_head" or some better name?) but we map the children even when the head is unrecognized. The original head can go into an attribute. Actually this is almost what K"extension" currently is - so there's already a half-formed plan to do this kind of thing.
In addition I think we should put the error message for K"expr_syntax" in desugaring so macro expansion of unknown heads isn't an error.
Any potential bugs in mind? I'm actually more worried about case 2 (mapping children) than case 3 (give up and don't convert), since case 2 can have incorrect round-tripping behaviour if an invalid Expr is a valid SyntaxTree with different semantics. Unfortunately the only correct solutions are:
I decided to inch towards (A), where case 2 is incorrect but not problematic in practice. I left unknown "must round-trip" stuff as Expr since it's probably going to be used by an Expr-macro immediately after, and there isn't any provenance to lose. It would eat some CPU cycles, but wouldn't be incorrect to recursively convert the Expr one-to-one under |
Not specifically right now. But having a mixed representation seems more confusing to reason about and feels like fertile ground for such bugs.
hmm ok. Do you have an example of this? Maybe we already have this problem in the existing eager conversion code?
Hmm. I don't love this tradeoff. Though old macros can't even expand to old macrocalls in the existing system and have things generally work so I guess there's precedent in some sense 😅 |
Anywhere SyntaxTree uses a representation that is an invalid Expr ( |
Is the following what you're concerned about: macro blah()
v = Expr(:var, :x)
quote
@process_var $v
@process_var x
end
end
macro process_var(ex)
if ex isa Expr && ex.head == :var
"var node"
else
"something else"
end
end
julia> @macroexpand @blah
quote
#= REPL[11]:4 =#
"var node"
#= REPL[11]:5 =#
"something else"
endThis will indeed produce different results in new macro expansion if we blindly convert between The same thing can happen in reverse if a new macro produces an invalid |
|
Yup, that is the concern |
Right, it's a tricky one. I'd prefer to go fully one way or the other in terms of conversions. ie, there's a further option (C) to add to your list This should allow old macros to call new ones.
I found the following bug: julia> macro quote_expansion_unknown_expr()
inner_quote = Expr(:quote, Expr(:unknown_head, Expr(:$, :x)))
quote
x = :hi
$inner_quote
end
end
julia> @quote_expansion_unknown_expr
:($(Expr(:unknown_head, :hi)))
julia> using JuliaLowering
julia> JuliaLowering.include_string(Main, "@quote_expansion_unknown_expr")
SyntaxTree with attributes kind,value,name_val,syntax_flags,source,scope_layer
:($(Expr(:unknown_head, :($(Expr(:$, :x)))))) :: expr_syntax │
julia> JuliaLowering.include_string(Main, "@quote_expansion_unknown_expr", expr_compat_mode=true)
:($(Expr(:unknown_head, :($(Expr(:$, :x))))))In the current scheme, any function we call inside macro_expansion.jl needs to be prepared to accept
I still don't understand why everything inside an unknown head needs to become unknown itself. I know there's a few cases where nesting matters and I agree these could be problematic. But I think they might be rare enough that we could deal with them? |
|
That is indeed a bug; thanks for finding it!
I'm operating under the assumption that SyntaxTree is not meant to support arbitrary heads, and supporting this is an old-macro-compatibility specific thing, so this would be the easiest way to produce something that round-trips for the next macro to clean up. You're right that quote would be a problem.
I think it would be easy to make mostly correct, but nearly impossible to make fully correct (defining that as "semantics, if any, are preserved, and every Expr is representable as a SyntaxTree that can round-trip back to the same Expr"). Unknown heads are the easy case; the concerning case is heads known to one representation and not the other, or known to both with different expected structure.
Doesn't the last part mean what we do now (converting after each expansion so I'm happy with the idea of waiting until after macro expansion to convert so we can just assume everything is valid, though. Can we just have |
Currently, we have a pile of assertions in
expr_to_syntaxtreechecking thevalidity of the given Expr. This means JuliaLowering can't handle old macros
calling old or new macros with unrecognized Expr forms---in the "old calling
old" case, this is breakage, and in the "old calling new" case it's still a
downgrade to the macro system.
expr_to_syntaxtreehandles macro input andquoted expressions, so it can't assume anything about the AST structure.
This PR attempts to define
expr_to_syntaxtreeon all inputs in a sane way.eis one of the known forms with a different representation inSyntaxTree, convert it.
e.headcorresponds to a knownJuliaSyntax.Kind, insert thatand recurse on its children.
K"expr_syntax"node witheas its.value. We'llstill get an error as we did before if we try and evaluate this, but a future
expansion is expected to clean it up.
The assertions have been removed, but their contents are generally kept for
deciding between cases (1) and (2) above.