• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37737

04 Apr 2024 03:40AM UTC coverage: 81.432% (+0.009%) from 81.423%
#37737

push

local

web-flow
Update stable version in README.md (#53947)

[skip ci]

70716 of 86841 relevant lines covered (81.43%)

15733895.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.2
/stdlib/InteractiveUtils/src/macros.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# macro wrappers for various reflection functions
4

5
import Base: typesof, insert!, replace_ref_begin_end!, infer_effects
6

7
separate_kwargs(args...; kwargs...) = (args, values(kwargs))
714✔
8

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)
28✔
15
    if !(ex isa Expr) || ((ex.head !== :. || !(ex.args[2] isa Expr)) &&
45✔
16
                          (ex.head !== :call || string(ex.args[1])[1] != '.'))
17
        newarg = Symbol('x', i)
16✔
18
        if Meta.isexpr(ex, :...)
16✔
19
            push!(args, only(ex.args))
1✔
20
            return Expr(:..., newarg), i+1
1✔
21
        else
22
            push!(args, ex)
15✔
23
            return newarg, i+1
15✔
24
        end
25
    end
26
    (start, branches) = ex.head === :. ? (1, ex.args[2].args) : (2, ex.args)
12✔
27
    length_branches = length(branches)::Int
12✔
28
    for j in start:length_branches
12✔
29
        branch, i = recursive_dotcalls!(branches[j], args, i)
34✔
30
        branches[j] = branch
19✔
31
    end
26✔
32
    return ex, i
12✔
33
end
34

35
function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
283✔
36
    if Meta.isexpr(ex0, :ref)
473✔
37
        ex0 = replace_ref_begin_end!(ex0)
24✔
38
    end
39
    # assignments get bypassed: @edit a = f(x) <=> @edit f(x)
40
    if isa(ex0, Expr) && ex0.head == :(=) && isa(ex0.args[1], Symbol) && isempty(kws)
242✔
41
        return gen_call_with_extracted_types(__module__, fcn, ex0.args[2])
13✔
42
    end
43
    if isa(ex0, Expr)
238✔
44
        if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call)
235✔
45
            if length(ex0.args) != 2
221✔
46
                return Expr(:call, :error, "ill-formed do call")
×
47
            end
48
            i = findlast(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args[1].args)
574✔
49
            args = copy(ex0.args[1].args)
139✔
50
            insert!(args, (isnothing(i) ? 2 : 1+i::Int), ex0.args[2])
138✔
51
            ex0 = Expr(:call, args...)
86✔
52
        end
53
        if ex0.head === :. || (ex0.head === :call && ex0.args[1] !== :.. && string(ex0.args[1])[1] == '.')
548✔
54
            codemacro = startswith(string(fcn), "code_")
50✔
55
            if codemacro && (ex0.head === :call || ex0.args[2] isa Expr)
99✔
56
                # Manually wrap a dot call in a function
57
                args = Any[]
9✔
58
                ex, i = recursive_dotcalls!(copy(ex0), args)
114✔
59
                xargs = [Symbol('x', j) for j in 1:i-1]
96✔
60
                dotfuncname = gensym("dotfunction")
11✔
61
                dotfuncdef = Expr(:local, Expr(:(=), Expr(:call, dotfuncname, xargs...), ex))
29✔
62
                return quote
70✔
63
                    $(esc(dotfuncdef))
64
                    local args = $typesof($(map(esc, args)...))
65
                    $(fcn)($(esc(dotfuncname)), args; $(kws...))
66
                end
67
            elseif !codemacro
14✔
68
                fully_qualified_symbol = true # of the form A.B.C.D
11✔
69
                ex1 = ex0
16✔
70
                while ex1 isa Expr && ex1.head === :.
17✔
71
                    fully_qualified_symbol = (length(ex1.args) == 2 &&
27✔
72
                                              ex1.args[2] isa QuoteNode &&
73
                                              ex1.args[2].value isa Symbol)
74
                    fully_qualified_symbol || break
×
75
                    ex1 = ex1.args[1]
×
76
                end
×
77
                fully_qualified_symbol &= ex1 isa Symbol
×
78
                if fully_qualified_symbol
×
79
                    return quote
×
80
                        local arg1 = $(esc(ex0.args[1]))
81
                        if isa(arg1, Module)
