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

JuliaLang / julia / #38203

26 Aug 2025 10:51PM UTC coverage: 78.544% (+0.2%) from 78.315%
#38203

push

local

web-flow
System image compression with zstd (#59227)

Revived version of #48244, with a slightly different approach. This
version looks for a function pointer called `jl_image_unpack` inside
compiled system images and invokes it to get the `jl_image_buf_t`
struct. Two implementations, `jl_image_unpack_zstd` and
`jl_image_unpack_uncomp` are provided (for comparison). The zstd
compression is applied only to the heap image, and not the compiled
code, since that can be shared across Julia processes.

TODO: test a few different compression settings and enable by default.

Example data from un-trimmed juliac "hello world":
```
156M  hello-uncomp
 43M  hello-zstd
 48M  hello-zstd-1
 45M  hello-zstd-5
 43M  hello-zstd-15
 39M  hello-zstd-22

$ hyperfine -w3 ./hello-uncomp 
Benchmark 1: ./hello-uncomp
  Time (mean ± σ):      74.4 ms ±   0.8 ms    [User: 51.9 ms, System: 19.0 ms]
  Range (min … max):    73.0 ms …  76.6 ms    39 runs

$ hyperfine -w3 ./hello-zstd-1
Benchmark 1: ./hello-zstd-1
  Time (mean ± σ):     152.4 ms ±   0.5 ms    [User: 138.2 ms, System: 12.0 ms]
  Range (min … max):   151.4 ms … 153.2 ms    19 runs
 
$ hyperfine -w3 ./hello-zstd-5 
Benchmark 1: ./hello-zstd-5
  Time (mean ± σ):     154.3 ms ±   0.5 ms    [User: 139.6 ms, System: 12.4 ms]
  Range (min … max):   153.5 ms … 155.2 ms    19 runs

$ hyperfine -w3 ./hello-zstd-15
Benchmark 1: ./hello-zstd-15
  Time (mean ± σ):     135.9 ms ±   0.5 ms    [User: 121.6 ms, System: 12.0 ms]
  Range (min … max):   135.1 ms … 136.5 ms    21 runs
 
$ hyperfine -w3 ./hello-zstd-22
Benchmark 1: ./hello-zstd-22
  Time (mean ± σ):     149.0 ms ±   0.6 ms    [User: 134.7 ms, System: 12.1 ms]
  Range (min … max):   147.7 ms … 150.4 ms    19 runs
```

---------

Co-authored-by: Gabriel Baraldi <baraldigabriel@gmail.com>

1 of 2 new or added lines in 1 file covered. (50.0%)

302 existing lines in 13 files now uncovered.

48863 of 62211 relevant lines covered (78.54%)

9804672.05 hits per line

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

94.19
/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
using Base: insert!, replace_ref_begin_end!,
6
    infer_return_type, infer_exception_type, infer_effects, code_ircode, isexpr
7

8
# defined in Base so it's possible to time all imports, including InteractiveUtils and its deps
9
# via. `Base.@time_imports` etc.
10
import Base: @time_imports, @trace_compile, @trace_dispatch
11

12
typesof_expr(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(Any[esc(reescape(get_typeof, a)) for a in args]...)}), where_params)
320✔
13
typesof_expr_unescaped(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(Any[reescape(get_typeof, a) for a in args]...)}), where_params)
64✔
14

15
function extract_where_parameters(ex::Expr)
16
    isexpr(ex, :where) || return ex, nothing
645✔
17
    ex.args[1], ex.args[2:end]
10✔
18
end
19

20
function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}})
6✔
21
    isnothing(where_params) && return ex
384✔
22
    Expr(:where, ex, esc.(where_params)...)
6✔
23
end
24

25
function reescape(f::Function, @nospecialize ex)
1,304✔
26
    isa(ex, Expr) || return f(ex)
2,020✔
27
    unescaped = Meta.unescape(ex)
588✔
28
    new = f(unescaped)
588✔
29
    return reescape(new, ex)
588✔
30
end
31

32
function reescape(@nospecialize(unescaped_expr), @nospecialize(original_expr))
856✔
33
    if isexpr(original_expr, :escape)
856✔
34
        return reescape(Expr(:escape, unescaped_expr), original_expr.args[1])
268✔
35
    elseif isexpr(original_expr, :var"hygienic-scope")
588✔
UNCOV
36
        return reescape(Expr(:var"hygienic-scope", unescaped_expr, original_expr.args[2]), original_expr.args[1])
×
37
    else
38
        return unescaped_expr
588✔
39
    end
40
end
41

42
get_typeof(ex::Ref) = ex[]
181✔
43
function get_typeof(@nospecialize ex)
967✔
44
    isexpr(ex, :(::), 1) && return ex.args[1]
967✔
45
    isexpr(ex, :(::), 2) && return ex.args[2]
874✔
46
    if isexpr(ex, :..., 1)
864✔
47
        splatted = ex.args[1]
