Skip to content

Commit

Permalink
Export at-invoke and make it use Core.Typeof instead of Any (Ju…
Browse files Browse the repository at this point in the history
…liaLang#45807)

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 authored and pcjentsch committed Aug 18, 2022
1 parent bd23ab2 commit aa952ae
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 aa952ae

Please sign in to comment.