Skip to content

Commit 3136bf4

Browse files
authored
Provide an inverted linetable for all Julia versions (#135)
JuliaLang/julia#52415 introduced a fundamental change in how line information is encoded. Because this package delves into internals, there were a couple tests that failed on Julia 1.12+. This adds a utility, `CodeTracking.linetable_scopes`, that makes it easier to work with the new representation across Julia versions. Importantly, this is "forward-looking," meaning it mimics Julia 1.12+ representations for older Julia versions. That should help with adoption of the new representation.
1 parent 90f3dcb commit 3136bf4

File tree

4 files changed

+64
-10
lines changed

4 files changed

+64
-10
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
julia -e '
4141
using Pkg
4242
Pkg.develop(path=".")
43-
Pkg.add(url="https://github.com/timholy/Revise.jl")
43+
Pkg.add("Revise")
4444
Pkg.test("Revise")
4545
'
4646
- name: Test while running Revise
@@ -49,8 +49,13 @@ jobs:
4949
TERM="xterm" julia --project -i --code-coverage -e '
5050
using InteractiveUtils, REPL, Revise, Pkg
5151
Pkg.add("ColorTypes")
52-
@async(Base.run_main_repl(true, true, false, true, false))
53-
sleep(2)
52+
t = @async(
53+
VERSION >= v"1.12.0-DEV.612" ? Base.run_main_repl(true, true, :no, true) :
54+
VERSION >= v"1.11.0-DEV.222" ? Base.run_main_repl(true, true, :no, true, false) :
55+
Base.run_main_repl(true, true, false, true, false))
56+
isdefined(Base, :errormonitor) && Base.errormonitor(t)
57+
while (!isdefined(Base, :active_repl_backend) || isnothing(Base.active_repl_backend)) sleep(0.1) end
58+
pushfirst!(Base.active_repl_backend.ast_transforms, Revise.revise_first)
5459
cd("test")
5560
include("runtests.jl")
5661
if Base.VERSION.major == 1 && Base.VERSION.minor >= 9

src/CodeTracking.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Otherwise `loc` will be `(filepath, line)`.
9898
"""
9999
function whereis(sf::StackTraces.StackFrame)
100100
sf.linfo === nothing && return nothing
101-
return whereis(sf, sf.linfo.def)
101+
return whereis(sf, getmethod(sf.linfo))
102102
end
103103

104104
"""

src/utils.jl

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ function linerange(def::Expr)
179179
end
180180
linerange(arg) = linerange(convert(Expr, arg)) # Handle Revise's RelocatableExpr
181181

182-
function findline(ex, order)
182+
function findline(ex::Expr, order)
183183
ex.head === :line && return ex.args[1], true
184184
for a in order(ex.args)
185185
a isa LineNumberNode && return a.line, true
@@ -194,6 +194,55 @@ end
194194
fileline(lin::LineInfoNode) = String(lin.file), lin.line
195195
fileline(lnn::LineNumberNode) = String(lnn.file), lnn.line
196196

197+
if VERSION v"1.12.0-DEV.173" # https://github.com/JuliaLang/julia/pull/52415
198+
function linetable_scopes(m::Method)
199+
src = Base.uncompressed_ast(m)
200+
lts = [Vector{Base.Compiler.IRShow.LineInfoNode}() for _ = eachindex(src.code)]
201+
for pc = eachindex(src.code)
202+
Base.IRShow.append_scopes!(lts[pc], pc, src.debuginfo, m)
203+
end
204+
return lts
205+
end
206+
else
207+
function linetable_scopes(m::Method)
208+
src = Base.uncompressed_ast(m)
209+
lt, cl = src.linetable, src.codelocs
210+
lts = [Vector{Core.LineInfoNode}() for _ = eachindex(src.code)]
211+
for pc = eachindex(src.code)
212+
iszero(cl[pc]) && continue
213+
scope = lts[pc]
214+
push!(scope, lt[cl[pc]])
215+
while (k = last(scope).inlined_at) != Int32(0)
216+
push!(scope, lt[k])
217+
end
218+
if length(scope) > 1
219+
reverse!(scope)
220+
end
221+
end
222+
return lts
223+
end
224+
end
225+
@doc """
226+
scopes = linetable_scopes(m::Method)
227+
228+
Return an array of "scopes" for each statement in the lowered code for `m`. If
229+
`src = Base.uncompressed_ast(m)`, then `scopes[pc]` is an vector of
230+
`LineInfoNode` objects that represent the scopes active at the statement at
231+
position `pc` in `src.code`.
232+
233+
`scopes[pc]` may have length larger than 1, where the first entry is for the
234+
source location in `m`, and any later entries reflect code from inlining.
235+
236+
The precise type of these entries varies with Julia version,
237+
`Base.Compiler.IRShow.LineInfoNode` objects on Julia 1.12 and up, and
238+
`Core.LineInfoNode` objects on earlier versions. These objects differ in some of
239+
their fields. `:method`, `:file`, and `:line` are common to both types.
240+
""" linetable_scopes
241+
242+
getmethod(m::Method) = m
243+
getmethod(mi::Core.MethodInstance) = getmethod(mi.def)
244+
getmethod(ci::Core.CodeInstance) = getmethod(ci.def)
245+
197246
# This regex matches the pseudo-file name of a REPL history entry.
198247
const rREPL = r"^REPL\[(\d+)\]$"
199248
# Match anonymous function names

test/runtests.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ isdefined(Main, :Revise) ? Main.Revise.includet("script.jl") : include("script.j
129129
@info "hello"
130130
end
131131
m = first(methods(f150))
132-
src = Base.uncompressed_ast(m)
133-
idx = findfirst(lin -> String(lin.file) == @__FILE__, src.linetable)
134-
lin = src.linetable[idx]
132+
scopes = CodeTracking.linetable_scopes(m)
133+
idx = findfirst(sc -> all(lin -> String(lin.file) == @__FILE__, sc), scopes)
134+
lin = first(scopes[idx])
135135
file, line = whereis(lin, m)
136136
@test endswith(file, String(lin.file))
137-
idx = findfirst(lin -> String(lin.file) != @__FILE__, src.linetable)
138-
lin = src.linetable[idx]
137+
idx = findfirst(sc -> !all(lin -> String(lin.file) == @__FILE__, sc), scopes)
138+
lin = first(scopes[idx])
139139
file, line = whereis(lin, m)
140140
if !Sys.iswindows()
141141
@test endswith(file, String(lin.file))

0 commit comments

Comments
 (0)