From 076855fa19307eec2f0f9c78e0ecfd896724aa9c Mon Sep 17 00:00:00 2001 From: manuelbb-upb Date: Tue, 24 Jun 2025 12:31:21 +0200 Subject: [PATCH 1/2] implement `static_which` --- src/Tricks.jl | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/Tricks.jl b/src/Tricks.jl index c8c8de3..ccec955 100644 --- a/src/Tricks.jl +++ b/src/Tricks.jl @@ -145,6 +145,79 @@ else end end + +""" + static_which(f, [type_tuple::Type{<:Tuple}, throw_error::Union{Val{true}, Val{false}}]) + +Similar to `Base.which` this returns the method of `f` that would be used by `invoke` for +the given argument type tuple. +If `throw_error==Val(true)`, then an error is thrown if no suitable method is found. +If `throw_error==Val(false)` and there is no method, then `nothing` is returned. +Default is `throw_error=Val(false)`. +""" +static_which(@nospecialize(f)) = static_which(f, Tuple{Vararg{Any}}) +@static if VERSION >= v"1.10.0-DEV.609" + function __static_which(world, source, T, self, f, _T, _throw_error::Type{throw_error}) where throw_error + tt = _combine_signature_type(f, T) + match, _ = Core.Compiler._findsup(tt, nothing, world) + + if isnothing(match) + if throw_error <: Val{true} + #= + This is what happens in `Base`: + ``` + me = MethodError(f, T, world) + ee = ErrorException(sprint(io -> begin + println(io, "Calling invoke(f, t, args...) would throw:"); + Base.showerror(io, me); + end)) + throw(ee) + ``` + We cannot easily replicate this behavior: + 1) Instead of `f::Function`, in the generated code we have `f::Type{<:Function}`. + To define `me` we could do something like `f.instance`, but only for + singleton function types; function-like structs wouldn't work. + 2) `Base.showerror(io, me)` performs code reflection, disallowed in generated + functions. Moreover, the method in `base/errorshow.jl` is non-trivial to + replicate with our static alternatives. + + Thus, we only throw with some short message: + =# + throw("Calling invoke(f, t, args...) would throw an error.") + else + matched_method = match + end + else + matched_method = match.method + end + + # Now we add the edges so if a method is defined this recompiles + ci = create_codeinfo_with_returnvalue([Symbol("#self#"), :f, :_T, :throw_error], [:T], (:T,), :($matched_method)) + return ci + end + @eval function static_which(@nospecialize(f) , @nospecialize(_T::Type{T}) , @nospecialize(throw_error::Union{Val{true}, Val{false}}=Val(false))) where {T <: Tuple} + $(Expr(:meta, :generated, __static_which)) + $(Expr(:meta, :generated_only)) + end +else + @generated function static_which(@nospecialize(f) , @nospecialize(_T::Type{T}) , @nospecialize(throw_error::Union{Val{true}, Val{false}}=Val(false))) where {T <: Tuple} + world = typemax(UInt) + tt = _combine_signature_type(f, T) + match, _ = Core.Compiler._findsup(tt, nothing, world) + if isnothing(match) + if throw_error <: Val{true} + throw("Calling invoke(f, t, args...) would throw an error.") + else + matched_method = match + end + else + matched_method = match.method + end + ci = create_codeinfo_with_returnvalue([Symbol("#self#"), :f, :_T, :throw_error], [:T], (:T,), :($matched_method)) + return ci + end +end + @static if VERSION < v"1.3" const compat_hasmethod = hasmethod else From 411969d3f51dc9c66cf49d8a005cbed1309db5f4 Mon Sep 17 00:00:00 2001 From: manuelbb-upb Date: Fri, 27 Jun 2025 13:25:39 +0200 Subject: [PATCH 2/2] `static_which`: actually add edges --- src/Tricks.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tricks.jl b/src/Tricks.jl index ccec955..c122b93 100644 --- a/src/Tricks.jl +++ b/src/Tricks.jl @@ -190,9 +190,9 @@ static_which(@nospecialize(f)) = static_which(f, Tuple{Vararg{Any}}) else matched_method = match.method end - + ci = create_codeinfo_with_returnvalue([Symbol("#self#"), :f, :_T, :throw_error], [:T], (:T,), :($matched_method)) # Now we add the edges so if a method is defined this recompiles - ci = create_codeinfo_with_returnvalue([Symbol("#self#"), :f, :_T, :throw_error], [:T], (:T,), :($matched_method)) + ci.edges = _method_table_all_edges_all_methods(f, T, world) return ci end @eval function static_which(@nospecialize(f) , @nospecialize(_T::Type{T}) , @nospecialize(throw_error::Union{Val{true}, Val{false}}=Val(false))) where {T <: Tuple} @@ -214,6 +214,7 @@ else matched_method = match.method end ci = create_codeinfo_with_returnvalue([Symbol("#self#"), :f, :_T, :throw_error], [:T], (:T,), :($matched_method)) + ci.edges = _method_table_all_edges_all_methods(f, T, world) return ci end end