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

JuliaLang / julia / #37649

12 Oct 2023 05:15AM UTC coverage: 87.56% (+0.9%) from 86.678%
#37649

push

local

web-flow
Reinstate load-time Pkg.precompile (#51672)

5 of 5 new or added lines in 1 file covered. (100.0%)

72441 of 82733 relevant lines covered (87.56%)

12848798.04 hits per line

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

23.21
/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))
700✔
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)
×
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 Meta.isexpr(ex, :...)
×
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
    length_branches = length(branches)::Int
×
28
    for j in start:length_branches
×
29
        branch, i = recursive_dotcalls!(branches[j], args, i)
×
30
        branches[j] = branch
×
31
    end
×
32
    return ex, i
×
33
end
34

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

152
    ex = Meta.lower(__module__, ex0)
×
153
    if !isa(ex, Expr)
×
154
        return Expr(:call, :error, "expression is not a function call or symbol")
×
155
    end
156

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

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

202
for fname in [:which, :less, :edit, :functionloc]
1✔
203
    @eval begin
4✔
204
        macro ($fname)(ex0)
5✔
205
            gen_call_with_extracted_types(__module__, $(Expr(:quote, fname)), ex0)
5✔
206
        end
207
    end
208
end
4✔
209

210
macro which(ex0::Symbol)
211
    ex0 = QuoteNode(ex0)
212
    return :(which($__module__, $ex0))
213
end
214

215
for fname in [:code_warntype, :code_llvm, :code_native, :infer_effects]
216
    @eval begin
217
        macro ($fname)(ex0...)
218
            gen_call_with_extracted_types_and_kwargs(__module__, $(Expr(:quote, fname)), ex0)
219
        end
220
    end
221
end
222

223
macro code_typed(ex0...)
2✔
224
    thecall = gen_call_with_extracted_types_and_kwargs(__module__, :code_typed, ex0)
2✔
225
    quote
2✔
226
        local results = $thecall
227
        length(results) == 1 ? results[1] : results
228
    end
229
end
230

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

239
macro time_imports(ex)
240
    quote
241
        try
242
            Base.Threads.atomic_add!(Base.TIMING_IMPORTS, 1)
243
            $(esc(ex))
244
        finally
245
            Base.Threads.atomic_sub!(Base.TIMING_IMPORTS, 1)
246
        end
247
    end
248
end
249

250
"""
251
    @functionloc
252

253
Applied to a function or macro call, it evaluates the arguments to the specified call, and
254
returns a tuple `(filename,line)` giving the location for the method that would be called for those arguments.
255
It calls out to the [`functionloc`](@ref) function.
256
"""
257
:@functionloc
258

259
"""
260
    @which
261

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

267
See also: [`@less`](@ref), [`@edit`](@ref).
268
"""
269
:@which
270

271
"""
272
    @less
273

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

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

281
"""
282
    @edit
283

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

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

291
"""
292
    @code_typed
293

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

297
    @code_typed optimize=true foo(x)
298

299
to control whether additional optimizations, such as inlining, are also applied.
300

301
See also: [`code_typed`](@ref), [`@code_warntype`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
302
"""
303
:@code_typed
304

305
"""
306
    @code_lowered
307

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

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

315
"""
316
    @code_warntype
317

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

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

325
"""
326
    @code_llvm
327

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

333
    @code_llvm raw=true dump_module=true debuginfo=:default f(x)
334
    @code_llvm optimize=false f(x)
335

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

341
See also: [`code_llvm`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_native`](@ref).
342
"""
343
:@code_llvm
344

345
"""
346
    @code_native
347

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

351
Set any of the optional keyword arguments `syntax`, `debuginfo`, `binary` or `dump_module`
352
by putting it before the function call, like this:
353

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

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

361
See also: [`code_native`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref).
362
"""
363
:@code_native
364

365
"""
366
    @time_imports
367

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

371
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.
372

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

375
!!! note
376
    During the load process a package sequentially imports all of its dependencies, not just its direct dependencies.
377

378
```julia-repl
379
julia> @time_imports using CSV
380
     50.7 ms  Parsers 17.52% compilation time
381
      0.2 ms  DataValueInterfaces
382
      1.6 ms  DataAPI
383
      0.1 ms  IteratorInterfaceExtensions
384
      0.1 ms  TableTraits
385
     17.5 ms  Tables
386
     26.8 ms  PooledArrays
387
    193.7 ms  SentinelArrays 75.12% compilation time
388
      8.6 ms  InlineStrings
389
     20.3 ms  WeakRefStrings
390
      2.0 ms  TranscodingStreams
391
      1.4 ms  Zlib_jll
392
      1.8 ms  CodecZlib
393
      0.8 ms  Compat
394
     13.1 ms  FilePathsBase 28.39% compilation time
395
   1681.2 ms  CSV 92.40% compilation time
396
```
397

398
!!! compat "Julia 1.8"
399
    This macro requires at least Julia 1.8
400

401
"""
402
:@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

© 2025 Coveralls, Inc