150✔
48
        isexpr(splatted, :(::), 1) && return Expr(:curly, :(Core.Vararg), splatted.args[1])
150✔
49
        return :(Any[Core.Typeof(x) for x in $splatted]...)
146✔
50
    end
51
    return :(Core.Typeof($ex))
714✔
52
end
53

54
function is_broadcasting_call(ex)
711✔
55
    isa(ex, Expr) || return false
726✔
56
    # Standard broadcasting: f.(x)
57
    isexpr(ex, :.) && length(ex.args) ≥ 2 && isexpr(ex.args[2], :tuple) && return true
711✔
58
    # Infix broadcasting: x .+ y, x .<< y, etc.
59
    if isexpr(ex, :call)
686✔
60
        f = ex.args[1]
512✔
61
        f == :.. && return false
1,024✔
62
        string(f)[1] == '.' && return true
510✔
63
    end
64
    return false
659✔
65
end
66
is_broadcasting_expr(ex) = is_broadcasting_call(ex) || is_broadcasting_assignment(ex)
1,387✔
67
function is_broadcasting_assignment(ex)
698✔
68
    isa(ex, Expr) || return false
713✔
69
    isexpr(ex, :.) && return false
698✔
70
    head = string(ex.head)
672✔
71
    # x .= y, x .+= y, x .<<= y, etc.
72
    head[begin] == '.' && head[end] == '=' && return true
700✔
73
    return false
646✔
74
end
75

76
"""
77
Transform a dot expression into one where each argument has been replaced by a
78
variable "xj" (with j an integer from 1 to the returned i).
79
The list `args` contains the original arguments that have been replaced.
80
"""
81
function recursive_dotcalls!(ex, args, i=1)
108✔
82
    if is_broadcasting_expr(ex)
199✔
83
        if is_broadcasting_assignment(ex)
37✔
84
            (start, branches) = (1, ex.args)
8✔
85
        elseif isexpr(ex, :.)
29✔
86
            (start, branches) = (1, ex.args[2].args)
14✔
87
        else
88
            (start, branches) = (2, ex.args)
15✔
89
        end
90
        for j in start:length(branches)::Int
37✔
91
            branch, i = recursive_dotcalls!(branches[j], args, i)
74✔
92
            branches[j] = branch
74✔
93
        end
111✔
94
        return ex, i
37✔
95
    elseif isexpr(ex, :parameters)
71✔
96
        for j in eachindex(ex.args)
7✔
97
            param, i = recursive_dotcalls!(ex.args[j], args, i)
7✔
98
            ex.args[j] = param
7✔
99
        end
7✔
100
        return ex, i
7✔
101
    end
102
    newarg = Symbol('x', i)
64✔
103
    if isexpr(ex, :...)
64✔
104
        newarg = Expr(:..., newarg)
2✔
105
        push!(args, only(ex.args))
2✔
106
    elseif isexpr(ex, :kw)
62✔
107
        newarg = Expr(:kw, ex.args[1], newarg)
14✔
108
        push!(args, ex.args[end])
14✔
109
    else
110
        push!(args, ex)
48✔
111
    end
112
    return newarg, i+1
64✔
113
end
114

115
function extract_farg(@nospecialize arg)
156✔
116
    !isexpr(arg, :(::), 1) && return arg
156✔
117
    fT = arg.args[1]
1✔
UNCOV
118
    :($construct_callable($fT))
×
119
end
120

UNCOV
121
function construct_callable(@nospecialize(func::Type))
×
122
    # Support function singleton types such as `(::typeof(f))(args...)`
UNCOV
123
    Base.issingletontype(func) && isdefined(func, :instance) && return func.instance
×
124
    # Don't support type annotations otherwise, we don't want to give wrong answers
125
    # for callables such as `(::Returns{Int})(args...)` where using `Returns{Int}`
126
    # would give us code for the constructor, not for the callable object.
