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

JuliaLang / julia / #37591

pending completion
#37591

push

local

web-flow
Allocation Profiler: Types for all allocations (#50337)

Pass the types to the allocator functions.

-------

Before this PR, we were missing the types for allocations in two cases:

1. allocations from codegen
2. allocations in `gc_managed_realloc_`

The second one is easy: those are always used for buffers, right?

For the first one: we extend the allocation functions called from
codegen, to take the type as a parameter, and set the tag there.

I kept the old interfaces around, since I think that they cannot be
removed due to supporting legacy code?

------

An example of the generated code:
```julia
  %ptls_field6 = getelementptr inbounds {}**, {}*** %4, i64 2
  %13 = bitcast {}*** %ptls_field6 to i8**
  %ptls_load78 = load i8*, i8** %13, align 8
  %box = call noalias nonnull dereferenceable(32) {}* @ijl_gc_pool_alloc_typed(i8* %ptls_load78, i32 1184, i32 32, i64 4366152144) #7
```

Fixes #43688.
Fixes #45268.

Co-authored-by: Valentin Churavy <vchuravy@users.noreply.github.com>

72755 of 84117 relevant lines covered (86.49%)

22738368.36 hits per line

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

85.53
/base/expr.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads)
8,387✔
4
isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n
×
5
const is_expr = isexpr
6

7
## symbols ##
8

9
"""
10
    gensym([tag])
11

12
Generates a symbol which will not conflict with other variable names (in the same module).
13
"""
14
gensym() = ccall(:jl_gensym, Ref{Symbol}, ())
486✔
15

16
gensym(s::String) = ccall(:jl_tagged_gensym, Ref{Symbol}, (Ptr{UInt8}, Csize_t), s, sizeof(s))
241✔
17

18
gensym(ss::String...) = map(gensym, ss)
×
19
gensym(s::Symbol) = ccall(:jl_tagged_gensym, Ref{Symbol}, (Ptr{UInt8}, Csize_t), s, -1 % Csize_t)
23✔
20

21
"""
22
    @gensym
23

24
Generates a gensym symbol for a variable. For example, `@gensym x y` is transformed into
25
`x = gensym("x"); y = gensym("y")`.
26
"""
27
macro gensym(names...)
1✔
28
    blk = Expr(:block)
1✔
29
    for name in names
1✔
30
        push!(blk.args, :($(esc(name)) = gensym($(string(name)))))
1✔
31
    end
1✔
32
    push!(blk.args, :nothing)
1✔
33
    return blk
1✔
34
end
35

36
## expressions ##
37

38
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head
6,799,012,105✔
39
isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n
64,767✔
40

41
copy(e::Expr) = exprarray(e.head, copy_exprargs(e.args))
141,398,264✔
42

43
# copy parts of an AST that the compiler mutates
44
function copy_exprs(@nospecialize(x))
457,100,536✔
45
    if isa(x, Expr)
457,100,536✔
46
        return copy(x)
141,381,280✔
47
    elseif isa(x, PhiNode)
315,719,256✔
48
        values = x.values
3,082,569✔
49
        nvalues = length(values)
3,082,569✔
50
        new_values = Vector{Any}(undef, nvalues)
3,082,569✔
51
        @inbounds for i = 1:nvalues
6,161,272✔
52
            isassigned(values, i) || continue
6,004,124✔
53
            new_values[i] = copy_exprs(values[i])
6,004,111✔
54
        end
8,929,545✔
55
        return PhiNode(copy(x.edges), new_values)
3,082,569✔
56
    elseif isa(x, PhiCNode)
312,636,687✔
57
        values = x.values
25✔
58
        nvalues = length(values)
25✔
59
        new_values = Vector{Any}(undef, nvalues)
25✔
60
        @inbounds for i = 1:nvalues
50✔
61
            isassigned(values, i) || continue
35✔
62
            new_values[i] = copy_exprs(values[i])
35✔
63
        end
45✔
64
        return PhiCNode(new_values)
25✔
65
    end
66
    return x
312,636,662✔
67
end
68
copy_exprargs(x::Array{Any,1}) = Any[copy_exprs(@inbounds x[i]) for i in 1:length(x)]
343,753,236✔
69

70
@eval exprarray(head::Symbol, arg::Array{Any,1}) = $(Expr(:new, :Expr, :head, :arg))
141,400,186✔
71

72
# create copies of the CodeInfo definition, and any mutable fields
73
function copy(c::CodeInfo)
7,679,378✔
74
    cnew = ccall(:jl_copy_code_info, Ref{CodeInfo}, (Any,), c)
7,679,378✔
75
    cnew.code = copy_exprargs(cnew.code)
119,342,753✔
76
    cnew.slotnames = copy(cnew.slotnames)
7,679,378✔
77
    cnew.slotflags = copy(cnew.slotflags)
7,679,378✔
78
    if cnew.slottypes !== nothing
7,679,378✔
79
        cnew.slottypes = copy(cnew.slottypes)
5,322,755✔
80
    end
81
    cnew.codelocs  = copy(cnew.codelocs)
7,679,378✔
82
    cnew.linetable = copy(cnew.linetable::Union{Vector{Any},Vector{Core.LineInfoNode}})
15,357,766✔
83
    cnew.ssaflags  = copy(cnew.ssaflags)
7,679,378✔
84
    cnew.edges     = cnew.edges === nothing ? nothing : copy(cnew.edges::Vector)
7,679,379✔
85
    ssavaluetypes  = cnew.ssavaluetypes
7,679,378✔
86
    ssavaluetypes isa Vector{Any} && (cnew.ssavaluetypes = copy(ssavaluetypes))
7,679,378✔
87
    return cnew
7,679,378✔
88
end
89

90

91
==(x::Expr, y::Expr) = x.head === y.head && isequal(x.args, y.args)
13,463✔
92
==(x::QuoteNode, y::QuoteNode) = isequal(x.value, y.value)
1,513✔
93
==(stmt1::Core.PhiNode, stmt2::Core.PhiNode) = stmt1.edges == stmt2.edges && stmt1.values == stmt2.values
×
94

