diff --git a/base/show.jl b/base/show.jl index bf607554cddfe..a2eb5664ab223 100644 --- a/base/show.jl +++ b/base/show.jl @@ -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 @@ -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 @@ -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)) || @@ -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 @@ -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) @@ -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) @@ -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, ')') @@ -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 @@ -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) @@ -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) @@ -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 @@ -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 @@ -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) @@ -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 @@ -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") @@ -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 @@ -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)) @@ -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 diff --git a/src/julia-parser.scm b/src/julia-parser.scm index f4d661bddac6f..5172748c55d09 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -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) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 8c515b3330532..9d556b9186c7e 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -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...) @@ -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...) diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index a2bdeab7f11e8..10ed2ea165cb1 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -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]) diff --git a/test/show.jl b/test/show.jl index f128f2c21778f..e89f745454398 100644 --- a/test/show.jl +++ b/test/show.jl @@ -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)))"