Skip to content
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

Add introspection macros support for dot syntax #35522

Merged
merged 2 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions stdlib/InteractiveUtils/src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ import Base: typesof, insert!

separate_kwargs(args...; kwargs...) = (args, kwargs.data)

"""
Transform a dot expression into one where each argument has been replaced by a
variable "xj" (with j an integer from 1 to the returned i).
The list `args` contains the original arguments that have been replaced.
"""
function recursive_dotcalls!(ex, args, i=1)
if !(ex isa Expr) || ((ex.head !== :. || !(ex.args[2] isa Expr)) &&
(ex.head !== :call || string(ex.args[1])[1] != '.'))
newarg = Symbol('x', i)
if ex.head === :...
push!(args, only(ex.args))
return Expr(:..., newarg), i+1
else
push!(args, ex)
return newarg, i+1
end
end
(start, branches) = ex.head === :. ? (1, ex.args[2].args) : (2, ex.args)
for j in start:length(branches)
branch, i = recursive_dotcalls!(branches[j], args, i)
branches[j] = branch
end
return ex, i
end

function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
if isa(ex0, Expr)
if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call)
Expand All @@ -17,6 +42,45 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
insert!(args, (isnothing(i) ? 2 : i+1), ex0.args[2])
ex0 = Expr(:call, args...)
end
if ex0.head === :. || (ex0.head === :call && string(ex0.args[1])[1] == '.')
codemacro = startswith(string(fcn), "code_")
if codemacro && ex0.args[2] isa Expr
# Manually wrap a dot call in a function
args = Any[]
ex, i = recursive_dotcalls!(copy(ex0), args)
xargs = [Symbol('x', j) for j in 1:i-1]
dotfuncname = gensym("dotfunction")
dotfuncdef = Expr(:local, Expr(:(=), Expr(:call, dotfuncname, xargs...), ex))
return quote
$(esc(dotfuncdef))
local args = typesof($(map(esc, args)...))
$(fcn)($(esc(dotfuncname)), args; $(kws...))
end
elseif !codemacro
fully_qualified_symbol = true # of the form A.B.C.D
ex1 = ex0
while ex1 isa Expr && ex1.head === :.
fully_qualified_symbol = (length(ex1.args) == 2 &&
ex1.args[2] isa QuoteNode &&
ex1.args[2].value isa Symbol)
fully_qualified_symbol || break
ex1 = ex1.args[1]
end
fully_qualified_symbol &= ex1 isa Symbol
if fully_qualified_symbol
if string(fcn) == "which"
return quote $(fcn)($(esc(ex0.args[1])), $(ex0.args[2])) end
else
return Expr(:call, :error, "expression is not a function call or symbol")
end
elseif ex0.args[2] isa Expr
return Expr(:call, :error, "dot expressions are not lowered to "
* "a single function call, so @$fcn cannot analyze "
* "them. You may want to use Meta.@lower to identify "
* "which function call to target.")
end
end
end
if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
return quote
local arg1 = $(esc(ex0.args[1]))
Expand All @@ -34,10 +98,10 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
if isa(lhs, Expr)
if lhs.head === :(.)
return Expr(:call, fcn, Base.setproperty!,
Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)))
Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)), kws...)
elseif lhs.head === :ref
return Expr(:call, fcn, Base.setindex!,
Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...))
Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...), kws...)
end
end
elseif ex0.head === :vcat || ex0.head === :typed_vcat
Expand All @@ -55,22 +119,22 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
Expr(:call, typesof,
(ex0.head === :vcat ? [] : Any[esc(ex0.args[1])])...,
Expr(:tuple, lens...),
map(esc, vcat(rows...))...))
map(esc, vcat(rows...))...), kws...)
else
return Expr(:call, fcn, f,
Expr(:call, typesof, map(esc, ex0.args)...))
Expr(:call, typesof, map(esc, ex0.args)...), kws...)
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,
Expr(:call, typesof, map(esc, ex0.args)...))
Expr(:call, typesof, map(esc, ex0.args)...), kws...)
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] ]...})
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...)
end

ex = Meta.lower(__module__, ex0)
Expand All @@ -89,13 +153,14 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
Expr(:call, typesof, map(esc, ex.args[3:end])...)))
else
exret = Expr(:call, fcn, esc(ex.args[1]),
Expr(:call, typesof, map(esc, ex.args[2:end])...))
Expr(:call, typesof, map(esc, ex.args[2:end])...), kws...)
end
end
if ex.head === :thunk || exret.head === :none
exret = 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")
* "break it down to simpler parts if possible. "
* "In some cases, you may want to use Meta.@lower.")
end
return exret
end
Expand Down
14 changes: 14 additions & 0 deletions stdlib/InteractiveUtils/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,20 @@ B33163(x) = x
@test !(@code_typed optimize=false A33163(1, y=2))[1].inferred
@test !(@code_typed optimize=false B33163(1))[1].inferred

@test_throws MethodError (@code_lowered wrongkeyword=true 3 + 4)

# Issue #14637
@test (@which Base.Base.Base.nothing) == Core
@test_throws ErrorException (@functionloc Base.nothing)
@test (@code_typed (3//4).num)[2] == Int

# Issue #28615
@test_throws ErrorException (@which [1, 2] .+ [3, 4])
@test (@code_typed optimize=true max.([1,7], UInt.([4])))[2] == Vector{UInt}
@test (@code_typed Ref.([1,2])[1].x)[2] == Int
@test (@code_typed max.(Ref(true).x))[2] == Bool
@test !isempty(@code_typed optimize=false max.(Ref.([5, 6])...))

module ReflectionTest
using Test, Random, InteractiveUtils

Expand Down
3 changes: 3 additions & 0 deletions stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -916,3 +916,6 @@ end
end
end

# Issue 20620
@test @inferred(.![true, false]) == [false, true]
@test @inferred([3, 4] .- [1, 2] .+ [-2, -2]) == [0, 0]