82
                            $(if string(fcn) == "which"
83
                                  :(which(arg1, $(ex0.args[2])))
×
84
                              else
85
                                  :(error("expression is not a function call"))
×
86
                              end)
87
                        else
88
                            local args = $typesof($(map(esc, ex0.args)...))
89
                            $(fcn)(Base.getproperty, args)
90
                        end
91
                    end
92
                else
93
                    return Expr(:call, :error, "dot expressions are not lowered to "
×
94
                                * "a single function call, so @$fcn cannot analyze "
95
                                * "them. You may want to use Meta.@lower to identify "
96
                                * "which function call to target.")
97
                end
98
            end
99
        end
100
        if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
954✔
101
            return quote
×
102
                local arg1 = $(esc(ex0.args[1]))
103
                local args, kwargs = $separate_kwargs($(map(esc, ex0.args[2:end])...))
104
                $(fcn)(Core.kwcall,
105
                       Tuple{typeof(kwargs), Core.Typeof(arg1), map(Core.Typeof, args)...};
106
                       $(kws...))
107
            end
108
        elseif ex0.head === :call
×
109
            if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int)
×
110
                return Expr(:call, fcn, :(Base.literal_pow),
×
111
                            Expr(:call, typesof, esc(ex0.args[1]), esc(ex0.args[2]),
112
                                 esc(Val(ex0.args[3]))))
113
            end
114
            return Expr(:call, fcn, esc(ex0.args[1]),
×
115
                        Expr(:call, typesof, map(esc, ex0.args[2:end])...),
116
                        kws...)
117
        elseif ex0.head === :(=) && length(ex0.args) == 2
×
118
            lhs, rhs = ex0.args
×
119
            if isa(lhs, Expr)
×
120
                if lhs.head === :(.)
×
121
                    return Expr(:call, fcn, Base.setproperty!,
×
122
                                Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)), kws...)
123
                elseif lhs.head === :ref
×
124
                    return Expr(:call, fcn, Base.setindex!,
×
125
                                Expr(:call, typesof, esc(lhs.args[1]), esc(rhs), map(esc, lhs.args[2:end])...), kws...)
126
                end
127
            end
128
        elseif ex0.head === :vcat || ex0.head === :typed_vcat
×
129
            if ex0.head === :vcat
×
130
                f, hf = Base.vcat, Base.hvcat
×
131
                args = ex0.args
×
132
            else
133
                f, hf = Base.typed_vcat, Base.typed_hvcat
×
134
                args = ex0.args[2:end]
×
135
            end
136
            if any(a->isa(a,Expr) && a.head === :row, args)
6✔
137
                rows = Any[ (isa(x,Expr) && x.head === :row ? x.args : Any[x]) for x in args ]
×
138
                lens = map(length, rows)
×
139
                return Expr(:call, fcn, hf,
×
140
                            Expr(:call, typesof,
141
                                 (ex0.head === :vcat ? [] : Any[esc(ex0.args[1])])...,
142
                                 Expr(:tuple, lens...),
143
                                 map(esc, vcat(rows...))...), kws...)
144
            else
145
                return Expr(:call, fcn, f,
×
146
                            Expr(:call, typesof, map(esc, ex0.args)...), kws...)
147
            end
148
        else
149
            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)
×
150
                if ex0.head === head
×
151
                    return Expr(:call, fcn, f,
×
152
                                Expr(:call, typesof, map(esc, ex0.args)...), kws...)
153
                end
154
            end
×
155
        end
156
    end
157
    if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
×
158
        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...)
×
159
    end
160

161
    ex = Meta.lower(__module__, ex0)
×
162
    if !isa(ex, Expr)
×
163
        return Expr(:call, :error, "expression is not a function call or symbol")
×
164
    end
165

166
    exret = Expr(:none)
×
167
    if ex.head === :call
×
168
        if any(e->(isa(e, Expr) && e.head === :(...)), ex0.args) &&
×
169
            (ex.args[1] === GlobalRef(Core,:_apply_iterate) ||
170
             ex.args[1] === GlobalRef(Base,:_apply_iterate))
171
            # check for splatting
172
            exret = Expr(:call, ex.args[2], fcn,
×
173
                        Expr(:tuple, esc(ex.args[3]),
174
                            Expr(:call, typesof, map(esc, ex.args[4:end])...)))
175
        else
176
            exret = Expr(:call, fcn, esc(ex.args[1]),
×
177
                         Expr(:call, typesof, map(esc, ex.args[2:end])...), kws...)
