diff --git a/Project.toml b/Project.toml index 7d93d67..12320aa 100644 --- a/Project.toml +++ b/Project.toml @@ -4,11 +4,13 @@ version = "0.5.3" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [compat] DataStructures = "0.17" +ExprTools = "0.1" julia = "1" [extras] diff --git a/src/MacroTools.jl b/src/MacroTools.jl index fc089e7..a7ce2b0 100644 --- a/src/MacroTools.jl +++ b/src/MacroTools.jl @@ -1,7 +1,9 @@ module MacroTools using DataStructures, Markdown, Random -export @match, @capture +using ExprTools: splitdef, combinedef + +export @match, @capture, splitdef include("match/match.jl") include("match/types.jl") diff --git a/src/utils.jl b/src/utils.jl index 5d7a690..9fd0e29 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, splitdef, splitarg + longdef, shortdef, @expand, makeif, prettify, splitarg """ assoc!(d, k, v) @@ -261,89 +261,6 @@ function gatherwheres(ex) end end -""" splitdef(fdef) - -Match any function definition - -```julia -function name{params}(args; kwargs)::rtype where {whereparams} - body -end -``` - -and return `Dict(:name=>..., :args=>..., etc.)`. The definition can be rebuilt by -calling `MacroTools.combinedef(dict)`, or explicitly with - -``` -rtype = get(dict, :rtype, :Any) -all_params = [get(dict, :params, [])..., get(dict, :whereparams, [])...] -:(function \$(dict[:name]){\$(all_params...)}(\$(dict[:args]...); - \$(dict[:kwargs]...))::\$rtype - \$(dict[:body]) - end) -``` -""" -function splitdef(fdef) - error_msg = "Not a function definition: $(repr(fdef))" - @assert(@capture(longdef1(fdef), - function (fcall_ | fcall_) body_ end), - "Not a function definition: $fdef") - fcall_nowhere, whereparams = gatherwheres(fcall) - @assert(@capture(fcall_nowhere, ((func_(args__; kwargs__)) | - (func_(args__; kwargs__)::rtype_) | - (func_(args__)) | - (func_(args__)::rtype_))), - error_msg) - @assert(@capture(func, (fname_{params__} | fname_)), error_msg) - di = Dict(:name=>fname, :args=>args, - :kwargs=>(kwargs===nothing ? [] : kwargs), :body=>body) - if rtype !== nothing; di[:rtype] = rtype end - if whereparams !== nothing; di[:whereparams] = whereparams end - if params !== nothing; di[:params] = params end - di -end - -""" - combinedef(dict::Dict) - -`combinedef` is the inverse of `splitdef`. It takes a splitdef-like Dict -and returns a function definition. """ -function combinedef(dict::Dict) - rtype = get(dict, :rtype, nothing) - params = get(dict, :params, []) - wparams = get(dict, :whereparams, []) - body = block(dict[:body]) - name = dict[:name] - name_param = isempty(params) ? name : :($name{$(params...)}) - # We need the `if` to handle parametric inner/outer constructors like - # SomeType{X}(x::X) where X = SomeType{X}(x, x+2) - if isempty(wparams) - if rtype==nothing - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...)) - $(body.args...) - end) - else - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...))::$rtype - $(body.args...) - end) - end - else - if rtype==nothing - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...)) where {$(wparams...)} - $(body.args...) - end) - else - @q(function $name_param($(dict[:args]...); - $(dict[:kwargs]...))::$rtype where {$(wparams...)} - $(body.args...) - end) - end - end -end - """ combinearg(arg_name, arg_type, is_splat, default) diff --git a/test/split.jl b/test/split.jl index 52540e0..2496919 100644 --- a/test/split.jl +++ b/test/split.jl @@ -1,61 +1,8 @@ using MacroTools: splitstructdef, combinestructdef -macro nothing_macro() -end +macro nothing_macro() end @test @expand(@nothing_macro) === nothing -macro splitcombine(fundef) # should be a no-op - dict = splitdef(fundef) - esc(MacroTools.combinedef(dict)) -end - -# Macros for testing that splitcombine doesn't break -# macrocalls in bodies -macro zeroarg() - :(1) -end -macro onearg(x) - :(1+$(esc(x))) -end - -let - # Ideally we'd compare the result against :(function f(x)::Int 10 end), - # but it fails because of :line and :block differences - @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]) == - [(:a, :Any, false, 2), (:x, :Int, false, :nothing), - (:y, :Any, false, nothing), (:args, :Any, true, nothing)] - @test splitarg(:(::Int)) == (nothing, :Int, false, nothing) - - @splitcombine foo(x) = x+2 - @test foo(10) == 12 - @splitcombine add(a, b=2; c=3, d=4)::Float64 = a+b+c+d - @test add(1; d=10) === 16.0 - @splitcombine fparam(a::T) where {T} = T - @test fparam([]) == Vector{Any} - struct Orange end - @splitcombine (::Orange)(x) = x+2 - @test Orange()(10) == 12 - @splitcombine fwhere(a::T) where T = T - @test fwhere(10) == Int - @splitcombine manywhere(x::T, y::Vector{U}) where T <: U where U = (T, U) - @test manywhere(1, Number[2.0]) == (Int, Number) - @splitcombine fmacro0() = @zeroarg - @test fmacro0() == 1 - @splitcombine fmacro1() = @onearg 1 - @test fmacro1() == 2 - - struct Foo{A, B} - a::A - b::B - end - # Parametric outer constructor - @splitcombine Foo{A}(a::A) where A = Foo{A, A}(a,a) - @test Foo{Int}(2) == Foo{Int, Int}(2, 2) -end - @testset "combinestructdef, splitstructdef" begin ex = :(struct S end) @test ex |> splitstructdef |> combinestructdef |> Base.remove_linenums! ==