95
"""
96
    macroexpand(m::Module, x; recursive=true)
97

98
Take the expression `x` and return an equivalent expression with all macros removed (expanded)
99
for executing in module `m`.
100
The `recursive` keyword controls whether deeper levels of nested macros are also expanded.
101
This is demonstrated in the example below:
102
```julia-repl
103
julia> module M
104
           macro m1()
105
               42
106
           end
107
           macro m2()
108
               :(@m1())
109
           end
110
       end
111
M
112

113
julia> macroexpand(M, :(@m2()), recursive=true)
114
42
115

116
julia> macroexpand(M, :(@m2()), recursive=false)
117
:(#= REPL[16]:6 =# M.@m1)
118
```
119
"""
120
function macroexpand(m::Module, @nospecialize(x); recursive=true)
289✔
121
    if recursive
32✔
122
        ccall(:jl_macroexpand, Any, (Any, Any), x, m)
257✔
123
    else
124
        ccall(:jl_macroexpand1, Any, (Any, Any), x, m)
×
125
    end
126
end
127

128
"""
129
    @macroexpand
130

131
Return equivalent expression with all macros removed (expanded).
132

133
There are differences between `@macroexpand` and [`macroexpand`](@ref).
134

135
* While [`macroexpand`](@ref) takes a keyword argument `recursive`, `@macroexpand`
136
  is always recursive. For a non recursive macro version, see [`@macroexpand1`](@ref).
137

138
* While [`macroexpand`](@ref) has an explicit `module` argument, `@macroexpand` always
139
  expands with respect to the module in which it is called.
140

141
This is best seen in the following example:
142
```julia-repl
143
julia> module M
144
           macro m()
145
               1
146
           end
147
           function f()
148
               (@macroexpand(@m),
149
                macroexpand(M, :(@m)),
150
                macroexpand(Main, :(@m))
151
               )
152
           end
153
       end
154
M
155

156
julia> macro m()
157
           2
158
       end
159
@m (macro with 1 method)
160

161
julia> M.f()
162
(1, 1, 2)
163
```
164
With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module `M` in the example).
165
With `macroexpand` the expression expands in the module given as the first argument.
166
"""
167
macro macroexpand(code)
34✔
168
    return :(macroexpand($__module__, $(QuoteNode(code)), recursive=true))
34✔
169
end
170

171

172
"""
173
    @macroexpand1
174

175
Non recursive version of [`@macroexpand`](@ref).
176
"""
177
macro macroexpand1(code)
178
    return :(macroexpand($__module__, $(QuoteNode(code)), recursive=false))
179
end
180

181
## misc syntax ##
182

183
"""
184
    Core.eval(m::Module, expr)
185

186
Evaluate an expression in the given module and return the result.
187
"""
188
Core.eval
189

190
"""
191
    @inline
192

193
Give a hint to the compiler that this function is worth inlining.
194

195
Small functions typically do not need the `@inline` annotation,
196
as the compiler does it automatically. By using `@inline` on bigger functions,
197
an extra nudge can be given to the compiler to inline it.
198

199
`@inline` can be applied immediately before a function definition or within a function body.
200

201
```julia
202
# annotate long-form definition
203
@inline function longdef(x)
204
    ...
205
end
206

207
# annotate short-form definition
208
@inline shortdef(x) = ...
209

210
# annotate anonymous function that a `do` block creates
211
f() do
212
    @inline
213
    ...
214
end
215
```
216

217
!!! compat "Julia 1.8"
218
    The usage within a function body requires at least Julia 1.8.
219

220
---
221
    @inline block
222

223
Give a hint to the compiler that calls within `block` are worth inlining.
224

225
```julia
226
# The compiler will try to inline `f`
227
@inline f(...)
228

229
# The compiler will try to inline `f`, `g` and `+`
230
@inline f(...) + g(...)
231
```
232

233
!!! note
234
    A callsite annotation always has the precedence over the annotation applied to the
235
    definition of the called function:
236
    ```julia
237
    @noinline function explicit_noinline(args...)
238
        # body
239
    end
240

241
    let
242
        @inline explicit_noinline(args...) # will be inlined
243
    end
244
    ```
245

246
!!! note
247
    When there are nested callsite annotations, the innermost annotation has the precedence:
248
    ```julia
249
    @noinline let a0, b0 = ...
250
        a = @inline f(a0)  # the compiler will try to inline this call
251
        b = f(b0)          # the compiler will NOT try to inline this call
252
        return a, b
253
    end
254
    ```
255

256
!!! warning
257
    Although a callsite annotation will try to force inlining in regardless of the cost model,
258
    there are still chances it can't succeed in it. Especially, recursive calls can not be
259
    inlined even if they are annotated as `@inline`d.
260

261
!!! compat "Julia 1.8"
262
    The callsite annotation requires at least Julia 1.8.
263
"""
264
macro inline(x)
1,953✔
265
    return annotate_meta_def_or_block(x, :inline)
1,953✔
266
end
267

268
"""
269
    @noinline
270

271
Give a hint to the compiler that it should not inline a function.
272

273
Small functions are typically inlined automatically.
274
By using `@noinline` on small functions, auto-inlining can be
275
prevented.
276

277
`@noinline` can be applied immediately before a function definition or within a function body.
278

279
```julia
280
# annotate long-form definition
281
@noinline function longdef(x)
282
    ...
283
end
284

285
# annotate short-form definition
286
@noinline shortdef(x) = ...
287

288
# annotate anonymous function that a `do` block creates
289
f() do
290
    @noinline
291
    ...
292
end
293
```
294

295
!!! compat "Julia 1.8"
296
    The usage within a function body requires at least Julia 1.8.
297

298
---
299
    @noinline block
300

301
Give a hint to the compiler that it should not inline the calls within `block`.
302

303
```julia
304
# The compiler will try to not inline `f`
305
@noinline f(...)
306

307
# The compiler will try to not inline `f`, `g` and `+`
308
@noinline f(...) + g(...)
309
```
310

311
!!! note
312
    A callsite annotation always has the precedence over the annotation applied to the
313
    definition of the called function:
314
    ```julia
315
    @inline function explicit_inline(args...)
316
        # body
317
    end
318

319
    let
320
        @noinline explicit_inline(args...) # will not be inlined
321
    end
322
    ```
323

324
!!! note
325
    When there are nested callsite annotations, the innermost annotation has the precedence:
326
    ```julia
327
    @inline let a0, b0 = ...
328
        a = @noinline f(a0)  # the compiler will NOT try to inline this call
329
        b = f(b0)            # the compiler will try to inline this call
330
        return a, b
331
    end
332
    ```
333

334
!!! compat "Julia 1.8"
335
    The callsite annotation requires at least Julia 1.8.
336

337
---
338
!!! note
339
    If the function is trivial (for example returning a constant) it might get inlined anyway.
340
"""
341
macro noinline(x)
253✔
342
    return annotate_meta_def_or_block(x, :noinline)