UNCOV
127
    throw(ArgumentError("If the function type is explicitly provided via a type annotation, it must be a singleton whose only instance is the callable object.
×
128
                         To remove this restriction, the reflection macro must set `use_signature_tuple = true` if the reflection function supports a single signature tuple type argument, such as `Tuple{typeof(f), argtypes...}`"))
129
end
130

131
function separate_kwargs(exs::Vector{Any})
162✔
132
    args = []
162✔
133
    kwargs = []
162✔
134
    for ex in exs
162✔
135
        if isexpr(ex, :kw)
487✔
136
            push!(kwargs, ex)
4✔
137
        elseif isexpr(ex, :parameters)
483✔
138
            for kw in ex.args
158✔
139
                push!(kwargs, kw)
165✔
140
            end
165✔
141
        else
142
            push!(args, ex)
325✔
143
        end
144
    end
487✔
145
    args, kwargs
162✔
146
end
147

148
function are_kwargs_valid(kwargs::Vector{Any})
149
    for kwarg in kwargs
162✔
150
        isexpr(kwarg, :..., 1) && continue
169✔
151
        isexpr(kwarg, :kw, 2) && isa(kwarg.args[1], Symbol) && continue
19✔
152
        isexpr(kwarg, :(::), 2) && continue
4✔
153
        isa(kwarg, Symbol) && continue
2✔
154
        isexpr(kwarg, :escape) && continue
1✔
155
        isexpr(kwarg, :var"hygienic-scope") && continue
1✔
156
        return false
1✔
157
    end
168✔
158
    return true
161✔
159
end
160

161
# Generate an expression that merges `kwargs` onto a single `NamedTuple`
162
function generate_merged_namedtuple_type(kwargs::Vector{Any})
161✔
163
    nts = Any[]
161✔
164
    ntargs = Pair{Symbol, Any}[]
161✔
165
    for ex in kwargs
161✔
166
        if isexpr(ex, :..., 1)
168✔
167
            if !isempty(ntargs)
150✔
168
                # Construct a `NamedTuple` containing the previous parameters.
169
                push!(nts, generate_namedtuple_type(ntargs))
4✔
170
                empty!(ntargs)
4✔
171
            end
172
            push!(nts, Expr(:call, typeof_nt, ex.args[1]))
150✔
173
        elseif isexpr(ex, :kw, 2)
18✔
174
            push!(ntargs, ex.args[1]::Symbol => reescape(get_typeof, ex.args[2]))
15✔
175
        elseif isexpr(ex, :(::), 2)
3✔
176
            push!(ntargs, ex.args[1]::Symbol => reescape(get_typeof, ex))
2✔
177
        else
178
            push!(ntargs, ex => reescape(get_typeof, ex))
1✔
179
        end
180
    end
168✔
181
    !isempty(ntargs) && push!(nts, generate_namedtuple_type(ntargs))
161✔
182
    return :($merge_namedtuple_types($(nts...)))
161✔
183
end
184

185
function generate_namedtuple_type(ntargs::Vector{Pair{Symbol, Any}})
18✔
186
    names = Expr(:tuple)
18✔
187
    tt = Expr(:curly, :Tuple)
18✔
188
    for (name, type) in ntargs
18✔
189
        push!(names.args, QuoteNode(name))
18✔
190
        push!(tt.args, type)
18✔
191
    end
18✔
192
    return :(NamedTuple{$names, $tt})
18✔
193
end
194

195
typeof_nt(nt::NamedTuple) = typeof(nt)
8✔
196
typeof_nt(nt::Base.Pairs) = typeof(values(nt))
389✔
197

198
function merge_namedtuple_types(nt::Type{<:NamedTuple}, nts::Type{<:NamedTuple}...)
387✔
199
    @nospecialize
408✔
200
    isempty(nts) && return nt
408✔
201
    names = Symbol[]
6✔
202
    types = Any[]
6✔
203
    for nt in (nt, nts...)
6✔
204
        for (name, type) in zip(fieldnames(nt), fieldtypes(nt))
13✔
205
            i = findfirst(==(name), names)
21✔
206
            if isnothing(i)
17✔
207
                push!(names, name)
9✔
208
                push!(types, type)
9✔
209
            else
210
                types[i] = type
4✔
211
            end
212
        end
13✔
213
    end
19✔
214
    return NamedTuple{Tuple(names), Tuple{types...}}
6✔
215
end
216

217
function gen_call(fcn, args, where_params, kws; use_signature_tuple::Bool, not_an_opaque_closure::Bool = true)
640✔
218
    f, args... = args
628✔
219
    args = collect(Any, args)
628✔
220
    if !use_signature_tuple
320✔
221
        f = esc(reescape(extract_farg, f))
156✔
222
        tt = typesof_expr(args, where_params)
156✔
223
        return :($fcn($f, $tt; $(kws...)))
156✔
224
    end
225
    # We use a signature tuple only if we are sure we won't get an opaque closure as first argument.
226
    # If we do get one, we have to use the 2-argument form.
227
    if isexpr(f, :(::)) || not_an_opaque_closure
325✔
228
        # We have a type, not a value, so not an opaque closure.
229
        sigt = typesof_expr(Any[f, args...], where_params)
100✔
230
        return :($fcn($sigt; $(kws...)))
100✔
231
    end
232
    tt = typesof_expr(args, where_params)
64✔
233
    sigt = typesof_expr_unescaped(Any[:f, esc.(args)...], where_params)
64✔
234
    return quote
64✔
235
        f = $(esc(f))
2✔
236
        if isa(f, Core.OpaqueClosure)
2✔
237
            $fcn(f, $tt; $(kws...))
238
        else
239
            $fcn($sigt; $(kws...))
2✔
240
        end
241
    end
242
end
243

244
function expand_ref_begin_end!(f::Function, ex, __module__::Module)
19✔
245
    arr = ex.args[1]
19✔
246
    args = copy(ex.args)
38✔
247
    new = replace_ref_begin_end!(__module__, ex)
19✔
248
    modified = ex.args .≠ args
38✔
249
    if any(modified) && (isexpr(arr, :(::), 1) || isexpr(arr, :(::), 2) || isexpr(arr, :..., 1))
27✔
250
        return Expr(:call, :error, "`begin` or `end` cannot be used with a type-annotated left-hand side argument for an indexing syntax")
2✔
251
    end
252
    call = f(ex)
17✔
253
    !any(modified) && return call
34✔
254
    fixup_hygiene_for_ref_temporary!(new)
8✔
255
    # We have to mutate `ex`, then return `new` which evaluates `arr` before use.
256
    ex.head = call.head
8✔
257
    ex.args = call.args
8✔
258
    return new
8✔
259
end
260

261
function fixup_hygiene_for_ref_temporary!(ex)
8✔
262
    # Match the local variable `##S#...` so we may escape its definition.
263
    # We don't want to use `escs = 1` in `replace_ref_begin_end_!` because
264
    # then we delegate escaping to this function, whereas we otherwise manage
265
    # ourselves the escaping in all other code paths.
266
    isexpr(ex, :block) || return
8✔
267
    decl = ex.args[1]
8✔
268
    isexpr(decl, :local, 1) || return
8✔
269
    assignment = decl.args[1]
8✔
270
    isexpr(assignment, :(=), 2) || return
8✔
271
    variable = assignment.args[1]
8✔
272
    startswith(string(variable), "##S#") || return
8✔
273
    decl.args[1] = esc(assignment)
8✔
274
end
275

276
is_code_macro(fcn) = startswith(string(fcn), "code_")
7✔
277

278
"""
279
    gen_call_with_extracted_types(__module__, fcn, ex, kws = Expr[]; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false, use_signature_tuple = false)
280

281
Destructures the input expression `ex` into a function call or a binding access, then generates a call to either:
282
- `fcn(f, tt; kws...)`
283
- `fcn(sigt; kws...)` # if `use_signature_tuple = true`
284
- `fcn(mod, name; kws...)` # if `supports_binding_reflection = true`
285

286
## `fcn` API requirements
287

288
`fcn` is a user function expected to satisfy the following API:
289
- `fcn(f, tt)`: `f` is a value (such as `sum`, unlike `typeof(sum)`), and `tt := Tuple{argtypes...}`
290
  is a `Tuple` holding argument types. `f` may be a `Core.OpaqueClosure`.
291

292
If `use_signature_tuple = true`:
293
- `fcn(sigt)`: `sigt := Tuple{typeof(f), argtypes...}` represents the low-level signature tuple to be used for introspection.
294

295
If `supports_binding_reflection = true`:
296
- `fcn(mod::Module, name::Symbol)`: `name` is the name of a binding that may or may not exist in `mod`.
297

298
!!! warning
299
    This function is not public and may be subject to breaking changes. However, we recognize that it may
300
    be very convenient for macro developers, and as it is already used by a certain number of packages,
301
    we will do our best to avoid breakages.
302

303
## Examples
304

305
Here are a few usage patterns that may help you get started.
306

307
For most "code" macros (`@code_typed`, `@code_llvm`, `@code_native` etc):
308
```julia
309
    gen_call_with_extracted_types(__module__, fcn, ex, kws; is_source_reflection = false, use_signature_tuple = true #= may be false =#)
310
```
311

312
For source reflection macros (`@which`, `@edit`, `@less` etc):
313
```julia
314
    gen_call_with_extracted_types(__module__, fcn, ex, kws; is_source_reflection = true, use_signature_tuple = true #= may be false =#)
315
```
316

317
# Extended help
318

319
## Type annotations
320

321
Type annotations may be used instead of concrete values for the callable or for any of the arguments. The generated code
322
will directly use the right-hand side of the type annotation instead of extracting the type of a value at runtime.
323

324
This is particularly useful for callable objects (notably, for those that are hard to construct by hand on the spot),
325
or when wanting to provide a type that is not concrete. However, support for callable objects requires setting
326
`use_signature_tuple` to true, which is not a default (see the corresponding section below).
327

328
Constraints on type parameters are also supported with a `where` syntax, enabling these patterns:
329
- `f(x::Vector{T}, y::T) where {T}`
330
- `(::Returns{T})() where {T<:Real}`
331
- `(::MyPolynomial{N,T})(::T, ::AbstractArray{T,N}) where {N,T}`
332

333
Type-annotated expressions may be mixed with runtime values, as in `x + ::Float64`.
334

335
## Broadcasting
336

337
When `ex` is a broadcasting expression (a broadcasted assignment `a .+= b` or a broadcasted function call `a .+ b`),
338
there is no actual function that corresponds to this expression because lowering maps it to more than one call.
339

340
If `is_source_reflection` is true, we assume that `fcn` uses provenance information (e.g. used by `@edit` to go
341
to a source location, or `@which` to get the method matching the input). In this case, we don't have a clear
342
semantic source to give (shall it be `broadcasted`, or `materialize`, or something else?), so we return a throwing
343
expression.
344

345
However, if provenance is not of interest, we define an intermediate function on the spot that performs the broadcast,
346
then carry on using this function. For example, for the input expression `a .+ b`, we emit the anonymous function
347
`(a, b) -> a .+ b` then call `fcn` just as if the user had issued a call to this anonymous function. That should be the
348
desired behavior for most macros that want to map an expression to the corresponding generated code, as in `@code_typed`
349
or `@code_llvm` for instance.
350

351
## Binding reflection
352

353
Expressions of the form `a.b` (or `a.b.c` and so on) are by default interpreted as calls to `getproperty`.
354
However, if the value corresponding to the left-hand side (`a`, `a.b`, etc) is a module, some implementations
355
may instead be interested in the binding lookup, instead of the function call. If that is the case,
356
`supports_binding_reflection` may be set to `true` which will emit a call to `fcn(a, :b)` (or `fcn(a.b, :c)` etc).
357

358
## Tuple signature type
359

360
If `use_signature_tuple = true`, then a single tuple consisting of `Tuple{ft, argtypes...}` will be formed
361
and provided to `fcn`. `fcn` is then expected to use `ft` as the callable type with no further transformation.
362

363
This behavior is required to enable support type-annotated callable objects.
364

365
To understand this requirement, we'll use `code_typed` as an example. `code_typed(f, ())` interprets its input as the signature
366
`Tuple{typeof(f)}`, and `code_typed(Returns{Int}, ())` interprets that as the signature `Tuple{Type{Returns{Int}}}`, corresponding
367
to the type constructor.
368
To remove the ambiguity, `code_typed` must support an implementation that directly accepts a function type. This implementation
369
is assumed to be the method for `fcn(sigt::Type{<:Tuple})`.
370
"""
371
function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false, use_signature_tuple = false)
808✔
372
    # Ignore assignments (e.g. `@edit a = f(x)` gets turned into `@edit f(x)`)
373
    if isa(ex0, Expr) && ex0.head === :(=) && isa(ex0.args[1], Symbol)
333✔
374
        return gen_call_with_extracted_types(__module__, fcn, ex0.args[2], kws; is_source_reflection, supports_binding_reflection, use_signature_tuple)
4✔
375
    end
376
    where_params = nothing
329✔
377
    if isa(ex0, Expr)
329✔
378
        ex0, where_params = extract_where_parameters(ex0)
645✔
379
    end
380
    if isa(ex0, Expr)
329✔
381
        if ex0.head === :do && isexpr(get(ex0.args, 1, nothing), :call)
325✔
382
            # Normalize `f(args...) do ... end` calls to `f(do_anonymous_function, args...)`
383
            if length(ex0.args) != 2
2✔
UNCOV
384
                return Expr(:call, :error, "ill-formed do call")
×
385
            end
386
            i = findlast(@nospecialize(a)->(isexpr(a, :kw) || isexpr(a, :parameters)), ex0.args[1].args)
10✔
387
            args = copy(ex0.args[1].args)
2✔
388
            insert!(args, (isnothing(i) ? 2 : 1+i::Int), ex0.args[2])
2✔
389
            ex0 = Expr(:call, args...)
2✔
390
        end
391
        if is_broadcasting_expr(ex0) && !is_source_reflection
630✔
392
            # Manually wrap top-level broadcasts in a function.
393
            # We don't do that if `fcn` reflects into the source,
394
            # because that destroys provenance information.
395
            args = Any[]
27✔
396
            ex, i = recursive_dotcalls!(copy(ex0), args)
27✔
397
            xargs = [Symbol('x', j) for j in 1:i-1]
27✔
398
            dotfuncname = gensym("dotfunction")
27✔
399
            call = gen_call(fcn, Any[dotfuncname, args...], where_params, kws; use_signature_tuple)
27✔
400
            return quote
27✔
401
                let $(esc(:($dotfuncname($(xargs...)) = $ex)))
402
                    $call
403
                end
404
            end
405
        elseif isexpr(ex0, :.) && is_source_reflection
298✔
406
            # If `ex0` has the form A.B (or some chain A.B.C.D) and `fcn` reflects into the source,
407
            # `A` (or `A.B.C`) may be a module, in which case `fcn` is probably more interested in
408
            # the binding rather than the `getproperty` call.
409
            # If binding reflection is not supported, we generate an error; `getproperty(::Module, field)`
410
            # is not going to be interesting to reflect into, so best to allow future non-breaking support
411
            # for binding reflection in case the macro may eventually support that.
412
            fully_qualified_symbol = true
5✔
413
            ex1 = ex0
5✔
414
            while ex1 isa Expr && ex1.head === :.
12✔
415
                fully_qualified_symbol = (length(ex1.args) == 2 &&
7✔
416
                                            ex1.args[2] isa QuoteNode &&
417
                                            ex1.args[2].value isa Symbol)
418
                fully_qualified_symbol || break
7✔
419
                ex1 = ex1.args[1]
7✔
420
            end
7✔
421
            fully_qualified_symbol &= ex1 isa Symbol
5✔
422
            if fully_qualified_symbol || isexpr(ex1, :(::), 1)
6✔
423
                call_reflection = gen_call(fcn, [getproperty; ex0.args], where_params, kws; use_signature_tuple)
10✔
424
                isexpr(ex0.args[1], :(::), 1) && return call_reflection
5✔
425
                if supports_binding_reflection
4✔
426
                    binding_reflection = :($fcn(arg1, $(ex0.args[2]); $(kws...)))
2✔
427
                else
428
                    binding_reflection = :(error("expression is not a function call"))
2✔
429
                end
430
                return quote
4✔
431
                    local arg1 = $(esc(ex0.args[1]))
432
                    if isa(arg1, Module)
433
                        $binding_reflection
434
                    else
435
                        $call_reflection
436
                    end
437
                end
438
            end
439
        end
440
        if is_broadcasting_expr(ex0)
585✔
441
            return Expr(:call, :error, "dot expressions are not lowered to "
2✔
442
                * "a single function call, so @$fcn cannot analyze "
443
                * "them. You may want to use Meta.@lower to identify "
444
                * "which function call to target.")
445
        end
446
        if any(@nospecialize(a)->(isexpr(a, :kw) || isexpr(a, :parameters)), ex0.args)
2,096✔
447
            args, kwargs = separate_kwargs(ex0.args)
162✔
448
            are_kwargs_valid(kwargs) || return quote
163✔
449
                error("keyword argument format unrecognized; they must be of the form `x` or `x = <value>`")
450
                $(esc(ex0)) # trigger syntax errors if any
451
            end
452
            nt = generate_merged_namedtuple_type(kwargs)
161✔
453
            nt = Ref(nt) # ignore `get_typeof` handling
161✔
454
            return gen_call(fcn, Any[Core.kwcall, nt, args...], where_params, kws; use_signature_tuple)
161✔
455
        elseif ex0.head === :call
129✔
456
            args = copy(ex0.args)
162✔
457
            if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int)
