Skip to content

Improve performance of depth-limited type printing #58240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,10 @@ function showerror(io::IO, ex::MethodError)
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_signature_function(iob, type_limited_string_from_context(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)
# str = type_limited_string_from_context(io, str)
print(io, str)
end
# catch the two common cases of element-wise addition and subtraction
Expand Down
237 changes: 157 additions & 80 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -696,76 +696,14 @@ function show_can_elide(p::TypeVar, wheres::Vector, elide::Int, env::SimpleVecto
end

function show_typeparams(io::IO, env::SimpleVector, orig::SimpleVector, wheres::Vector)
n = length(env)
elide = length(wheres)
function egal_var(p::TypeVar, @nospecialize o)
return o isa TypeVar &&
ccall(:jl_types_egal, Cint, (Any, Any), p.ub, o.ub) != 0 &&
ccall(:jl_types_egal, Cint, (Any, Any), p.lb, o.lb) != 0
end
for i = n:-1:1
p = env[i]
if p isa TypeVar
if i == n && egal_var(p, orig[i]) && show_can_elide(p, wheres, elide, env, i)
n -= 1
elide -= 1
elseif p.lb === Union{} && isgensym(p.name) && show_can_elide(p, wheres, elide, env, i)
elide -= 1
elseif p.ub === Any && isgensym(p.name) && show_can_elide(p, wheres, elide, env, i)
elide -= 1
end
end
end
if n > 0
print(io, "{")
for i = 1:n
p = env[i]
if p isa TypeVar
if p.lb === Union{} && something(findfirst(@nospecialize(w) -> w === p, wheres), 0) > elide
print(io, "<:")
show(io, p.ub)
elseif p.ub === Any && something(findfirst(@nospecialize(w) -> w === p, wheres), 0) > elide
print(io, ">:")
show(io, p.lb)
else
show(io, p)
end
else
show(io, p)
end
i < n && print(io, ", ")
end
print(io, "}")
end
resize!(wheres, elide)
show(io, string_typeparams(io, env, orig, wheres)
nothing
end

function show_typealias(io::IO, name::GlobalRef, x::Type, env::SimpleVector, wheres::Vector)
if !(get(io, :compact, false)::Bool)
# Print module prefix unless alias is visible from module passed to
# IOContext. If :module is not set, default to Main.
# nothing can be used to force printing prefix.
from = get(io, :module, Main)
if (from === nothing || !isvisible(name.name, name.mod, from))
show(io, name.mod)
print(io, ".")
end
end
print(io, name.name)
isempty(env) && return
io = IOContext(io)
for p in wheres
io = IOContext(io, :unionall_env => p)
end
orig = getfield(name.mod, name.name)
vars = TypeVar[]
while orig isa UnionAll
push!(vars, orig.var)
orig = orig.body
end
show_typeparams(io, env, Core.svec(vars...), wheres)
nothing
(s, _) = string_type_alias_and_params(io, name, x, env, wheres)
show(io, s)
return nothing
end

function make_wheres(io::IO, env::SimpleVector, @nospecialize(x::Type))
Expand Down Expand Up @@ -2568,21 +2506,21 @@ function show_signature_function(io::IO, @nospecialize(ft), demangle=false, farg
if ft <: Function && isa(uw, DataType) && isempty(uw.parameters) && _isself(uw)
uwmod = parentmodule(uw)
if qualified && !isexported(uwmod, uw.name.mt.name) && uwmod !== Main
print_within_stacktrace(io, uwmod, '.', bold=true)
print_within_stacktrace(io, string_type_depth_limited(io, uwmod), '.', bold=true)
end
s = sprint(show_sym, (demangle ? demangle_function_name : identity)(uw.name.mt.name), context=io)
print_within_stacktrace(io, s, bold=true)
elseif isType(ft) && (f = ft.parameters[1]; !isa(f, TypeVar))
uwf = unwrap_unionall(f)
parens = isa(f, UnionAll) && !(isa(uwf, DataType) && f === uwf.name.wrapper)
parens && print(io, "(")
print_within_stacktrace(io, f, bold=true)
print_within_stacktrace(io, string_type_depth_limited(io, f), bold=true)
parens && print(io, ")")
else
if html
print(io, "($fargname::<b>", ft, "</b>)")
print(io, "($fargname::<b>", string_type_depth_limited(io, ft), "</b>)")
else
print_within_stacktrace(io, "($fargname::", ft, ")", bold=true)
print_within_stacktrace(io, "($fargname::", string_type_depth_limited(io, ft), ")", bold=true)
end
end
nothing
Expand Down Expand Up @@ -2629,46 +2567,185 @@ function show_tuple_as_call(out::IO, name::Symbol, sig::Type;
print_within_stacktrace(io, argnames[i]; color=:light_black)
end
print(io, "::")
print_type_bicolor(env_io, sig[i]; use_color = get(io, :backtrace, false)::Bool)
print_type_bicolor(env_io, string_type_depth_limited(io, sig[i]); use_color = get(io, :backtrace, false)::Bool)
end
if kwargs !== nothing
print(io, "; ")
first = true
for (k, t) in kwargs
first || print(io, ", ")
first = false
print_within_stacktrace(io, k; color=:light_black)
print_within_stacktrace(io, string_type_depth_limited(io, k); color=:light_black)
if t == pairs(NamedTuple)
# omit type annotation for splat keyword argument
print(io, "...")
else
print(io, "::")
print_type_bicolor(io, t; use_color = get(io, :backtrace, false)::Bool)
print_type_bicolor(io, string_type_depth_limited(io, t); use_color = get(io, :backtrace, false)::Bool)
end
end
end
print_within_stacktrace(io, ")", bold=true)
show_method_params(io, tv)
str = String(take!(buf))
str = type_limited_string_from_context(out, str)
print(out, str)
nothing
end

function type_limited_string_from_context(out::IO, str::String)
function type_limited_string_from_context(out::IO, @nospecialize(T))
typelimitflag = get(out, :stacktrace_types_limited, nothing)
if typelimitflag isa RefValue{Bool}
sz = get(out, :displaysize, Base.displaysize_(out))::Tuple{Int, Int}
str_lim = type_depth_limit(str, max(sz[2], 120))
if sizeof(str_lim) < sizeof(str)
typelimitflag[] = true
end
# str_lim = type_depth_limit(str, max(sz[2], 120))
str_lim = string_type_depth_limited(T, max(sz[2], 120))
# if sizeof(str_lim) < sizeof(str) # this would be slow, do we need this?
# typelimitflag[] = true
# end
str = str_lim
end
return str
return string(T)
end

# limit nesting depth of `{ }` until string textwidth is less than `n`

function _max_display_size(io::IO)
sz = get(io, :displaysize, displaysize(io))::Tuple{Int, Int}
return max(sz[2], 120)
end

# string_type_depth_limited(@nospecialize(T), _width = nothing; maxdepth = nothing)::String =
# string_type_depth_limited(stdout, _width, T; maxdepth)

function string_type_depth_limited(io::IO, _width, @nospecialize(T); maxdepth = nothing)::String
str_lim = _string_type_depth_limited(io, T, maxdepth)
width = isnothing(_width) ? _max_display_size(io) : _width
return Base.type_depth_limit(str_lim, width; maxdepth)
end

function string_typeparams(io::IO, env::Core.SimpleVector, orig::Core.SimpleVector, wheres::Vector)
n = length(env)
elide = length(wheres)
function egal_var(p::TypeVar, @nospecialize o)
return o isa TypeVar &&
ccall(:jl_types_egal, Cint, (Any, Any), p.ub, o.ub) != 0 &&
ccall(:jl_types_egal, Cint, (Any, Any), p.lb, o.lb) != 0
end
for i = n:-1:1
p = env[i]
if p isa TypeVar
if i == n && egal_var(p, orig[i]) && Base.show_can_elide(p, wheres, elide, env, i)
n -= 1
elide -= 1
elseif p.lb === Union{} && isgensym(p.name) && Base.show_can_elide(p, wheres, elide, env, i)
elide -= 1
elseif p.ub === Any && isgensym(p.name) && Base.show_can_elide(p, wheres, elide, env, i)
elide -= 1
end
end
end
s_result = if n > 0
params_string = join(
map(1:n) do i
p = env[i]
suffix = i < n ? ", " : ""
if p isa TypeVar
if p.lb === Union{} && something(findfirst(@nospecialize(w) -> w === p, wheres), 0) > elide
string("<:", p.ub, suffix)
elseif p.ub === Any && something(findfirst(@nospecialize(w) -> w === p, wheres), 0) > elide
string(">:", p.lb, suffix)
else
string(p, suffix)
end
else
string(p, suffix)
end
end
)
string("{", params_string, "}")
else
""
end
resize!(wheres, elide)
return s_result
end

function string_type_alias_and_params(io::IO, name::Core.GlobalRef, x::Type, env::Core.SimpleVector, wheres::Vector)
alias = if !(get(io, :compact, false)::Bool)
# Print module prefix unless alias is visible from module passed to
# IOContext. If :module is not set, default to Main.
# nothing can be used to force printing prefix.
from = get(io, :module, Main)
if (from === nothing || !Base.isvisible(name.name, name.mod, from))
string(name.mod, ".", name.name)
else
string(name.name)
end
else
string(name.name)
end
if isempty(env)
return (alias, ())
end
io = IOContext(io)
for p in wheres
io = IOContext(io, :unionall_env => p)
end
orig = getfield(name.mod, name.name)
vars = TypeVar[]
while orig isa UnionAll
push!(vars, orig.var)
orig = orig.body
end
return string(alias, string_typeparams(io, env, Core.svec(vars...), wheres))
end

function _string_type_depth_limited(io::IO, @nospecialize(T), maxdepth, depth = 0)::String
maxdepth === nothing && return string(T)
param_length(T) = hasproperty(T, :parameters) ? length(T.parameters) : length(T)
if hasproperty(T, :name)
properT = Base.makeproper(io, T)
alias = Base.make_typealias(properT)

if alias === nothing
s = T.name.wrapper
if depth > maxdepth
return ""
elseif depth == maxdepth
return "$s{…}"
elseif depth + 1 == maxdepth
param_length(T) == 0 && return string(s)
if all(x->param_length(x)==1, T.parameters)
ps = map(T.parameters) do p
_string_type_depth_limited(io, p, maxdepth, depth+1)
end
return "$s{$(join(ps, ", "))}"
else
return "$s{…}"
end
else
param_length(T) == 0 && return string(s)
ps = map(T.parameters) do p
_string_type_depth_limited(io, p, maxdepth, depth+1)
end
return "$s{$(join(ps, ", "))}"
end
else
wheres = Base.make_wheres(io, alias[2], T)
return string_type_alias_and_params(io, alias[1], T, alias[2], wheres)
end
elseif T isa UnionAll
return string(T)
else
if depth > maxdepth
return ""
elseif depth == maxdepth
return "…"
else
return string(T)
end
end
end

function type_depth_limit(str::String, n::Int; maxdepth = nothing)
depth = 0
width_at = Int[] # total textwidth at each nesting depth
Expand Down
62 changes: 38 additions & 24 deletions test/stacktraces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -209,32 +209,32 @@ end
struct F49231{a,b,c,d,e,f,g} end
(::F49231)(a,b,c) = error("oops")

@testset "type_depth_limit" begin
@testset "string_type_depth_limited" begin
tdl = Base.type_depth_limit

str = repr(typeof(view([1, 2, 3], 1:2)))
@test tdl(str, 0, maxdepth = 1) == "SubArray{…}"
@test tdl(str, 0, maxdepth = 2) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(str, 0, maxdepth = 3) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{…}}, true}"
@test tdl(str, 0, maxdepth = 4) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{$Int}}, true}"
@test tdl(str, 3) == "SubArray{…}"
@test tdl(str, 44) == "SubArray{…}"
@test tdl(str, 45) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(str, 59) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(str, 60) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{…}}, true}"
@test tdl(str, 100) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{$Int}}, true}"

