Skip to content

Commit

Permalink
Export at-invoke and make it use Core.Typeof instead of Any
Browse files Browse the repository at this point in the history
The macro was introduced in Julia 1.7 but was not exported. Previously,
when an argument's type was unspecified, the type used was `Any`. This
doesn't play well with types passed as arguments: for example, `x % T`
has different meanings for `T` a type or a value, so if `T` is left
untyped in `at-invoke rem(x::S, T)`, the method to call is ambiguous. On
the other hand, if the macro expands `rem(x::S, T)` to use
`Core.Typeof(T)`, the resulting expression will interpret `T` as a type
and the likelihood of method ambiguities is significantly decreased.
  • Loading branch information
ararslan committed Jun 25, 2022
1 parent a60c76e commit 46666d9
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 50 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Language changes
binary minus falls back to addition `-(x, y) = x + (-y)`, and, at the most generic level,
left- and right-division fall back to multiplication with the inverse from left and right,
respectively, as stated in the docstring. ([#44564])
* The `@invoke` macro introduced in 1.7 is now exported. Additionally, it now uses `Core.Typeof(x)`
rather than `Any` when a type annotation is omitted for an argument `x` so that types passed
as arguments are handled correctly. ([#45807])

Compiler/Runtime improvements
-----------------------------
Expand Down
4 changes: 2 additions & 2 deletions base/compiler/ssair/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ length(s::String) = Base.length(s)
end

import Base: show_unquoted
using Base: printstyled, with_output_color, prec_decl
using Base: printstyled, with_output_color, prec_decl, @invoke

function Base.show(io::IO, cfg::CFG)
for (idx, block) in enumerate(cfg.blocks)
Expand Down Expand Up @@ -817,7 +817,7 @@ function Base.show(io::IO, t::TriState)
if s !== nothing
printstyled(io, s; color = tristate_color(t))
else # unknown state, redirect to the fallback printing
Base.@invoke show(io::IO, t::Any)
@invoke show(io::IO, t::Any)
end
end

Expand Down
3 changes: 2 additions & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1047,4 +1047,5 @@ export
@goto,
@view,
@views,
@static
@static,
@invoke
45 changes: 30 additions & 15 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1855,30 +1855,45 @@ hasproperty(x, s::Symbol) = s in propertynames(x)
"""
@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)`.
Provides a convenient way to call [`invoke`](@ref) by expanding
`@invoke f(arg1::T1, arg2::T2; kwargs...)` to `invoke(f, Tuple{T1,T2}, arg1, arg2; kwargs...)`.
When an argument's type annotation is omitted, it's replaced with `Core.Typeof` that argument.
To invoke a method where an argument is untyped or explicitly typed as `Any`, annotate the
argument with `::Any`.
# Examples
```jldoctest
julia> @macroexpand @invoke f(x::T, y)
:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y))
julia> @invoke 420::Integer % Unsigned
0x00000000000001a4
```
!!! compat "Julia 1.7"
This macro requires Julia 1.7 or later.
!!! compat "Julia 1.9"
This macro is exported as of Julia 1.9.
"""
macro invoke(ex)
f, args, kwargs = destructure_callex(ex)
newargs, newargtypes = Any[], Any[]
for i = 1:length(args)
x = args[i]
if isexpr(x, :(::))
a = x.args[1]
t = x.args[2]
types = Expr(:curly, :Tuple)
out = Expr(:call, GlobalRef(Core, :invoke))
isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...))
push!(out.args, f)
push!(out.args, types)
for arg in args
if isexpr(arg, :(::))
push!(out.args, arg.args[1])
push!(types.args, arg.args[2])
else
a = x
t = GlobalRef(Core, :Any)
push!(out.args, arg)
push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg))
end
push!(newargs, a)
push!(newargtypes, t)
end
return esc(:($(GlobalRef(Core, :invoke))($(f), Tuple{$(newargtypes...)}, $(newargs...); $(kwargs...))))
return esc(out)
end

