Skip to content

Commit 8004e44

Browse files
committed
ui: use stacktrace-like view for showing reports
Since the stacktrace view shown by Julia Base upon exception is more familiar to general users, it would be better to use the same view for showing JET's abstract stacktrace. Previously JET tries to "infer" the textual code representation of the call-sites of each stack-frame, but now JET does not need to do so, and JET can show the method signature of each abstract stack-frame from their `MethodInstance` that is already available. Therefore, this commit may result in some performance improvement. Note that JET still computes the textual code representation with type annotations for each error point. We may replace that part with TypedSyntax.jl in the future since it would be more robust and nicer. \## Examples: `@report_call sum([])` > Before ```julia ═════ 1 possible error found ═════ ┌ @ reducedim.jl:996 Base.:(var"#sum#821")(:, pairs(NamedTuple()), #self#, a) │┌ @ reducedim.jl:996 Base._sum(a, dims) ││┌ @ reducedim.jl:1000 Base.:(var"#_sum#823")(pairs(NamedTuple()), #self#, a, _3) │││┌ @ reducedim.jl:1000 Base._sum(identity, a, :) ││││┌ @ reducedim.jl:1001 Base.:(var"#_sum#824")(pairs(NamedTuple()), #self#, f, a, _4) │││││┌ @ reducedim.jl:1001 mapreduce(f, Base.add_sum, a) ││││││┌ @ reducedim.jl:357 Base.:(var"#mapreduce#814")(:, Base._InitialValue(), #self#, f, op, A) │││││││┌ @ reducedim.jl:357 Base._mapreduce_dim(f, op, init, A, dims) ││││││││┌ @ reducedim.jl:365 Base._mapreduce(f, op, IndexStyle(A), A) │││││││││┌ @ reduce.jl:432 Base.mapreduce_empty_iter(f, op, A, Base.IteratorEltype(A)) ││││││││││┌ @ reduce.jl:380 Base.reduce_empty_iter(Base.MappingRF(f, op), itr, ItrEltype) │││││││││││┌ @ reduce.jl:384 Base.reduce_empty(op, eltype(itr)) ││││││││││││┌ @ reduce.jl:361 Base.mapreduce_empty(op.f, op.rf, T) │││││││││││││┌ @ reduce.jl:372 Base.reduce_empty(op, T) ││││││││││││││┌ @ reduce.jl:352 Base.reduce_empty(+, T) │││││││││││││││┌ @ reduce.jl:343 zero(T) ││││││││││││││││┌ @ missing.jl:106 Base.throw(Base.MethodError(zero, tuple(Base.Any))) │││││││││││││││││ MethodError: no method matching zero(::Type{Any}): Base.throw(Base.MethodError(zero, tuple(Base.Any)::Tuple{DataType})::MethodError) ││││││││││││││││└────────────────── ``` > After ```julia ═════ 1 possible error found ═════ ┌ sum(a::Vector{Any}) @ Base ./reducedim.jl:996 │┌ sum(a::Vector{Any}; dims::Colon, kw::Base.@kwargs{}) @ Base ./reducedim.jl:996 ││┌ _sum(a::Vector{Any}, ::Colon) @ Base ./reducedim.jl:1000 │││┌ _sum(a::Vector{Any}, ::Colon; kw::Base.@kwargs{}) @ Base ./reducedim.jl:1000 ││││┌ _sum(f::typeof(identity), a::Vector{Any}, ::Colon) @ Base ./reducedim.jl:1001 │││││┌ _sum(f::typeof(identity), a::Vector{Any}, ::Colon; kw::Base.@kwargs{}) @ Base ./reducedim.jl:1001 ││││││┌ mapreduce(f::typeof(identity), op::typeof(Base.add_sum), A::Vector{Any}) @ Base ./reducedim.jl:357 │││││││┌ mapreduce(f::typeof(identity), op::typeof(Base.add_sum), A::Vector{Any}; dims::Colon, init::Base._InitialValue) @ Base ./reducedim.jl:357 ││││││││┌ _mapreduce_dim(f::typeof(identity), op::typeof(Base.add_sum), ::Base._InitialValue, A::Vector{Any}, ::Colon) @ Base ./reducedim.jl:365 │││││││││┌ _mapreduce(f::typeof(identity), op::typeof(Base.add_sum), ::IndexLinear, A::Vector{Any}) @ Base ./reduce.jl:432 ││││││││││┌ mapreduce_empty_iter(f::typeof(identity), op::typeof(Base.add_sum), itr::Vector{Any}, ItrEltype::Base.HasEltype) @ Base ./reduce.jl:380 │││││││││││┌ reduce_empty_iter(op::Base.MappingRF{typeof(identity), typeof(Base.add_sum)}, itr::Vector{Any}, ::Base.HasEltype) @ Base ./reduce.jl:384 ││││││││││││┌ reduce_empty(op::Base.MappingRF{typeof(identity), typeof(Base.add_sum)}, ::Type{Any}) @ Base ./reduce.jl:361 │││││││││││││┌ mapreduce_empty(::typeof(identity), op::typeof(Base.add_sum), T::Type{Any}) @ Base ./reduce.jl:372 ││││││││││││││┌ reduce_empty(::typeof(Base.add_sum), ::Type{Any}) @ Base ./reduce.jl:352 │││││││││││││││┌ reduce_empty(::typeof(+), ::Type{Any}) @ Base ./reduce.jl:343 ││││││││││││││││┌ zero(::Type{Any}) @ Base ./missing.jl:106 │││││││││││││││││ MethodError: no method matching zero(::Type{Any}): Base.throw(Base.MethodError(zero, tuple(Base.Any)::Tuple{DataType})::MethodError) ││││││││││││││││└──────────────────── ``` \## Example 2 ```julia getprop(x) = x.nonexist; report_call((Regex,)) do r getprop(r) end ``` > Before ```julia ═════ 1 possible error found ═════ ┌ @ REPL[30]:2 getprop(r) │┌ @ REPL[29]:1 x.nonexist ││┌ @ Base.jl:37 Base.getfield(x, f) │││ type Regex has no field nonexist ││└────────────── ``` > After ```julia ═════ 1 possible error found ═════ ┌ (::var"#3#4")(r::Regex) @ Main ./REPL[12]:2 │┌ getprop(x::Regex) @ Main ./REPL[11]:1 ││┌ getproperty(x::Regex, f::Symbol) @ Base ./Base.jl:37 │││ type Regex has no field nonexist: Base.getfield(x::Regex, f::Symbol) ││└──────────────────── ```
1 parent 717a6d0 commit 8004e44

