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")
elseif ex.func === :var"dict key"
print(io, "$(limitrepr(ex.got)) is not a valid key for type $(ex.expected)")
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 === :var"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")
showerror(io::IO, ex::ErrorException) = print(io, ex.msg)
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")
if isdefined(ex, :scope)
scope = ex.scope
if scope isa Module
print(io, " in `$scope`")
elseif scope === :static_parameter
print(io, " in static parameter matching")
else
print(io, " in $scope scope")
end
end
Experimental.show_error_hints(io, ex)
end
function showerror(io::IO, ex::InexactError)
print(io, "InexactError: ", ex.func, '(')
T = first(ex.args)
nameof(T) === ex.func || print(io, T, ", ")
# `join` calls `string` on its arguments, which shadows the size of e.g. Inf16
# as `string(Inf16) == "Inf"` instead of "Inf16". Thus we cannot use `join` here.
for arg in ex.args[2:end-1]
show(io, arg)
print(io, ", ")
end
show(io, ex.args[end])
print(io, ")")
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)
@nospecialize io
# 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, Tuple)
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
arg_types_param::SimpleVector = (unwrap_unionall(arg_types)::DataType).parameters
san_arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param]
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
print(io, "MethodError: ")
ft = typeof(f)
f_is_function = false
kwargs = []
if f === Core.kwcall && length(arg_types_param) >= 2 && arg_types_param[1] <: NamedTuple && !is_arg_types
# if this is a kwcall, reformat it as a call with kwargs
# TODO: handle !is_arg_types here (aka invoke with kwargs), which needs a value for `f`
local kwt
let args = ex.args::Tuple
f = args[2]
ft = typeof(f)
kwt = typeof(args[1])
ex = MethodError(f, args[3:end], ex.world)
end
arg_types_param = arg_types_param[3:end]
san_arg_types_param = san_arg_types_param[3:end]
keys = kwt.parameters[1]::Tuple
kwargs = Any[(keys[i], fieldtype(kwt, i)) for i in 1:length(keys)]
arg_types = rewrap_unionall(Tuple{arg_types_param...}, arg_types)
end
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 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
if is_arg_types
print(io, "no method matching invoke ")
else
print(io, "no method matching ")
end
buf = IOBuffer()
iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
show_signature_function(iob, Core.Typeof(f))
show_tuple_as_call(iob, :function, arg_types; hasfirst=false, kwargs = isempty(kwargs) ? nothing : kwargs)
str = String(take!(buf))
str = type_limited_string_from_context(io, str)
print(io, str)
end
# catch the two common cases of element-wise addition and subtraction
if (f === Base.:+ || f === Base.:-) && length(san_arg_types_param) == 2
# we need one array of numbers and one number, in any order
if any(x -> x <: AbstractArray{<:Number}, san_arg_types_param) &&
any(x -> x <: Number, san_arg_types_param)
nounf = f === Base.:+ ? "addition" : "subtraction"
varnames = ("scalar", "array")
first, second = san_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
let name = ft.name.mt.name
if f_is_function && isdefined(Base, name)
basef = getfield(Base, name)
if basef !== f && hasmethod(basef, arg_types)
print(io, "\nYou may have intended to import ")
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
end
end
end
if ex.world == typemax(UInt) || hasmethod(f, arg_types, world=ex.world)
if ex.world == typemax(UInt) || isempty(kwargs)
print(io, "\nThis error has been manually thrown, explicitly, so the method may exist but be intentionally marked as unimplemented.")
else
print(io, "\nThis method may not support any keyword arguments.")
end
elseif hasmethod(f, arg_types) && !hasmethod(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).")
elseif f isa Function
print(io, "\nThe function `$f` exists, but no method is defined for this combination of argument types.")
elseif f isa Type
print(io, "\nThe type `$f` exists, but no method is defined for this combination of argument types when trying to construct it.")
else
print(io, "\nThe object of type `$(typeof(f))` exists, but no method is defined for this combination of argument types when trying to treat it as a callable object.")
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, san_arg_types_param, kwargs)
try
show_method_candidates(io, ex, kwargs)
catch ex
@error "Error showing method candidates, aborted" exception=ex,catch_backtrace()
end
nothing
end
striptype(::Type{T}) where {T} = T
striptype(::Any) = nothing
function showerror_ambiguous(io::IO, meths, f, args::Type)
@nospecialize f args
print(io, "MethodError: ")
show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f))
show_tuple_as_call(io, :var"", args, hasfirst=false)
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 ")
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, kwargs=[])
@nospecialize io
is_arg_types = !isa(ex.args, Tuple)
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
arg_types_param = Any[(unwrap_unionall(arg_types)::DataType).parameters...]
arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param]
# 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 = String[]
line_score = Int[]
# 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 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)
push!(lines, String(take!(buf)))
push!(line_score, -(right_matches * 2 + (length(arg_types_param) < 2 ? 1 : 0)))
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:")
permute!(lines, sortperm(line_score))
i = 0
for line in lines
println(io)
if i >= 3
print(io, " ...")
break
end
i += 1
print(io, line)
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 above ", 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
# 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
file = fixup_stdlib_path(file)
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
# 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 === :var""
# 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
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)
# handler for displaying a hint in case the user tries to call setindex! on
# something that doesn't support it:
# - a number (probably attempting to use wrong indexing)
# eg: a = [1 2; 3 4]; a[1][2] = 5
# - a type (probably tried to initialize without parentheses)
# eg: d = Dict; d["key"] = 2
function nonsetable_type_hint_handler(io, ex, arg_types, kwargs)
@nospecialize
if ex.f == setindex!
T = arg_types[1]
if T <: Number
print(io, "\nAre you trying to index into an array? For multi-dimensional arrays, separate the indices with commas: ")
printstyled(io, "a[1, 2]", color=:cyan)
print(io, " rather than a[1][2]")
else isType(T)
Tx = T.parameters[1]
print(io, "\nYou attempted to index the type $Tx, rather than an instance of the type. Make sure you create the type using its constructor: ")
printstyled(io, "d = $Tx([...])", color=:cyan)
print(io, " rather than d = $Tx")
end
end
end
Experimental.register_error_hint(nonsetable_type_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)
# Display a hint in case the user tries to use the min or max function on an iterable
# or tries to use something like `collect` on an iterator without defining either IteratorSize or length
function methods_on_iterable(io, ex, arg_types, kwargs)
@nospecialize
f = ex.f
if (f === max || f === min) && length(arg_types) == 1 && Base.isiterable(only(arg_types))
f_correct = f === max ? "maximum" : "minimum"
print(io, "\nFinding the $f_correct of an iterable is performed with `$f_correct`.")
end
if (f === Base.length || f === Base.size) && length(arg_types) >= 1
arg_type_tuple = Tuple{arg_types...}
if hasmethod(iterate, arg_type_tuple)
iterkind = IteratorSize(arg_types[1])
if iterkind isa HasLength
print(io, "\nYou may need to implement the `length` method or define `IteratorSize` for this type to be `SizeUnknown`.")
elseif iterkind isa HasShape
print(io, "\nYou may need to implement the `length` and `size` methods for `IteratorSize` `HasShape`.")
end
end
end
nothing
end
Experimental.register_error_hint(methods_on_iterable, 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)