81✔
458
                pushfirst!(args, Base.literal_pow)
34✔
459
                args[4] = :(Val($(ex0.args[3])))
17✔
460
            end
461
            return gen_call(fcn, args, where_params, kws; use_signature_tuple, not_an_opaque_closure = false)
81✔
462
        elseif ex0.head === :(=) && length(ex0.args) == 2
48✔
463
            lhs, rhs = ex0.args
5✔
464
            if isa(lhs, Expr)
5✔
465
                if lhs.head === :(.)
5✔
466
                    return gen_call(fcn, Any[Base.setproperty!, lhs.args..., rhs], where_params, kws; use_signature_tuple)
2✔
467
                elseif lhs.head === :ref
3✔
468
                    return expand_ref_begin_end!(lhs, __module__) do ex
3✔
469
                        gen_call(fcn, Any[setindex!, ex.args[1], rhs, ex.args[2:end]...], where_params, kws; use_signature_tuple)
3✔
470
                    end
471
                end
472
            end
473
        elseif ex0.head === :vcat || ex0.head === :typed_vcat
82✔
474
            if ex0.head === :vcat
10✔
475
                f, hf = Base.vcat, Base.hvcat
4✔
476
                args = ex0.args
4✔
477
            else
478
                f, hf = Base.typed_vcat, Base.typed_hvcat