File tree

8 files changed

+112
-152
lines changed

8 files changed

+112
-152
lines changed

examples/find_unstable_api.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ function JETInterface.print_report_message(io::IO, (; g)::UnstableAPI)
121121
msg = lazy"usage of unstable API `$mod.$name` found"
122122
print(io, "usage of unstable API `", mod, '.', name, "` found")
123123
end
124-
JETInterface.print_signature(::UnstableAPI) = false
125124
JETInterface.report_color(::UnstableAPI) = :yellow
126125

127126
function (::UnstableAPIAnalysisPass)(::Type{UnstableAPI}, analyzer::UnstableAPIAnalyzer, sv, @nospecialize(e))

src/abstractinterpret/abstractanalyzer.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,9 @@ end
540540
@withmixedhash struct VirtualFrameNoLinfo
541541
file::Symbol
542542
line::Int
543-
sig::Signature
543+
# sig::Signature
544544
# linfo::MethodInstance # ignore the identity of `MethodInstance`
545-
VirtualFrameNoLinfo(vf::VirtualFrame) = new(vf.file, vf.line, vf.sig)
545+
VirtualFrameNoLinfo(vf::VirtualFrame) = new(vf.file, vf.line#=, vf.sig=#)
546546
end
547547
@withmixedhash struct DefaultReportIdentity
548548
T::DataType

src/abstractinterpret/inferenceerrorreport.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ collected during abstract interpration, not collected from actual execution.
5050
@withmixedhash struct VirtualFrame
5151
file::Symbol
5252
line::Int
53-
sig::Signature
5453
linfo::MethodInstance
5554
end
5655

@@ -71,17 +70,15 @@ const INFERENCE_ERROR_REPORT_FIELD_DECLS = [
7170

7271
# get location information at the given program counter (or a current counter if not specified)
7372
function get_virtual_frame(state::StateAtPC)
74-
sig = get_sig(state)
7573
file, line = get_file_line(state)
7674
linfo = isa(state, MethodInstance) ? state : first(state).linfo
77-
return VirtualFrame(file, line, sig, linfo)
75+
return VirtualFrame(file, line, linfo)
7876
end
7977
get_virtual_frame(sv::InferenceState) = get_virtual_frame((sv, get_currpc(sv)))
8078
get_virtual_frame(caller::InferenceResult) = get_virtual_frame(get_linfo(caller))
8179
function get_virtual_frame(linfo::MethodInstance)
82-
sig = get_sig(linfo)
8380
file, line = get_file_line(linfo)
84-
return VirtualFrame(file, line, sig, linfo)
81+
return VirtualFrame(file, line, linfo)
8582
end
8683

8784
get_file_line(s::StateAtPC) = get_file_line(get_lin(s)::LineInfoNode)
@@ -97,8 +94,11 @@ end
9794
# signature
9895
# ---------
9996

100-
get_sig(mi::MethodInstance) = Signature(Any[mi])
10197
@inline get_sig(s::StateAtPC, @nospecialize(x=get_stmt(s))) = Signature(get_sig_nowrap(s, x))
98+
get_sig(sv::InferenceState) = get_sig((sv, get_currpc(sv)))
99+
100+
get_sig(mi::MethodInstance) = Signature(Any[mi])
101+
get_sig(caller::InferenceResult) = get_sig(get_linfo(caller))
102102

103103
function get_sig_nowrap(@nospecialize args...)
104104
sig = Any[]
@@ -529,7 +529,9 @@ Base.show(io::IO, ::MIME"application/prs.juno.inline", report::InferenceErrorRep
529529
# the default constructor to create a report from abstract interpretation
530530
function (T::Type{<:InferenceErrorReport})(state, @nospecialize(spec_args...))
531531
vf = get_virtual_frame(state)
532-
return T([vf], vf.sig, spec_args...)
532+
vst = VirtualFrame[vf]
533+
sig = get_sig(state)
534+
return T(vst, sig, spec_args...)
533535
end
534536

535537
# utility

src/analyzers/jetanalyzer.jl

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -516,15 +516,17 @@ This is reported only when it's not caught by control flow.
516516
end
517517
function UncaughtExceptionReport(sv::InferenceState, throw_calls::Vector{Tuple{Int,Expr}})
518518
vf = get_virtual_frame(sv.linfo)
519-
sig = Any[]
519+
vst = VirtualFrame[vf]
520+
sigs = Any[]
520521
ncalls = length(throw_calls)
521522
for (i, (pc, call)) in enumerate(throw_calls)
522523
call_sig = get_sig_nowrap((sv, pc), call)
523-
append!(sig, call_sig)
524-
i ncalls && push!(sig, ", ")
524+
append!(sigs, call_sig)
525+
i ncalls && push!(sigs, ", ")
525526
end
527+
sig = Signature(sigs)
526528
single_error = ncalls == 1
527-
return UncaughtExceptionReport([vf], Signature(sig), single_error)
529+
return UncaughtExceptionReport(vst, sig, single_error)
528530
end
529531
function print_report_message(io::IO, r::UncaughtExceptionReport)
530532
msg = r.single_error ? "may throw" : "may throw either of"
@@ -845,7 +847,6 @@ function print_report_message(io::IO, r::UndefVarErrorReport)
845847
end
846848
print(io, " is not defined")
847849
end
848-
print_signature(::UndefVarErrorReport) = false
849850

850851
# undefined global variable report passes
851852

@@ -1105,10 +1106,8 @@ abstract type AbstractBuiltinErrorReport <: InferenceErrorReport end
11051106
@nospecialize(f)
11061107
argtypes::Argtypes
11071108
msg::AbstractString
1108-
print_signature::Bool = false
11091109
end
11101110
print_report_message(io::IO, r::BuiltinErrorReport) = print(io, r.msg)
1111-
print_signature(r::BuiltinErrorReport) = r.print_signature
11121111
const GENERAL_BUILTIN_ERROR_MSG = "invalid builtin function call"
11131112

11141113
@static if VERSION v"1.10.0-DEV.197"
@@ -1218,7 +1217,7 @@ function (::BasicPass)(::Type{AbstractBuiltinErrorReport}, analyzer::JETAnalyzer
12181217
end
12191218
if @static VERSION >= v"1.10.0-DEV.197" ? (ret isa IntrinsicError) : false
12201219
msg = LazyString(f, ": ", ret.reason)
1221-
report = BuiltinErrorReport(sv, f, argtypes, msg, #=print_signature=#true)
1220+
report = BuiltinErrorReport(sv, f, argtypes, msg)
12221221
add_new_report!(analyzer, sv.result, report)
12231222
return true
12241223
end
@@ -1388,7 +1387,7 @@ function handle_invalid_builtins!(analyzer::JETAnalyzer, sv::InferenceState, @no
13881387
# we don't bail out using `basic_filter` here because the native tfuncs are already very permissive
13891388
if ret === Bottom
13901389
msg = GENERAL_BUILTIN_ERROR_MSG
1391-
report = BuiltinErrorReport(sv, f, argtypes, msg, #=print_signature=#true)
1390+
report = BuiltinErrorReport(sv, f, argtypes, msg)
13921391
add_new_report!(analyzer, sv.result, report)
13931392
return true
13941393
end
@@ -1401,7 +1400,6 @@ end
14011400
msg::String = "this builtin function call may throw"
14021401
end
14031402
print_report_message(io::IO, r::UnsoundBuiltinErrorReport) = print(io, r.msg)
1404-
print_signature(::UnsoundBuiltinErrorReport) = true
14051403

14061404
function (::SoundPass)(::Type{AbstractBuiltinErrorReport}, analyzer::JETAnalyzer, sv::InferenceState, @nospecialize(f), argtypes::Argtypes, @nospecialize(rt))
14071405
@assert !(f === throw) "`throw` calls should be handled either by the report pass of `SeriousExceptionReport` or `UncaughtExceptionReport`"

src/ui/print.jl

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -255,33 +255,98 @@ function print_stack(io, report, config, wrote_linfos, depth = 1)
255255
push!(wrote_linfos, linfo_hash)
256256

257257
# print current frame and go into deeper
258-
should_print && print_frame(io, frame, config, depth)
258+
if should_print
259+
color = RAIL_COLORS[(depth)%N_RAILS+1]
260+
print_rails(io, depth-1)
261+
printstyled(io, ""; color)
262+
print_frame_sig(io, frame)
263+
print(io, " ")
264+
print_frame_loc(io, frame, config, color)
265+
println(io)
266+
end
259267
print_stack(io, report, config, wrote_linfos, depth + 1)
260268
end
261269

262-
function print_frame(io, frame, config, depth, color = RAIL_COLORS[(depth)%N_RAILS+1])
263-
print_rails(io, depth-1)
270+
function print_frame_sig(io, frame)
271+
mi = frame.linfo
272+
m = mi.def
273+
if m isa Module
274+
Base.show_mi(io, mi, #=from_stackframe=#true)
275+
else
276+
show_spec_sig(IOContext(io, :backtrace=>true), m, mi.specTypes)
277+
end
278+
end
264279

265-
s = string("┌ @ ", (config.fullpath ? tofullpath : identity)(string(frame.file)), ':', frame.line)
266-
printstyled(io, s, ' '; color)
267-
print_signature(io, frame.sig, config)
268-
println(io)
280+
function print_frame_loc(io, frame, config, color)
281+
def = frame.linfo.def
282+
mod = def isa Module ? def : def.module
283+
path = String(frame.file)
284+
if config.fullpath
285+
path = tofullpath(path)
286+
elseif !isabspath(path)
287+
path = "./" * path
288+
end
289+
printstyled(io, "@ "; color)
290+
# IDEA use the same coloring as the Base stacktrace?
291+
# modulecolor = get!(Base.STACKTRACE_FIXEDCOLORS, mod) do
292+
# popfirst!(Base.STACKTRACE_MODULECOLORS)
293+
# end
294+
modulecolor = color
295+
printstyled(io, mod; color = modulecolor)
296+
printstyled(io, ' ', path, ':', frame.line; color)
297+
end
269298

270-
return length(s) # the length of frame info string
299+
@static if VERSION v"1.10.0-DEV.1394"
300+
using Base.StackTraces: show_spec_sig
301+
else
302+
function show_spec_sig(io::IO, m::Method, @nospecialize(sig::Type))
303+
if get(io, :limit, :false)::Bool
304+
if !haskey(io, :displaysize)
305+
io = IOContext(io, :displaysize => displaysize(io))
306+
end
307+
end
308+
argnames = Base.method_argnames(m)
309+
argnames = replace(argnames, :var"#unused#" => :var"")
310+
if m.nkw > 0
311+
# rearrange call kw_impl(kw_args..., func, pos_args...) to func(pos_args...; kw_args)
312+
kwarg_types = Any[ fieldtype(sig, i) for i = 2:(1+m.nkw) ]
313+
uw = Base.unwrap_unionall(sig)::DataType
314+
pos_sig = Base.rewrap_unionall(Tuple{uw.parameters[(m.nkw+2):end]...}, sig)
315+
kwnames = argnames[2:(m.nkw+1)]
316+
for i = 1:length(kwnames)
317+
str = string(kwnames[i])::String
318+
if endswith(str, "...")
319+
kwnames[i] = Symbol(str[1:end-3])
320+
end
321+
end
322+
Base.show_tuple_as_call(io, m.name, pos_sig;
323+
demangle=true,
324+
kwargs=zip(kwnames, kwarg_types),
325+
argnames=argnames[m.nkw+2:end])
326+
else
327+
Base.show_tuple_as_call(io, m.name, sig; demangle=true, argnames)
328+
end
271329
end
330+
end # @static if VERSION ≥ v"1.10.0-DEV.1394"
272331

273332
function print_error_frame(io, report, config, depth)
274333
frame = report.vst[depth]
275-
276334
color = report_color(report)
277-
len = print_frame(io, frame, config, depth, color)
335+
336+
print_rails(io, depth-1)
337+
printstyled(io, ""; color)
338+
print_frame_sig(io, frame)
339+
print(io, " ")
340+
print_frame_loc(io, frame, config, color)
341+
println(io)
342+
278343
print_rails(io, depth-1)
279344
printstyled(io, ""; color)
280345
print_report(io, report)
281346
println(io)
282347

283348
print_rails(io, depth-1)
284-
printlnstyled(io, '', ''^len; color)
349+
printlnstyled(io, '', ''^20; color)
285350
end
286351

287352
function print_report(io::IO, report::InferenceErrorReport)

src/ui/vscode.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ module VSCode
33
import ..JET:
44
gen_postprocess,
55
tofullpath,
6-
print_signature,
76
AbstractAnalyzer,
87
JETToplevelResult,
98
ToplevelErrorReport,
109
JETCallResult,
1110
InferenceErrorReport,
1211
get_reports,
13-
print_report
12+
print_report,
13+
print_frame_sig
1414

1515
# common
1616
# ======
@@ -102,8 +102,7 @@ function vscode_diagnostics(analyzer::Analyzer,
102102
line = showpoint.line,
103103
severity = 1, # 0: Error, 1: Warning, 2: Information, 3: Hint
104104
relatedInformation = map((order ? identity : reverse)(report.vst)) do frame
105-
sig = sprint(print_signature, frame.sig, (; annotate_types = true))
106-
return (; msg = postprocess(sig),
105+
return (; msg = postprocess(sprint(print_frame_sig, frame)),
107106
path = tovscodepath(frame.file),
108107
line = frame.line,
109108
)

0 commit comments

Comments
 (0)