From 69664ef1308f671f61bc5b9b37ace6890d0e2a1f Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 3 Dec 2020 12:57:52 +0900 Subject: [PATCH] define at-invoke macro: - provides easier syntax to call `Core.invoke`, e.g. `@invoke f(a::Integer)` will be expanded into `invoke(f, Tuple{Integer}, a)` - when type annotation is omitted, the argument type is specified as `Any` Built on top of #37971 --- base/util.jl | 24 +++++++++++++++++++++++- doc/src/base/base.md | 1 + test/misc.jl | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/base/util.jl b/base/util.jl index 348b09f3f4c1c..1cdca3e0c3af5 100644 --- a/base/util.jl +++ b/base/util.jl @@ -509,6 +509,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...) @@ -517,6 +534,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) @@ -532,7 +554,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 47d581ba76ce6..240aec3d17db2 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 70af5595b1074..e43b4178e4f6d 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -672,6 +672,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.