str = repr(Vector{V} where V<:AbstractVector{T} where T<:Real)
@test tdl(str, 0, maxdepth = 1) == "Vector{…} where {…}"
@test tdl(str, 0, maxdepth = 2) == "Vector{V} where {T<:Real, V<:AbstractVector{…}}"
@test tdl(str, 0, maxdepth = 3) == "Vector{V} where {T<:Real, V<:AbstractVector{T}}"
@test tdl(str, 20) == "Vector{…} where {…}"
@test tdl(str, 46) == "Vector{…} where {…}"
@test tdl(str, 47) == "Vector{V} where {T<:Real, V<:AbstractVector{T}}"

str = "F49231{Vector,Val{('}','}')},Vector{Vector{Vector{Vector}}},Tuple{Int,Int,Int,Int,Int,Int,Int},Int,Int,Int}"
@test tdl(str, 105) == "F49231{Vector,Val{('}','}')},Vector{Vector{Vector{…}}},Tuple{Int,Int,Int,Int,Int,Int,Int},Int,Int,Int}"
@test tdl(str, 85) == "F49231{Vector,Val{…},Vector{…},Tuple{…},Int,Int,Int}"
typ = typeof(view([1, 2, 3], 1:2))
@test tdl(typ, 0, maxdepth = 1) == "SubArray{…}"
@test tdl(typ, 0, maxdepth = 2) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(typ, 0, maxdepth = 3) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{…}}, true}"
@test tdl(typ, 0, maxdepth = 4) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{$Int}}, true}"
@test tdl(typ, 3) == "SubArray{…}"
@test tdl(typ, 44) == "SubArray{…}"
@test tdl(typ, 45) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(typ, 59) == "SubArray{$Int, 1, Vector{…}, Tuple{…}, true}"
@test tdl(typ, 60) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{…}}, true}"
@test tdl(typ, 100) == "SubArray{$Int, 1, Vector{$Int}, Tuple{UnitRange{$Int}}, true}"

