Skip to content

Commit

Permalink
fix #32775, fix #34023, issues showing kw and parameters exprs (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson authored and KristofferC committed Apr 11, 2020
1 parent 8d5a1a6 commit 697305f
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 57 deletions.
118 changes: 72 additions & 46 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,8 @@ function operator_associativity(s::Symbol)
return :left
end

is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head))
is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n
is_expr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && (ex.head === head)
is_expr(@nospecialize(ex), head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n

is_quoted(ex) = false
is_quoted(ex::QuoteNode) = true
Expand Down Expand Up @@ -991,7 +991,8 @@ function show_block(io::IO, head, arg, block, i::Int, quote_level::Int)
end

# show an indented list
function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::Int=0, enclose_operators::Bool=false)
function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::Int=0, enclose_operators::Bool=false,
kw::Bool=false)
n = length(items)
n == 0 && return
indent += indent_width
Expand All @@ -1004,20 +1005,27 @@ function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::In
(item isa Real && item < 0))) ||
(enclose_operators && item isa Symbol && isoperator(item))
parens && print(io, '(')
show_unquoted(io, item, indent, parens ? 0 : prec, quote_level)
if kw && is_expr(item, :kw, 2)
show_unquoted(io, Expr(:(=), item.args[1], item.args[2]), indent, parens ? 0 : prec, quote_level)
elseif kw && is_expr(item, :(=), 2)
show_unquoted_expr_fallback(io, item, indent, quote_level)
else
show_unquoted(io, item, indent, parens ? 0 : prec, quote_level)
end
parens && print(io, ')')
first = false
end
end
# show an indented list inside the parens (op, cl)
function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0, quote_level=0, encl_ops=false)
function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0, quote_level=0, encl_ops=false, kw::Bool=false)
print(io, op)
show_list(io, items, sep, indent, prec, quote_level, encl_ops)
show_list(io, items, sep, indent, prec, quote_level, encl_ops, kw)
print(io, cl)
end

# show a normal (non-operator) function call, e.g. f(x, y) or A[z]
function show_call(io::IO, head, func, func_args, indent, quote_level)
# kw: `=` expressions are parsed with head `kw` in this context
function show_call(io::IO, head, func, func_args, indent, quote_level, kw::Bool)
op, cl = expr_calls[head]
if (isa(func, Symbol) && func !== :(:) && !(head === :. && isoperator(func))) ||
(isa(func, Expr) && (func.head === :. || func.head === :curly || func.head === :macroname)) ||
Expand All @@ -1033,12 +1041,12 @@ function show_call(io::IO, head, func, func_args, indent, quote_level)
end
if !isempty(func_args) && isa(func_args[1], Expr) && func_args[1].head === :parameters
print(io, op)
show_list(io, func_args[2:end], ", ", indent, 0, quote_level)
show_list(io, func_args[2:end], ", ", indent, 0, quote_level, false, kw)
print(io, "; ")
show_list(io, func_args[1].args, ", ", indent, 0, quote_level)
show_list(io, func_args[1].args, ", ", indent, 0, quote_level, false, kw)
print(io, cl)
else
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level)
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level, false, kw)
end
end

Expand Down Expand Up @@ -1147,7 +1155,7 @@ function show_generator(io, ex, indent, quote_level)
end

function valid_import_path(@nospecialize ex)
return Meta.isexpr(ex, :(.)) && length((ex::Expr).args) > 0 && all(a->isa(a,Symbol), (ex::Expr).args)
return is_expr(ex, :(.)) && length((ex::Expr).args) > 0 && all(a->isa(a,Symbol), (ex::Expr).args)
end

function show_import_path(io::IO, ex, quote_level)
Expand Down Expand Up @@ -1194,6 +1202,22 @@ end
# as an ordinary symbol, which is true in indexing expressions.
const beginsym = gensym(:beginsym)

function show_unquoted_expr_fallback(io::IO, ex::Expr, indent::Int, quote_level::Int)
print(io, "\$(Expr(")
show(io, ex.head)
for arg in ex.args
print(io, ", ")
if isa(arg, Expr)
print(io, ":(")
show_unquoted(io, arg, indent, 0, quote_level+1)
print(io, ")")
else
show(io, arg)
end
end
print(io, "))")
end

# TODO: implement interpolated strings
function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::Int = 0)
head, args, nargs = ex.head, ex.args, length(ex.args)
Expand All @@ -1204,7 +1228,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
item = args[1]
# field
field = unquoted(args[2])
parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) && !Meta.isexpr(item, :(.))
parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) && !is_expr(item, :(.))
parens && print(io, '(')
show_unquoted(io, item, indent, 0, quote_level)
parens && print(io, ')')
Expand All @@ -1231,8 +1255,27 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
show_list(io, args, head_, indent, func_prec, quote_level, true)
end

