From b1ca583c30356bde89508e69b96f9a6c1a97cbb8 Mon Sep 17 00:00:00 2001
From: Jeff Bezanson <jeff.bezanson@gmail.com>
Date: Mon, 22 Jun 2020 22:10:56 -0400
Subject: [PATCH] add versions of `code_typed` and `which` that accept tuple
 types

---
 base/reflection.jl | 42 ++++++++++++++++++++++++++++++++++++++----
 test/reflection.jl |  6 ++++++
 2 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/base/reflection.jl b/base/reflection.jl
index 96035b178ffde..9b6ae00e153f3 100644
--- a/base/reflection.jl
+++ b/base/reflection.jl
@@ -1081,10 +1081,31 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple);
                     debuginfo::Symbol=:default,
                     world = get_world_counter(),
                     interp = Core.Compiler.NativeInterpreter(world))
-    ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
     if isa(f, Core.Builtin)
         throw(ArgumentError("argument is not a generic function"))
     end
+    ft = Core.Typeof(f)
+    if isa(types, Type)
+        u = unwrap_unionall(types)
+        tt = rewrap_unionall(Tuple{ft, u.parameters...}, types)
+    else
+        tt = Tuple{ft, types...}
+    end
+    return code_typed_by_type(tt; optimize, debuginfo, world, interp)
+end
+
+"""
+    code_typed_by_type(types::Type{<:Tuple}; ...)
+
+Similar to [`code_typed`](@ref), except the argument is a tuple type describing
+a full signature to query.
+"""
+function code_typed_by_type(@nospecialize(tt::Type);
+                            optimize=true,
+                            debuginfo::Symbol=:default,
+                            world = get_world_counter(),
+                            interp = Core.Compiler.NativeInterpreter(world))
+    ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions")
     if @isdefined(IRShow)
         debuginfo = IRShow.debuginfo(debuginfo)
     elseif debuginfo === :default
@@ -1093,10 +1114,14 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple);
     if debuginfo !== :source && debuginfo !== :none
         throw(ArgumentError("'debuginfo' must be either :source or :none"))
     end
-    types = to_tuple_type(types)
+    tt = to_tuple_type(tt)
+    meths = _methods_by_ftype(tt, -1, world)
+    if meths === false
+        error("signature does not correspond to a generic function")
+    end
     asts = []
-    for x in _methods(f, types, -1, world)
-        meth = func_for_method_checked(x[3], types, x[2])
+    for x in meths
+        meth = func_for_method_checked(x[3], tt, x[2])
         (code, ty) = Core.Compiler.typeinf_code(interp, meth, x[1], x[2], optimize)
         code === nothing && error("inference not successful") # inference disabled?
         debuginfo === :none && remove_linenums!(code)
@@ -1135,6 +1160,15 @@ function which(@nospecialize(f), @nospecialize(t))
     end
     t = to_tuple_type(t)
     tt = signature_type(f, t)
+    return which(tt)
+end
+
+"""
+    which(types::Type{<:Tuple})
+
+Returns the method that would be called by the given type signature (as a tuple type).
+"""
+function which(@nospecialize(tt::Type))
     m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt))
     if m === nothing
         error("no unique matching method found for the specified argument types")
diff --git a/test/reflection.jl b/test/reflection.jl
index ecdf788584bd2..8758f7ca65940 100644
--- a/test/reflection.jl
+++ b/test/reflection.jl
@@ -525,6 +525,11 @@ let
     @test !isdefined(mi.cache, :next)
 end
 
+# code_typed_by_type
+@test Base.code_typed_by_type(Tuple{Type{<:Val}})[1][2] == Val
+@test Base.code_typed_by_type(Tuple{typeof(sin), Float64})[1][2] === Float64
+@test_throws ErrorException("signature does not correspond to a generic function") Base.code_typed_by_type(Tuple{Any})
+
 # New reflection methods in 0.6
 struct ReflectionExample{T<:AbstractFloat, N}
     x::Tuple{T, N}
@@ -820,6 +825,7 @@ f20872(::Val, ::Val) = false
 @test which(f20872, Tuple{Val,Val}).sig == Tuple{typeof(f20872), Val, Val}
 @test which(f20872, Tuple{Val,Val{N}} where N).sig == Tuple{typeof(f20872), Val, Val}
 @test_throws ErrorException which(f20872, Tuple{Any,Val{N}} where N)
+@test which(Tuple{typeof(f20872), Val{1}, Val{2}}).sig == Tuple{typeof(f20872), Val, Val}
 
 module M29962 end
 # make sure checking if a binding is deprecated does not resolve it