Skip to content

Commit ad6752b

Browse files
authored
fix #32775, fix #34023, issues showing kw and parameters exprs (#34038)
1 parent 4470c94 commit ad6752b

File tree

5 files changed

+110
-57
lines changed

5 files changed

+110
-57
lines changed

base/show.jl

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -934,8 +934,8 @@ function operator_associativity(s::Symbol)
934934
return :left
935935
end
936936

937-
is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head))
938-
is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n
937+
is_expr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && (ex.head === head)
938+
is_expr(@nospecialize(ex), head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n
939939

940940
is_quoted(ex) = false
941941
is_quoted(ex::QuoteNode) = true
@@ -991,7 +991,8 @@ function show_block(io::IO, head, arg, block, i::Int, quote_level::Int)
991991
end
992992

993993
# show an indented list
994-
function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::Int=0, enclose_operators::Bool=false)
994+
function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::Int=0, enclose_operators::Bool=false,
995+
kw::Bool=false)
995996
n = length(items)
996997
n == 0 && return
997998
indent += indent_width
@@ -1004,20 +1005,27 @@ function show_list(io::IO, items, sep, indent::Int, prec::Int=0, quote_level::In
10041005
(item isa Real && item < 0))) ||
10051006
(enclose_operators && item isa Symbol && isoperator(item))
10061007
parens && print(io, '(')
1007-
show_unquoted(io, item, indent, parens ? 0 : prec, quote_level)
1008+
if kw && is_expr(item, :kw, 2)
1009+
show_unquoted(io, Expr(:(=), item.args[1], item.args[2]), indent, parens ? 0 : prec, quote_level)
1010+
elseif kw && is_expr(item, :(=), 2)
1011+
show_unquoted_expr_fallback(io, item, indent, quote_level)
1012+
else
1013+
show_unquoted(io, item, indent, parens ? 0 : prec, quote_level)
1014+
end
10081015
parens && print(io, ')')
10091016
first = false
10101017
end
10111018
end
10121019
# show an indented list inside the parens (op, cl)
1013-
function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0, quote_level=0, encl_ops=false)
1020+
function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0, quote_level=0, encl_ops=false, kw::Bool=false)
10141021
print(io, op)
1015-
show_list(io, items, sep, indent, prec, quote_level, encl_ops)
1022+
show_list(io, items, sep, indent, prec, quote_level, encl_ops, kw)
10161023
print(io, cl)
10171024
end
10181025

10191026
# show a normal (non-operator) function call, e.g. f(x, y) or A[z]
1020-
function show_call(io::IO, head, func, func_args, indent, quote_level)
1027+
# kw: `=` expressions are parsed with head `kw` in this context
1028+
function show_call(io::IO, head, func, func_args, indent, quote_level, kw::Bool)
10211029
op, cl = expr_calls[head]
10221030
if (isa(func, Symbol) && func !== :(:) && !(head === :. && isoperator(func))) ||
10231031
(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)
10331041
end
10341042
if !isempty(func_args) && isa(func_args[1], Expr) && func_args[1].head === :parameters
10351043
print(io, op)
1036-
show_list(io, func_args[2:end], ", ", indent, 0, quote_level)
1044+
show_list(io, func_args[2:end], ", ", indent, 0, quote_level, false, kw)
10371045
print(io, "; ")
1038-
show_list(io, func_args[1].args, ", ", indent, 0, quote_level)
1046+
show_list(io, func_args[1].args, ", ", indent, 0, quote_level, false, kw)
10391047
print(io, cl)
10401048
else
1041-
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level)
1049+
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level, false, kw)
10421050
end
10431051
end
10441052

@@ -1147,7 +1155,7 @@ function show_generator(io, ex, indent, quote_level)
11471155
end
11481156

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

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

1205+
function show_unquoted_expr_fallback(io::IO, ex::Expr, indent::Int, quote_level::Int)
1206+
print(io, "\$(Expr(")
1207+
show(io, ex.head)
1208+
for arg in ex.args
1209+
print(io, ", ")
1210+
if isa(arg, Expr)
1211+
print(io, ":(")
1212+
show_unquoted(io, arg, indent, 0, quote_level+1)
1213+
print(io, ")")
1214+
else
1215+
show(io, arg)
1216+
end
1217+
end
1218+
print(io, "))")
1219+
end
1220+
11971221
# TODO: implement interpolated strings
11981222
function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::Int = 0)
11991223
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
12041228
item = args[1]
12051229
# field
12061230
field = unquoted(args[2])
1207-
parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) && !Meta.isexpr(item, :(.))
1231+
parens = !is_quoted(item) && !(item isa Symbol && isidentifier(item)) && !is_expr(item, :(.))
12081232
parens && print(io, '(')
12091233
show_unquoted(io, item, indent, 0, quote_level)
12101234
parens && print(io, ')')
@@ -1231,8 +1255,27 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
12311255
show_list(io, args, head_, indent, func_prec, quote_level, true)
12321256
end
12331257