6✔
479
                args = ex0.args[2:end]
6✔
480
            end
481
            if any(@nospecialize(a)->isa(a,Expr) && a.head === :row, args)
32✔
482
                rows = Any[ (isa(x,Expr) && x.head === :row ? x.args : Any[x]) for x in args ]
6✔
483
                lens = map(length, rows)
6✔
484
                args = Any[Expr(:tuple, lens...); vcat(rows...)]
6✔
485
                ex0.head === :typed_vcat && pushfirst!(args, ex0.args[1])
6✔
486
                return gen_call(fcn, Any[hf, args...], where_params, kws; use_signature_tuple)
6✔
487
            else
488
                return gen_call(fcn, Any[f, ex0.args...], where_params, kws; use_signature_tuple)
4✔
489
            end
490
        elseif ex0.head === :ref
33✔
491
            return expand_ref_begin_end!(ex0, __module__) do ex
16✔
492
                gen_call(fcn, Any[getindex, ex.args...], where_params, kws; use_signature_tuple)
14✔
493
            end
494
        else
495
            for (head, f) in Any[:hcat => Base.hcat,
17✔
496
                                 :(.) => Base.getproperty,
497
                                 :vect => Base.vect,
498
                                 Symbol("'") => Base.adjoint,
499
                                 :typed_hcat => Base.typed_hcat,
500
                                 :string => string]
