-
-
Notifications
You must be signed in to change notification settings - Fork 80
Fix anonymous function edge cases #210
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
Conversation
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.
Great PR, thanks!
I'm at work, so sorry that I cannot investigate it in depth, but julia> ff = eval( longdef(:((a::Int; b=2) -> a + b)))
#26 (generic function with 1 method)
julia> ff(2, b=11)
13
julia> longdef(:((a::Int; b=2) -> a + b))
:(function a::Int, #= REPL[32]:1 =#, b = 2 # weird
#= REPL[32]:1 =#
a + b
end) While @assert(@capture(longdef1(fdef),
function (fcall_ | fcall_) body_ end),
"Not a function definition: $fdef") instead of If you're in a rush, I'll be happy to oblige and merge as is, though. Would you prefer that? This PR is a clear improvement. |
I think given that these are currently not parsed correctly at all, it’s probably better to go with it to fix the bugs, and then polish things later. What do you think? |
I'm a bit torn; if the current But if I'll try to merge either this PR or some other fix by tomorrow. |
I found another issue in On master: julia> dump(longdef(quote; (a::Int; b=2) -> a; end))
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol block
args: Array{Any}((3,))
1: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: LineNumberNode
line: Int64 1
file: Symbol REPL[2]
3: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[2]
2: Symbol a On this PR (now): julia> dump(longdef(quote; (a::Int; b=2) -> a; end))
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Expr
head: Symbol parameters
args: Array{Any}((1,))
1: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[1]
2: Symbol a We can compare this to the expected result: julia> :(function (a::Int; b=2); a; end) |> dump
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Expr
head: Symbol parameters
args: Array{Any}((1,))
1: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: Expr
head: Symbol block
args: Array{Any}((3,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[3]
2: LineNumberNode
line: Int64 1
file: Symbol REPL[3]
3: Symbol a so we see it's now closer to the true AST. In all honesty I have no idea why the master branch output evaluates... If you copy-paste the julia> longdef(quote; (a::Int; b=2) -> a; end)
:(function a::Int, #= REPL[1]:1 =#, b = 2
#= REPL[1]:1 =#
a
end)
julia> function a::Int, #= REPL[1]:1 =#, b = 2
#= REPL[1]:1 =#
a
end
ERROR: ParseError:
# Error @ REPL[2]:1:10
function a::Int, #= REPL[1]:1 =#, b = 2
# ╙ ── Invalid signature in function definition
Stacktrace:
[1] top-level scope
@ none:1 So I really don't know why it evaluates at all. Even if it evaluates somehow, I think it's not safe. It doesn't even print correctly. So the fact it works now might not imply it continues to work in the future, because the printing clearly isn't set up for it. |
I would tend to agree with Sukera's post, that it's a printing problem, but the expression is fundamentally correct Julia. MacroTools is a fundamental package, and the expression returned from |
That is a fair point. But I would push back and make the point that: if Julia itself doesn't correctly print the output of longdef for this subtle parsing edgecase (which results in bugs #209 and #185), then there is an extremely low probability that someone's code is correctly handling this. Worst case, it does, it gets reported, and then just revert the change and make it a breaking release. But I think it's better to worry about known and reported bugs (#209 #185) than theoretical low-probability bugs due to someone possibly making use of an incorrect output that even Julia doesn't handle. Note that this julia> longdef(:((a::Int; b=2) -> a+b+c)) |> dump
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol block
args: Array{Any}((3,))
1: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: LineNumberNode
line: Int64 1
file: Symbol REPL[14]
3: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[14]
2: Expr
head: Symbol call
args: Array{Any}((4,))
1: Symbol +
2: Symbol a
3: Symbol b
4: Symbol c
julia> longdef(:((a::Int; b=2, c=3) -> a+b+c)) |> dump
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Expr
head: Symbol parameters
args: Array{Any}((2,))
1: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol c
2: Int64 3
2: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[13]
2: Expr
head: Symbol call
args: Array{Any}((4,))
1: Symbol +
2: Symbol a
3: Symbol b
4: Symbol c So only if someone is handling the single arg case for This PR normalizes the format to the julia> longdef(:((a::Int; b=2) -> a+b+c)) |> dump
Expr
head: Symbol function
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Expr
head: Symbol parameters
args: Array{Any}((1,))
1: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol b
2: Int64 2
2: Expr
head: Symbol ::
args: Array{Any}((2,))
1: Symbol a
2: Symbol Int
2: Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 1
file: Symbol REPL[1]
2: Expr
head: Symbol call
args: Array{Any}((4,))
1: Symbol +
2: Symbol a
3: Symbol b
4: Symbol c This is the form that actually matches what Julia outputs: julia> longdef(:((a::Int; b=2) -> a+b+c))
:(function (a::Int,; b = 2)
#= REPL[1]:1 =#
a + b + c
end)
julia> :(function (a::Int; b=2)
a+b+c
end)
:(function (a::Int,; b = 2)
#= REPL[2]:1 =#
#= REPL[2]:2 =#
a + b + c
end) |
Thinking more about this, I'm assuming it's actually more often that people will assume the second Especially because if you look at the AST for |
Those are good arguments, I'm sold. If |
This fixes a few classes of anonymous functions that could not be parsed before. @cstjean do you think you could review this? I really need this patch to address some major bugs for BorrowChecker.jl and DispatchDoctor.jl, which both depend on MacroTools.jl for function parsing.
This Fixes #209 Fixes #185
More notes
Here are some functions that could not be parsed with
splitdef
before this change, but now work:(a::Int; b=2) -> 1
(args...) -> 1
(arg; kws...) -> 1
All of these functions hit the error
but now they can parse fine:
I've verified 100% test coverage of the code diff. I've also verified all the existing tests pass.
PR Checklist