Skip to content

Commit 4c7ae77

Browse files
authoredApr 24, 2020
Add introspection macros support for dot syntax (#35522)
1 parent 641f444 commit 4c7ae77

File tree

3 files changed

+90
-8
lines changed

3 files changed

+90
-8
lines changed
 

‎stdlib/InteractiveUtils/src/macros.jl

+73-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ import Base: typesof, insert!
66

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

9+
"""
10+
Transform a dot expression into one where each argument has been replaced by a
11+
variable "xj" (with j an integer from 1 to the returned i).
12+
The list `args` contains the original arguments that have been replaced.
13+
"""
14+
function recursive_dotcalls!(ex, args, i=1)
15+
if !(ex isa Expr) || ((ex.head !== :. || !(ex.args[2] isa Expr)) &&
16+
(ex.head !== :call || string(ex.args[1])[1] != '.'))
17+
newarg = Symbol('x', i)
18+
if ex.head === :...
19+
push!(args, only(ex.args))
20+
return Expr(:..., newarg), i+1
21+
else
22+
push!(args, ex)
23+
return newarg, i+1
24+
end
25+
end
26+
(start, branches) = ex.head === :. ? (1, ex.args[2].args) : (2, ex.args)
27+
for j in start:length(branches)
28+
branch, i = recursive_dotcalls!(branches[j], args, i)
29+
branches[j] = branch
30+
end
31+
return ex, i
32+
end
33+
934
function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
1035
if isa(ex0, Expr)
1136
if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call)
@@ -17,6 +42,45 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
1742
insert!(args, (isnothing(i) ? 2 : i+1), ex0.args[2])
1843
ex0 = Expr(:call, args...)
1944
end
45+
if ex0.head === :. || (ex0.head === :call && string(ex0.args[1])[1] == '.')
46+
codemacro = startswith(string(fcn), "code_")
47+
if codemacro && ex0.args[2] isa Expr
48+
# Manually wrap a dot call in a function
49+
args = Any[]
50+
ex, i = recursive_dotcalls!(copy(ex0), args)
51+
xargs = [Symbol('x', j) for j in 1:i-1]
52+
dotfuncname = gensym("dotfunction")
53+
dotfuncdef = Expr(:local, Expr(:(=), Expr(:call, dotfuncname, xargs...), ex))
54+
return quote
55+
$(esc(dotfuncdef))
56+
local args = typesof($(map(esc, args)...))
57+
$(fcn)($(esc(dotfuncname)), args; $(kws...))
58+
end
59+
elseif !codemacro
60+
fully_qualified_symbol = true # of the form A.B.C.D
61+
ex1 = ex0
62+
while ex1 isa Expr && ex1.head === :.
63+
fully_qualified_symbol = (length(ex1.args) == 2 &&
64+
ex1.args[2] isa QuoteNode &&
65+
ex1.args[2].value isa Symbol)
66+
fully_qualified_symbol || break
67+
ex1 = ex1.args[1]
68+
end
69+
fully_qualified_symbol &= ex1 isa Symbol
70+
if fully_qualified_symbol
71+
if string(fcn) == "which"
72+
return quote $(fcn)($(esc(ex0.args[1])), $(ex0.args[2])) end
73+
else
74+
return Expr(:call, :error, "expression is not a function call or symbol")
75+
end
76+
elseif ex0.args[2] isa Expr
77+
return Expr(:call, :error, "dot expressions are not lowered to "
78+
* "a single function call, so @$fcn cannot analyze "
79+
* "them. You may want to use Meta.@lower to identify "
80+
* "which function call to target.")
81+
end
82+
end
83+
end
2084
if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
2185
return quote
2286
local arg1 = $(esc(ex0.args[1]))
@@ -34,10 +98,10 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
3498
if isa(lhs, Expr)
3599
if lhs.head === :(.)
36100
return Expr(:call, fcn, Base.setproperty!,
37-
Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)))
101+
Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)), kws...)
38102
elseif lhs.head === :ref
39103
return Expr(:call, fcn, Base.setindex!,
40-
Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...))
104+
Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...), kws...)
41105
end
42106
end
43107
elseif ex0.head === :vcat || ex0.head === :typed_vcat
@@ -55,22 +119,22 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
55119
Expr(:call, typesof,
56120
(ex0.head === :vcat ? [] : Any[esc(ex0.args[1])])...,
57121
Expr(:tuple, lens...),
58-
map(esc, vcat(rows...))...))
122+
map(esc, vcat(rows...))...), kws...)
59123
else
60124
return Expr(:call, fcn, f,
61-
Expr(:call, typesof, map(esc, ex0.args)...))
125+
Expr(:call, typesof, map(esc, ex0.args)...), kws...)
62126
end
63127
else
64128
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)
65129
if ex0.head === head
66130
return Expr(:call, fcn, f,
67-
Expr(:call, typesof, map(esc, ex0.args)...))
131+
Expr(:call, typesof, map(esc, ex0.args)...), kws...)
68132
end
69133
end
70134
end
71135
end
72136
if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
73-
return Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, #=__module__=#Module, Any[ Core.Typeof(a) for a in ex0.args[3:end] ]...})
137+
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...)
74138
end
75139

76140
ex = Meta.lower(__module__, ex0)
@@ -89,13 +153,14 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
89153
Expr(:call, typesof, map(esc, ex.args[3:end])...)))
90154
else
91155
exret = Expr(:call, fcn, esc(ex.args[1]),
92-
Expr(:call, typesof, map(esc, ex.args[2:end])...))
156+
Expr(:call, typesof, map(esc, ex.args[2:end])...), kws...)
93157
end
94158
end
95159
if ex.head === :thunk || exret.head === :none
96160
exret = Expr(:call, :error, "expression is not a function call, "
97161
* "or is too complex for @$fcn to analyze; "
98-
* "break it down to simpler parts if possible")
162+
* "break it down to simpler parts if possible. "
163+
* "In some cases, you may want to use Meta.@lower.")
99164
end
100165
return exret
101166
end

‎stdlib/InteractiveUtils/test/runtests.jl

+14
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,20 @@ B33163(x) = x
318318
@test !(@code_typed optimize=false A33163(1, y=2))[1].inferred
319319
@test !(@code_typed optimize=false B33163(1))[1].inferred
320320

321+
@test_throws MethodError (@code_lowered wrongkeyword=true 3 + 4)
322+
323+
# Issue #14637
324+
@test (@which Base.Base.Base.nothing) == Core
325+
@test_throws ErrorException (@functionloc Base.nothing)
326+
@test (@code_typed (3//4).num)[2] == Int
327+
328+
# Issue #28615
329+
@test_throws ErrorException (@which [1, 2] .+ [3, 4])
330+
@test (@code_typed optimize=true max.([1,7], UInt.([4])))[2] == Vector{UInt}
331+
@test (@code_typed Ref.([1,2])[1].x)[2] == Int
332+
@test (@code_typed max.(Ref(true).x))[2] == Bool
333+
@test !isempty(@code_typed optimize=false max.(Ref.([5, 6])...))
334+
321335
module ReflectionTest
322336
using Test, Random, InteractiveUtils
323337

‎stdlib/Test/test/runtests.jl

+3
Original file line numberDiff line numberDiff line change
@@ -916,3 +916,6 @@ end
916916
end
917917
end
918918

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

0 commit comments

Comments
 (0)
Please sign in to comment.