253✔
343
end
344

345
"""
346
    @constprop setting [ex]
347

348
Control the mode of interprocedural constant propagation for the annotated function.
349

350
Two `setting`s are supported:
351

352
- `@constprop :aggressive [ex]`: apply constant propagation aggressively.
353
  For a method where the return type depends on the value of the arguments,
354
  this can yield improved inference results at the cost of additional compile time.
355
- `@constprop :none [ex]`: disable constant propagation. This can reduce compile
356
  times for functions that Julia might otherwise deem worthy of constant-propagation.
357
  Common cases are for functions with `Bool`- or `Symbol`-valued arguments or keyword arguments.
358

359
`@constprop` can be applied immediately before a function definition or within a function body.
360

361
```julia
362
# annotate long-form definition
363
@constprop :aggressive function longdef(x)
364
  ...
365
end
366

367
# annotate short-form definition
368
@constprop :aggressive shortdef(x) = ...
369

370
# annotate anonymous function that a `do` block creates
371
f() do
372
    @constprop :aggressive
373
    ...
374
end
375
```
376

377
!!! compat "Julia 1.10"
378
  The usage within a function body requires at least Julia 1.10.
379
"""
380
macro constprop(setting, ex)
32✔
381
    sym = constprop_setting(setting)
32✔
382
    isa(ex, Expr) && return esc(pushmeta!(ex, sym))
32✔
383
    throw(ArgumentError(LazyString("Bad expression `", ex, "` in `@constprop settings ex`")))
×
384
end
385
macro constprop(setting)
386
    sym = constprop_setting(setting)
387
    return Expr(:meta, sym)
388
end
389

390
function constprop_setting(@nospecialize setting)
32✔
391
    isa(setting, QuoteNode) && (setting = setting.value)
32✔
392
    if setting === :aggressive
32✔
393
        return :aggressive_constprop
21✔
394
    elseif setting === :none
11✔
395
        return :no_constprop
11✔
396
    end
397
    throw(ArgumentError(LazyString("@constprop "), setting, "not supported"))
×
398
end
399

