diff --git a/base/util.jl b/base/util.jl index 81399720c84a3..e91ab3780824f 100644 --- a/base/util.jl +++ b/base/util.jl @@ -515,6 +515,23 @@ function _kwdef!(blk, params_args, call_args) blk end +""" + @invoke f(arg::T, ...; kwargs...) + +Provides a convenient way to call [`invoke`](@ref); +`@invoke f(arg1::T1, arg2::T2; kwargs...)` will be expanded into `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`. +When an argument's type annotation is omitted, it's specified as `Any` argument, e.g. +`@invoke f(arg1::T, arg2)` will be expanded into `invoke(f, Tuple{T,Any}, arg1, arg2)`. +""" +macro invoke(ex) + f, args, kwargs = destructure_callex(ex) + arg2typs = map(args) do x + is_expr(x, :(::)) ? (x.args...,) : (x, GlobalRef(Core, :Any)) + end + args, argtypes = first.(arg2typs), last.(arg2typs) + return esc(:($(GlobalRef(Core, :invoke))($(f), Tuple{$(argtypes...)}, $(args...); $(kwargs...)))) +end + """ @invokelatest f(args...; kwargs...) @@ -523,6 +540,11 @@ Provides a convenient way to call [`Base.invokelatest`](@ref). `Base.invokelatest(f, args...; kwargs...)`. """ macro invokelatest(ex) + f, args, kwargs = destructure_callex(ex) + return esc(:($(GlobalRef(Base, :invokelatest))($(f), $(args...); $(kwargs...)))) +end + +function destructure_callex(ex) is_expr(ex, :call) || throw(ArgumentError("a call expression f(args...; kwargs...) should be given")) f = first(ex.args) @@ -538,7 +560,7 @@ macro invokelatest(ex) end end - esc(:($(GlobalRef(Base, :invokelatest))($(f), $(args...); $(kwargs...)))) + return f, args, kwargs end # testing diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 5e67a6f034e6c..15da9b4e0b4fb 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -234,6 +234,7 @@ Core.Function Base.hasmethod Core.applicable Core.invoke +Base.@invoke Base.invokelatest Base.@invokelatest new diff --git a/test/misc.jl b/test/misc.jl index 41b92785fa5b6..d3318838f6ce8 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -704,6 +704,46 @@ let foo() = begin @test bar() == 1 end +@testset "@invoke macro" begin + # test against `invoke` doc example + let + f(x::Real) = x^2 + f(x::Integer) = 1 + Base.@invoke f(x::Real) + @test f(2) == 5 + end + + let + f1(::Integer) = Integer + f1(::Real) = Real; + f2(x::Real) = _f2(x) + _f2(::Integer) = Integer + _f2(_) = Real + @test f1(1) === Integer + @test f2(1) === Integer + @test Base.@invoke(f1(1::Real)) === Real + @test Base.@invoke(f2(1::Real)) === Integer + end + + # when argment's type annotation is omitted, it should be specified as `Any` + let + f(_) = Any + f(x::Integer) = Integer + @test f(1) === Integer + @test Base.@invoke(f(1::Any)) === Any + @test Base.@invoke(f(1)) === Any + end + + # handle keyword arguments correctly + let + f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2) + f(::Integer; kwargs...) = error("don't call me") + + @test_throws Exception f(1; kw1 = 1, kw2 = 2) + @test 3 == Base.@invoke f(1::Any; kw1 = 1, kw2 = 2) + @test 3 == Base.@invoke f(1; kw1 = 1, kw2 = 2) + end +end + # Endian tests # For now, we only support little endian. # Add an `Sys.ARCH` test for big endian when/if we add support for that.