501
                ex0.head === head || continue
67✔
502
                return gen_call(fcn, Any[f, ex0.args...], where_params, kws; use_signature_tuple)
12✔
503
            end
55✔
504
        end
505
    end
506
    if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
9✔
507
        args = [#=__source__::=#LineNumberNode, #=__module__::=#Module, Core.Typeof.(ex0.args[3:end])...]
5✔
508
        return gen_call(fcn, Any[ex0.args[1], Ref.(args)...], where_params, kws; use_signature_tuple)
5✔
509
    end
510

511
    ex = Meta.lower(__module__, ex0)
4✔
512
    isa(ex, Expr) || return Expr(:call, :error, "expression is not a function call or symbol")
8✔
513

UNCOV
514
    return Expr(:call, :error, "expression is not a function call, \
×
515
                                    or is too complex for @$fcn to analyze; \
516
                                    break it down to simpler parts if possible. \
517
                                    In some cases, you may want to use Meta.@lower.")
518
end
519

520
"""
521
Same behaviour as `gen_call_with_extracted_types` except that keyword arguments
522
of the form "foo=bar" are passed on to the called function as well.
523
The keyword arguments must be given before the mandatory argument.
524
"""
525
function gen_call_with_extracted_types_and_kwargs(__module__, fcn, ex0; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false, use_signature_tuple = false)
119✔
526
    kws = Expr[]
95✔
527
    arg = ex0[end] # Mandatory argument
95✔
528
    for i in 1:length(ex0)-1
95✔
529
        x = ex0[i]
24✔
530
        if x isa Expr && x.head === :(=) # Keyword given of the form "foo=bar"
24✔
531
            if length(x.args) != 2
24✔
UNCOV
532
                return Expr(:call, :error, "Invalid keyword argument: $x")
×
533
            end
534
            push!(kws, Expr(:kw, esc(x.args[1]), esc(x.args[2])))
24✔
535
        else
UNCOV
536
            return Expr(:call, :error, "@$fcn expects only one non-keyword argument")
×
537
        end
538
    end
24✔
539
    return gen_call_with_extracted_types(__module__, fcn, arg, kws; is_source_reflection, supports_binding_reflection, use_signature_tuple)
95✔
540
end
541

542
for fname in [:which, :less, :edit, :functionloc]
543
    @eval begin
544
        macro ($fname)(ex0)
92✔
545
            gen_call_with_extracted_types(__module__, $(Expr(:quote, fname)), ex0, Expr[];
92✔
546
                                          is_source_reflection = true,
547
                                          supports_binding_reflection = $(fname === :which),
548
                                          use_signature_tuple = true)
549
        end
550
    end
551
end
552

553
macro which(ex0::Symbol)
1✔
554
    ex0 = QuoteNode(ex0)
1✔
555
    return :(which($__module__, $ex0))
1✔
556
end
557

558
for fname in [:code_warntype, :code_llvm, :code_native,
559
              :infer_return_type, :infer_effects, :infer_exception_type]
560
    @eval macro ($fname)(ex0...)
6✔
561
        gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false, use_signature_tuple = $(in(fname, [:code_warntype, :code_llvm, :code_native])))
6✔
562
    end
563
end
564

565
for fname in [:code_typed, :code_lowered, :code_ircode]
566
    @eval macro ($fname)(ex0...)
78✔
567
        thecall = gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false, use_signature_tuple = true)