178
        end
179
    end
180
    if ex.head === :thunk || exret.head === :none
×
181
        exret = Expr(:call, :error, "expression is not a function call, "
×
182
                                  * "or is too complex for @$fcn to analyze; "
183
                                  * "break it down to simpler parts if possible. "
184
                                  * "In some cases, you may want to use Meta.@lower.")
185
    end
186
    return exret
×
187
end
188

189
"""
190
Same behaviour as `gen_call_with_extracted_types` except that keyword arguments
191
of the form "foo=bar" are passed on to the called function as well.
192
The keyword arguments must be given before the mandatory argument.
193
"""
194
function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0)
56✔
195
    kws = Expr[]
56✔
196
    arg = ex0[end] # Mandatory argument
56✔
197
    for i in 1:length(ex0)-1
56✔
198
        x = ex0[i]
9✔
199
        if x isa Expr && x.head === :(=) # Keyword given of the form "foo=bar"
9✔
200
            if length(x.args) != 2
9✔
201
                return Expr(:call, :error, "Invalid keyword argument: $x")
×
202
            end
203
            push!(kws, Expr(:kw, esc(x.args[1]), esc(x.args[2])))
9✔
204
        else
205
            return Expr(:call, :error, "@$fcn expects only one non-keyword argument")
×
206
        end
207
    end
9✔
208
    return gen_call_with_extracted_types(__module__, fcn, arg, kws)
56✔
209
end
210

211
for fname in [:which, :less, :edit, :functionloc]
212
    @eval begin
213
        macro ($fname)(ex0)
49✔
214
            gen_call_with_extracted_types(__module__, $(Expr(:quote, fname)), ex0)
49✔
215
        end
216
    end
217
end
218

219
macro which(ex0::Symbol)
1✔
220
    ex0 = QuoteNode(ex0)
1✔
221
    return :(which($__module__, $ex0))
1✔
222
end
223

224
for fname in [:code_warntype, :code_llvm, :code_native, :infer_effects]
225
    @eval begin
226
        macro ($fname)(ex0...)
4✔
227
            gen_call_with_extracted_types_and_kwargs(__module__, $(Expr(:quote, fname)), ex0)
4✔
228
        end
229
    end
230
end
231

232
macro code_typed(ex0...)
27✔
233
    thecall = gen_call_with_extracted_types_and_kwargs(__module__, :code_typed, ex0)
27✔
234
    quote
27✔
235
        local results = $thecall
2✔
236
        length(results) == 1 ? results[1] : results
1✔
237
    end
238
end
239

240
macro code_lowered(ex0...)
16✔
241
    thecall = gen_call_with_extracted_types_and_kwargs(__module__, :code_lowered, ex0)
16✔
242
    quote
16✔
243
        local results = $thecall
244
        length(results) == 1 ? results[1] : results
245
    end
246
end
247

248
macro time_imports(ex)
1✔
249
    quote
1✔
250
        try
251
            Base.Threads.atomic_add!(Base.TIMING_IMPORTS, 1)
252
            $(esc(ex))
253
        finally
254
            Base.Threads.atomic_sub!(Base.TIMING_IMPORTS, 1)
255
        end
256
    end
257
end
258

259
"""
260
    @functionloc
261

262
Applied to a function or macro call, it evaluates the arguments to the specified call, and
263
returns a tuple `(filename,line)` giving the location for the method that would be called for those arguments.
264
It calls out to the [`functionloc`](@ref) function.
265
"""
266
:@functionloc
267

268
"""
269
    @which
270

271
Applied to a function or macro call, it evaluates the arguments to the specified call, and
272
returns the `Method` object for the method that would be called for those arguments. Applied
273
to a variable, it returns the module in which the variable was bound. It calls out to the
274
[`which`](@ref) function.
275

276
See also: [`@less`](@ref), [`@edit`](@ref).
277
"""
278
:@which
279

280
"""
281
    @less
282

283
Evaluates the arguments to the function or macro call, determines their types, and calls the [`less`](@ref)
284
function on the resulting expression.
285

286
See also: [`@edit`](@ref), [`@which`](@ref), [`@code_lowered`](@ref).
287
"""
288
:@less
289

290
"""
291
    @edit
292

293
Evaluates the arguments to the function or macro call, determines their types, and calls the [`edit`](@ref)
294
function on the resulting expression.
295

296
See also: [`@less`](@ref), [`@which`](@ref).
297
"""
298
:@edit
299