typ = Vector{V} where V<:AbstractVector{T} where T<:Real
@test tdl(typ, 0, maxdepth = 1) == "Vector{…} where {…}"
@test tdl(typ, 0, maxdepth = 2) == "Vector{V} where {T<:Real, V<:AbstractVector{…}}"
@test tdl(typ, 0, maxdepth = 3) == "Vector{V} where {T<:Real, V<:AbstractVector{T}}"
@test tdl(typ, 20) == "Vector{…} where {…}"
@test tdl(typ, 46) == "Vector{…} where {…}"
@test tdl(typ, 47) == "Vector{V} where {T<:Real, V<:AbstractVector{T}}"

typ = F49231{Vector,Val{('}','}')},Vector{Vector{Vector{Vector}}},Tuple{Int,Int,Int,Int,Int,Int,Int},Int,Int,Int}
@test tdl(typ, 105) == "F49231{Vector,Val{('}','}')},Vector{Vector{Vector{…}}},Tuple{Int,Int,Int,Int,Int,Int,Int},Int,Int,Int}"
@test tdl(typ, 85) == "F49231{Vector,Val{…},Vector{…},Tuple{…},Int,Int,Int}"

# Stacktrace
a = UInt8(81):UInt8(160)
Expand All @@ -259,6 +259,20 @@ struct F49231{a,b,c,d,e,f,g} end
@test contains(str, "[2] \e[0m\e[1m(::$F49231{Vector, Val{…}, Vector{…}, NTuple{…}, $Int, $Int, $Int})\e[22m\e[0m\e[1m(\e[22m\e[90ma\e[39m::\e[0m$Int, \e[90mb\e[39m::\e[0m$Int, \e[90mc\e[39m::\e[0m$Int\e[0m\e[1m)\e[22m\n\e[90m")
end

Base.@kwdef struct F55952{A,B}
num::Int = 1
end

@testset "Depth-limited type printing performance for highly nested types" begin
nest_val(na, nb, ::Val{1}) = F55952{na, nb}()
nest_val(na, nb, ::Val{n}) where {n} = nest_val(F55952{na, nb}, F55952{na, nb}, Val(n-1))
nest_val(na, nb, n::Int) = nest_val(na, nb, Val(n))
nest_val(n) = nest_val(1, 1, n)
# be careful with changing to a larger number
# ~10 seconds before #55952 is fixed:
@test 1 > @elapsed sprint(show, typeof(nest_val(23)))
end

@testset "Base.StackTraces docstrings" begin
@test isempty(Docs.undocumented_names(StackTraces))
end