78✔
568
        quote
78✔
569
            local results = $thecall
1✔
570
            length(results) == 1 ? results[1] : results
1✔
571
        end
572
    end
573
end
574

575
"""
576
    @functionloc
577

578
Applied to a function or macro call, it evaluates the arguments to the specified call, and
579
returns a tuple `(filename,line)` giving the location for the method that would be called for those arguments.
580
It calls out to the [`functionloc`](@ref) function.
581
"""
582
:@functionloc
583

584
"""
585
    @which
586

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

592
See also: [`@less`](@ref), [`@edit`](@ref).
593
"""
594
:@which
595

596
"""
597
    @less
598

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

602
See also: [`@edit`](@ref), [`@which`](@ref), [`@code_lowered`](@ref).
603
"""
604
:@less
605

606
"""
607
    @edit
608

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

612
See also: [`@less`](@ref), [`@which`](@ref).
613
"""
614
:@edit
615

616
"""
617
    @code_typed
618

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

622
    @code_typed optimize=true foo(x)
623

624
to control whether additional optimizations, such as inlining, are also applied.
625

626
See also: [`code_typed`](@ref), [`@code_warntype`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
627
"""
628
:@code_typed
629

630
"""
631
    @code_lowered
632

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

636
See also: [`code_lowered`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
637
"""
638
:@code_lowered
639

640
"""
641
    @code_warntype
642

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

646
See also: [`code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
647
"""
648
:@code_warntype
649

650
"""
651
    @code_llvm
652

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

658
    @code_llvm raw=true dump_module=true debuginfo=:default f(x)
659
    @code_llvm optimize=false f(x)
660

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

666
See also: [`code_llvm`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_native`](@ref).
667
"""
668
:@code_llvm
669

670
"""
671
    @code_native
672

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

676
Set any of the optional keyword arguments `syntax`, `debuginfo`, `binary` or `dump_module`
677
by putting it before the function call, like this:
678

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

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

686
See also: [`code_native`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref).
687
"""
688
:@code_native
689

