https://github.com/JuliaLang/julia
Tip revision: 1b4bfa298731c17f691044b5ec879676b9963afd authored by Keno Fischer on 04 October 2023, 16:07:06 UTC
WIP
WIP
Tip revision: 1b4bfa2
errorshow.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
"""
showerror(io, e)
Show a descriptive representation of an exception object `e`.
This method is used to display the exception after a call to [`throw`](@ref).
# Examples
```jldoctest
julia> struct MyException <: Exception
msg::String
end
julia> function Base.showerror(io::IO, err::MyException)
print(io, "MyException: ")
print(io, err.msg)
end
julia> err = MyException("test exception")
MyException("test exception")
julia> sprint(showerror, err)
"MyException: test exception"
julia> throw(MyException("test exception"))
ERROR: MyException: test exception
```
"""
showerror(io::IO, ex) = show(io, ex)
show_index(io::IO, x::Any) = show(io, x)
show_index(io::IO, x::Slice) = show_index(io, x.indices)
show_index(io::IO, x::LogicalIndex) = summary(io, x.mask)
show_index(io::IO, x::OneTo) = print(io, "1:", x.stop)
show_index(io::IO, x::Colon) = print(io, ':')
function showerror(io::IO, ex::Meta.ParseError)
if isnothing(ex.detail)
print(io, "ParseError(", repr(ex.msg), ")")
else
showerror(io, ex.detail)
end
end
function showerror(io::IO, ex::BoundsError)
print(io, "BoundsError")
if isdefined(ex, :a)
print(io, ": attempt to access ")
summary(io, ex.a)
if isdefined(ex, :i)
print(io, " at index [")
if ex.i isa AbstractRange
print(io, ex.i)
elseif ex.i isa AbstractString
show(io, ex.i)
else
for (i, x) in enumerate(ex.i)
i > 1 && print(io, ", ")
show_index(io, x)
end
end
print(io, ']')
end
end
Experimental.show_error_hints(io, ex)
end
function showerror(io::IO, ex::TypeError)
print(io, "TypeError: ")
if ex.expected === Bool
print(io, "non-boolean (", typeof(ex.got), ") used in boolean context")
else
if isvarargtype(ex.got)
targs = (ex.got,)
elseif isa(ex.got, Type)
targs = ("Type{", ex.got, "}")
else
targs = ("a value of type $(typeof(ex.got))",)
end
if ex.context == ""
ctx = "in $(ex.func)"
elseif ex.func === Symbol("keyword argument")
ctx = "in keyword argument $(ex.context)"
else
ctx = "in $(ex.func), in $(ex.context)"
end
print(io, ctx, ", expected ", ex.expected, ", got ", targs...)
end
Experimental.show_error_hints(io, ex)
end
function showerror(io::IO, ex, bt; backtrace=true)
try
showerror(io, ex)
finally
backtrace && show_backtrace(io, bt)
end
end
function showerror(io::IO, ex::LoadError, bt; backtrace=true)
!isa(ex.error, LoadError) && print(io, "LoadError: ")
showerror(io, ex.error, bt, backtrace=backtrace)
print(io, "\nin expression starting at $(ex.file):$(ex.line)")
end
showerror(io::IO, ex::LoadError) = showerror(io, ex, [])
function showerror(io::IO, ex::InitError, bt; backtrace=true)
print(io, "InitError: ")
showerror(io, ex.error, bt, backtrace=backtrace)
print(io, "\nduring initialization of module ", ex.mod)
end
showerror(io::IO, ex::InitError) = showerror(io, ex, [])
function showerror(io::IO, ex::DomainError)
if isa(ex.val, AbstractArray)
compact = get(io, :compact, true)::Bool
limit = get(io, :limit, true)::Bool
print(IOContext(io, :compact => compact, :limit => limit),
"DomainError with ", ex.val)
else
print(io, "DomainError with ", ex.val)
end
if isdefined(ex, :msg)
print(io, ":\n", ex.msg)
end
Experimental.show_error_hints(io, ex)
nothing
end
function showerror(io::IO, ex::SystemError)
if @static(Sys.iswindows() ? ex.extrainfo isa WindowsErrorInfo : false)
errstring = Libc.FormatMessage(ex.extrainfo.errnum)
extrainfo = ex.extrainfo.extrainfo
else
errstring = Libc.strerror(ex.errnum)
extrainfo = ex.extrainfo
end
if extrainfo === nothing
print(io, "SystemError: $(ex.prefix): ", errstring)
else
print(io, "SystemError (with $extrainfo): $(ex.prefix): ", errstring)
end
end
showerror(io::IO, ::DivideError) = print(io, "DivideError: integer division error")
showerror(io::IO, ::StackOverflowError) = print(io, "StackOverflowError:")
showerror(io::IO, ::UndefRefError) = print(io, "UndefRefError: access to undefined reference")
showerror(io::IO, ::EOFError) = print(io, "EOFError: read end of file")
function showerror(io::IO, ex::ErrorException)
print(io, ex.msg)
if ex.msg == "type String has no field data"
println(io)
print(io, "Use `codeunits(str)` instead.")
end
end
showerror(io::IO, ex::KeyError) = (print(io, "KeyError: key ");
show(io, ex.key);
print(io, " not found"))
showerror(io::IO, ex::InterruptException) = print(io, "InterruptException:")
showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: ", ex.msg)
showerror(io::IO, ex::DimensionMismatch) = print(io, "DimensionMismatch: ", ex.msg)
showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: ", ex.msg)
showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: ", ex.msg)
showerror(io::IO, ex::UndefKeywordError) =
print(io, "UndefKeywordError: keyword argument `$(ex.var)` not assigned")
function showerror(io::IO, ex::UndefVarError)
print(io, "UndefVarError: `$(ex.var)` not defined")
Experimental.show_error_hints(io, ex)
end
function showerror(io::IO, ex::InexactError)
print(io, "InexactError: ", ex.func, '(')
nameof(ex.T) === ex.func || print(io, ex.T, ", ")
print(io, ex.val, ')')
Experimental.show_error_hints(io, ex)
end
function showerror(io::IO, ex::CanonicalIndexError)
print(io, "CanonicalIndexError: ", ex.func, " not defined for ", ex.type)
end
typesof(@nospecialize args...) = Tuple{Any[ Core.Typeof(args[i]) for i in 1:length(args) ]...}
function print_with_compare(io::IO, @nospecialize(a::DataType), @nospecialize(b::DataType), color::Symbol)
if a.name === b.name
Base.show_type_name(io, a.name)
n = length(a.parameters)
n > 0 || return
print(io, '{')
for i = 1:n
if i > length(b.parameters)
printstyled(io, a.parameters[i], color=color)
else
print_with_compare(io::IO, a.parameters[i], b.parameters[i], color)
end
i < n && print(io, ',')
end
print(io, '}')
else
printstyled(io, a; color=color)
end
end
function print_with_compare(io::IO, @nospecialize(a), @nospecialize(b), color::Symbol)
if a === b
print(io, a)
else
printstyled(io, a; color=color)
end
end
function show_convert_error(io::IO, ex::MethodError, arg_types_param)
# See #13033
T = striptype(ex.args[1])
if T === nothing
print(io, "First argument to `convert` must be a Type, got ", ex.args[1])
else
p2 = arg_types_param[2]
print_one_line = isa(T, DataType) && isa(p2, DataType) && T.name != p2.name
printstyled(io, "Cannot `convert` an object of type ")
print_one_line || printstyled(io, "\n ")
print_with_compare(io, p2, T, :light_green)
printstyled(io, " to an object of type ")
print_one_line || printstyled(io, "\n ")
print_with_compare(io, T, p2, :light_red)
end
end
function showerror(io::IO, ex::MethodError)
# ex.args is a tuple type if it was thrown from `invoke` and is
# a tuple of the arguments otherwise.
is_arg_types = isa(ex.args, DataType)
arg_types = (is_arg_types ? ex.args : typesof(ex.args...))::DataType
f = ex.f
meth = methods_including_ambiguous(f, arg_types)
if isa(meth, MethodList) && length(meth) > 1
return showerror_ambiguous(io, meth, f, arg_types)
end
arg_types_param::SimpleVector = arg_types.parameters
show_candidates = true
print(io, "MethodError: ")
ft = typeof(f)
f_is_function = false
kwargs = ()
if f === Core.kwcall && !is_arg_types
f = (ex.args::Tuple)[2]
ft = typeof(f)
arg_types_param = arg_types_param[3:end]
kwargs = pairs(ex.args[1])
ex = MethodError(f, ex.args[3:end::Int], ex.world)
end
name = ft.name.mt.name
if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types
f_is_function = true
show_convert_error(io, ex, arg_types_param)
elseif f === mapreduce_empty || f === reduce_empty
print(io, "reducing over an empty collection is not allowed; consider supplying `init` to the reducer")
show_candidates = false
elseif isempty(methods(f)) && isa(f, DataType) && isabstracttype(f)
print(io, "no constructors have been defined for ", f)
elseif isempty(methods(f)) && !isa(f, Function) && !isa(f, Type)
print(io, "objects of type ", ft, " are not callable")
else
if ft <: Function && isempty(ft.parameters) && _isself(ft)
f_is_function = true
end
print(io, "no method matching ")
show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f))
print(io, "(")
for (i, typ) in enumerate(arg_types_param)
print(io, "::", typ)
i == length(arg_types_param) || print(io, ", ")
end
if !isempty(kwargs)
print(io, "; ")
for (i, (k, v)) in enumerate(kwargs)
print(io, k, "::", typeof(v))
i == length(kwargs)::Int || print(io, ", ")
end
end
print(io, ")")
end
# catch the two common cases of element-wise addition and subtraction
if (f === Base.:+ || f === Base.:-) && length(arg_types_param) == 2
# we need one array of numbers and one number, in any order
if any(x -> x <: AbstractArray{<:Number}, arg_types_param) &&
any(x -> x <: Number, arg_types_param)
nounf = f === Base.:+ ? "addition" : "subtraction"
varnames = ("scalar", "array")
first, second = arg_types_param[1] <: Number ? varnames : reverse(varnames)
fstring = f === Base.:+ ? "+" : "-" # avoid depending on show_default for functions (invalidation)
print(io, "\nFor element-wise $nounf, use broadcasting with dot syntax: $first .$fstring $second")
end
end
if ft <: AbstractArray
print(io, "\nUse square brackets [] for indexing an Array.")
end
# Check for local functions that shadow methods in Base
if f_is_function && isdefined(Base, name)
basef = getfield(Base, name)
if basef !== ex.f && hasmethod(basef, arg_types)
print(io, "\nYou may have intended to import ")
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
end
end
if (ex.world != typemax(UInt) && hasmethod(ex.f, arg_types) &&
!hasmethod(ex.f, arg_types, world = ex.world))
curworld = get_world_counter()
print(io, "\nThe applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).")
end
if !is_arg_types
# Check for row vectors used where a column vector is intended.
vec_args = []
hasrows = false
for arg in ex.args
isrow = isa(arg,Array) && ndims(arg)::Int==2 && size(arg,1)::Int==1
hasrows |= isrow
push!(vec_args, isrow ? vec(arg) : arg)
end
if hasrows && applicable(f, vec_args...) && isempty(kwargs)
print(io, "\n\nYou might have used a 2d row vector where a 1d column vector was required.",
"\nNote the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].",
"\nYou can convert to a column vector with the vec() function.")
end
end
Experimental.show_error_hints(io, ex, arg_types_param, kwargs)
show_candidates && try
show_method_candidates(io, ex, kwargs)
catch ex
@error "Error showing method candidates, aborted" exception=ex,catch_backtrace()
end
end
striptype(::Type{T}) where {T} = T
striptype(::Any) = nothing
function showerror_ambiguous(io::IO, meths, f, args)
print(io, "MethodError: ")
show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f))
print(io, "(")
p = args.parameters
for (i,a) in enumerate(p)
print(io, "::", a)
i < length(p) && print(io, ", ")
end
println(io, ") is ambiguous.\n\nCandidates:")
sigfix = Any
for m in meths
print(io, " ")
show_method(io, m; digit_align_width=0)
println(io)
sigfix = typeintersect(m.sig, sigfix)
end
if isa(unwrap_unionall(sigfix), DataType) && sigfix <: Tuple
let sigfix=sigfix
if all(m->morespecific(sigfix, m.sig), meths)
print(io, "\nPossible fix, define\n ")
Base.show_tuple_as_call(io, :function, sigfix)
else
print(io, "To resolve the ambiguity, try making one of the methods more specific, or ")
print(io, "adding a new method more specific than any of the existing applicable methods.")
end
end
println(io)
end
nothing
end
#Show an error by directly calling jl_printf.
#Useful in Base submodule __init__ functions where stderr isn't defined yet.
function showerror_nostdio(err, msg::AbstractString)
stderr_stream = ccall(:jl_stderr_stream, Ptr{Cvoid}, ())
ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, msg)
ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, ":\n")
ccall(:jl_static_show, Csize_t, (Ptr{Cvoid},Any), stderr_stream, err)
ccall(:jl_printf, Cint, (Ptr{Cvoid},Cstring), stderr_stream, "\n")
end
stacktrace_expand_basepaths()::Bool = Base.get_bool_env("JULIA_STACKTRACE_EXPAND_BASEPATHS", false) === true
stacktrace_contract_userdir()::Bool = Base.get_bool_env("JULIA_STACKTRACE_CONTRACT_HOMEDIR", true) === true
stacktrace_linebreaks()::Bool = Base.get_bool_env("JULIA_STACKTRACE_LINEBREAKS", false) === true
function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=())
is_arg_types = isa(ex.args, DataType)
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
arg_types_param = Any[arg_types.parameters...]
# Displays the closest candidates of the given function by looping over the
# functions methods and counting the number of matching arguments.
f = ex.f
ft = typeof(f)
lines = []
# These functions are special cased to only show if first argument is matched.
special = f === convert || f === getindex || f === setindex!
funcs = Tuple{Any,Vector{Any}}[(f, arg_types_param)]
# An incorrect call method produces a MethodError for convert.
# It also happens that users type convert when they mean call. So
# pool MethodErrors for these two functions.
if f === convert && !isempty(arg_types_param)
at1 = arg_types_param[1]
if isType(at1) && !Core.Compiler.has_free_typevars(at1)
push!(funcs, (at1.parameters[1], arg_types_param[2:end]))
end
end
for (func, arg_types_param) in funcs
for method in methods(func)
buf = IOBuffer()
iob0 = iob = IOContext(buf, io)
tv = Any[]
if func isa Core.OpaqueClosure
sig0 = signature_type(func, typeof(func).parameters[1])
else
sig0 = method.sig
end
while isa(sig0, UnionAll)
push!(tv, sig0.var)
iob = IOContext(iob, :unionall_env => sig0.var)
sig0 = sig0.body
end
sig0 = sig0::DataType
s1 = sig0.parameters[1]
if sig0 === Tuple || !isa(func, rewrap_unionall(s1, method.sig))
# function itself doesn't match or is a builtin
continue
else
print(iob, " ")
show_signature_function(iob, s1)
end
print(iob, "(")
t_i = copy(arg_types_param)
right_matches = 0
sig = sig0.parameters[2:end]
for i = 1 : min(length(t_i), length(sig))
i > 1 && print(iob, ", ")
# If isvarargtype then it checks whether the rest of the input arguments matches
# the varargtype
if Base.isvarargtype(sig[i])
sigstr = (unwrapva(unwrap_unionall(sig[i])), "...")
j = length(t_i)
else
sigstr = (sig[i],)
j = i
end
# Checks if the type of arg 1:i of the input intersects with the current method
t_in = typeintersect(rewrap_unionall(Tuple{sig[1:i]...}, method.sig),
rewrap_unionall(Tuple{t_i[1:j]...}, method.sig))
# If the function is one of the special cased then it should break the loop if
# the type of the first argument is not matched.
t_in === Union{} && special && i == 1 && break
if t_in === Union{}
if get(io, :color, false)::Bool
let sigstr=sigstr
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::", sigstr...)
end
end
else
print(iob, "!Matched::", sigstr...)
end
# If there is no typeintersect then the type signature from the method is
# inserted in t_i this ensures if the type at the next i matches the type
# signature then there will be a type intersect
t_i[i] = sig[i]
else
right_matches += j==i ? 1 : 0
print(iob, "::", sigstr...)
end
end
special && right_matches == 0 && continue
if length(t_i) > length(sig) && !isempty(sig) && Base.isvarargtype(sig[end])
# It ensures that methods like f(a::AbstractString...) gets the correct
# number of right_matches
for t in arg_types_param[length(sig):end]
if t <: rewrap_unionall(unwrapva(unwrap_unionall(sig[end])), method.sig)
right_matches += 1
end
end
end
if right_matches > 0 || length(arg_types_param) < 2
if length(t_i) < length(sig)
# If the methods args is longer than input then the method
# arguments is printed as not a match
for (k, sigtype) in enumerate(sig[length(t_i)+1:end])
sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype
if Base.isvarargtype(sigtype)
sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...")
else
sigstr = (sigtype,)
end
if !((min(length(t_i), length(sig)) == 0) && k==1)
print(iob, ", ")
end
if k == 1 && Base.isvarargtype(sigtype)
# There wasn't actually a mismatch - the method match failed for
# some other reason, e.g. world age. Just print the sigstr.
print(iob, sigstr...)
elseif get(io, :color, false)::Bool
let sigstr=sigstr
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::", sigstr...)
end
end
else
print(iob, "!Matched::", sigstr...)
end
end
end
kwords = kwarg_decl(method)
if !isempty(kwords)
print(iob, "; ")
join(iob, kwords, ", ")
end
print(iob, ")")
show_method_params(iob0, tv)
file, line = updated_methodloc(method)
if file === nothing
file = string(method.file)
end
stacktrace_contract_userdir() && (file = contractuser(file))
if !isempty(kwargs)::Bool
unexpected = Symbol[]
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
for (k, v) in kwargs
if !(k::Symbol in kwords)
push!(unexpected, k::Symbol)
end
end
end
if !isempty(unexpected)
Base.with_output_color(Base.error_color(), iob) do iob
plur = length(unexpected) > 1 ? "s" : ""
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
end
end
end
if ex.world < reinterpret(UInt, method.primary_world)
print(iob, " (method too new to be called from this world context.)")
elseif ex.world > reinterpret(UInt, method.deleted_world)
print(iob, " (method deleted before this world age.)")
end
println(iob)
m = parentmodule_before_main(method)
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)
# TODO: indicate if it's in the wrong world
push!(lines, (buf, right_matches))
end
end
end
if !isempty(lines) # Display up to three closest candidates
Base.with_output_color(:normal, io) do io
print(io, "\n\nClosest candidates are:")
sort!(lines, by = x -> -x[2])
i = 0
for line in lines
println(io)
if i >= 3
print(io, " ...")
break
end
i += 1
print(io, String(take!(line[1])))
end
println(io) # extra newline for spacing to stacktrace
end
end
end
# In case the line numbers in the source code have changed since the code was compiled,
# allow packages to set a callback function that corrects them.
# (Used by Revise and perhaps other packages.)
#
# Set this with
# Base.update_stackframes_callback[] = my_updater!
# where my_updater! takes a single argument and works in-place. The argument will be a
# Vector{Any} storing tuples (sf::StackFrame, nrepetitions::Int), and the updater should
# replace `sf` as needed.
const update_stackframes_callback = Ref{Function}(identity)
const STACKTRACE_MODULECOLORS = Iterators.Stateful(Iterators.cycle([:magenta, :cyan, :green, :yellow]))
const STACKTRACE_FIXEDCOLORS = IdDict(Base => :light_black, Core => :light_black)
function show_full_backtrace(io::IO, trace::Vector; print_linebreaks::Bool)
num_frames = length(trace)
ndigits_max = ndigits(num_frames)
println(io, "\nStacktrace:")
for (i, (frame, n)) in enumerate(trace)
print_stackframe(io, i, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS)
if i < num_frames
println(io)
print_linebreaks && println(io)
end
end
end
const BIG_STACKTRACE_SIZE = 50 # Arbitrary constant chosen here
function show_reduced_backtrace(io::IO, t::Vector)
recorded_positions = IdDict{UInt, Vector{Int}}()
#= For each frame of hash h, recorded_positions[h] is the list of indices i
such that hash(t[i-1]) == h, ie the list of positions in which the
frame appears just before. =#
displayed_stackframes = []
repeated_cycle = Tuple{Int,Int,Int}[]
# First: line to introuce the "cycle repetition" message
# Second: length of the cycle
# Third: number of repetitions
frame_counter = 1
while frame_counter < length(t)
(last_frame, n) = t[frame_counter]
frame_counter += 1 # Indicating the next frame
current_hash = hash(last_frame)
positions = get(recorded_positions, current_hash, Int[])
recorded_positions[current_hash] = push!(positions, frame_counter)
repetitions = 0
for index_p in length(positions)-1:-1:1 # More recent is more likely
p = positions[index_p]
cycle_length = frame_counter - p
i = frame_counter
j = p
while i < length(t) && t[i] == t[j]
i += 1
j += 1
end
if j >= frame_counter-1
#= At least one cycle repeated =#
repetitions = div(i - frame_counter + 1, cycle_length)
push!(repeated_cycle, (length(displayed_stackframes), cycle_length, repetitions))
frame_counter += cycle_length * repetitions - 1
break
end
end
if repetitions==0
push!(displayed_stackframes, (last_frame, n))
end
end
try invokelatest(update_stackframes_callback[], displayed_stackframes) catch end
println(io, "\nStacktrace:")
ndigits_max = ndigits(length(t))
push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty
frame_counter = 1
for i in 1:length(displayed_stackframes)
(frame, n) = displayed_stackframes[i]
print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS)
if i < length(displayed_stackframes)
println(io)
stacktrace_linebreaks() && println(io)
end
while repeated_cycle[1][1] == i # never empty because of the initial (0,0,0)
cycle_length = repeated_cycle[1][2]
repetitions = repeated_cycle[1][3]
popfirst!(repeated_cycle)
printstyled(io,
"--- the last ", cycle_length, " lines are repeated ",
repetitions, " more time", repetitions>1 ? "s" : "", " ---", color = :light_black)
if i < length(displayed_stackframes)
println(io)
stacktrace_linebreaks() && println(io)
end
frame_counter += cycle_length * repetitions
end
frame_counter += 1
end
end
# Print a stack frame where the module color is determined by looking up the parent module in
# `modulecolordict`. If the module does not have a color, yet, a new one can be drawn
# from `modulecolorcycler`.
function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolordict, modulecolorcycler)
m = Base.parentmodule(frame)
modulecolor = if m !== nothing
m = parentmodule_before_main(m)
get!(() -> popfirst!(modulecolorcycler), modulecolordict, m)
else
:default
end
print_stackframe(io, i, frame, n, ndigits_max, modulecolor)
end
# Gets the topmost parent module that isn't Main
function parentmodule_before_main(m::Module)
while parentmodule(m) !== m
pm = parentmodule(m)
pm == Main && break
m = pm
end
m
end
parentmodule_before_main(x) = parentmodule_before_main(parentmodule(x))
# Print a stack frame where the module color is set manually with `modulecolor`.
function print_stackframe(io, i, frame::StackFrame, n::Int, ndigits_max, modulecolor)
file, line = string(frame.file), frame.line
file = fixup_stdlib_path(file)
stacktrace_expand_basepaths() && (file = something(find_source_file(file), file))
stacktrace_contract_userdir() && (file = contractuser(file))
# Used by the REPL to make it possible to open
# the location of a stackframe/method in the editor.
if haskey(io, :last_shown_line_infos)
push!(io[:last_shown_line_infos], (string(frame.file), frame.line))
end
inlined = getfield(frame, :inlined)
modul = parentmodule(frame)
digit_align_width = ndigits_max + 2
# frame number
print(io, " ", lpad("[" * string(i) * "]", digit_align_width))
print(io, " ")
StackTraces.show_spec_linfo(IOContext(io, :backtrace=>true), frame)
if n > 1
printstyled(io, " (repeats $n times)"; color=:light_black)
end
println(io)
# @ Module path / file : line
print_module_path_file(io, modul, file, line; modulecolor, digit_align_width)
# inlined
printstyled(io, inlined ? " [inlined]" : "", color = :light_black)
end
function print_module_path_file(io, modul, file, line; modulecolor = :light_black, digit_align_width = 0)
printstyled(io, " " ^ digit_align_width * "@", color = :light_black)
# module
if modul !== nothing && modulecolor !== nothing
print(io, " ")
printstyled(io, modul, color = modulecolor)
end
# filepath
stacktrace_expand_basepaths() && (file = something(find_source_file(file), file))
stacktrace_contract_userdir() && (file = contractuser(file))
print(io, " ")
dir = dirname(file)
!isempty(dir) && printstyled(io, dir, Filesystem.path_separator, color = :light_black)
# filename, separator, line
printstyled(io, basename(file), ":", line; color = :light_black, underline = true)
end
function show_backtrace(io::IO, t::Vector)
if haskey(io, :last_shown_line_infos)
empty!(io[:last_shown_line_infos])
end
# this will be set to true if types in the stacktrace are truncated
limitflag = Ref(false)
io = IOContext(io, :stacktrace_types_limited => limitflag)
# t is a pre-processed backtrace (ref #12856)
if t isa Vector{Any}
filtered = t
else
filtered = process_backtrace(t)
end
isempty(filtered) && return
if length(filtered) == 1 && StackTraces.is_top_level_frame(filtered[1][1])
f = filtered[1][1]::StackFrame
if f.line == 0 && f.file === Symbol("")
# don't show a single top-level frame with no location info
return
end
end
if length(filtered) > BIG_STACKTRACE_SIZE
show_reduced_backtrace(IOContext(io, :backtrace => true), filtered)
return
else
try invokelatest(update_stackframes_callback[], filtered) catch end
# process_backtrace returns a Vector{Tuple{Frame, Int}}
show_full_backtrace(io, filtered; print_linebreaks = stacktrace_linebreaks())
end
if limitflag[]
print(io, "\nSome type information was truncated. Use `show(err)` to see complete types.")
end
nothing
end
# For improved user experience, filter out frames for include() implementation
# - see #33065. See also #35371 for extended discussion of internal frames.
function _simplify_include_frames(trace)
kept_frames = trues(length(trace))
first_ignored = nothing
for i in length(trace):-1:1
frame::StackFrame, _ = trace[i]
mod = parentmodule(frame)
if first_ignored === nothing
if mod === Base && frame.func === :_include
# Hide include() machinery by default
first_ignored = i
end
else
first_ignored = first_ignored::Int
# Hack: allow `mod==nothing` as a workaround for inlined functions.
# TODO: Fix this by improving debug info.
if mod in (Base,Core,nothing) && 1+first_ignored-i <= 5
if frame.func === :eval
kept_frames[i:first_ignored] .= false
first_ignored = nothing
end
else
# Bail out to avoid hiding frames in unexpected circumstances
first_ignored = nothing
end
end
end
if first_ignored !== nothing
kept_frames[1:first_ignored] .= false
end
return trace[kept_frames]
end
# Collapse frames that have the same location (in some cases)
function _collapse_repeated_frames(trace)
kept_frames = trues(length(trace))
last_frame = nothing
for i in 1:length(trace)
frame::StackFrame, _ = trace[i]
if last_frame !== nothing && frame.file == last_frame.file && frame.line == last_frame.line
#=
Handles this case:
f(g, a; kw...) = error();
@inline f(a; kw...) = f(identity, a; kw...);
f(1)
which otherwise ends up as:
[4] #f#4 <-- useless
@ ./REPL[2]:1 [inlined]
[5] f(a::Int64)
@ Main ./REPL[2]:1
=#
if startswith(sprint(show, last_frame), "#")
kept_frames[i-1] = false
end
#= Handles this case
g(x, y=1, z=2) = error();
g(1)
which otherwise ends up as:
[2] g(x::Int64, y::Int64, z::Int64)
@ Main ./REPL[1]:1
[3] g(x::Int64) <-- useless
@ Main ./REPL[1]:1
=#
if frame.linfo isa MethodInstance && last_frame.linfo isa MethodInstance &&
frame.linfo.def isa Method && last_frame.linfo.def isa Method
m, last_m = frame.linfo.def::Method, last_frame.linfo.def::Method
params, last_params = Base.unwrap_unionall(m.sig).parameters, Base.unwrap_unionall(last_m.sig).parameters
if last_m.nkw != 0
pos_sig_params = last_params[(last_m.nkw+2):end]
issame = true
if pos_sig_params == params
kept_frames[i] = false
end
end
if length(last_params) > length(params)
issame = true
for i = 1:length(params)
issame &= params[i] == last_params[i]
end
if issame
kept_frames[i] = false
end
end
end
# TODO: Detect more cases that can be collapsed
end
last_frame = frame
end
return trace[kept_frames]
end
function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
n = 0
last_frame = StackTraces.UNKNOWN
count = 0
ret = Any[]
for i in eachindex(t)
lkups = t[i]
if lkups isa StackFrame
lkups = [lkups]
else
lkups = StackTraces.lookup(lkups)
end
for lkup in lkups
if lkup === StackTraces.UNKNOWN
continue
end
if (lkup.from_c && skipC)
continue
end
code = lkup.linfo
if code isa MethodInstance
def = code.def
if def isa Method && def.name !== :kwcall && def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,Any,Vararg}
# hide kwcall() methods, which are probably internal keyword sorter methods
# (we print the internal method instead, after demangling
# the argument list, since it has the right line number info)
continue
end
elseif !lkup.from_c
lkup.func === :kwcall && continue
end
count += 1
if count > limit
break
end
if lkup.file != last_frame.file || lkup.line != last_frame.line || lkup.func != last_frame.func || lkup.linfo !== last_frame.linfo
if n > 0
push!(ret, (last_frame, n))
end
n = 1
last_frame = lkup
else
n += 1
end
end
count > limit && break
end
if n > 0
push!(ret, (last_frame, n))
end
trace = _simplify_include_frames(ret)
trace = _collapse_repeated_frames(trace)
return trace
end
function show_exception_stack(io::IO, stack)
# Display exception stack with the top of the stack first. This ordering
# means that the user doesn't have to scroll up in the REPL to discover the
# root cause.
nexc = length(stack)
for i = nexc:-1:1
if nexc != i
printstyled(io, "\ncaused by: ", color=error_color())
end
exc, bt = stack[i]
showerror(io, exc, bt, backtrace = bt!==nothing)
i == 1 || println(io)
end
end
# Defined here rather than error.jl for bootstrap ordering
function show(io::IO, ip::InterpreterIP)
print(io, typeof(ip))
if ip.code isa Core.CodeInfo
print(io, " in top-level CodeInfo for $(ip.mod) at statement $(Int(ip.stmt))")
else
print(io, " in $(ip.code) at statement $(Int(ip.stmt))")
end
end
# handler for displaying a hint in case the user tries to call
# the instance of a number (probably missing the operator)
# eg: (1 + 2)(3 + 4)
function noncallable_number_hint_handler(io, ex, arg_types, kwargs)
@nospecialize
if ex.f isa Number
print(io, "\nMaybe you forgot to use an operator such as ")
printstyled(io, "*, ^, %, / etc. ", color=:cyan)
print(io, "?")
end
end
Experimental.register_error_hint(noncallable_number_hint_handler, MethodError)
# Display a hint in case the user tries to use the + operator on strings
# (probably attempting concatenation)
function string_concatenation_hint_handler(io, ex, arg_types, kwargs)
@nospecialize
if (ex.f === +) && all(i -> i <: AbstractString, arg_types)
print(io, "\nString concatenation is performed with ")
printstyled(io, "*", color=:cyan)
print(io, " (See also: https://docs.julialang.org/en/v1/manual/strings/#man-concatenation).")
end
end
Experimental.register_error_hint(string_concatenation_hint_handler, MethodError)
# ExceptionStack implementation
size(s::ExceptionStack) = size(s.stack)
getindex(s::ExceptionStack, i::Int) = s.stack[i]
function show(io::IO, ::MIME"text/plain", stack::ExceptionStack)
nexc = length(stack)
printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n")
show_exception_stack(io, stack)
end
show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack)