Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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