690
"""
691
    @time_imports
692

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

696
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.
697

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

700
!!! note
701
    During the load process a package sequentially imports all of its dependencies, not just its direct dependencies.
702

703
```julia-repl
704
julia> @time_imports using CSV
705
     50.7 ms  Parsers 17.52% compilation time
706
      0.2 ms  DataValueInterfaces
707
      1.6 ms  DataAPI
708
      0.1 ms  IteratorInterfaceExtensions
709
      0.1 ms  TableTraits
710
     17.5 ms  Tables
711
     26.8 ms  PooledArrays
712
    193.7 ms  SentinelArrays 75.12% compilation time
713
      8.6 ms  InlineStrings
714
     20.3 ms  WeakRefStrings
715
      2.0 ms  TranscodingStreams
716
      1.4 ms  Zlib_jll
717
      1.8 ms  CodecZlib
718
      0.8 ms  Compat
719
     13.1 ms  FilePathsBase 28.39% compilation time
720
   1681.2 ms  CSV 92.40% compilation time
721
```
722

723
!!! compat "Julia 1.8"
724
    This macro requires at least Julia 1.8
725

726
"""
727
:@time_imports
728

729
"""
730
    @trace_compile
731

732
A macro to execute an expression and show any methods that were compiled (or recompiled in yellow),
733
like the julia args `--trace-compile=stderr --trace-compile-timing` but specifically for a call.
734

735
```julia-repl
736
julia> @trace_compile rand(2,2) * rand(2,2)
737
#=   39.1 ms =# precompile(Tuple{typeof(Base.rand), Int64, Int64})
738
#=  102.0 ms =# precompile(Tuple{typeof(Base.:(*)), Array{Float64, 2}, Array{Float64, 2}})
739
2×2 Matrix{Float64}:
740
 0.421704  0.864841
741
 0.211262  0.444366
742
```
743

744
!!! compat "Julia 1.12"
745
    This macro requires at least Julia 1.12
746

747
"""
748
:@trace_compile
749

750
"""
751
    @trace_dispatch
752

753
A macro to execute an expression and report methods that were compiled via dynamic dispatch,
754
like the julia arg `--trace-dispatch=stderr` but specifically for a call.
755

756
!!! compat "Julia 1.12"
757
    This macro requires at least Julia 1.12
758

759
"""
760
:@trace_dispatch
761

762
"""
763
    @activate Component
764

765
Activate a newly loaded copy of an otherwise builtin component. The `Component`
766
to be activated will be resolved using the ordinary rules of module resolution
767
in the current environment.
768

769
When using `@activate`, additional options for a component may be specified in
770
square brackets `@activate Compiler[:option1, :option]`
771

772
Currently `Compiler` and `JuliaLowering` are the only available components that
773
may be activatived.
774

775
For `@activate Compiler`, the following options are available:
776
1. `:reflection` - Activate the compiler for reflection purposes only.
777
                   The ordinary reflection functionality in `Base` and `InteractiveUtils`.
778
                   Will use the newly loaded compiler. Note however, that these reflection
779
                   functions will still interact with the ordinary native cache (both loading
780
                   and storing). An incorrect compiler implementation may thus corrupt runtime
781
                   state if reflection is used. Use external packages like `Cthulhu.jl`
782
                   introspecting compiler behavior with a separated cache partition.
783

784
2. `:codegen`   - Activate the compiler for internal codegen purposes. The new compiler
785
                  will be invoked whenever the runtime requests compilation.
786

787
`@activate Compiler` without options is equivalent to `@activate Compiler[:reflection]`.
788

789
"""
790
macro activate(what)
38✔
791
    options = Symbol[]
38✔
792
    if isexpr(what, :ref)
38✔
UNCOV
793
        Component = what.args[1]
×
UNCOV
794
        for i = 2:length(what.args)
×
UNCOV
795
            arg = what.args[i]
×
UNCOV
796
            if !isa(arg, QuoteNode) || !isa(arg.value, Symbol)
×
UNCOV
797
                error("Usage Error: Option $arg is not a symbol")
×
798
            end
UNCOV
799
            push!(options, arg.value)
×
UNCOV
800
        end
×
801
    else
802
        Component = what
38✔
803
    end
804
    if !isa(Component, Symbol)
38✔
UNCOV
805
        error("Usage Error: Component $Component is not a symbol")
×
806
    end
807
    allowed_components = (:Compiler, :JuliaLowering)
38✔
808
    if !(Component in allowed_components)
38✔
UNCOV
809
        error("Usage Error: Component $Component is not recognized. Expected one of $allowed_components")
×
810
    end
811
    if Component === :Compiler && isempty(options)
38✔
812
        push!(options, :reflection)
38✔
813
    end
814
    options = map(options) do opt
38✔
815
        Expr(:kw, opt, true)
38✔
816
    end
817
    return :(Base.require($__module__, $(QuoteNode(Component))).activate!(; $(options...)))
38✔
818
end
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