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

JuliaLang / julia / 1507

16 Apr 2026 07:37PM UTC coverage: 77.907% (+0.1%) from 77.793%
1507

push

buildkite

web-flow
[JuliaLowering] Fix `ccall` nontrivial library expression within tuple (#61585)

Found precompiling Parsers in
https://github.com/JuliaLang/julia/pull/61576.

Another place the `K"static_eval"` form wasn't quite accurate. In the
first arg of a foreigncall, if it is a tuple, we do want the "no
referencing globals" property of `K"static_eval"`, but we also want it
to be semi-inert: not converted in `est_to_dst`, and not desugared. We
do want the expression to go through scope resolution, though (it's
surprising this works in either lowering implementation). We don't have
a way of converting this hybrid thing to Expr after lowering, so I've
written one. I don't think there's much point in trying to treat this
like it isn't a special case, so I've just called it
`K"foreigncall_arg1"`.

65433 of 83989 relevant lines covered (77.91%)

24425574.46 hits per line

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

96.3
/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(:($make_tuple_type(Any[$(Any[esc(reescape(get_typeof, a)) for a in args]...)])), where_params)
1,023✔
13
typesof_expr_unescaped(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:($make_tuple_type(Any[$(Any[reescape(get_typeof, a) for a in args]...)])), where_params)
255✔
14

15
function make_tuple_type(types::Vector{Any})
1,740✔
16
    vararg = -1
1,740✔
17
    for i in eachindex(types)
1,740✔
18
        i == 1 && continue # ignore function type
6,024✔
19
        type = types[i]
4,290✔
20
        if isa(type, Core.TypeofVararg)
4,290✔
21
            vararg !== -1 && throw(ArgumentError("More than one `Core.Vararg` type present in argument tuple ($type detected after $(types[vararg])); if provided, it must be unique"))
78✔
22
            vararg = i
75✔
23
            if isdefined(type, :N)
75✔
24
                n = length(types) - vararg + 1
12✔
25
                n > type.N && throw(ArgumentError("Expected at most $(type.N) types after `$type`, found $n instead"))
12✔
26
            end
27
        elseif vararg !== -1
4,212✔
28
            ref = types[vararg]
51✔
29
            if isdefined(ref, :T) && !skip_type_check(ref.T) && !skip_type_check(type)
51✔
30
                !(type <: ref.T) && throw(ArgumentError("Inconsistent type `$type` detected after `$ref`; `$type <: $(ref.T)` must hold"))
30✔
31
            end
32
        end
33
    end
10,296✔
34
    vararg === -1 && return Tuple{types...}
1,722✔
35
    return Tuple{@view(types[1:vararg])...}
57✔
36
end
37

38
skip_type_check(@nospecialize(T)) = Core.has_free_typevars(T)
81✔
39

40
function extract_where_parameters(ex::Expr)
41
    isexpr(ex, :where) || return ex, nothing
2,043✔
42
    ex.args[1], ex.args[2:end]
66✔
43
end
44

45
function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}})
54✔
46
    isnothing(where_params) && return ex
1,278✔
47
    Expr(:where, ex, esc.(where_params)...)
54✔
48
end
49

50
function reescape(f::Function, @nospecialize ex)
4,515✔
51
    isa(ex, Expr) || return f(ex)
6,888✔
52
    unescaped = Meta.unescape(ex)
2,142✔
53
    new = f(unescaped)
2,142✔
54
    return Meta.reescape(new, ex)
2,142✔
55
end
56

57
get_typeof(ex::Ref) = ex[]
543✔
58
function get_typeof(@nospecialize ex)
3,504✔
59
    isexpr(ex, :(::), 1) && return ex.args[1]
3,504✔
60
    isexpr(ex, :(::), 2) && return ex.args[2]
3,105✔
61
    if isexpr(ex, :..., 1)
2,997✔
62
        splatted = ex.args[1]
468✔
63
        isexpr(splatted, :(::)) && return Expr(:curly, :(Core.Vararg), splatted.args[end])
468✔
64
        return :(Any[Core.Typeof(x) for x in $splatted]...)
438✔
65
    end
66
    return :(Core.Typeof($ex))
2,529✔
67
end
68

69
function is_broadcasting_call(ex)
2,259✔
70
    isa(ex, Expr) || return false
2,304✔
71
    # Standard broadcasting: f.(x)