400
"""
401
    @assume_effects setting... [ex]
402

403
Override the compiler's effect modeling for the given method or foreign call.
404
`@assume_effects` can be applied immediately before a function definition or within a function body.
405
It can also be applied immediately before a `@ccall` expression.
406

407
!!! compat "Julia 1.8"
408
    Using `Base.@assume_effects` requires Julia version 1.8.
409

410
# Examples
411
```jldoctest
412
julia> Base.@assume_effects :terminates_locally function pow(x)
413
           # this :terminates_locally allows `pow` to be constant-folded
414
           res = 1
415
           1 < x < 20 || error("bad pow")
416
           while x > 1
417
               res *= x
418
               x -= 1
419
           end
420
           return res
421
       end
422
pow (generic function with 1 method)
423

424
julia> code_typed() do
425
           pow(12)
426
       end
427
1-element Vector{Any}:
428
 CodeInfo(
429
1 ─     return 479001600
430
) => Int64
431

432
julia> code_typed() do
433
           map((2,3,4)) do x
434
               # this :terminates_locally allows this anonymous function to be constant-folded
435
               Base.@assume_effects :terminates_locally
436
               res = 1
437
               1 < x < 20 || error("bad pow")
438
               while x > 1
439
                   res *= x
440
                   x -= 1
441
               end
442
               return res
443
           end
444
       end
445
1-element Vector{Any}:
446
 CodeInfo(
447
1 ─     return (2, 6, 24)
448
) => Tuple{Int64, Int64, Int64}
449

450
julia> Base.@assume_effects :total !:nothrow @ccall jl_type_intersection(Vector{Int}::Any, Vector{<:Integer}::Any)::Any
451
Vector{Int64} (alias for Array{Int64, 1})
452
```
453

454
!!! compat "Julia 1.10"
455
  The usage within a function body requires at least Julia 1.10.
456

457
!!! warning
458
    Improper use of this macro causes undefined behavior (including crashes,
459
    incorrect answers, or other hard to track bugs). Use with care and only as a
460
    last resort if absolutely required. Even in such a case, you SHOULD take all
461
    possible steps to minimize the strength of the effect assertion (e.g.,
462
    do not use `:total` if `:nothrow` would have been sufficient).
463

464
In general, each `setting` value makes an assertion about the behavior of the
465
function, without requiring the compiler to prove that this behavior is indeed
466
true. These assertions are made for all world ages. It is thus advisable to limit
467
the use of generic functions that may later be extended to invalidate the
468
assumption (which would cause undefined behavior).
469

470
The following `setting`s are supported.
471
- `:consistent`
472
- `:effect_free`
473
- `:nothrow`
474
- `:terminates_globally`
475
- `:terminates_locally`
476
- `:notaskstate`
477
- `:inaccessiblememonly`
478
- `:foldable`
479
- `:removable`
480
- `:total`
481

482
# Extended help
483

484
---
485
## `:consistent`
486

487
The `:consistent` setting asserts that for egal (`===`) inputs:
488
- The manner of termination (return value, exception, non-termination) will always be the same.
489
- If the method returns, the results will always be egal.
490

491
!!! note
492
    This in particular implies that the method must not return a freshly allocated
493
    mutable object. Multiple allocations of mutable objects (even with identical
494
    contents) are not egal.
495

496
!!! note
497
    The `:consistent`-cy assertion is made world-age wise. More formally, write
498
    ``fáµ¢`` for the evaluation of ``f`` in world-age ``i``, then we require:
499
    ```math
500
    ∀ i, x, y: x ≡ y → fᵢ(x) ≡ fᵢ(y)
501
    ```
502
    However, for two world ages ``i``, ``j`` s.t. ``i ≠ j``, we may have ``fᵢ(x) ≢ fⱼ(y)``.
503

504
    A further implication is that `:consistent` functions may not make their
505
    return value dependent on the state of the heap or any other global state
506
    that is not constant for a given world age.
507

508
!!! note
509
    The `:consistent`-cy includes all legal rewrites performed by the optimizer.
510
    For example, floating-point fastmath operations are not considered `:consistent`,
511
    because the optimizer may rewrite them causing the output to not be `:consistent`,
512
    even for the same world age (e.g. because one ran in the interpreter, while
513
    the other was optimized).
514

515
!!! note
516
    The `:consistent`-cy assertion currently includes the assertion that the function
517
    will not execute any undefined behavior (for any input). Note that undefined behavior
518
    may technically cause the function to violate other effect assertions (such as
519
    `:nothrow` or `:effect_free`) as well, but we do not model this, and all effects
520
    except `:consistent` assume the absence of undefined behavior.
521

522
!!! note
523
    If `:consistent` functions terminate by throwing an exception, that exception
524
    itself is not required to meet the egality requirement specified above.
525

526
---
527
## `:effect_free`
528

529
The `:effect_free` setting asserts that the method is free of externally semantically
530
visible side effects. The following is an incomplete list of externally semantically
531
visible side effects:
532
- Changing the value of a global variable.
533
- Mutating the heap (e.g. an array or mutable value), except as noted below
534
- Changing the method table (e.g. through calls to eval)
535
- File/Network/etc. I/O
536
- Task switching
537

538
However, the following are explicitly not semantically visible, even if they
539
may be observable:
540
- Memory allocations (both mutable and immutable)
541
- Elapsed time
542
- Garbage collection
543
- Heap mutations of objects whose lifetime does not exceed the method (i.e.
544
  were allocated in the method and do not escape).
545
- The returned value (which is externally visible, but not a side effect)
546

547
The rule of thumb here is that an externally visible side effect is anything
548
that would affect the execution of the remainder of the program if the function
549
were not executed.
550

551
!!! note
552
    The `:effect_free` assertion is made both for the method itself and any code
553
    that is executed by the method. Keep in mind that the assertion must be
554
    valid for all world ages and limit use of this assertion accordingly.
555

556
---
557
## `:nothrow`
558

559
The `:nothrow` settings asserts that this method does not terminate abnormally
560
(i.e. will either always return a value or never return).
561

562
!!! note
563
    It is permissible for `:nothrow` annotated methods to make use of exception
564
    handling internally as long as the exception is not rethrown out of the
565
    method itself.
566

567
!!! note
568
    `MethodErrors` and similar exceptions count as abnormal termination.
569

570
---
571
## `:terminates_globally`
572

573
The `:terminates_globally` settings asserts that this method will eventually terminate
574
(either normally or abnormally), i.e. does not loop indefinitely.
575

576
!!! note
577
    This `:terminates_globally` assertion covers any other methods called by the annotated method.
578

579
!!! note
580
    The compiler will consider this a strong indication that the method will
581
    terminate relatively *quickly* and may (if otherwise legal), call this
582
    method at compile time. I.e. it is a bad idea to annotate this setting
583
    on a method that *technically*, but not *practically*, terminates.
584

585
---
586
## `:terminates_locally`
587

588
The `:terminates_locally` setting is like `:terminates_globally`, except that it only
589
applies to syntactic control flow *within* the annotated method. It is thus
590
a much weaker (and thus safer) assertion that allows for the possibility of
591
non-termination if the method calls some other method that does not terminate.
592

593
!!! note
594
    `:terminates_globally` implies `:terminates_locally`.
595

596
---
597
## `:notaskstate`
598

599
The `:notaskstate` setting asserts that the method does not use or modify the
600
local task state (task local storage, RNG state, etc.) and may thus be safely
601
moved between tasks without observable results.
602

603
!!! note
604
    The implementation of exception handling makes use of state stored in the
605
    task object. However, this state is currently not considered to be within
606
    the scope of `:notaskstate` and is tracked separately using the `:nothrow`
607
    effect.
608

609
!!! note
610
    The `:notaskstate` assertion concerns the state of the *currently running task*.
611
    If a reference to a `Task` object is obtained by some other means that
612
    does not consider which task is *currently* running, the `:notaskstate`
613
    effect need not be tainted. This is true, even if said task object happens
614
    to be `===` to the currently running task.
615

616
!!! note
617
    Access to task state usually also results in the tainting of other effects,
618
    such as `:effect_free` (if task state is modified) or `:consistent` (if
619
    task state is used in the computation of the result). In particular,
620
    code that is not `:notaskstate`, but is `:effect_free` and `:consistent`
621
    may still be dead-code-eliminated and thus promoted to `:total`.
622

623
---
624
## `:inaccessiblememonly`
625

626
The `:inaccessiblememonly` setting asserts that the method does not access or modify
627
externally accessible mutable memory. This means the method can access or modify mutable
628
memory for newly allocated objects that is not accessible by other methods or top-level
629
execution before return from the method, but it can not access or modify any mutable
630
global state or mutable memory pointed to by its arguments.
631

632
!!! note
633
    Below is an incomplete list of examples that invalidate this assumption:
634
    - a global reference or `getglobal` call to access a mutable global variable
635
    - a global assignment or `setglobal!` call to perform assignment to a non-constant global variable
636
    - `setfield!` call that changes a field of a global mutable variable
637

638
!!! note
639
    This `:inaccessiblememonly` assertion covers any other methods called by the annotated method.
640

641
---
642
## `:foldable`
643

644
This setting is a convenient shortcut for the set of effects that the compiler
645
requires to be guaranteed to constant fold a call at compile time. It is
646
currently equivalent to the following `setting`s:
647
- `:consistent`
648
- `:effect_free`
649
- `:terminates_globally`
650

651
!!! note
652
    This list in particular does not include `:nothrow`. The compiler will still
653
    attempt constant propagation and note any thrown error at compile time. Note
654
    however, that by the `:consistent`-cy requirements, any such annotated call
655
    must consistently throw given the same argument values.
656

657
!!! note
658
    An explicit `@inbounds` annotation inside the function will also disable
659
    constant folding and not be overridden by `:foldable`.
660

661
---
662
## `:removable`
663

664
This setting is a convenient shortcut for the set of effects that the compiler
665
requires to be guaranteed to delete a call whose result is unused at compile time.
666
It is currently equivalent to the following `setting`s:
667
- `:effect_free`
668
- `:nothrow`
669
- `:terminates_globally`
670

671
---
672
## `:total`
673

674
This `setting` is the maximum possible set of effects. It currently implies
675
the following other `setting`s:
676
- `:consistent`
677
- `:effect_free`
678
- `:nothrow`
679
- `:terminates_globally`
680
- `:notaskstate`
681
- `:inaccessiblememonly`
682

683
!!! warning
684
    `:total` is a very strong assertion and will likely gain additional semantics
685
    in future versions of Julia (e.g. if additional effects are added and included
686
    in the definition of `:total`). As a result, it should be used with care.
687
    Whenever possible, prefer to use the minimum possible set of specific effect
688
    assertions required for a particular application. In cases where a large
689
    number of effect overrides apply to a set of functions, a custom macro is
690
    recommended over the use of `:total`.
691

692
---
693
## Negated effects
694

695
Effect names may be prefixed by `!` to indicate that the effect should be removed
696
from an earlier meta effect. For example, `:total !:nothrow` indicates that while
697
the call is generally total, it may however throw.
698
"""
699
macro assume_effects(args...)
19✔
700
    lastex = args[end]
