Skip to content

Commit

Permalink
define at-invoke macro:
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
aviatesk committed Dec 4, 2020
1 parent 8dd959e commit 4688d88
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
24 changes: 23 additions & 1 deletion base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
Expand All @@ -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)
Expand All @@ -532,7 +554,7 @@ macro invokelatest(ex)
end
end

esc(:($(GlobalRef(Base, :invokelatest))($(f), $(args...); $(kwargs...))))
return f, args, kwargs
end

# testing
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ Core.Function
Base.hasmethod
Core.applicable
Core.invoke
Base.@invoke
Base.invokelatest
Base.@invokelatest
new
Expand Down
40 changes: 40 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 4688d88

Please sign in to comment.