72
    isexpr(ex, :.) && length(ex.args) ≥ 2 && isexpr(ex.args[2], :tuple) && return true
2,259✔
73
    # Infix broadcasting: x .+ y, x .<< y, etc.
74
    if isexpr(ex, :call)
2,184✔
75
        f = ex.args[1]
1,662✔
76
        f == :.. && return false
3,324✔
77
        string(f)[1] == '.' && return true
1,656✔
78
    end
79
    return false
2,103✔
80
end
81
is_broadcasting_expr(ex) = is_broadcasting_call(ex) || is_broadcasting_assignment(ex)
4,413✔
82
function is_broadcasting_assignment(ex)
2,220✔
83
    isa(ex, Expr) || return false
2,265✔
84
    isexpr(ex, :.) && return false
2,220✔
85
    head = string(ex.head)
2,142✔
86
    # x .= y, x .+= y, x .<<= y, etc.
87
    head[begin] == '.' && head[end] == '=' && return true
2,226✔
88
    return false
2,064✔
89
end
90

91
"""
92
Transform a dot expression into one where each argument has been replaced by a
93
variable "xj" (with j an integer from 1 to the returned i).
94
The list `args` contains the original arguments that have been replaced.
95
"""
96
function recursive_dotcalls!(ex, args, i=1)
324✔
97
    if is_broadcasting_expr(ex)
597✔
98
        if is_broadcasting_assignment(ex)
111✔
99
            (start, branches) = (1, ex.args)
27✔
100
        elseif isexpr(ex, :.)
87✔
101
            (start, branches) = (1, ex.args[2].args)
42✔
102
        else
103
            (start, branches) = (2, ex.args)
45✔
104
        end
105
        for j in start:length(branches)::Int
111✔
106
            branch, i = recursive_dotcalls!(branches[j], args, i)
222✔
107
            branches[j] = branch
222✔
108
        end
333✔
109
        return ex, i
111✔
110
    elseif isexpr(ex, :parameters)
213✔
111
        for j in eachindex(ex.args)
21✔
112
            param, i = recursive_dotcalls!(ex.args[j], args, i)
21✔
113
            ex.args[j] = param
21✔
114
        end
21✔
115
        return ex, i
21✔
116
    end
117
    newarg = Symbol('x', i)
192✔
118
    if isexpr(ex, :...)
192✔
119
        newarg = Expr(:..., newarg)
6✔
120
        push!(args, only(ex.args))
6✔
121
    elseif isexpr(ex, :kw)
186✔
122
        newarg = Expr(:kw, ex.args[1], newarg)
42✔
123
        push!(args, ex.args[end])
42✔
124
    else
125
        push!(args, ex)
144✔
126
    end
127
    return newarg, i+1
192✔
128
end
129

130
function extract_farg(@nospecialize arg)
468✔
131
    !isexpr(arg, :(::), 1) && return arg
468✔
132
    fT = arg.args[1]
×
133
    :($construct_callable($fT))
×
134
end
135

136
function construct_callable(@nospecialize(func::Type))
×
137
    # Support function singleton types such as `(::typeof(f))(args...)`
138
    Base.issingletontype(func) && isdefined(func, :instance) && return func.instance
×
139
    # Don't support type annotations otherwise, we don't want to give wrong answers
140
    # for callables such as `(::Returns{Int})(args...)` where using `Returns{Int}`
141
    # would give us code for the constructor, not for the callable object.
142
    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.