# list (i.e. "(1, 2, 3)" or "[1, 2, 3]")
elseif haskey(expr_parens, head) || # :tuple/:vcat
elseif head === :tuple
print(io, "(")
if nargs > 0 && is_expr(args[1], :parameters)
if nargs == 1 && isempty(args[1].args)
# TODO: for now avoid printing (;)
show_unquoted_expr_fallback(io, args[1], indent, quote_level)
print(io, ',')
else
show_list(io, args[2:end], ", ", indent, 0, quote_level)
nargs == 2 && print(io, ',')
print(io, "; ")
show_list(io, args[1].args, ", ", indent, 0, quote_level, false, true)
end
else
show_list(io, args, ", ", indent, 0, quote_level)
nargs == 1 && print(io, ',')
end
print(io, ")")

# list-like forms, e.g. "[1, 2, 3]"
elseif haskey(expr_parens, head) || # :vcat etc.
head === :typed_vcat || head === :typed_hcat
# print the type and defer to the untyped case
if head === :typed_vcat || head === :typed_hcat
Expand All @@ -1255,12 +1298,8 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
end
head !== :row && print(io, op)
show_list(io, args, sep, indent, 0, quote_level)
if nargs == 1
if head === :tuple
print(io, ',')
elseif head === :vcat
print(io, ';')
end
if nargs == 1 && head === :vcat
print(io, ';')
end
head !== :row && print(io, cl)

Expand All @@ -1274,8 +1313,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
end
func_args = args[2:end]

# :kw exprs are only parsed inside parenthesized calls
if any(a->is_expr(a, :kw), func_args)
show_call(io, head, func, func_args, indent, quote_level, true)

# scalar multiplication (i.e. "100x")
if (func === :* &&
elseif (func === :* &&
length(func_args)==2 && isa(func_args[1], Real) && isa(func_args[2], Symbol))
if func_prec <= prec
show_enclosed_list(io, '(', func_args, "", ')', indent, func_prec, quote_level)
Expand Down Expand Up @@ -1313,12 +1356,12 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
print(io, ")")
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level)
else
show_call(io, head, func, func_args, indent, quote_level)
show_call(io, head, func, func_args, indent, quote_level, true)
end

# normal function (i.e. "f(x,y)")
else
show_call(io, head, func, func_args, indent, quote_level)
show_call(io, head, func, func_args, indent, quote_level, true)
end

# new expr
Expand All @@ -1328,7 +1371,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
# other call-like expressions ("A[1,2]", "T{X,Y}", "f.(X,Y)")
elseif haskey(expr_calls, head) && nargs >= 1 # :ref/:curly/:calldecl/:(.)
funcargslike = head === :(.) ? args[2].args : args[2:end]
show_call(head == :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level)
show_call(head == :ref ? IOContext(io, beginsym=>true) : io, head, args[1], funcargslike, indent, quote_level, head !== :curly)

# comprehensions
elseif head === :typed_comprehension && nargs == 2
Expand Down Expand Up @@ -1386,7 +1429,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In

# block with argument
elseif head in (:for,:while,:function,:macro,:if,:elseif,:let) && nargs==2
if Meta.isexpr(args[2], :block)
if is_expr(args[2], :block)
show_block(IOContext(io, beginsym=>false), head, args[1], args[2], indent, quote_level)
else
show_block(IOContext(io, beginsym=>false), head, args[1], Expr(:block, args[2]), indent, quote_level)
Expand Down Expand Up @@ -1478,7 +1521,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
# prec=-1 and hide the line number argument from the argument list
mname = allow_macroname(args[1])
if prec >= 0
show_call(io, :call, mname, args[3:end], indent, quote_level)
show_call(io, :call, mname, args[3:end], indent, quote_level, false)
else
show_args = Vector{Any}(undef, nargs - 1)
show_args[1] = mname
Expand Down Expand Up @@ -1552,7 +1595,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In

elseif head === :quote && nargs == 1 && isa(args[1], Symbol)
show_unquoted_quote_expr(IOContext(io, beginsym=>false), args[1]::Symbol, indent, 0, quote_level+1)
elseif head === :quote && nargs == 1 && Meta.isexpr(args[1], :block)
elseif head === :quote && nargs == 1 && is_expr(args[1], :block)
show_block(IOContext(io, beginsym=>false), "quote", Expr(:quote, args[1].args...), indent,
quote_level+1)
print(io, "end")
Expand All @@ -1576,11 +1619,6 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
elseif head === :null
print(io, "nothing")

elseif head === :kw && nargs == 2
show_unquoted(io, args[1], indent+indent_width, 0, quote_level)
print(io, '=')
show_unquoted(io, args[2], indent+indent_width, 0, quote_level)

elseif head === :string
print(io, '"')
for x in args
Expand Down Expand Up @@ -1642,7 +1680,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In

