Skip to content

Commit 560676d

Browse files
authored
define at-invoke macro: (#38438)
- 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
1 parent 29d5d60 commit 560676d

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

base/util.jl

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,23 @@ function _kwdef!(blk, params_args, call_args)
515515
blk
516516
end
517517

518+
"""
519+
@invoke f(arg::T, ...; kwargs...)
520+
521+
Provides a convenient way to call [`invoke`](@ref);
522+
`@invoke f(arg1::T1, arg2::T2; kwargs...)` will be expanded into `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`.
523+
When an argument's type annotation is omitted, it's specified as `Any` argument, e.g.
524+
`@invoke f(arg1::T, arg2)` will be expanded into `invoke(f, Tuple{T,Any}, arg1, arg2)`.
525+
"""
526+
macro invoke(ex)
527+
f, args, kwargs = destructure_callex(ex)
528+
arg2typs = map(args) do x
529+
is_expr(x, :(::)) ? (x.args...,) : (x, GlobalRef(Core, :Any))
530+
end
531+
args, argtypes = first.(arg2typs), last.(arg2typs)
532+
return esc(:($(GlobalRef(Core, :invoke))($(f), Tuple{$(argtypes...)}, $(args...); $(kwargs...))))
533+
end
534+
518535
"""
519536
@invokelatest f(args...; kwargs...)
520537
@@ -523,6 +540,11 @@ Provides a convenient way to call [`Base.invokelatest`](@ref).
523540
`Base.invokelatest(f, args...; kwargs...)`.
524541
"""
525542
macro invokelatest(ex)
543+
f, args, kwargs = destructure_callex(ex)
544+
return esc(:($(GlobalRef(Base, :invokelatest))($(f), $(args...); $(kwargs...))))
545+
end
546+
547+
function destructure_callex(ex)
526548
is_expr(ex, :call) || throw(ArgumentError("a call expression f(args...; kwargs...) should be given"))
527549

528550
f = first(ex.args)
@@ -538,7 +560,7 @@ macro invokelatest(ex)
538560
end
539561
end
540562

541-
esc(:($(GlobalRef(Base, :invokelatest))($(f), $(args...); $(kwargs...))))
563+
return f, args, kwargs
542564
end
543565

544566
# testing

doc/src/base/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ Core.Function
234234
Base.hasmethod
235235
Core.applicable
236236
Core.invoke
237+
Base.@invoke
237238
Base.invokelatest
238239
Base.@invokelatest
239240
new

test/misc.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,46 @@ let foo() = begin
704704
@test bar() == 1
705705
end
706706

707+
@testset "@invoke macro" begin
708+
# test against `invoke` doc example
709+
let
710+
f(x::Real) = x^2
711+
f(x::Integer) = 1 + Base.@invoke f(x::Real)
712+
@test f(2) == 5
713+
end
714+
715+
let
716+
f1(::Integer) = Integer
717+
f1(::Real) = Real;
718+
f2(x::Real) = _f2(x)
719+
_f2(::Integer) = Integer
720+
_f2(_) = Real
721+
@test f1(1) === Integer
722+
@test f2(1) === Integer
723+
@test Base.@invoke(f1(1::Real)) === Real
724+
@test Base.@invoke(f2(1::Real)) === Integer
725+
end
726+
727+
# when argment's type annotation is omitted, it should be specified as `Any`
728+
let
729+
f(_) = Any
730+
f(x::Integer) = Integer
731+
@test f(1) === Integer
732+
@test Base.@invoke(f(1::Any)) === Any
733+
@test Base.@invoke(f(1)) === Any
734+
end
735+
736+
# handle keyword arguments correctly
737+
let
738+
f(a; kw1 = nothing, kw2 = nothing) = a + max(kw1, kw2)
739+
f(::Integer; kwargs...) = error("don't call me")
740+
741+
@test_throws Exception f(1; kw1 = 1, kw2 = 2)
742+
@test 3 == Base.@invoke f(1::Any; kw1 = 1, kw2 = 2)
743+
@test 3 == Base.@invoke f(1; kw1 = 1, kw2 = 2)
744+
end
745+
end
746+
707747
# Endian tests
708748
# For now, we only support little endian.
709749
# Add an `Sys.ARCH` test for big endian when/if we add support for that.

0 commit comments

Comments
 (0)