Skip to content

Commit 30e4960

Browse files
authored
Merge pull request #197 from Krastanov/juliasyntax1.10alt
misc fixes to `flatten`
2 parents d1937f9 + b829874 commit 30e4960

File tree

6 files changed

+110
-9
lines changed

6 files changed

+110
-9
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MacroTools"
22
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
3-
version = "0.5.10"
3+
version = "0.5.11"
44

55
[deps]
66
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"

src/utils.jl

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ See also: [`combinearg`](@ref)
435435
function splitarg(arg_expr)
436436
if @capture(arg_expr, arg_expr2_ = default_)
437437
# This assert will only be triggered if a `nothing` literal was somehow spliced into the Expr.
438-
# A regular `nothing` default value is a `Symbol` when it gets here. See #178
438+
# A regular `nothing` default value is a `Symbol` when it gets here. See #178
439439
@assert default !== nothing "splitarg cannot handle `nothing` as a default. Use a quoted `nothing` if possible. (MacroTools#35)"
440440
else
441441
arg_expr2 = arg_expr
@@ -458,16 +458,38 @@ function flatten1(ex)
458458
for x in ex.args
459459
isexpr(x, :block) ? append!(ex′.args, x.args) : push!(ex′.args, x)
460460
end
461-
# Don't use `unblock` to preserve line nos
461+
# Don't use `unblock` to preserve line nos.
462462
return length(ex′.args) == 1 ? ex′.args[1] : ex′
463463
end
464464

465+
# Helpers for flattening try blocks
466+
bflatten(x) = x
467+
function bflatten(x::Expr) # flatten down to a block (i.e. a `begin symbol end` is turned into `begin symbol end`, unlike `flatten` which would just return the symbol)
468+
fx = flatten(x)
469+
return isexpr(fx, :block) || !isexpr(x, :block) ? fx : Expr(:block,fx)
470+
end
471+
465472
"""
466473
flatten(ex)
467474
468475
Flatten any redundant blocks into a single block, over the whole expression.
469476
"""
470-
flatten(ex) = postwalk(flatten1, ex)
477+
function flatten end
478+
479+
flatten(x) = x
480+
function flatten(x::Expr)
481+
if isexpr(x, :try) # begin _ end can be turned to _ everywhere in julia _except_ in try blocks. See #196 for details
482+
3 <= length(x.args) <= 5 || error("Misformed `try` block.")
483+
isa(x.args[2], Symbol) || x.args[2] == false || error("Misformed `try` block.")
484+
return Expr(x.head, map(bflatten, x.args)...)
485+
# args[2] can be a symbol or false
486+
# args[3] can be a block (or false if there is no catch, which requires an else or finally)
487+
# args[4] can be a block (or false if there is no finally but there is an else) if it exists
488+
# args[5] can only be a block if it exists
489+
else
490+
return flatten1(Expr(x.head, map(flatten, x.args)...))
491+
end
492+
end
471493

472494
function makeif(clauses, els = nothing)
473495
foldr((c, ex)->:($(c[1]) ? $(c[2]) : $ex), clauses; init=els)

test/flatten_try.jl

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using MacroTools: flatten, striplines
2+
3+
4+
@testset "flatten try" begin # see julia#50710 and MacroTools#194
5+
# These tests were prompted due to the following two issues:
6+
# 1. on all Julia versions `flatten(striplines(:(try catch; false finally false end)))`
7+
# was completely breaking the try block due to turning `begin false end` into `false`
8+
# which have drastically different interpretations as 3rd or 4th argument of a try block.
9+
# 2. only on Julia 1.10, due to the new parser generating slightly more annotations (line number nodes)
10+
# `flatten(:(try f() catch end))` was turning into `Expr(:try, call, false, linenumbernode)`
11+
# instead of `Expr(:try, call, false, empty_block)`. Downstream consumers of the AST
12+
# were not expecting the line number node and were breaking, which is how this issue was
13+
# discovered.
14+
# The two issues have the same underlying cause: `begin expr end` was being turned into `expr`
15+
# which is valid everywhere in julia except in try blocks.
16+
# Notice that issue 1 is triggered only if one uses `striplines`. As such it was not really
17+
# triggered in the wild. However, issue 2 was seen with the slight modification to
18+
# parser annotations in Julia 1.10 which led to the discovery of issue 1.
19+
exs = [
20+
quote try; f(); catch; end; end,
21+
quote try; f(); catch; else; finally; end; end,
22+
quote try; f(); catch E; else; finally; end; end,
23+
quote try; f(); catch; finally; end; end,
24+
quote try; f(); catch E; finally; end; end,
25+
quote try; f(); catch E; 3+3; finally; 4+4; end; end,
26+
quote try; f(); catch E; 3+3; else; 2+2; finally; 4+4; end; end,
27+
quote try; f(); finally; end; end,
28+
quote try; f(); catch; false; finally; end; end,
29+
quote try; f(); catch; else; finally; false; end; end,
30+
quote try; f(); catch; else; end; end,
31+
quote try; f(); catch; 3+3; else; 2+2; end; end,
32+
quote try; f(); catch E; else; end; end,
33+
quote try; f(); catch E; 3+3; else; 2+2; end; end
34+
]
35+
for ex in exs
36+
#@show ex
37+
@test flatten(ex) |> striplines == ex |> striplines
38+
@test flatten(striplines(ex)) == striplines(ex).args[1]
39+
end
40+
@test 123 == eval(flatten(striplines(:(try error() catch; 123 finally end))))
41+
@test 123 == eval(flatten(:(try error() catch; 123 finally end)))
42+
@test 234 == eval(flatten(striplines(:(try 1+1 catch; false; else 234; finally end))))
43+
@test 234 == eval(flatten(:(try 1+1 catch; false; else 234; finally end)))
44+
for (exa, exb) in [
45+
(quote try; begin f(); g(); end; catch; end; end, quote try; f(); g(); catch; end; end),
46+
(quote try; catch; begin f(); g(); end; end; end, quote try; catch; f(); g(); end; end),
47+
(quote try; begin f(); g(); end; catch; finally; begin m(); n(); end; end; end, quote try; f(); g(); catch; finally; m(); n(); end; end)
48+
]
49+
@test exa |> flatten |> striplines == exb |> striplines
50+
@test exa |> striplines |> flatten == (exb |> striplines).args[1]
51+
end
52+
# unnatural expressions that can not be generated by the Julia parser, but still get accepted and we do not want to break
53+
for ex in [
54+
Expr(:try, 1, false, 2)
55+
Expr(:try, 1, false, false, false)
56+
]
57+
@test flatten(ex)==ex
58+
end
59+
end

