-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
InteractiveUtils: Support callable objects as functions in introspection macros #58905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
1fae8e7
395feea
16c6e5e
33e4b05
3ffce1e
ec2f4c8
ef7727e
394f15d
76f030c
84dd768
865739e
5f5db13
57fbef2
551e0ea
12dacbe
8dea09e
c9ec156
73a9256
e7b88ee
c8b7dc7
ae98622
8a60b9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,18 +18,19 @@ end | |
|
||
function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}}) | ||
isnothing(where_params) && return ex | ||
Expr(:where, ex, esc.(where_params)...) | ||
Expr(:where, ex, where_params...) | ||
end | ||
|
||
get_typeof(ex::Ref) = ex[] | ||
function get_typeof(@nospecialize ex) | ||
isexpr(ex, :(::), 1) && return esc(ex.args[1]) | ||
isexpr(ex, :(::), 2) && return esc(ex.args[2]) | ||
isexpr(ex, :(::), 1) && return ex.args[1] | ||
isexpr(ex, :(::), 2) && return ex.args[2] | ||
if isexpr(ex, :..., 1) | ||
splatted = ex.args[1] | ||
isexpr(splatted, :(::), 1) && return Expr(:curly, :Vararg, esc(splatted.args[1])) | ||
return :(Any[Core.Typeof(x) for x in $(esc(splatted))]...) | ||
isexpr(splatted, :(::), 1) && return Expr(:curly, :Vararg, splatted.args[1]) | ||
return :(Any[Core.Typeof(x) for x in $splatted]...) | ||
end | ||
return :(Core.Typeof($(esc(ex)))) | ||
return :(Core.Typeof($ex)) | ||
end | ||
|
||
function is_broadcasting_call(ex) | ||
|
@@ -94,8 +95,8 @@ function recursive_dotcalls!(ex, args, i=1) | |
end | ||
|
||
function extract_farg(@nospecialize arg) | ||
!isexpr(arg, :(::), 1) && return esc(arg) | ||
fT = esc(arg.args[1]) | ||
!isexpr(arg, :(::), 1) && return arg | ||
fT = arg.args[1] | ||
:($construct_callable($fT)) | ||
end | ||
|
||
|
@@ -105,7 +106,8 @@ function construct_callable(@nospecialize(func::Type)) | |
# Don't support type annotations otherwise, we don't want to give wrong answers | ||
# for callables such as `(::Returns{Int})(args...)` where using `Returns{Int}` | ||
# would give us code for the constructor, not for the callable object. | ||
throw(ArgumentError("If a function type is explicitly provided, it must be a singleton whose only instance is the callable object")) | ||
throw(ArgumentError("If a function type is explicitly provided, it must be a singleton whose only instance is the callable object. | ||
To alleviate this restriction, the reflection macro may set `use_signature_tuple = true` if using `gen_call_with_extracted_types`.")) | ||
end | ||
|
||
function separate_kwargs(exs::Vector{Any}) | ||
|
@@ -147,7 +149,7 @@ function generate_merged_namedtuple_type(kwargs::Vector{Any}) | |
push!(nts, generate_namedtuple_type(ntargs)) | ||
empty!(ntargs) | ||
end | ||
push!(nts, Expr(:call, typeof_nt, esc(ex.args[1]))) | ||
push!(nts, Expr(:call, typeof_nt, ex.args[1])) | ||
elseif isexpr(ex, :kw, 2) | ||
push!(ntargs, ex.args[1]::Symbol => get_typeof(ex.args[2])) | ||
elseif isexpr(ex, :(::), 2) | ||
|
@@ -190,12 +192,30 @@ function merge_namedtuple_types(nt::Type{<:NamedTuple}, nts::Type{<:NamedTuple}. | |
end | ||
end | ||
end | ||
NamedTuple{Tuple(names), Tuple{types...}} | ||
return NamedTuple{Tuple(names), Tuple{types...}} | ||
end | ||
|
||
function gen_call(fcn, args, where_params, kws; use_signature_tuple::Bool) | ||
f, args... = args | ||
args = collect(Any, args) | ||
!use_signature_tuple && return :($fcn($(esc(extract_farg(f))), $(esc(typesof_expr(args, where_params))); $(kws...))) | ||
# We use a signature tuple only if we are sure we won't get an opaque closure as first argument. | ||
# If we do get one, we have to use the 2-argument form. | ||
with_signature_tuple = :($fcn($(esc(typesof_expr(Any[f, args...], where_params))); $(kws...))) | ||
isexpr(f, :(::)) && return with_signature_tuple # we have a type, not a value, so not an OpaqueClosure | ||
return quote | ||
f = $(esc(f)) | ||
if isa(f, Core.OpaqueClosure) | ||
$fcn(f, $(esc(typesof_expr(args, where_params))); $(kws...)) | ||
else | ||
$with_signature_tuple | ||
end | ||
end | ||
end | ||
|
||
is_code_macro(fcn) = startswith(string(fcn), "code_") | ||
|
||
function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false) | ||
function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false, use_signature_tuple = false) | ||
serenity4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if isexpr(ex0, :ref) | ||
ex0 = replace_ref_begin_end!(ex0) | ||
end | ||
|
@@ -249,10 +269,10 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so | |
end | ||
fully_qualified_symbol &= ex1 isa Symbol | ||
if fully_qualified_symbol || isexpr(ex1, :(::), 1) | ||
call_reflection = :($(fcn)(Base.getproperty, $(typesof_expr(ex0.args, where_params)))) | ||
call_reflection = gen_call(fcn, [:(Base.getproperty); ex0.args], where_params, kws; use_signature_tuple) | ||
isexpr(ex0.args[1], :(::), 1) && return call_reflection | ||
if supports_binding_reflection | ||
binding_reflection = :($fcn(arg1, $(ex0.args[2]))) | ||
binding_reflection = :($fcn(arg1, $(ex0.args[2]); $(kws...))) | ||
else | ||
binding_reflection = :(error("expression is not a function call")) | ||
end | ||
|
@@ -279,28 +299,22 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so | |
$(esc(ex0)) # trigger syntax errors if any | ||
end | ||
nt = generate_merged_namedtuple_type(kwargs) | ||
tt = rewrap_where(:(Tuple{$nt, $(get_typeof.(args)...)}), where_params) | ||
return :($(fcn)(Core.kwcall, $tt; $(kws...))) | ||
nt = Ref(nt) # ignore `get_typeof` handling | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems very sketchy, since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's... completely true. That should be changed. |
||
return gen_call(fcn, Any[:(Core.kwcall), nt, args...], where_params, kws; use_signature_tuple) | ||
elseif ex0.head === :call | ||
argtypes = Any[get_typeof(arg) for arg in ex0.args[2:end]] | ||
args = copy(ex0.args) | ||
if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int) | ||
farg = :(Base.literal_pow) | ||
pushfirst!(argtypes, :(typeof(^))) | ||
argtypes[3] = :(Val{$(ex0.args[3])}) | ||
else | ||
farg = extract_farg(ex0.args[1]) | ||
pushfirst!(args, :(Base.literal_pow)) | ||
args[4] = :(Val($(ex0.args[3]))) | ||
end | ||
tt = rewrap_where(:(Tuple{$(argtypes...)}), where_params) | ||
return Expr(:call, fcn, farg, tt, kws...) | ||
return gen_call(fcn, args, where_params, kws; use_signature_tuple) | ||
elseif ex0.head === :(=) && length(ex0.args) == 2 | ||
lhs, rhs = ex0.args | ||
if isa(lhs, Expr) | ||
if lhs.head === :(.) | ||
return Expr(:call, fcn, Base.setproperty!, | ||
typesof_expr(Any[lhs.args..., rhs], where_params), kws...) | ||
return gen_call(fcn, Any[:(Base.setproperty!), lhs.args..., rhs], where_params, kws; use_signature_tuple) | ||
elseif lhs.head === :ref | ||
return Expr(:call, fcn, Base.setindex!, | ||
typesof_expr(Any[lhs.args[1], rhs, lhs.args[2:end]...], where_params), kws...) | ||
return gen_call(fcn, Any[:(Base.setindex!), lhs.args[1], rhs, lhs.args[2:end]...], where_params, kws; use_signature_tuple) | ||
end | ||
end | ||
elseif ex0.head === :vcat || ex0.head === :typed_vcat | ||
|
@@ -316,54 +330,43 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so | |
lens = map(length, rows) | ||
args = Any[Expr(:tuple, lens...); vcat(rows...)] | ||
ex0.head === :typed_vcat && pushfirst!(args, ex0.args[1]) | ||
return Expr(:call, fcn, hf, typesof_expr(args, where_params), kws...) | ||
return gen_call(fcn, Any[hf, args...], where_params, kws; use_signature_tuple) | ||
else | ||
return Expr(:call, fcn, f, typesof_expr(ex0.args, where_params), kws...) | ||
return gen_call(fcn, Any[f, ex0.args...], where_params, kws; use_signature_tuple) | ||
end | ||
else | ||
for (head, f) in (:ref => Base.getindex, :hcat => Base.hcat, :(.) => Base.getproperty, :vect => Base.vect, Symbol("'") => Base.adjoint, :typed_hcat => Base.typed_hcat, :string => string) | ||
if ex0.head === head | ||
return Expr(:call, fcn, f, typesof_expr(ex0.args, where_params), kws...) | ||
return gen_call(fcn, Any[f, ex0.args...], where_params, kws; use_signature_tuple) | ||
end | ||
end | ||
end | ||
end | ||
if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions* | ||
return Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, #=__module__=#Module, Any[ Core.Typeof(a) for a in ex0.args[3:end] ]...}, kws...) | ||
args = [#=__source__::=#LineNumberNode, #=__module__::=#Module, Core.Typeof.(ex0.args[3:end])...] | ||
return gen_call(fcn, Any[ex0.args[1], Ref.(args)...], where_params, kws; use_signature_tuple) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment about |
||
end | ||
|
||
ex = Meta.lower(__module__, ex0) | ||
if !isa(ex, Expr) | ||
return Expr(:call, :error, "expression is not a function call or symbol") | ||
end | ||
|
||
exret = Expr(:none) | ||
if ex.head === :call | ||
if any(@nospecialize(x) -> isexpr(x, :...), ex0.args) && | ||
(ex.args[1] === GlobalRef(Core,:_apply_iterate) || | ||
ex.args[1] === GlobalRef(Base,:_apply_iterate)) | ||
# check for splatting | ||
Comment on lines
-389
to
-392
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did this get deleted by accident? I don't see why you are still calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I never encountered any execution of that code, and at some point it was committed with an unconditional error (it was using an undefined variable) so I am quite confident it was dead code. My speculative explanation for it would be that we started generating
|
||
exret = Expr(:call, ex.args[2], fcn, | ||
Expr(:tuple, extract_farg(ex.args[3]), typesof_expr(ex.args[4:end], where_params))) | ||
else | ||
exret = Expr(:call, fcn, extract_farg(ex.args[1]), typesof_expr(ex.args[2:end], where_params), kws...) | ||
end | ||
end | ||
if ex.head === :thunk || exret.head === :none | ||
exret = Expr(:call, :error, "expression is not a function call, " | ||
return Expr(:call, :error, "expression is not a function call, " | ||
* "or is too complex for @$fcn to analyze; " | ||
* "break it down to simpler parts if possible. " | ||
* "In some cases, you may want to use Meta.@lower.") | ||
end | ||
return exret | ||
return Expr(:none) | ||
end | ||
|
||
""" | ||
Same behaviour as `gen_call_with_extracted_types` except that keyword arguments | ||
of the form "foo=bar" are passed on to the called function as well. | ||
The keyword arguments must be given before the mandatory argument. | ||
""" | ||
function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false) | ||
function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false, use_signature_tuple = false) | ||
kws = Expr[] | ||
arg = ex0[end] # Mandatory argument | ||
for i in 1:length(ex0)-1 | ||
|
@@ -377,7 +380,7 @@ function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0; is_sourc | |
return Expr(:call, :error, "@$fcn expects only one non-keyword argument") | ||
end | ||
end | ||
return gen_call_with_extracted_types(__module__, fcn, arg, kws; is_source_reflection, supports_binding_reflection) | ||
return gen_call_with_extracted_types(__module__, fcn, arg, kws; is_source_reflection, supports_binding_reflection, use_signature_tuple) | ||
end | ||
|
||
for fname in [:which, :less, :edit, :functionloc] | ||
|
@@ -398,13 +401,13 @@ end | |
for fname in [:code_warntype, :code_llvm, :code_native, | ||
:infer_return_type, :infer_effects, :infer_exception_type] | ||
@eval macro ($fname)(ex0...) | ||
gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false) | ||
gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false, use_signature_tuple = $(in(fname, [:code_warntype, :code_llvm, :code_native]))) | ||
serenity4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
end | ||
|
||
for fname in [:code_typed, :code_lowered, :code_ircode] | ||
@eval macro ($fname)(ex0...) | ||
thecall = gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false) | ||
thecall = gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false, use_signature_tuple = true) | ||
quote | ||
local results = $thecall | ||
length(results) == 1 ? results[1] : results | ||
|
Uh oh!
There was an error while loading. Please reload this page.