1234-
# list (i.e. "(1, 2, 3)" or "[1, 2, 3]")
1235-
elseif haskey(expr_parens, head) || # :tuple/:vcat
1258+
elseif head === :tuple
1259+
print(io, "(")
1260+
if nargs > 0 && is_expr(args[1], :parameters)
1261+
if nargs == 1 && isempty(args[1].args)
1262+
# TODO: for now avoid printing (;)
1263+
show_unquoted_expr_fallback(io, args[1], indent, quote_level)
1264+
print(io, ',')
1265+
else
1266+
show_list(io, args[2:end], ", ", indent, 0, quote_level)
1267+
nargs == 2 && print(io, ',')
1268+
print(io, "; ")
1269+
show_list(io, args[1].args, ", ", indent, 0, quote_level, false, true)
1270+
end
1271+
else
1272+
show_list(io, args, ", ", indent, 0, quote_level)
1273+
nargs == 1 && print(io, ',')
1274+
end
1275+
print(io, ")")
1276+
1277+
# list-like forms, e.g. "[1, 2, 3]"
1278+
elseif haskey(expr_parens, head) || # :vcat etc.
12361279
head === :typed_vcat || head === :typed_hcat
12371280
# print the type and defer to the untyped case
12381281
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
12551298
end
12561299
head !== :row && print(io, op)
12571300
show_list(io, args, sep, indent, 0, quote_level)
1258-
if nargs == 1
1259-
if head === :tuple
1260-
print(io, ',')
1261-
elseif head === :vcat
1262-
print(io, ';')
1263-
end
1301+
if nargs == 1 && head === :vcat
1302+
print(io, ';')
12641303
end
12651304
head !== :row && print(io, cl)
12661305

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