300
"""
301
    @code_typed
302

303
Evaluates the arguments to the function or macro call, determines their types, and calls
304
[`code_typed`](@ref) on the resulting expression. Use the optional argument `optimize` with
305

306
    @code_typed optimize=true foo(x)
307

308
to control whether additional optimizations, such as inlining, are also applied.
309

310
See also: [`code_typed`](@ref), [`@code_warntype`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
311
"""
312
:@code_typed
313

314
"""
315
    @code_lowered
316

317
Evaluates the arguments to the function or macro call, determines their types, and calls
318
[`code_lowered`](@ref) on the resulting expression.
319

320
See also: [`code_lowered`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
321
"""
322
:@code_lowered
323

324
"""
325
    @code_warntype
326

327
Evaluates the arguments to the function or macro call, determines their types, and calls
328
[`code_warntype`](@ref) on the resulting expression.
329

330
See also: [`code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
331
"""
332
:@code_warntype
333

334
"""
335
    @code_llvm
336

337
Evaluates the arguments to the function or macro call, determines their types, and calls
338
[`code_llvm`](@ref) on the resulting expression.
339
Set the optional keyword arguments `raw`, `dump_module`, `debuginfo`, `optimize`
340
by putting them and their value before the function call, like this:
341

342
    @code_llvm raw=true dump_module=true debuginfo=:default f(x)
343
    @code_llvm optimize=false f(x)
344

345
`optimize` controls whether additional optimizations, such as inlining, are also applied.
346
`raw` makes all metadata and dbg.* calls visible.
347
`debuginfo` may be one of `:source` (default) or `:none`,  to specify the verbosity of code comments.
348
`dump_module` prints the entire module that encapsulates the function.
349

350
See also: [`code_llvm`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_native`](@ref).
351
"""
352
:@code_llvm
353

354
"""
355
    @code_native
356

357
Evaluates the arguments to the function or macro call, determines their types, and calls
358
[`code_native`](@ref) on the resulting expression.
359

360
Set any of the optional keyword arguments `syntax`, `debuginfo`, `binary` or `dump_module`
361
by putting it before the function call, like this:
362

363
    @code_native syntax=:intel debuginfo=:default binary=true dump_module=false f(x)
364

365
* Set assembly syntax by setting `syntax` to `:intel` (default) for Intel syntax or `:att` for AT&T syntax.
366
* Specify verbosity of code comments by setting `debuginfo` to `:source` (default) or `:none`.
367
* If `binary` is `true`, also print the binary machine code for each instruction precedented by an abbreviated address.
368
* If `dump_module` is `false`, do not print metadata such as rodata or directives.
369

370
See also: [`code_native`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref).
371
"""
372
:@code_native
373

374
"""
375
    @time_imports
376

377
A macro to execute an expression and produce a report of any time spent importing packages and their
378
dependencies. Any compilation time will be reported as a percentage, and how much of which was recompilation, if any.
379

380
One line is printed per package or package extension. The duration shown is the time to import that package itself, not including the time to load any of its dependencies.
381

382
On Julia 1.9+ [package extensions](@ref man-extensions) will show as Parent → Extension.
383

384
!!! note
385
    During the load process a package sequentially imports all of its dependencies, not just its direct dependencies.
386

387
```julia-repl
388
julia> @time_imports using CSV
389
     50.7 ms  Parsers 17.52% compilation time
390
      0.2 ms  DataValueInterfaces
391
      1.6 ms  DataAPI
392
      0.1 ms  IteratorInterfaceExtensions
393
      0.1 ms  TableTraits
394
     17.5 ms  Tables
395
     26.8 ms  PooledArrays
396
    193.7 ms  SentinelArrays 75.12% compilation time
397
      8.6 ms  InlineStrings
398
     20.3 ms  WeakRefStrings
399
      2.0 ms  TranscodingStreams
400
      1.4 ms  Zlib_jll
401
      1.8 ms  CodecZlib
402
      0.8 ms  Compat
403
     13.1 ms  FilePathsBase 28.39% compilation time
404
   1681.2 ms  CSV 92.40% compilation time
405
```
406

407
!!! compat "Julia 1.8"
408
    This macro requires at least Julia 1.8
409

410
"""
411
:@time_imports
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc