From 46666d945a0a327abdf2b1e80a5af154d132b0ee Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Fri, 24 Jun 2022 09:44:45 -0700 Subject: [PATCH] Export `at-invoke` and make it use `Core.Typeof` instead of `Any` 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. --- NEWS.md | 3 ++ base/compiler/ssair/show.jl | 4 +- base/exports.jl | 3 +- base/reflection.jl | 45 ++++++++++++------- stdlib/LinearAlgebra/src/diagonal.jl | 4 +- stdlib/LinearAlgebra/src/generic.jl | 2 +- test/compiler/AbstractInterpreter.jl | 8 ++-- test/compiler/EscapeAnalysis/EAUtils.jl | 4 +- .../EscapeAnalysis/interprocedural.jl | 4 +- test/compiler/inference.jl | 16 +++---- test/compiler/inline.jl | 10 ++--- test/misc.jl | 20 +++++---- 12 files changed, 73 insertions(+), 50 deletions(-) diff --git a/NEWS.md b/NEWS.md index c08fe50152fe2..434e38078b01c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 ----------------------------- diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 0f8954f6b3131..51abf2a228de3 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -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) @@ -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 diff --git a/base/exports.jl b/base/exports.jl index 304d48d24bdcd..428e6894bbafe 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1047,4 +1047,5 @@ export @goto, @view, @views, - @static + @static, + @invoke diff --git a/base/reflection.jl b/base/reflection.jl index a725e2d59916a..644714c8440cb 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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 """ diff --git a/stdlib/LinearAlgebra/src/diagonal.jl b/stdlib/LinearAlgebra/src/diagonal.jl index f4e6d427dceeb..fab0f36660c46 100644 --- a/stdlib/LinearAlgebra/src/diagonal.jl +++ b/stdlib/LinearAlgebra/src/diagonal.jl @@ -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) diff --git a/stdlib/LinearAlgebra/src/generic.jl b/stdlib/LinearAlgebra/src/generic.jl index 7ff99c75db771..36ced82eb6c7d 100644 --- a/stdlib/LinearAlgebra/src/generic.jl +++ b/stdlib/LinearAlgebra/src/generic.jl @@ -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}, ) diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 9d1be42891042..74775b8e77213 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -49,7 +49,7 @@ 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 @@ -57,7 +57,7 @@ end |> only === Union{Float64,Nothing} 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 @@ -65,7 +65,7 @@ end |> !Core.Compiler.is_nonoverlayed 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 @@ -73,7 +73,7 @@ end |> Core.Compiler.is_nonoverlayed 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 diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index f06f5e0ef8983..f71cc20387733 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -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}() @@ -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 diff --git a/test/compiler/EscapeAnalysis/interprocedural.jl b/test/compiler/EscapeAnalysis/interprocedural.jl index 42a2505e03c08..756e5489ed637 100644 --- a/test/compiler/EscapeAnalysis/interprocedural.jl +++ b/test/compiler/EscapeAnalysis/interprocedural.jl @@ -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 diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 262e6eb6448db..8fc63f42ada87 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 3dac08370c123..8f138d282398b 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -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 @@ -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 @@ -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 diff --git a/test/misc.jl b/test/misc.jl index c8153eef9ec3a..6f0e6457be7ea 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -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 @@ -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 @@ -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