test/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,8 @@ include("split.jl")
88
include("destruct.jl")
99
include("utils.jl")
1010

11+
if VERSION>=v"1.8" # because of new try/else syntax
12+
include("flatten_try.jl")
13+
end
14+
1115
end

test/split.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let
3636
args = splitdef(:(f(a::Int = 1) = 1))[:args]
3737
@test map(splitarg, args) == [(:a, :Int, false, 1)]
3838
args = splitdef(:(f(a::Int ... = 1) = 1))[:args]
39-
@test map(splitarg, args) == [(:a, :Int, true, 1)] # issue 165
39+
@test map(splitarg, args) == [(:a, :Int, true, 1)] # issue 165
4040

4141
@splitcombine foo(x) = x+2
4242
@test foo(10) == 12
@@ -88,7 +88,7 @@ end
8888
@testset "combinestructdef, splitstructdef" begin
8989
ex = :(struct S end)
9090
@test ex |> splitstructdef |> combinestructdef |> Base.remove_linenums! ==
91-
:(struct S <: Any end)
91+
:(struct S <: Any end) |> MacroTools.striplines
9292

9393
@test splitstructdef(ex) == Dict(
9494
:constructors => Any[],
@@ -101,7 +101,7 @@ end
101101
ex = :(mutable struct T end)
102102
@test splitstructdef(ex)[:mutable] === true
103103
@test ex |> splitstructdef |> combinestructdef |> Base.remove_linenums! ==
104-
:(mutable struct T <: Any end)
104+
:(mutable struct T <: Any end) |> MacroTools.striplines
105105

106106
ex = :(struct S{A,B} <: AbstractS{B}
107107
a::A
@@ -125,7 +125,7 @@ end
125125
constructors = splitstructdef(ex)[:constructors]
126126
@test length(constructors) == 1
127127
@test first(constructors) ==
128-
:((S(a::A) where A) = new{A}()) |> MacroTools.flatten
128+
:((S(a::A) where A) = new{A}()) |> MacroTools.striplines |> MacroTools.flatten
129129

130130
@test_throws ArgumentError splitstructdef(:(call_ex(arg)))
131131
end

test/utils.jl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using MacroTools: isdef
1+
using MacroTools: isdef, flatten, striplines
22

33
@testset "utils" begin
44
ex1 = :(function foo(a) return a; end)
@@ -23,3 +23,19 @@ using MacroTools: isdef
2323
ex10 = :(f(a::S, b::T)::Union{S,T} where {S,T} = rand() < 0.5 ? a : b)
2424
@test isdef(ex10)
2525
end
26+
27+
@testset "flatten" begin
28+
@test flatten(quote begin; begin; f(); g(); end; begin; h(); end; f(); end; end) |> striplines == quote f(); g(); h(); f() end |> striplines
29+
end
30+
31+
@testset "flatten try" begin # see julia#50710 and MacroTools#194 # only tests that do not include `else` -- for the full set of tests see flatten_try.jl
32+
exs = [
33+
quote try; f(); catch; end; end,
34+
quote try; f(); catch; finally; end; end,
35+
quote try; f(); catch E; finally; end; end,
36+
quote try; f(); catch E; 3+3; finally; 4+4; end; end,
37+
]
38+
for ex in exs
39+
@test flatten(ex) |> striplines == ex |> striplines
40+
end
41+
end

0 commit comments

Comments
 (0)