diff --git a/src/utils.jl b/src/utils.jl index 357236a..a562c48 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,5 +1,5 @@ export @esc, isexpr, isline, iscall, rmlines, unblock, block, inexpr, namify, isdef, - longdef, shortdef, @expand, makeif, prettify, combinedef, splitdef, splitarg + longdef, shortdef, @expand, makeif, prettify, combinedef, splitdef, splitarg, combinearg """ assoc!(d, k, v) @@ -406,7 +406,10 @@ end `combinearg` is the inverse of [`splitarg`](@ref). """ function combinearg(arg_name, arg_type, is_splat, default) - a = arg_name===nothing ? :(::$arg_type) : :($arg_name::$arg_type) + @assert arg_name !== nothing || arg_type !== nothing + a = arg_name===nothing ? :(::$arg_type) : + arg_type==:Any && is_splat ? arg_name : # see #177 and julia#43625 + :($arg_name::$arg_type) a2 = is_splat ? Expr(:..., a) : a return default === nothing ? a2 : Expr(:kw, a2, default) end @@ -430,19 +433,21 @@ julia> map(splitarg, (:(f(a=2, x::Int=nothing, y, args...))).args[2:end]) See also: [`combinearg`](@ref) """ function splitarg(arg_expr) - splitvar(arg) = - (@match arg begin - ::T_ => (nothing, T) - name_::T_ => (name, T) - x_ => (x, :Any) - end)::NTuple{2,Any} # the pattern `x_` matches any expression - (is_splat = @capture(arg_expr, arg_expr2_...)) || (arg_expr2 = arg_expr) - if @capture(arg_expr2, arg_ = default_) - @assert default !== nothing "splitarg cannot handle `nothing` as a default. Use a quoted `nothing` if possible. (MacroTools#35)" - return (splitvar(arg)..., is_splat, default) + if @capture(arg_expr, arg_expr2_ = default_) + # This assert will only be triggered if a `nothing` literal was somehow spliced into the Expr. + # A regular `nothing` default value is a `Symbol` when it gets here. See #178 + @assert default !== nothing "splitarg cannot handle `nothing` as a default. Use a quoted `nothing` if possible. (MacroTools#35)" else - return (splitvar(arg_expr2)..., is_splat, nothing) + arg_expr2 = arg_expr end + is_splat = @capture(arg_expr2, arg_expr3_...) + is_splat || (arg_expr3 = arg_expr2) + (arg_name, arg_type) = (@match arg_expr3 begin + ::T_ => (nothing, T) + name_::T_ => (name, T) + x_ => (x, :Any) + end)::NTuple{2,Any} # the pattern `x_` matches any expression + return (arg_name, arg_type, is_splat, default) end diff --git a/test/split.jl b/test/split.jl index 3e56b79..9fc7801 100644 --- a/test/split.jl +++ b/test/split.jl @@ -6,6 +6,8 @@ end macro splitcombine(fundef) # should be a no-op dict = splitdef(fundef) + dict[:args] = map(arg->combinearg(splitarg(arg)...), dict[:args]) + dict[:kwargs] = map(arg->combinearg(splitarg(arg)...), dict[:kwargs]) esc(MacroTools.combinedef(dict)) end @@ -24,10 +26,17 @@ let @test longdef(:(f(x)::Int = 10)).head == :function @test longdef(:(f(x::T) where U where T = 2)).head == :function @test shortdef(:(function f(x)::Int 10 end)).head != :function - @test map(splitarg, (:(f(a=2, x::Int=nothing, y, args...))).args[2:end]) == + @test map(splitarg, (:(f(a=2, x::Int=nothing, y::Any, args...))).args[2:end]) == [(:a, :Any, false, 2), (:x, :Int, false, :nothing), (:y, :Any, false, nothing), (:args, :Any, true, nothing)] @test splitarg(:(::Int)) == (nothing, :Int, false, nothing) + kwargs = splitdef(:(f(; a::Int = 1, b...) = 1))[:kwargs] + @test map(splitarg, kwargs) == + [(:a, :Int, false, 1), (:b, :Any, true, nothing)] + args = splitdef(:(f(a::Int = 1) = 1))[:args] + @test map(splitarg, args) == [(:a, :Int, false, 1)] + args = splitdef(:(f(a::Int ... = 1) = 1))[:args] + @test map(splitarg, args) == [(:a, :Int, true, 1)] # issue 165 @splitcombine foo(x) = x+2 @test foo(10) == 12 @@ -47,6 +56,12 @@ let @splitcombine fmacro1() = @onearg 1 @test fmacro1() == 2 + @splitcombine bar(; a::Int = 1, b...) = 2 + @test bar(a=3, x = 1, y = 2) == 2 + @splitcombine qux(a::Int... = 0) = sum(a) + @test qux(1, 2, 3) == 6 + @test qux() == 0 + struct Foo{A, B} a::A b::B