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

JuliaLang / julia / #37829

04 Jul 2024 08:47PM UTC coverage: 86.588% (+0.9%) from 85.714%
#37829

push

local

web-flow
Support `@opaque Tuple{T,U...}->RT (...)->...` syntax for explicit arg/return types (#54947)

This gives users a way to explicitly specify the return type of an
OpaqueClosure, and it also removes the old syntax `@opaque AT ...` in
preference of `@opaque AT->_ ...`

10 of 12 new or added lines in 2 files covered. (83.33%)

962 existing lines in 39 files now uncovered.

76341 of 88166 relevant lines covered (86.59%)

15255050.41 hits per line

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

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

35
function gen_call_with_extracted_types(__module__, fcn, ex0, kws=Expr[])
153✔
36
    if Meta.isexpr(ex0, :ref)
280✔
UNCOV
37
        ex0 = replace_ref_begin_end!(ex0)
×
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)
150✔
UNCOV
41
        return gen_call_with_extracted_types(__module__, fcn, ex0.args[2])
×
42
    end
43
    if isa(ex0, Expr)
150✔
44
        if ex0.head === :do && Meta.isexpr(get(ex0.args, 1, nothing), :call)
150✔
UNCOV
45
            if length(ex0.args) != 2
×
46
                return Expr(:call, :error, "ill-formed do call")
×
47
            end
UNCOV
48
            i = findlast(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args[1].args)
×
UNCOV
49
            args = copy(ex0.args[1].args)
×
UNCOV
50
            insert!(args, (isnothing(i) ? 2 : 1+i::Int), ex0.args[2])
×
UNCOV
51
            ex0 = Expr(:call, args...)
×
52
        end
53
        if ex0.head === :. || (ex0.head === :call && ex0.args[1] !== :.. && string(ex0.args[1])[1] == '.')
300✔
UNCOV
54
            codemacro = startswith(string(fcn), "code_")
×
UNCOV
55
            if codemacro && (ex0.head === :call || ex0.args[2] isa Expr)
×
56
                # Manually wrap a dot call in a function
UNCOV
57
                args = Any[]
×
UNCOV
58
                ex, i = recursive_dotcalls!(copy(ex0), args)
×
UNCOV
59
                xargs = [Symbol('x', j) for j in 1:i-1]
×
UNCOV
60
                dotfuncname = gensym("dotfunction")
×
UNCOV
61
                dotfuncdef = Expr(:local, Expr(:(=), Expr(:call, dotfuncname, xargs...), ex))
×
UNCOV
62
                return quote
×
63
                    $(esc(dotfuncdef))
64
                    local args = $typesof($(map(esc, args)...))
65
                    $(fcn)($(esc(dotfuncname)), args; $(kws...))
66
                end
UNCOV
67
            elseif !codemacro
×
UNCOV
68
                fully_qualified_symbol = true # of the form A.B.C.D
×
UNCOV
69
                ex1 = ex0
×
UNCOV
70
                while ex1 isa Expr && ex1.head === :.
×
UNCOV
71
                    fully_qualified_symbol = (length(ex1.args) == 2 &&
×
72
                                              ex1.args[2] isa QuoteNode &&
73
                                              ex1.args[2].value isa Symbol)
UNCOV
74
                    fully_qualified_symbol || break
×
UNCOV
75
                    ex1 = ex1.args[1]
×
UNCOV
76
                end
×
UNCOV
77
                fully_qualified_symbol &= ex1 isa Symbol
×
UNCOV
78
                if fully_qualified_symbol
×
UNCOV
79
                    return quote
×
80
                        local arg1 = $(esc(ex0.args[1]))
81
                        if isa(arg1, Module)
82
                            $(if string(fcn) == "which"
UNCOV
83
                                  :(which(arg1, $(ex0.args[2])))
×
84
                              else
UNCOV
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
UNCOV
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)
939✔
101
            return quote
128✔
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
22✔
109
            if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int)
22✔
UNCOV
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]),
22✔
115
                        Expr(:call, typesof, map(esc, ex0.args[2:end])...),
116
                        kws...)
UNCOV
117
        elseif ex0.head === :(=) && length(ex0.args) == 2
×
UNCOV
118
            lhs, rhs = ex0.args
×
UNCOV
119
            if isa(lhs, Expr)
×
UNCOV
120
                if lhs.head === :(.)
×
UNCOV
121
                    return Expr(:call, fcn, Base.setproperty!,
×
122
                                Expr(:call, typesof, map(esc, lhs.args)..., esc(rhs)), kws...)
UNCOV
123
                elseif lhs.head === :ref
×
UNCOV
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
UNCOV
128
        elseif ex0.head === :vcat || ex0.head === :typed_vcat
×
UNCOV
129
            if ex0.head === :vcat
×
UNCOV
130
                f, hf = Base.vcat, Base.hvcat
×
UNCOV
131
                args = ex0.args
×
132
            else
UNCOV
133
                f, hf = Base.typed_vcat, Base.typed_hvcat
×
UNCOV
134
                args = ex0.args[2:end]
×
135
            end
UNCOV
136
            if any(a->isa(a,Expr) && a.head === :row, args)
×
UNCOV
137
                rows = Any[ (isa(x,Expr) && x.head === :row ? x.args : Any[x]) for x in args ]
×
UNCOV
138
                lens = map(length, rows)
×
UNCOV
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
UNCOV
145
                return Expr(:call, fcn, f,
×
146
                            Expr(:call, typesof, map(esc, ex0.args)...), kws...)
147
            end
148
        else
UNCOV
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)
×
UNCOV
150
                if ex0.head === head
×
UNCOV
151
                    return Expr(:call, fcn, f,
×
152
                                Expr(:call, typesof, map(esc, ex0.args)...), kws...)
153
                end
UNCOV
154
            end
×
155
        end
156
    end
UNCOV
157
    if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
×
UNCOV
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

UNCOV
161
    ex = Meta.lower(__module__, ex0)
×
UNCOV
162
    if !isa(ex, Expr)
×
UNCOV
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)
20✔
195
    kws = Expr[]
20✔
196
    arg = ex0[end] # Mandatory argument
20✔
197
    for i in 1:length(ex0)-1
20✔
UNCOV
198
        x = ex0[i]
×
UNCOV
199
        if x isa Expr && x.head === :(=) # Keyword given of the form "foo=bar"
×
UNCOV
200
            if length(x.args) != 2
×
201
                return Expr(:call, :error, "Invalid keyword argument: $x")
×
202
            end
UNCOV
203
            push!(kws, Expr(:kw, esc(x.args[1]), esc(x.args[2])))
×
204
        else
205
            return Expr(:call, :error, "@$fcn expects only one non-keyword argument")
×
206
        end
UNCOV
207
    end
×
208
    return gen_call_with_extracted_types(__module__, fcn, arg, kws)
20✔
209
end
210

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

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

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

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

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

248
macro time_imports(ex)
249
    quote
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

© 2025 Coveralls, Inc