×
143
                         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...}`"))
144
end
145

146
function separate_kwargs(exs::Vector{Any})
486✔
147
    args = []
486✔
148
    kwargs = []
486✔
149
    for ex in exs
486✔
150
        if isexpr(ex, :kw)
1,461✔
151
            push!(kwargs, ex)
12✔
152
        elseif isexpr(ex, :parameters)
1,449✔
153
            for kw in ex.args
474✔
154
                push!(kwargs, kw)
495✔
155
            end
495✔
156
        else
157
            push!(args, ex)
975✔
158
        end
159
    end
1,461✔
160
    args, kwargs
486✔
161
end
162

163
function are_kwargs_valid(kwargs::Vector{Any})
164
    for kwarg in kwargs
486✔
165
        isexpr(kwarg, :..., 1) && continue
507✔
166
        isexpr(kwarg, :kw, 2) && isa(kwarg.args[1], Symbol) && continue
57✔
167
        isexpr(kwarg, :(::), 2) && continue
12✔
168
        isa(kwarg, Symbol) && continue
6✔
169
        isexpr(kwarg, :escape) && continue
3✔
170
        isexpr(kwarg, :var"hygienic-scope") && continue
3✔
171
        return false
3✔
172
    end
504✔
173
    return true
483✔
174
end
175

176
# Generate an expression that merges `kwargs` onto a single `NamedTuple`
177
function generate_merged_namedtuple_type(kwargs::Vector{Any})
483✔
178
    nts = Any[]
483✔
179
    ntargs = Pair{Symbol, Any}[]
483✔
180
    for ex in kwargs
483✔
181
        if isexpr(ex, :..., 1)
504✔
182
            if !isempty(ntargs)
450✔
183
                # Construct a `NamedTuple` containing the previous parameters.
184
                push!(nts, generate_namedtuple_type(ntargs))
12✔
185
                empty!(ntargs)
12✔
186
            end
187
            push!(nts, Expr(:call, typeof_nt, ex.args[1]))
450✔
188
        elseif isexpr(ex, :kw, 2)
54✔
189
            push!(ntargs, ex.args[1]::Symbol => reescape(get_typeof, ex.args[2]))
45✔
190
        elseif isexpr(ex, :(::), 2)
9✔
191
            push!(ntargs, ex.args[1]::Symbol => reescape(get_typeof, ex))
6✔
192
        else
193
            push!(ntargs, ex => reescape(get_typeof, ex))
3✔
194
        end
195
    end
504✔
196
    !isempty(ntargs) && push!(nts, generate_namedtuple_type(ntargs))
483✔
197
    return :($merge_namedtuple_types($(nts...)))
483✔
198
end
199

200
function generate_namedtuple_type(ntargs::Vector{Pair{Symbol, Any}})
54✔
201
    names = Expr(:tuple)
54✔
202
    tt = Expr(:curly, :Tuple)
54✔
203
    for (name, type) in ntargs
54✔
204
        push!(names.args, QuoteNode(name))
54✔
205
        push!(tt.args, type)
54✔
206
    end
54✔
207
    return :(NamedTuple{$names, $tt})
54✔
208
end
209

210
typeof_nt(nt::NamedTuple) = typeof(nt)
24✔
211
typeof_nt(nt::Base.Pairs) = typeof(values(nt))
1,167✔
212

213
function merge_namedtuple_types(nt::Type{<:NamedTuple}, nts::Type{<:NamedTuple}...)
1,161✔
214
    @nospecialize
1,224✔
215
    isempty(nts) && return nt
1,224✔
216
    names = Symbol[]
18✔
217
    types = Any[]
18✔
218
    for nt in (nt, nts...)
18✔
219
        for (name, type) in zip(fieldnames(nt), fieldtypes(nt))
39✔
220
            i = findfirst(==(name), names)
63✔
221
            if isnothing(i)
51✔
222
                push!(names, name)
27✔
223
                push!(types, type)
27✔
224
            else
225
                types[i] = type
12✔
226
            end
227
        end
39✔
228
    end
57✔
229
    return NamedTuple{Tuple(names), Tuple{types...}}
18✔
230
end
231

232
function gen_call(fcn, args, where_params, kws; use_signature_tuple::Bool, not_an_opaque_closure::Bool = true)
2,046✔
233
    f, args... = args
2,010✔
234
    args = collect(Any, args)
2,010✔
235
    if !use_signature_tuple
1,023✔
236
        f = esc(reescape(extract_farg, f))
468✔
237
        tt = typesof_expr(args, where_params)
468✔
238
        return :($fcn($f, $tt; $(kws...)))
468✔
239
    end
240
    # We use a signature tuple only if we are sure we won't get an opaque closure as first argument.
241
    # If we do get one, we have to use the 2-argument form.
242
    if isexpr(f, :(::)) || not_an_opaque_closure
1,101✔
243
        # We have a type, not a value, so not an opaque closure.
244
        sigt = typesof_expr(Any[f, args...], where_params)
300✔
245
        return :($fcn($sigt; $(kws...)))
300✔
246
    end
247
    tt = typesof_expr(args, where_params)
255✔
248
    sigt = typesof_expr_unescaped(Any[:f, esc.(args)...], where_params)
255✔
249
    return quote
255✔
250
        f = $(esc(f))
6✔
251
        if isa(f, Core.OpaqueClosure)
6✔
252
            $fcn(f, $tt; $(kws...))
253
        else
254
            $fcn($sigt; $(kws...))
12✔
255
        end
256
    end
257
end
258

259
function expand_ref_begin_end!(f::Function, ex, __module__::Module)
57✔
260
    arr = ex.args[1]
57✔
261
    args = copy(ex.args)
114✔
262
    new = replace_ref_begin_end!(__module__, ex)
57✔
263
    modified = ex.args .≠ args
114✔
264
    if any(modified) && (isexpr(arr, :(::), 1) || isexpr(arr, :(::), 2) || isexpr(arr, :..., 1))
81✔
265
        return Expr(:call, :error, "`begin` or `end` cannot be used with a type-annotated left-hand side argument for an indexing syntax")
6✔
266
    end
267
    call = f(ex)
51✔
268
    !any(modified) && return call
102✔
269
    fixup_hygiene_for_ref_temporary!(new)
24✔
270
    # We have to mutate `ex`, then return `new` which evaluates `arr` before use.
271
    ex.head = call.head
24✔
272
    ex.args = call.args
24✔
273
    return new
24✔
274
end
275

276
function fixup_hygiene_for_ref_temporary!(ex)
24✔
277
    # Match the local variable `##S#...` so we may escape its definition.