19✔
701
    inner = unwrap_macrocalls(lastex)
19✔
702
    if is_function_def(inner)
19✔
703
        ex = lastex
15✔
704
        idx = length(args)-1
15✔
705
    elseif isexpr(lastex, :macrocall) && lastex.args[1] === Symbol("@ccall")
4✔
706
        ex = lastex
3✔
707
        idx = length(args)-1
3✔
708
    else # anonymous function case
709
        ex = nothing
1✔
710
        idx = length(args)
1✔
711
    end
712
    (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly) =
19✔
713
        (false, false, false, false, false, false, false, false)
714
    for org_setting in args[1:idx]
19✔
715
        (setting, val) = compute_assumed_setting(org_setting)
20✔
716
        if setting === :consistent
20✔
717
            consistent = val
×
718
        elseif setting === :effect_free
20✔
719
            effect_free = val
2✔
720
        elseif setting === :nothrow
18✔
721
            nothrow = val
1✔
722
        elseif setting === :terminates_globally
17✔
723
            terminates_globally = val
4✔
724
        elseif setting === :terminates_locally
13✔
725
            terminates_locally = val
2✔
726
        elseif setting === :notaskstate
11✔
727
            notaskstate = val
2✔
728
        elseif setting === :inaccessiblememonly
9✔
729
            inaccessiblememonly = val
×
730
        elseif setting === :foldable
9✔
731
            consistent = effect_free = terminates_globally = val
3✔
732
        elseif setting === :removable
6✔
733
            effect_free = nothrow = terminates_globally = val
2✔
734
        elseif setting === :total
4✔
735
            consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = val
4✔
736
        else
737
            throw(ArgumentError("@assume_effects $org_setting not supported"))
×
738
        end
739
    end
20✔
740
    if is_function_def(inner)
19✔
741
        return esc(pushmeta!(ex, :purity,
15✔
742
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly))
743
    elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall")
4✔
744
        ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects"))
3✔
745
        insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride(
3✔
746
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly,
747
        )))
748
        return esc(ex)
3✔
749
    else # anonymous function case
750
        return Expr(:meta, Expr(:purity,
1✔
751
            consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly))
752
    end
753
end
754

755
function compute_assumed_setting(@nospecialize(setting), val::Bool=true)
21✔
756
    if isexpr(setting, :call) && setting.args[1] === :(!)
101✔
757
        return compute_assumed_setting(setting.args[2], !val)
2✔
758
    elseif isa(setting, QuoteNode)
40✔
759
        return compute_assumed_setting(setting.value, val)
20✔
760
    else
761
        return (setting, val)
20✔
762
    end
763
end
764

765
"""
766
    Base.@nospecializeinfer function f(args...)
767
        @nospecialize ...
768
        ...
769
    end
770
    Base.@nospecializeinfer f(@nospecialize args...) = ...
771

772
Tells the compiler to infer `f` using the declared types of `@nospecialize`d arguments.
773
This can be used to limit the number of compiler-generated specializations during inference.
774

775
# Example
776

777
```julia
778
julia> f(A::AbstractArray) = g(A)
779
f (generic function with 1 method)
780

781
julia> @noinline Base.@nospecializeinfer g(@nospecialize(A::AbstractArray)) = A[1]
782
g (generic function with 1 method)
783

784
julia> @code_typed f([1.0])
785
CodeInfo(
786
1 ─ %1 = invoke Main.g(_2::AbstractArray)::Any
787
└──      return %1
788
) => Any
789
```
790

791
In this example, `f` will be inferred for each specific type of `A`,
792
but `g` will only be inferred once with the declared argument type `A::AbstractArray`,
793
meaning that the compiler will not likely see the excessive inference time on it
794
while it can not infer the concrete return type of it.
795
Without the `@nospecializeinfer`, `f([1.0])` would infer the return type of `g` as `Float64`,
796
indicating that inference ran for `g(::Vector{Float64})` despite the prohibition on
797
specialized code generation.
798
"""
799
macro nospecializeinfer(ex)
800
    esc(isa(ex, Expr) ? pushmeta!(ex, :nospecializeinfer) : ex)
801
end
802

803
"""
804
    @propagate_inbounds
805

806
Tells the compiler to inline a function while retaining the caller's inbounds context.
807
"""
808
macro propagate_inbounds(ex)
138✔
809
    if isa(ex, Expr)
138✔
810
        pushmeta!(ex, :inline)
