diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index d3695cc437834..6bed2a0be0b07 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -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) @@ -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])) @@ -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 @@ -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) @@ -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 diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 808aa6de85981..7bbc2b321a440 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -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 diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index e50ae7f15429c..f3c0d4969ddfe 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -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]