elseif (head === :import || head === :using) &&
((nargs == 1 && (valid_import_path(args[1]) ||
(Meta.isexpr(args[1], :(:)) &&
(is_expr(args[1], :(:)) &&
length((args[1]::Expr).args) > 1 &&
all(valid_import_path, (args[1]::Expr).args)))) ||
all(valid_import_path, args))
Expand All @@ -1667,19 +1705,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
unhandled = true
end
if unhandled
print(io, "\$(Expr(")
show(io, ex.head)
for arg in args
print(io, ", ")
if isa(arg, Expr)
print(io, ":(")
show_unquoted(io, arg, indent, 0, quote_level+1)
print(io, ")")
else
show(io, arg)
end
end
print(io, "))")
show_unquoted_expr_fallback(io, ex, indent, quote_level)
end
nothing
end
Expand Down
5 changes: 1 addition & 4 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1684,10 +1684,7 @@
(and (pair? lst) (pair? (car lst)) (eq? (caar lst) 'parameters)))

(define (to-kws lst)
(map (lambda (x) (if (assignment? x)
`(kw ,@(cdr x))
x))
lst))
(map =-to-kw lst))

;; like parse-arglist, but with `for` parsed as a generator
(define (parse-call-arglist s closer)
Expand Down
4 changes: 2 additions & 2 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ Test Broken
julia> @test_broken 1 == 2 atol=0.1
Test Broken
Expression: ==(1, 2, atol=0.1)
Expression: ==(1, 2, atol = 0.1)
```
"""
macro test_broken(ex, kws...)
Expand Down Expand Up @@ -392,7 +392,7 @@ Test Broken
julia> @test_skip 1 == 2 atol=0.1
Test Broken
Skipped: ==(1, 2, atol=0.1)
Skipped: ==(1, 2, atol = 0.1)
```
"""
macro test_skip(ex, kws...)
Expand Down
10 changes: 5 additions & 5 deletions stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,18 @@ let fails = @testset NoThrowTestSet begin
end

let str = sprint(show, fails[11])
@test occursin("Expression: isapprox(1 / 2, 2 / 1, atol=1 / 1)", str)
@test occursin("Evaluated: isapprox(0.5, 2.0; atol=1.0)", str)
@test occursin("Expression: isapprox(1 / 2, 2 / 1, atol = 1 / 1)", str)
@test occursin("Evaluated: isapprox(0.5, 2.0; atol = 1.0)", str)
end

let str = sprint(show, fails[12])
@test occursin("Expression: isapprox(1 - 2, 2 - 1; atol=1 - 1)", str)
@test occursin("Evaluated: isapprox(-1, 1; atol=0)", str)
@test occursin("Expression: isapprox(1 - 2, 2 - 1; atol = 1 - 1)", str)
@test occursin("Evaluated: isapprox(-1, 1; atol = 0)", str)
end

let str = sprint(show, fails[13])
@test occursin("Expression: isapprox(1, 2; k...)", str)
@test occursin("Evaluated: isapprox(1, 2; atol=0, nans=true)", str)
@test occursin("Evaluated: isapprox(1, 2; atol = 0, nans = true)", str)
end

let str = sprint(show, fails[14])
Expand Down
30 changes: 30 additions & 0 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ end
@test_repr "import ..A: a, x, y.z"
@test_repr "import A.B, C.D"

# keyword args (issue #34023 and #32775)
@test_repr "f(a, b=c)"
@test_repr "f(a, b! = c)"
@test_repr "T{x=1}"
@test_repr "[a=1]"
@test_repr "a[x=1]"
@test_repr "f(; a=1)"
@test_repr "f(b=2; a=1)"
@test_repr "@f(1, y=3)"
@test_repr "n + (x=1)"
@test_repr "(;x=1)"
@test_repr "(x,;x=1)"
@test_repr "(a=1,;x=1)"
@test_repr "(a=1,b=2;x=1,y,:z=>2)"
@test repr(:((a,;b))) == ":((a,; b))"
@test repr(:((a=1,;x=2))) == ":((a = 1,; x = 2))"
@test repr(:((a=1,3;x=2))) == ":((a = 1, 3; x = 2))"
@test repr(:(g(a,; b))) == ":(g(a; b))"
for ex in [Expr(:call, :f, Expr(:(=), :x, 1)),
Expr(:ref, :f, Expr(:(=), :x, 1)),
Expr(:vect, 1, 2, Expr(:kw, :x, 1)),
Expr(:kw, :a, :b),
Expr(:curly, :T, Expr(:kw, :x, 1)),
Expr(:call, :+, :n, Expr(:kw, :x, 1)),
:((a=1,; $(Expr(:(=), :x, 2)))),
:(($(Expr(:(=), :a, 1)),; x = 2)),
Expr(:tuple, Expr(:parameters))]
@test eval(Meta.parse(repr(ex))) == ex
end

@test repr(Expr(:using, :Foo)) == ":(\$(Expr(:using, :Foo)))"
@test repr(Expr(:using, Expr(:(.), ))) == ":(\$(Expr(:using, :(\$(Expr(:.))))))"
@test repr(Expr(:import, :Foo)) == ":(\$(Expr(:import, :Foo)))"
Expand Down

0 comments on commit 697305f

Please sign in to comment.