138✔
811
        pushmeta!(ex, :propagate_inbounds)
138✔
812
    end
813
    esc(ex)
138✔
814
end
815

816
"""
817
    @polly
818

819
Tells the compiler to apply the polyhedral optimizer Polly to a function.
820
"""
821
macro polly(ex)
822
    esc(isa(ex, Expr) ? pushmeta!(ex, :polly) : ex)
823
end
824

825
## some macro utilities ##
826

827
unwrap_macrocalls(@nospecialize(x)) = x
1✔
828
function unwrap_macrocalls(ex::Expr)
18✔
829
    inner = ex
×
830
    while inner.head === :macrocall
4,761✔
831
        inner = inner.args[end]::Expr
47✔
832
    end
47✔
833
    return inner
4,714✔
834
end
835

836
function pushmeta!(ex::Expr, sym::Symbol, args::Any...)
2,490✔
837
    if isempty(args)
×
838
        tag = sym
×
839
    else
840
        tag = Expr(sym, args...)::Expr
15✔
841
    end
842

843
    inner = unwrap_macrocalls(ex)
2,521✔
844

845
    idx, exargs = findmeta(inner)
2,490✔
846
    if idx != 0
2,490✔
847
        push!(exargs[idx].args, tag)
336✔
848
    else
849
        body = inner.args[2]::Expr
2,322✔
850
        pushfirst!(body.args, Expr(:meta, tag))
2,322✔
851
    end
852
    ex
2,490✔
853
end
854

855
popmeta!(body, sym) = _getmeta(body, sym, true)
×
856
peekmeta(body, sym) = _getmeta(body, sym, false)
×
857

858
function _getmeta(body::Expr, sym::Symbol, delete::Bool)
×
859
    body.head === :block || return false, []
×
860
    _getmeta(body.args, sym, delete)
×
861
end
862
_getmeta(arg, sym, delete::Bool) = (false, [])
×
863
function _getmeta(body::Array{Any,1}, sym::Symbol, delete::Bool)
×
864
    idx, blockargs = findmeta_block(body, args -> findmetaarg(args,sym)!=0)
×
865
    if idx == 0
×
866
        return false, []
×
867
    end
868
    metaargs = blockargs[idx].args
×
869
    i = findmetaarg(blockargs[idx].args, sym)
×
870
    if i == 0
×
871
        return false, []
×
872
    end
873
    ret = isa(metaargs[i], Expr) ? (metaargs[i]::Expr).args : []
×
874
    if delete
×
875
        deleteat!(metaargs, i)
×
876
        isempty(metaargs) && deleteat!(blockargs, idx)
×
877
    end
878
    true, ret
×
879
end
880

881
# Find index of `sym` in a meta expression argument list, or 0.
882
function findmetaarg(metaargs, sym)
×
883
    for i = 1:length(metaargs)
×
884
        arg = metaargs[i]
×
885
        if (isa(arg, Symbol) && (arg::Symbol)    == sym) ||
×
886
           (isa(arg, Expr)   && (arg::Expr).head == sym)
887
            return i
×
888
        end
889
    end
×
890
    return 0
×
891
end
892

893
function annotate_meta_def_or_block(@nospecialize(ex), meta::Symbol)
2,206✔
894
    inner = unwrap_macrocalls(ex)
2,206✔
895
    if is_function_def(inner)
3,485✔
896
        # annotation on a definition
897
        return esc(pushmeta!(ex, meta))
2,167✔
898
    else
899
        # annotation on a block
900
        return Expr(:block,
39✔
901
                    Expr(meta, true),
902
                    Expr(:local, Expr(:(=), :val, esc(ex))),
903
                    Expr(meta, false),
904
                    :val)
905
    end
906
end
907

908
function is_short_function_def(@nospecialize(ex))
2,732✔
909
    isexpr(ex, :(=)) || return false
2,786✔
910
    while length(ex.args) >= 1 && isa(ex.args[1], Expr)
6,222✔
911
        (ex.args[1].head === :call) && return true
6,220✔
912
        (ex.args[1].head === :where || ex.args[1].head === :(::)) || return false
441✔
913
        ex = ex.args[1]
866✔
914
    end
433✔
915
    return false
1✔
916
end
917
is_function_def(@nospecialize(ex)) =
7,425✔
918
    return isexpr(ex, :function) || is_short_function_def(ex) || isexpr(ex, :->)
919

920
function findmeta(ex::Expr)
2,490✔
921
    if is_function_def(ex)
3,902✔
922
        body = ex.args[2]::Expr
2,490✔
923
        body.head === :block || error(body, " is not a block expression")
2,490✔
924
        return findmeta_block(ex.args)
2,490✔
925
    end
926
    error(ex, " is not a function expression")
×
927
end
928

929
findmeta(ex::Array{Any,1}) = findmeta_block(ex)
×
930

931
function findmeta_block(exargs, argsmatch=args->true)
2,490✔
932
    for i = 1:length(exargs)
17,427✔
933
        a = exargs[i]
14,019✔
934
        if isa(a, Expr)
14,019✔
935
            if a.head === :meta && argsmatch(a.args)
8,954✔
936
                return i, exargs
168✔
937
            elseif a.head === :block
8,786✔
938
                idx, exa = findmeta_block(a.args, argsmatch)
2,490✔
939
                if idx != 0
2,490✔
940
                    return idx, exa
168✔
941
                end
942
            end
943
        end
944
    end
22,722✔
945
    return 0, []
4,644✔
946
end
947

948
remove_linenums!(ex) = ex
48✔
949
function remove_linenums!(ex::Expr)
71,938✔
950
    if ex.head === :block || ex.head === :quote
139,147✔
951
        # remove line number expressions from metadata (not argument literal or inert) position
952
        filter!(ex.args) do x
5,622✔
953
            isa(x, Expr) && x.head === :line && return false
27,463✔
954
            isa(x, LineNumberNode) && return false
27,463✔
955
            return true
14,228✔
956
        end
957
    end
958
    for subex in ex.args
72,291✔
959
        subex isa Expr && remove_linenums!(subex)
156,095✔
960
    end
227,680✔
961
    return ex