"""
Expand Down
4 changes: 2 additions & 2 deletions stdlib/LinearAlgebra/src/diagonal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -756,8 +756,8 @@ end
/(u::AdjointAbsVec, D::Diagonal) = adjoint(adjoint(D) \ u.parent)
/(u::TransposeAbsVec, D::Diagonal) = transpose(transpose(D) \ u.parent)
# disambiguation methods: Call unoptimized version for user defined AbstractTriangular.
*(A::AbstractTriangular, D::Diagonal) = Base.@invoke *(A::AbstractMatrix, D::Diagonal)
*(D::Diagonal, A::AbstractTriangular) = Base.@invoke *(D::Diagonal, A::AbstractMatrix)
*(A::AbstractTriangular, D::Diagonal) = @invoke *(A::AbstractMatrix, D::Diagonal)
*(D::Diagonal, A::AbstractTriangular) = @invoke *(D::Diagonal, A::AbstractMatrix)

dot(x::AbstractVector, D::Diagonal, y::AbstractVector) = _mapreduce_prod(dot, x, D, y)

Expand Down
2 changes: 1 addition & 1 deletion stdlib/LinearAlgebra/src/generic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1501,7 +1501,7 @@ function axpy!(α::Number,
y::StridedVecLike{T}, ry::AbstractRange{<:Integer},
) where {T<:BlasFloat}
if Base.has_offset_axes(rx, ry)
return Base.@invoke axpy!(α,
return @invoke axpy!(α,
x::AbstractArray, rx::AbstractArray{<:Integer},
y::AbstractArray, ry::AbstractArray{<:Integer},
)
Expand Down
8 changes: 4 additions & 4 deletions test/compiler/AbstractInterpreter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,31 +49,31 @@ strangesin(x) = sin(x)
strangesin(x)
end |> only === Union{Float64,Nothing}
@test Base.return_types((Any,); interp=MTOverlayInterp()) do x
Base.@invoke strangesin(x::Float64)
@invoke strangesin(x::Float64)
end |> only === Union{Float64,Nothing}

# effect analysis should figure out that the overlayed method is used
@test Base.infer_effects((Float64,); interp=MTOverlayInterp()) do x
strangesin(x)
end |> !Core.Compiler.is_nonoverlayed
@test Base.infer_effects((Any,); interp=MTOverlayInterp()) do x
Base.@invoke strangesin(x::Float64)
@invoke strangesin(x::Float64)
end |> !Core.Compiler.is_nonoverlayed

# but it should never apply for the native compilation
@test Base.infer_effects((Float64,)) do x
strangesin(x)
end |> Core.Compiler.is_nonoverlayed
@test Base.infer_effects((Any,)) do x
Base.@invoke strangesin(x::Float64)
@invoke strangesin(x::Float64)
end |> Core.Compiler.is_nonoverlayed

# fallback to the internal method table
@test Base.return_types((Int,); interp=MTOverlayInterp()) do x
cos(x)
end |> only === Float64
@test Base.return_types((Any,); interp=MTOverlayInterp()) do x
Base.@invoke cos(x::Float64)
@invoke cos(x::Float64)
end |> only === Float64

# not fully covered overlay method match
Expand Down
4 changes: 2 additions & 2 deletions test/compiler/EscapeAnalysis/EAUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function CC.cache_result!(interp::EscapeAnalyzer, caller::InferenceResult)
if haskey(interp.cache, caller)
GLOBAL_ESCAPE_CACHE[caller.linfo] = interp.cache[caller]
end
return Base.@invoke CC.cache_result!(interp::AbstractInterpreter, caller::InferenceResult)
return @invoke CC.cache_result!(interp::AbstractInterpreter, caller::InferenceResult)
end

const GLOBAL_ESCAPE_CACHE = IdDict{MethodInstance,EscapeCache}()
Expand Down Expand Up @@ -276,7 +276,7 @@ end
function Base.show(io::IO, x::EscapeInfo)
name, color = get_name_color(x)
if isnothing(name)
Base.@invoke show(io::IO, x::Any)
@invoke show(io::IO, x::Any)
else
printstyled(io, name; color)
end
Expand Down
4 changes: 2 additions & 2 deletions test/compiler/EscapeAnalysis/interprocedural.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ let result = code_escapes((SafeRef{String},); optimize=false) do x
end
# InvokeCallInfo
let result = code_escapes((SafeRef{String},); optimize=false) do x
return Base.@invoke noescape(x::Any)
return @invoke noescape(x::Any)
end
@test has_no_escape(ignore_argescape(result.state[Argument(2)]))
end
let result = code_escapes((SafeRef{String},); optimize=false) do x
return Base.@invoke conditional_escape!(false::Any, x::Any)
return @invoke conditional_escape!(false::Any, x::Any)
end
@test has_no_escape(ignore_argescape(result.state[Argument(2)]))
end
Expand Down
16 changes: 8 additions & 8 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2129,7 +2129,7 @@ end
# `InterConditional` handling: `abstract_invoke`
ispositive(a) = isa(a, Int) && a > 0
@test Base.return_types((Any,)) do a
if Base.@invoke ispositive(a::Any)
if @invoke ispositive(a::Any)
return a
end
return 0
Expand Down Expand Up @@ -2297,7 +2297,7 @@ end

# work with `invoke`
@test Base.return_types((Any,Any)) do x, y
Base.@invoke ifelselike(isa(x, Int), x, y::Int)
@invoke ifelselike(isa(x, Int), x::Any, y::Int)
end |> only == Int

# don't be confused with vararg method
Expand Down Expand Up @@ -3766,16 +3766,16 @@ end
f(a::Number, sym::Bool) = sym ? Number : :number
end
@test (@eval m Base.return_types((Any,)) do a
Base.@invoke f(a::Any, true::Bool)
@invoke f(a::Any, true::Bool)
end) == Any[Type{Any}]
@test (@eval m Base.return_types((Any,)) do a
Base.@invoke f(a::Number, true::Bool)
@invoke f(a::Number, true::Bool)
end) == Any[Type{Number}]
@test (@eval m Base.return_types((Any,)) do a
Base.@invoke f(a::Any, false::Bool)
@invoke f(a::Any, false::Bool)
end) == Any[Symbol]
@test (@eval m Base.return_types((Any,)) do a
Base.@invoke f(a::Number, false::Bool)
@invoke f(a::Number, false::Bool)
end) == Any[Symbol]

# https://github.com/JuliaLang/julia/issues/41024
Expand All @@ -3790,7 +3790,7 @@ end
abstract type AbstractInterfaceExtended <: AbstractInterface end
Base.getproperty(x::AbstractInterfaceExtended, sym::Symbol) =
sym === :y ? getfield(x, sym)::Rational{Int} :
return Base.@invoke getproperty(x::AbstractInterface, sym::Symbol)
return @invoke getproperty(x::AbstractInterface, sym::Symbol)
end
@test (@eval m Base.return_types((AbstractInterfaceExtended,)) do x
x.x
Expand Down Expand Up @@ -4110,7 +4110,7 @@ end
# https://github.com/JuliaLang/julia/issues/44763
global x44763::Int = 0
increase_x44763!(n) = (global x44763; x44763 += n)
invoke44763(x) = Base.@invoke increase_x44763!(x)
invoke44763(x) = @invoke increase_x44763!(x)
@test Base.return_types() do
invoke44763(42)
end |> only === Int
Expand Down
10 changes: 5 additions & 5 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ Base.@constprop :aggressive function conditional_escape!(cnd, x)
return nothing
end
@test fully_eliminated((String,)) do x
Base.@invoke conditional_escape!(false::Any, x::Any)
@invoke conditional_escape!(false::Any, x::Any)
end

@testset "strides for ReshapedArray (PR#44027)" begin
Expand Down Expand Up @@ -1066,12 +1066,12 @@ let src = code_typed1() do
@test count(isnew, src.code) == 1
end
let src = code_typed1() do
Base.@invoke FooTheRef(nothing::Any)
@invoke FooTheRef(nothing::Any)
end
@test count(isnew, src.code) == 1
end
let src = code_typed1() do
Base.@invoke FooTheRef(0::Any)
@invoke FooTheRef(0::Any)
end
@test count(isnew, src.code) == 1
end
Expand All @@ -1084,11 +1084,11 @@ end
nothing
end
@test fully_eliminated() do
Base.@invoke FooTheRef(nothing::Any)
@invoke FooTheRef(nothing::Any)
nothing
end
@test fully_eliminated() do
Base.@invoke FooTheRef(0::Any)
@invoke FooTheRef(0::Any)
nothing
end

Expand Down
20 changes: 12 additions & 8 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ end
# test against `invoke` doc example
let
f(x::Real) = x^2
f(x::Integer) = 1 + Base.@invoke f(x::Real)
f(x::Integer) = 1 + @invoke f(x::Real)
@test f(2) == 5
end

Expand All @@ -908,17 +908,22 @@ end
_f2(_) = Real
@test f1(1) === Integer
@test f2(1) === Integer
@test Base.@invoke(f1(1::Real)) === Real
@test Base.@invoke(f2(1::Real)) === Integer
@test @invoke(f1(1::Real)) === Real
@test @invoke(f2(1::Real)) === Integer
end

# when argment's type annotation is omitted, it should be specified as `Any`
# when argment's type annotation is omitted, it should be specified as `Core.Typeof(x)`
let
f(_) = Any
f(x::Integer) = Integer
@test f(1) === Integer
@test Base.@invoke(f(1::Any)) === Any
@test Base.@invoke(f(1)) === Any
@test @invoke(f(1::Any)) === Any
@test @invoke(f(1)) === Integer

😎(x, y) = 1
😎(x, ::Type{Int}) = 2
# Without `Core.Typeof`, the first method would be called
@test @invoke(😎(1, Int)) == 2
end

# handle keyword arguments correctly
Expand All @@ -927,8 +932,7 @@ end
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)
@test 3 == @invoke f(1::Any; kw1 = 1, kw2 = 2)
end
end

Expand Down

0 comments on commit 46666d9

Please sign in to comment.