278
    # We don't want to use `escs = 1` in `replace_ref_begin_end_!` because
279
    # then we delegate escaping to this function, whereas we otherwise manage
280
    # ourselves the escaping in all other code paths.
281
    isexpr(ex, :block) || return
24✔
282
    decl = ex.args[1]
24✔
283
    isexpr(decl, :local, 1) || return
24✔
284
    assignment = decl.args[1]
24✔
285
    isexpr(assignment, :(=), 2) || return
24✔
286
    variable = assignment.args[1]
24✔
287
    startswith(string(variable), "##S#") || return
24✔
288
    decl.args[1] = esc(assignment)
24✔
289
end
290

291
is_code_macro(fcn) = startswith(string(fcn), "code_")
21✔
292

293
"""
294
    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)
295

296
Destructures the input expression `ex` into a function call or a binding access, then generates a call to either:
297
- `fcn(f, tt; kws...)`
298
- `fcn(sigt; kws...)` # if `use_signature_tuple = true`
299
- `fcn(mod, name; kws...)` # if `supports_binding_reflection = true`
300

301
## `fcn` API requirements
302

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

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

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

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

318
## Examples
319

320
Here are a few usage patterns that may help you get started.
321

322
For most "code" macros (`@code_typed`, `@code_llvm`, `@code_native` etc):
323
```julia
324
    gen_call_with_extracted_types(__module__, fcn, ex, kws; is_source_reflection = false, use_signature_tuple = true #= may be false =#)
325
```
326

327
For source reflection macros (`@which`, `@edit`, `@less` etc):
328
```julia
329
    gen_call_with_extracted_types(__module__, fcn, ex, kws; is_source_reflection = true, use_signature_tuple = true #= may be false =#)
330
```
331

332
# Extended help
333

334
## Type annotations
335

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

339
!!! compat "Julia 1.13"
340
    Support for type annotations requires at least Julia 1.13.
341

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

346
Constraints on type parameters are also supported with a `where` syntax, enabling these patterns:
347
- `f(x::Vector{T}, y::T) where {T}`
348
- `(::Returns{T})() where {T<:Real}`
349
- `(::MyPolynomial{N,T})(::T, ::AbstractArray{T,N}) where {N,T}`
350

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

353
## Broadcasting
354

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

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

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

369
## Binding reflection
370

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

376
## Tuple signature type
377

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

381
This behavior is required to enable support type-annotated callable objects.
382

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

530
    ex = Meta.lower(__module__, ex0)
12✔
531
    isa(ex, Expr) || return Expr(:call, :error, "expression is not a function call or symbol")
24✔
532

533
    return Expr(:call, :error, "expression is not a function call, \
×
534
                                    or is too complex for @$fcn to analyze; \
535
                                    break it down to simpler parts if possible. \
536
                                    In some cases, you may want to use Meta.@lower.")
537
end
538

539
"""
540
Same behaviour as `gen_call_with_extracted_types` except that keyword arguments
541
of the form "foo=bar" are passed on to the called function as well.
542
The keyword arguments must be given before the mandatory argument.
543
"""
544
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)
420✔
545
    kws = Expr[]
348✔
546
    arg = ex0[end] # Mandatory argument
348✔
547
    for i in 1:length(ex0)-1
348✔
548
        x = ex0[i]
72✔
549
        if x isa Expr && x.head === :(=) # Keyword given of the form "foo=bar"
72✔
550
            if length(x.args) != 2
72✔
551
                return Expr(:call, :error, "Invalid keyword argument: $x")
×
552
            end
553
            push!(kws, Expr(:kw, esc(x.args[1]), esc(x.args[2])))
72✔
554
        else
555
            return Expr(:call, :error, "@$fcn expects only one non-keyword argument")
×
556
        end
557
    end
72✔
558
    return gen_call_with_extracted_types(__module__, fcn, arg, kws; is_source_reflection, supports_binding_reflection, use_signature_tuple)
348✔
559
end
560

561
for fname in [:which, :less, :edit, :functionloc]
562
    @eval begin
563
        macro ($fname)(ex0)
276✔
564
            gen_call_with_extracted_types(__module__, $(Expr(:quote, fname)), ex0, Expr[];
276✔
565
                                          is_source_reflection = true,
566
                                          supports_binding_reflection = $(fname === :which),
567
                                          use_signature_tuple = true)
568
        end
569
    end
570
end
571

572
macro which(ex0::Symbol)
3✔
573
    ex0 = QuoteNode(ex0)
3✔
574
    return :(which($__module__, $ex0))
3✔
575
end
576

577
for fname in [:code_warntype, :code_llvm, :code_native,
578
              :infer_return_type, :infer_effects, :infer_exception_type]
579
    @eval macro ($fname)(ex0...)
18✔
580
        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])))
18✔
581
    end
582
end
583

584
for fname in [:code_typed, :code_lowered, :code_ircode]
585
    @eval macro ($fname)(ex0...)
297✔
586
        thecall = gen_call_with_extracted_types_and_kwargs(__module__, $(QuoteNode(fname)), ex0; is_source_reflection = false, use_signature_tuple = true)
297✔
587
        quote
297✔
588
            local results = $thecall
3✔
589
            length(results) == 1 ? results[1] : results
3✔
590
        end
591
    end
592
end
593

594
"""
595
    @functionloc