71,938✔
962
end
963
function remove_linenums!(src::CodeInfo)
872✔
964
    src.codelocs .= 0
45,259✔
965
    length(src.linetable) > 1 && resize!(src.linetable, 1)
872✔
966
    return src
872✔
967
end
968

969
replace_linenums!(ex, ln::LineNumberNode) = ex
×
970
function replace_linenums!(ex::Expr, ln::LineNumberNode)
1,640✔
971
    if ex.head === :block || ex.head === :quote
2,870✔
972
        # replace line number expressions from metadata (not argument literal or inert) position
973
        map!(ex.args, ex.args) do @nospecialize(x)
820✔
974
            isa(x, Expr) && x.head === :line && length(x.args) == 1 && return Expr(:line, ln.line)
820✔
975
            isa(x, Expr) && x.head === :line && length(x.args) == 2 && return Expr(:line, ln.line, ln.file)
820✔
976
            isa(x, LineNumberNode) && return ln
820✔
977
            return x
410✔
978
        end
979
    end
980
    # preserve any linenums inside `esc(...)` guards
981
    if ex.head !== :escape
1,640✔
982
        for subex in ex.args
1,640✔
983
            subex isa Expr && replace_linenums!(subex, ln)
1,640✔
984
        end
2,460✔
985
    end
986
    return ex
1,640✔
987
end
988

989
macro generated()
2✔
990
    return Expr(:generated)
2✔
991
end
992

993
"""
994
    @generated f
995

996
`@generated` is used to annotate a function which will be generated.
997
In the body of the generated function, only types of arguments can be read
998
(not the values). The function returns a quoted expression evaluated when the
999
function is called. The `@generated` macro should not be used on functions mutating
1000
the global scope or depending on mutable elements.
1001

1002
See [Metaprogramming](@ref) for further details.
1003

1004
# Examples
1005
```jldoctest
1006
julia> @generated function bar(x)
1007
           if x <: Integer
1008
               return :(x ^ 2)
1009
           else
1010
               return :(x)
1011
           end
1012
       end
1013
bar (generic function with 1 method)
1014

1015
julia> bar(4)
1016
16
1017

1018
julia> bar("baz")
1019
"baz"
1020
```
1021
"""
1022
macro generated(f)
46✔
1023
    if isa(f, Expr) && (f.head === :function || is_short_function_def(f))
61✔
1024
        body = f.args[2]
46✔
1025
        lno = body.args[1]
46✔
1026
        tmp = gensym("tmp")
46✔
1027
        return Expr(:escape,
46✔
1028
                    Expr(f.head, f.args[1],
1029
                         Expr(:block,
1030
                              lno,
1031
                              Expr(:if, Expr(:generated),
1032
                                   body,
1033
                                   Expr(:block,
1034
                                        Expr(:meta, :generated_only),
1035
                                        Expr(:return, nothing))))))
1036
    else
1037
        error("invalid syntax; @generated must be used with a function definition")
×
1038
    end
1039
end
1040

1041

1042
"""
1043
    @atomic var
1044
    @atomic order ex
1045

1046
Mark `var` or `ex` as being performed atomically, if `ex` is a supported expression.
1047
If no `order` is specified it defaults to :sequentially_consistent.
1048

1049
    @atomic a.b.x = new
1050
    @atomic a.b.x += addend
1051
    @atomic :release a.b.x = new
1052
    @atomic :acquire_release a.b.x += addend
1053

1054
Perform the store operation expressed on the right atomically and return the
1055
new value.
1056

1057
With `=`, this operation translates to a `setproperty!(a.b, :x, new)` call.
1058
With any operator also, this operation translates to a `modifyproperty!(a.b,
1059
:x, +, addend)[2]` call.
1060

1061
    @atomic a.b.x max arg2
1062
    @atomic a.b.x + arg2
1063
    @atomic max(a.b.x, arg2)
1064
    @atomic :acquire_release max(a.b.x, arg2)
1065
    @atomic :acquire_release a.b.x + arg2
1066
    @atomic :acquire_release a.b.x max arg2
1067

1068
Perform the binary operation expressed on the right atomically. Store the
1069
result into the field in the first argument and return the values `(old, new)`.
1070

1071
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call.
1072

1073

1074
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1075

1076
# Examples
1077
```jldoctest
1078
julia> mutable struct Atomic{T}; @atomic x::T; end
1079

1080
julia> a = Atomic(1)
1081
Atomic{Int64}(1)
1082

1083
julia> @atomic a.x # fetch field x of a, with sequential consistency
1084
1
1085

1086
julia> @atomic :sequentially_consistent a.x = 2 # set field x of a, with sequential consistency
1087
2
1088

1089
julia> @atomic a.x += 1 # increment field x of a, with sequential consistency
1090
3
1091

1092
julia> @atomic a.x + 1 # increment field x of a, with sequential consistency
1093
3 => 4
1094

1095
julia> @atomic a.x # fetch field x of a, with sequential consistency
1096
4
1097

1098
julia> @atomic max(a.x, 10) # change field x of a to the max value, with sequential consistency
1099
4 => 10
1100

1101
julia> @atomic a.x max 5 # again change field x of a to the max value, with sequential consistency
1102
10 => 10
1103
```
1104

1105
!!! compat "Julia 1.7"
1106
    This functionality requires at least Julia 1.7.
1107
"""
1108
macro atomic(ex)
35✔
1109
    if !isa(ex, Symbol) && !is_expr(ex, :(::))
35✔
1110
        return make_atomic(QuoteNode(:sequentially_consistent), ex)
27✔
1111
    end
1112
    return esc(Expr(:atomic, ex))
8✔
1113
end
1114
macro atomic(order, ex)
17✔
1115
    order isa QuoteNode || (order = esc(order))
17✔
1116
    return make_atomic(order, ex)
17✔
1117
end
1118
macro atomic(a1, op, a2)
3✔
1119
    return make_atomic(QuoteNode(:sequentially_consistent), a1, op, a2)
3✔
1120
end
1121
macro atomic(order, a1, op, a2)
2✔
1122
    order isa QuoteNode || (order = esc(order))
2✔
1123
    return make_atomic(order, a1, op, a2)