1316+
# :kw exprs are only parsed inside parenthesized calls
1317+
if any(a->is_expr(a, :kw), func_args)
1318+
show_call(io, head, func, func_args, indent, quote_level, true)
1319+
12771320
# scalar multiplication (i.e. "100x")
1278-
if (func === :* &&
1321+
elseif (func === :* &&
12791322
length(func_args)==2 && isa(func_args[1], Real) && isa(func_args[2], Symbol))
12801323
if func_prec <= prec
12811324
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
13131356
print(io, ")")
13141357
show_enclosed_list(io, op, func_args, ", ", cl, indent, 0, quote_level)
13151358
else
1316-
show_call(io, head, func, func_args, indent, quote_level)
1359+
show_call(io, head, func, func_args, indent, quote_level, true)
13171360
end
13181361

13191362
# normal function (i.e. "f(x,y)")
13201363
else
1321-
show_call(io, head, func, func_args, indent, quote_level)
1364+
show_call(io, head, func, func_args, indent, quote_level, true)
13221365
end
13231366

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

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

13871430
# block with argument
13881431
elseif head in (:for,:while,:function,:macro,:if,:elseif,:let) && nargs==2
1389-
if Meta.isexpr(args[2], :block)
1432+
if is_expr(args[2], :block)
13901433
show_block(IOContext(io, beginsym=>false), head, args[1], args[2], indent, quote_level)
13911434
else
13921435
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
14781521
# prec=-1 and hide the line number argument from the argument list
14791522
mname = allow_macroname(args[1])
14801523
if prec >= 0
1481-
show_call(io, :call, mname, args[3:end], indent, quote_level)
1524+
show_call(io, :call, mname, args[3:end], indent, quote_level, false)
14821525
else
14831526
show_args = Vector{Any}(undef, nargs - 1)
14841527
show_args[1] = mname
@@ -1552,7 +1595,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
15521595

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

1579-
elseif head === :kw && nargs == 2
1580-
show_unquoted(io, args[1], indent+indent_width, 0, quote_level)
1581-
print(io, '=')
1582-
show_unquoted(io, args[2], indent+indent_width, 0, quote_level)
1583-
15841622
elseif head === :string
15851623
print(io, '"')
15861624
for x in args
@@ -1642,7 +1680,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
16421680

16431681
elseif (head === :import || head === :using) &&
16441682
((nargs == 1 && (valid_import_path(args[1]) ||
1645-
(Meta.isexpr(args[1], :(:)) &&
1683+
(is_expr(args[1], :(:)) &&
16461684
length((args[1]::Expr).args) > 1 &&
16471685
all(valid_import_path, (args[1]::Expr).args)))) ||
16481686
all(valid_import_path, args))
@@ -1667,19 +1705,7 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int, quote_level::In
16671705
unhandled = true
16681706
end
16691707
if unhandled
1670-
print(io, "\$(Expr(")
1671-
show(io, ex.head)
1672-
for arg in args
1673-
print(io, ", ")
1674-
if isa(arg, Expr)
1675-
print(io, ":(")
1676-
show_unquoted(io, arg, indent, 0, quote_level+1)
1677-
print(io, ")")
1678-
else
1679-
show(io, arg)
1680-
end
1681-
end
1682-
print(io, "))")
1708+
show_unquoted_expr_fallback(io, ex, indent, quote_level)
16831709
end
16841710
nothing
16851711
end

src/julia-parser.scm

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,10 +1684,7 @@
16841684
(and (pair? lst) (pair? (car lst)) (eq? (caar lst) 'parameters)))
16851685

16861686
(define (to-kws lst)
1687-
(map (lambda (x) (if (assignment? x)
1688-
`(kw ,@(cdr x))
1689-
x))
1690-
lst))
1687+
(map =-to-kw lst))
16911688

16921689
;; like parse-arglist, but with `for` parsed as a generator
16931690
(define (parse-call-arglist s closer)

stdlib/Test/src/Test.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ Test Broken
363363
364364
julia> @test_broken 1 == 2 atol=0.1
365365
Test Broken
366-
Expression: ==(1, 2, atol=0.1)
366+
Expression: ==(1, 2, atol = 0.1)
367367
```
368368
"""
369369
macro test_broken(ex, kws...)
@@ -392,7 +392,7 @@ Test Broken
392392
393393
julia> @test_skip 1 == 2 atol=0.1
394394
Test Broken
395-
Skipped: ==(1, 2, atol=0.1)
395+
Skipped: ==(1, 2, atol = 0.1)
396396
```
397397
"""
398398
macro test_skip(ex, kws...)

stdlib/Test/test/runtests.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,18 +183,18 @@ let fails = @testset NoThrowTestSet begin
183183
end
184184

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

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

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

200200
let str = sprint(show, fails[14])

test/show.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,36 @@ end
202202
@test_repr "import ..A: a, x, y.z"
203203
@test_repr "import A.B, C.D"
204204

205+
# keyword args (issue #34023 and #32775)
206+
@test_repr "f(a, b=c)"
207+
@test_repr "f(a, b! = c)"
208+
@test_repr "T{x=1}"
209+
@test_repr "[a=1]"
210+
@test_repr "a[x=1]"
211+
@test_repr "f(; a=1)"
212+
@test_repr "f(b=2; a=1)"
213+
@test_repr "@f(1, y=3)"
214+
@test_repr "n + (x=1)"
215+
@test_repr "(;x=1)"
216+
@test_repr "(x,;x=1)"
217+
@test_repr "(a=1,;x=1)"
218+
@test_repr "(a=1,b=2;x=1,y,:z=>2)"
219+
@test repr(:((a,;b))) == ":((a,; b))"
220+
@test repr(:((a=1,;x=2))) == ":((a = 1,; x = 2))"
221+
@test repr(:((a=1,3;x=2))) == ":((a = 1, 3; x = 2))"
222+
@test repr(:(g(a,; b))) == ":(g(a; b))"
223+
for ex in [Expr(:call, :f, Expr(:(=), :x, 1)),
224+
Expr(:ref, :f, Expr(:(=), :x, 1)),
225+
Expr(:vect, 1, 2, Expr(:kw, :x, 1)),
226+
Expr(:kw, :a, :b),
227+
Expr(:curly, :T, Expr(:kw, :x, 1)),
228+
Expr(:call, :+, :n, Expr(:kw, :x, 1)),
229+
:((a=1,; $(Expr(:(=), :x, 2)))),
230+
:(($(Expr(:(=), :a, 1)),; x = 2)),
231+
Expr(:tuple, Expr(:parameters))]
232+
@test eval(Meta.parse(repr(ex))) == ex
233+
end
234+
205235
@test repr(Expr(:using, :Foo)) == ":(\$(Expr(:using, :Foo)))"
206236
@test repr(Expr(:using, Expr(:(.), ))) == ":(\$(Expr(:using, :(\$(Expr(:.))))))"
207237
@test repr(Expr(:import, :Foo)) == ":(\$(Expr(:import, :Foo)))"

0 commit comments

Comments
 (0)