596

597
Applied to a function or macro call, it evaluates the arguments to the specified call, and
598
returns a tuple `(filename,line)` giving the location for the method that would be called for those arguments.
599
It calls out to the [`functionloc`](@ref) function.
600
"""
601
:@functionloc
602

603
"""
604
    @which
605

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

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

615
"""
616
    @less
617

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

621
See also: [`@edit`](@ref), [`@which`](@ref), [`@code_lowered`](@ref).
622
"""
623
:@less
624

625
"""
626
    @edit
627

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

631
See also: [`@less`](@ref), [`@which`](@ref).
632
"""
633
:@edit
634

635
"""
636
    @code_typed
637

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

641
    @code_typed optimize=true foo(x)
642

643
to control whether additional optimizations, such as inlining, are also applied.
644

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

649
"""
650
    @code_lowered
651

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

655
See also: [`code_lowered`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_llvm`](@ref), [`@code_native`](@ref).
656
"""
657
:@code_lowered
658

659
"""
660
    @code_warntype
661

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

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

669
"""
670
    @code_llvm
671

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

677
    @code_llvm raw=true dump_module=true debuginfo=:default f(x)
678
    @code_llvm optimize=false f(x)
679

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

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

689
"""
690
    @code_native
691

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

695
Set any of the optional keyword arguments `syntax`, `debuginfo`, `binary` or `dump_module`
696
by putting it before the function call, like this:
697

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

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

705
See also: [`code_native`](@ref), [`@code_warntype`](@ref), [`@code_typed`](@ref), [`@code_lowered`](@ref), [`@code_llvm`](@ref).
706
"""
707
:@code_native
708

709
"""
710
    @time_imports
711

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

715
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.
716

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

719
!!! note
720
    During the load process a package sequentially imports all of its dependencies, not just its direct dependencies.
721

722
```julia-repl
723
julia> @time_imports using CSV
724
     50.7 ms  Parsers 17.52% compilation time
725
      0.2 ms  DataValueInterfaces
726
      1.6 ms  DataAPI
727
      0.1 ms  IteratorInterfaceExtensions
728
      0.1 ms  TableTraits
729
     17.5 ms  Tables
730
     26.8 ms  PooledArrays
731
    193.7 ms  SentinelArrays 75.12% compilation time
732
      8.6 ms  InlineStrings
733
     20.3 ms  WeakRefStrings
734
      2.0 ms  TranscodingStreams
735
      1.4 ms  Zlib_jll
736
      1.8 ms  CodecZlib
737
      0.8 ms  Compat
738
     13.1 ms  FilePathsBase 28.39% compilation time
739
   1681.2 ms  CSV 92.40% compilation time
740
```
741

742
!!! compat "Julia 1.8"
743
    This macro requires at least Julia 1.8
744

745
"""
746
:@time_imports
747

748
"""
749
    @trace_compile
750

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

754
```julia-repl
755
julia> @trace_compile rand(2,2) * rand(2,2)
756
#=   39.1 ms =# precompile(Tuple{typeof(Base.rand), Int64, Int64})
757
#=  102.0 ms =# precompile(Tuple{typeof(Base.:(*)), Array{Float64, 2}, Array{Float64, 2}})
758
2×2 Matrix{Float64}:
759
 0.421704  0.864841
760
 0.211262  0.444366
761
```
762

763
!!! compat "Julia 1.12"
764
    This macro requires at least Julia 1.12
765

766
"""
767
:@trace_compile
768

769
"""
770
    @trace_dispatch
771

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

775
!!! compat "Julia 1.12"
776
    This macro requires at least Julia 1.12
777

778
"""
779
:@trace_dispatch
780

781
"""
782
    @activate Component
783

784
Activate a newly loaded copy of an otherwise builtin component. The `Component`
785
to be activated will be resolved using the ordinary rules of module resolution
786
in the current environment.
787

788
When using `@activate`, additional options for a component may be specified in
789
square brackets `@activate Compiler[:option1, :option]`
790

791
Currently `Compiler` and `JuliaLowering` are the only available components that
792
may be activatived.
793

794
For `@activate Compiler`, the following options are available:
795
1. `:reflection` - Activate the compiler for reflection purposes only.
796
                   The ordinary reflection functionality in `Base` and `InteractiveUtils`.
797
                   Will use the newly loaded compiler. Note however, that these reflection
798
                   functions will still interact with the ordinary native cache (both loading
799
                   and storing). An incorrect compiler implementation may thus corrupt runtime
800
                   state if reflection is used. Use external packages like `Cthulhu.jl`
801
                   introspecting compiler behavior with a separated cache partition.
802

803
2. `:codegen`   - Activate the compiler for internal codegen purposes. The new compiler
804
                  will be invoked whenever the runtime requests compilation.
805

806
`@activate Compiler` without options is equivalent to `@activate Compiler[:reflection]`.
807

808
"""
809
macro activate(what)
112✔
810
    options = Symbol[]
112✔
811
    if isexpr(what, :ref)
112✔
812
        Component = what.args[1]
3✔
813
        for i = 2:length(what.args)
3✔
814
            arg = what.args[i]
6✔
815
            if !isa(arg, QuoteNode) || !isa(arg.value, Symbol)
12✔
816
                error("Usage Error: Option $arg is not a symbol")
×
817
            end
818
            push!(options, arg.value)
6✔
819
        end
6✔
820
    else
821
        Component = what
109✔
822
    end
823
    if !isa(Component, Symbol)
112✔
824
        error("Usage Error: Component $Component is not a symbol")
×
825
    end
826
    allowed_components = (:Compiler, :JuliaLowering)
112✔
827
    if !(Component in allowed_components)
112✔
828
        error("Usage Error: Component $Component is not recognized. Expected one of $allowed_components")
×
829
    end
830
    if Component === :Compiler && isempty(options)
112✔
831
        push!(options, :reflection)
109✔
832
    end
833
    options = map(options) do opt
112✔
834
        Expr(:kw, opt, true)
115✔
835
    end
836
    return :(let M = Base.require($__module__, $(QuoteNode(Component)))
112✔
837
                 @invokelatest M.activate!(; $(options...))
838
             end)
839
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