2✔
1124
end
1125
function make_atomic(order, ex)
44✔
1126
    @nospecialize
×
1127
    if ex isa Expr
44✔
1128
        if isexpr(ex, :., 2)
71✔
1129
            l, r = esc(ex.args[1]), esc(ex.args[2])
17✔
1130
            return :(getproperty($l, $r, $order))
17✔
1131
        elseif isexpr(ex, :call, 3)
45✔
1132
            return make_atomic(order, ex.args[2], ex.args[1], ex.args[3])
5✔
1133
        elseif ex.head === :(=)
22✔
1134
            l, r = ex.args[1], esc(ex.args[2])
11✔
1135
            if is_expr(l, :., 2)
11✔
1136
                ll, lr = esc(l.args[1]), esc(l.args[2])
10✔
1137
                return :(setproperty!($ll, $lr, $r, $order))
10✔
1138
            end
1139
        end
1140
        if length(ex.args) == 2
12✔
1141
            if ex.head === :(+=)
9✔
1142
                op = :+
5✔
1143
            elseif ex.head === :(-=)
4✔
1144
                op = :-
1✔
1145
            elseif @isdefined string
3✔
1146
                shead = string(ex.head)
3✔
1147
                if endswith(shead, '=')
3✔
1148
                    op = Symbol(shead[1:prevind(shead, end)])
2✔
1149
                end
1150
            end
1151
            if @isdefined(op)
9✔
1152
                return Expr(:ref, make_atomic(order, ex.args[1], op, ex.args[2]), 2)
8✔
1153
            end
1154
        end
1155
    end
1156
    error("could not parse @atomic expression $ex")
4✔
1157
end
1158
function make_atomic(order, a1, op, a2)
18✔
1159
    @nospecialize
×
1160
    is_expr(a1, :., 2) || error("@atomic modify expression missing field access")
22✔
1161
    a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2)
14✔
1162
    return :(modifyproperty!($a1l, $a1r, $op, $a2, $order))
14✔
1163
end
1164

1165

1166
"""
1167
    @atomicswap a.b.x = new
1168
    @atomicswap :sequentially_consistent a.b.x = new
1169

1170
Stores `new` into `a.b.x` and returns the old value of `a.b.x`.
1171

1172
This operation translates to a `swapproperty!(a.b, :x, new)` call.
1173

1174
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1175

1176
# Examples
1177
```jldoctest
1178
julia> mutable struct Atomic{T}; @atomic x::T; end
1179

1180
julia> a = Atomic(1)
1181
Atomic{Int64}(1)
1182

1183
julia> @atomicswap a.x = 2+2 # replace field x of a with 4, with sequential consistency
1184
1
1185

1186
julia> @atomic a.x # fetch field x of a, with sequential consistency
1187
4
1188
```
1189

1190
!!! compat "Julia 1.7"
1191
    This functionality requires at least Julia 1.7.
1192
"""
1193
macro atomicswap(order, ex)
2✔
1194
    order isa QuoteNode || (order = esc(order))
2✔
1195
    return make_atomicswap(order, ex)
2✔
1196
end
1197
macro atomicswap(ex)
1✔
1198
    return make_atomicswap(QuoteNode(:sequentially_consistent), ex)
1✔
1199
end
1200
function make_atomicswap(order, ex)
3✔
1201
    @nospecialize
×
1202
    is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment")
3✔
1203
    l, val = ex.args[1], esc(ex.args[2])
3✔
1204
    is_expr(l, :., 2) || error("@atomicswap expression missing field access")
3✔
1205
    ll, lr = esc(l.args[1]), esc(l.args[2])
3✔
1206
    return :(swapproperty!($ll, $lr, $val, $order))
3✔
1207
end
1208

1209

1210
"""
1211
    @atomicreplace a.b.x expected => desired
1212
    @atomicreplace :sequentially_consistent a.b.x expected => desired
1213
    @atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired
1214

1215
Perform the conditional replacement expressed by the pair atomically, returning
1216
the values `(old, success::Bool)`. Where `success` indicates whether the
1217
replacement was completed.
1218

1219
This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call.
1220

1221
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
1222

1223
# Examples
1224
```jldoctest
1225
julia> mutable struct Atomic{T}; @atomic x::T; end
1226

1227
julia> a = Atomic(1)
1228
Atomic{Int64}(1)
1229

1230
julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency
1231
(old = 1, success = true)
1232

1233
julia> @atomic a.x # fetch field x of a, with sequential consistency
1234
2
1235

1236
julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency
1237
(old = 2, success = false)
1238

1239
julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency
1240

1241
julia> @atomicreplace a.x xchg
1242
(old = 2, success = true)
1243

1244
julia> @atomic a.x # fetch field x of a, with sequential consistency
1245
0
1246
```
1247

1248
!!! compat "Julia 1.7"
1249
    This functionality requires at least Julia 1.7.
1250
"""
1251
macro atomicreplace(success_order, fail_order, ex, old_new)
4✔
1252
    fail_order isa QuoteNode || (fail_order = esc(fail_order))
4✔
1253
    success_order isa QuoteNode || (success_order = esc(success_order))
4✔
1254
    return make_atomicreplace(success_order, fail_order, ex, old_new)
4✔
1255
end
1256
macro atomicreplace(order, ex, old_new)
4✔
1257
    order isa QuoteNode || (order = esc(order))
4✔
1258
    return make_atomicreplace(order, order, ex, old_new)
4✔
1259
end
1260
macro atomicreplace(ex, old_new)
6✔
1261
    return make_atomicreplace(QuoteNode(:sequentially_consistent), QuoteNode(:sequentially_consistent), ex, old_new)
6✔
1262
end
1263
function make_atomicreplace(success_order, fail_order, ex, old_new)
14✔
1264
    @nospecialize
×
1265
    is_expr(ex, :., 2) || error("@atomicreplace expression missing field access")
15✔
1266
    ll, lr = esc(ex.args[1]), esc(ex.args[2])
13✔
1267
    if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
13✔
1268
        exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
8✔
1269
        return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order))
8✔
1270
    else
1271
        old_new = esc(old_new)
5✔
1272
        return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order))
5✔
1273
    end
1274
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