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

JuliaLang / julia / 1473

15 Mar 2026 04:25AM UTC coverage: 76.875% (-0.01%) from 76.886%
1473

push

buildkite

web-flow
inference: Cache tombstoned const-prop results to prevent non-termination (#61264)

When a const-prop frame encounters a cycle and gets poisoned,
`finishinfer!` marks the result as tombstoned and sets `cache_mode =
CACHE_MODE_NULL`, which prevents `promotecache!` from pushing the result
to the local inference cache.
Meanwhile, `constprop_cache_lookup` (added in #57545) skips tombstoned
entries entirely. This combination means the same const-prop is
re-attempted on every cycle iteration, causing inference to never
terminate when `aggressive_constant_propagation = true`.

Fix this by:
- In `const_prop_call`, explicitly pushing tombstoned results to the
inference cache after a successful but limited `typeinf`, and returning
`nothing` to fall back to the regular inference result.
- In `constprop_cache_lookup`, no longer skipping tombstoned entries so
they can be found on subsequent lookups, preventing re-attempts.
- In `const_prop_call` cache-hit path, checking `inf_result.tombstone`
and returning `nothing` (same as the existing cycle-hit handling).
- Removing the now-unnecessary tombstone check from
`OverlayCodeCache.get`, where the subsequent `overridden_by_const` and
`isdefined` checks already filter out such entries.

Note that #57545 added `tombstone && continue` to `cache_lookup` to
prevent `LimitedAccuracy` results from propagating to callers via
`const_prop_result`. This change removes that skip but achieves the same
protection by explicitly checking `inf_result.tombstone` in
`const_prop_call` and returning `nothing` instead of using the result.

Fixes #61257

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

12 of 14 new or added lines in 3 files covered. (85.71%)

89 existing lines in 8 files now uncovered.

63673 of 82827 relevant lines covered (76.87%)

23446802.16 hits per line

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

11.02
/stdlib/InteractiveUtils/src/editless.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
# editing and paging files
4

5
using Base: shell_split, shell_escape, find_source_file
6

7
"""
8
    EDITOR_CALLBACKS :: Vector{Function}
9

10
A vector of editor callback functions, which take as arguments `cmd`, `path`, `line`
11
and `column` and which is then expected to either open an editor and return `true` to
12
indicate that it has handled the request, or return `false` to decline the
13
editing request.
14
"""
15
const EDITOR_CALLBACKS = Function[]
16

17
"""
18
    define_editor(fn, pattern; wait=false)
19

20
Define a new editor matching `pattern` that can be used to open a file (possibly
21
at a given line number) using `fn`.
22

23
The `fn` argument is a function that determines how to open a file with the
24
given editor. It should take four arguments, as follows:
25

26
* `cmd` - a base command object for the editor
27
* `path` - the path to the source file to open
28
* `line` - the line number to open the editor at
29
* `column` - the column number to open the editor at
30

31
Editors which cannot open to a specific line with a command or a specific column
32
may ignore the `line` and/or `column` argument. The `fn` callback must return
33
either an appropriate `Cmd` object to open a file or `nothing` to indicate that
34
they cannot edit this file. Use `nothing` to indicate that this editor is not
35
appropriate for the current environment and another editor should be attempted.
36
It is possible to add more general editing hooks that need not spawn
37
external commands by pushing a callback directly to the vector `EDITOR_CALLBACKS`.
38

39
The `pattern` argument is a string, regular expression, or an array of strings
40
and regular expressions. For the `fn` to be called, one of the patterns must
41
match the value of `EDITOR`, `VISUAL` or `JULIA_EDITOR`. For strings, the string
42
must equal the [`basename`](@ref) of the first word of the editor command, with
43
its extension, if any, removed. E.g. "vi" doesn't match "vim -g" but matches
44
"/usr/bin/vi -m"; it also matches `vi.exe`. If `pattern` is a regex it is
45
matched against all of the editor command as a shell-escaped string. An array
46
pattern matches if any of its items match. If multiple editors match, the one
47
added most recently is used.
48

49
By default julia does not wait for the editor to close, running it in the
50
background. However, if the editor is terminal based, you will probably want to
51
set `wait=true` and julia will wait for the editor to close before resuming.
52

53
If one of the editor environment variables is set, but no editor entry matches it,
54
the default editor entry is invoked:
55

56
    (cmd, path, line, column) -> `\$cmd \$path`
57

58
Note that many editors are already defined. All of the following commands should
59
already work:
60

61
- emacs
62
- emacsclient
63
- vim
64
- nvim
65
- nano
66
- micro
67
- kak
68
- helix
69
- textmate
70
- mate
71
- kate
72
- subl
73
- atom
74
- notepad++
75
- Visual Studio Code
76
- open
77
- pycharm
78
- bbedit
79

80
# Examples
81

82
The following defines the usage of terminal-based `emacs`:
83

84
    define_editor(
85
        r"\\bemacs\\b.*\\s(-nw|--no-window-system)\\b", wait=true) do cmd, path, line
86
        `\$cmd +\$line \$path`
87
    end
88

89
!!! compat "Julia 1.4"
90
    `define_editor` was introduced in Julia 1.4.
91
"""
92
function define_editor(fn::Function, pattern; wait::Bool=false)
×
93
    callback = function (cmd::Cmd, path::AbstractString, line::Integer, column::Integer)
×
94
        editor_matches(pattern, cmd) || return false
×
95
        editor = if !applicable(fn, cmd, path, line, column)
×
96
            # Be backwards compatible with editors that did not define the newly added column argument
97
            fn(cmd, path, line)
×
98
        else
99
            fn(cmd, path, line, column)
×
100
        end
101
        if editor isa Cmd
×
102
            if wait
×
103
                run(editor) # blocks while editor runs
×
104
            else
105
                run(pipeline(editor, stderr=stderr), wait=false)
×
106
            end
107
            return true
×
108
        elseif editor isa Nothing
×
109
            return false
×
110
        end
111
        @warn "invalid editor value returned" pattern=pattern editor=editor
×
112
        return false
×
113
    end
114
    pushfirst!(EDITOR_CALLBACKS, callback)
×
115
end
116

117
editor_matches(p::Regex, cmd::Cmd) = occursin(p, shell_escape(cmd))
×
118
editor_matches(p::String, cmd::Cmd) = p == splitext(basename(first(cmd)))[1]
×
119
editor_matches(ps::AbstractArray, cmd::Cmd) = any(editor_matches(p, cmd) for p in ps)
×
120

121
function define_default_editors()
×
122
    # fallback: just call the editor with the path as argument
123
    define_editor(r".*") do cmd, path, line, column
×
124
        `$cmd $path`
×
125
    end
126
    # vim family
127
    for (editors, wait) in [
×
128
        [["vim", "vi", "nvim", "mvim"], true],
129
        [[r"\bgvim"], false],
×
130
    ]
131
        define_editor(editors; wait) do cmd, path, line, column
×
132
            cmd = line == 0 ? `$cmd $path` :
×
133
                column == 0 ? `$cmd +$line $path` :
×
134
                `$cmd "+normal $(line)G$(column)|" $path`
×
135
        end
136
    end
×
137
    define_editor("nano"; wait=true) do cmd, path, line, column
×
138
        cmd = `$cmd +$line,$column $path`
×
139
    end
140
    # emacs (must check that emacs not running in -t/-nw
141
    # before regex match for general emacs)
142
    for (editors, wait) in [
×
143
        [[r"\bemacs"], false],
×
144
        [[r"\bemacs\b.*\s(-nw|--no-window-system)\b",
×
145
          r"\bemacsclient\b.\s*-(-?nw|t|-?tty)\b"], true],
×
146
    ]
147
        define_editor(editors; wait) do cmd, path, line, column
×
148
            `$cmd +$line:$column $path`
×
149
        end
150
    end
×
151
    # other editors
152
    define_editor("gedit") do cmd, path, line, column
×
153
        `$cmd +$line:$column $path`
×
154
    end
155
    define_editor(["micro", "kak"]; wait=true) do cmd, path, line, column
×
156
        `$cmd +$line $path`
×
157
    end
158
    define_editor(["hx", "helix"]; wait=true) do cmd, path, line, column
×
159
        `$cmd $path:$line:$column`
×
160
    end
161
    define_editor(["textmate", "mate", "kate"]) do cmd, path, line, column
×
162
        `$cmd $path -l $line`
×
163
    end
164
    define_editor([r"\bsubl", r"\batom", "pycharm", "bbedit"]) do cmd, path, line, column
×
165
        `$cmd $path:$line`
×
166
    end
167
    define_editor(["code", "code-insiders"]) do cmd, path, line, column
×
168
        `$cmd -g $path:$line:$column`
×
169
    end
170
    define_editor(r"\bnotepad++") do cmd, path, line, column
×
171
        `$cmd $path -n$line`
×
172
    end
173
    if Sys.iswindows()
×
174
        define_editor(r"\bCODE\.EXE\b"i) do cmd, path, line, column
×
175
            `$cmd -g $path:$line:$column`
×
176
        end
177
        callback = function (cmd::Cmd, path::AbstractString, line::Integer)
×
178
            cmd == `open` || return false
×
179
            # don't emit this ccall on other platforms
180
            @static if Sys.iswindows()
×
181
                result = ccall((:ShellExecuteW, "shell32"), stdcall,
×
182
                               Int, (Ptr{Cvoid}, Cwstring, Cwstring,
183
                                     Ptr{Cvoid}, Ptr{Cvoid}, Cint),
184
                               C_NULL, "open", path, C_NULL, C_NULL, 10)
185
                systemerror(:edit, result ≤ 32)
×
186
            end
187
            return true
×
188
        end
189
        pushfirst!(EDITOR_CALLBACKS, callback)
×
190
    elseif Sys.isapple()
×
191
        define_editor("open") do cmd, path, line, column
×
192
            `open -t $path`
×
193
        end
194
    end
195
end
196
define_default_editors()
197

198
"""
199
    editor()
200

201
Determine the editor to use when running functions like `edit`. Returns a `Cmd`
202
object. Change editor by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR`
203
environment variables.
204
"""
205
function editor()
27✔
206
    # Note: the editor path can include spaces (if escaped) and flags.
207
    for var in ["JULIA_EDITOR", "VISUAL", "EDITOR"]
27✔
208
        str = get(ENV, var, nothing)
33✔
209
        str === nothing && continue
33✔
210
        isempty(str) && error("invalid editor \$$var: $(repr(str))")
24✔
211
        return Cmd(shell_split(str))
21✔
212
    end
9✔
213
    editor_file = "/etc/alternatives/editor"
3✔
214
    editor = (Sys.iswindows() || Sys.isapple()) ? "open" :
3✔
215
        isfile(editor_file) ? realpath(editor_file) : "emacs"
216
    return Cmd([editor])
3✔
217
end
218

219
"""
220
    edit(path::AbstractString, line::Integer=0, column::Integer=0)
221

222
Edit a file or directory optionally providing a line number to edit the file at.
223
Return to the `julia` prompt when you quit the editor. The editor can be changed
224
by setting `JULIA_EDITOR`, `VISUAL` or `EDITOR` as an environment variable.
225

226
!!! compat "Julia 1.9"
227
    The `column` argument requires at least Julia 1.9.
228

229
See also [`InteractiveUtils.define_editor`](@ref).
230
"""
231
function edit(path::AbstractString, line::Integer=0, column::Integer=0)
×
232
    path isa String || (path = convert(String, path))
×
233
    if endswith(path, ".jl")
×
234
        p = find_source_file(path)
×
235
        p !== nothing && (path = p)
×
236
    end
237
    cmd = editor()
×
238
    for callback in EDITOR_CALLBACKS
×
239
        if !applicable(callback, cmd, path, line, column)
×
240
            callback(cmd, path, line) && return
×
241
        else
242
            callback(cmd, path, line, column) && return
×
243
        end
244
    end
×
245
    # shouldn't happen unless someone has removed fallback entry
246
    error("no editor found")
×
247
end
248

249
"""
250
    edit(function, [types])
251
    edit(module)
252

253
Edit the definition of a function, optionally specifying a tuple of types to indicate which
254
method to edit. For modules, open the main source file. The module needs to be loaded with
255
`using` or `import` first.
256

257
!!! compat "Julia 1.1"
258
    `edit` on modules requires at least Julia 1.1.
259

260
To ensure that the file can be opened at the given line, you may need to call
261
`InteractiveUtils.define_editor` first.
262
"""
263
function edit(@nospecialize f)
×
264
    ms = methods(f).ms
×
265
    length(ms) == 1 && edit(functionloc(ms[1])...)
×
266
    length(ms) > 1 && return ms
×
267
    length(ms) == 0 && functionloc(f) # throws
×
268
    nothing
×
269
end
270
edit(m::Method) = edit(functionloc(m)...)
×
271
edit(@nospecialize(f), idx::Integer) = edit(methods(f).ms[idx])
×
272
edit(f, t) = (@nospecialize; edit(functionloc(f, t)...))
×
273
edit(@nospecialize argtypes::Union{Tuple, Type{<:Tuple}}) = edit(functionloc(argtypes)...)
×
274
edit(file::Nothing, line::Integer) = error("could not find source file for function")
×
275
function edit(m::Module)
3✔
276
    path = pathof(m)
3✔
277
    path === nothing && error("could not find source file for module: $m")
3✔
UNCOV
278
    edit(path)
×
279
end
280

281
# terminal pager
282

283
if Sys.iswindows()
284
    function less(file::AbstractString, line::Integer)
×
UNCOV
285
        pager = shell_split(get(ENV, "PAGER", "more"))
×
286
        if pager[1] == "more"
×
UNCOV
287
            g = ""
×
288
            line -= 1
×
289
        else
UNCOV
290
            g = "g"
×
291
        end
292
        run(Cmd(`$pager +$(line)$(g) \"$file\"`, windows_verbatim = true))
×
293
        nothing
×
294
    end
295
else
UNCOV
296
    function less(file::AbstractString, line::Integer)
×
UNCOV
297
        pager = shell_split(get(ENV, "PAGER", "less"))
×
UNCOV
298
        run(`$pager +$(line)g $file`)
×
UNCOV
299
        nothing
×
300
    end
301
end
302

303
"""
304
    less(file::AbstractString, [line::Integer])
305

306
Show a file using the default pager, optionally providing a starting line number. Returns to
307
the `julia` prompt when you quit the pager.
308
"""
UNCOV
309
less(file::AbstractString) = less(file, 1)
×
310

311
"""
312
    less(function, [types])
313

314
Show the definition of a function using the default pager, optionally specifying a tuple of
315
types to indicate which method to see.
316
"""
UNCOV
317
less(f)                   = less(functionloc(f)...)
×
UNCOV
318
less(f, @nospecialize t)  = less(functionloc(f,t)...)
×
UNCOV
319
less(file, line::Integer) = error("could not find source file for function")
×
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