Skip to content

Commit

Permalink
make printing of UnionAll typevars shorter
Browse files Browse the repository at this point in the history
fixes JuliaLang#34887

typevars in unionall type arguments can now be printed inlined if their
type bounds allow to do so (e.g. `<:UB` or `>:LB`).
typevars with named symbols are printed verbosely as before unless
`:compact => true` is set in the IOContext.

Before:

  julia> Ref{<:Any}
  Ref{var"#s2"} where var"#s2"
  julia> Ref{<:Ref{<:Ref}}
  Ref{var"#s1"} where var"#s1"<:(Ref{var"#s2"} where var"#s2"<:Ref)

After:

  julia> Ref{<:Any}
  Ref
  julia> Ref{<:Ref{<:Ref}}
  Ref{<:Ref{<:Ref}}
  • Loading branch information
stev47 committed May 14, 2020
1 parent a1f6448 commit f5998d0
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 34 deletions.
2 changes: 1 addition & 1 deletion base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()
sig0 = method.sig
while isa(sig0, UnionAll)
push!(tv, sig0.var)
iob = IOContext(iob, :unionall_env => sig0.var)
iob = IOContext(iob, :tv_standalone => sig0.var)
sig0 = sig0.body
end
s1 = sig0.parameters[1]
Expand Down
4 changes: 2 additions & 2 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function arg_decl_parts(m::Method)
if length(argnames) >= m.nargs
show_env = ImmutableDict{Symbol, Any}()
for t in tv
show_env = ImmutableDict(show_env, :unionall_env => t)
show_env = ImmutableDict(show_env, :tv_standalone => t)
end
decls = Tuple{String,String}[argtype_decl(show_env, argnames[i], sig, i, m.nargs, m.isva)
for i = 1:m.nargs]
Expand Down Expand Up @@ -110,7 +110,7 @@ function show_method_params(io::IO, tv)
end
x = tv[i]
show(io, x)
io = IOContext(io, :unionall_env => x)
io = IOContext(io, :tv_standalone => x)
end
print(io, "}")
end
Expand Down
144 changes: 115 additions & 29 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,81 @@ function show(io::IO, @nospecialize(x::Type))
end
end

# The `:unionall_tvs` io context records typevar naming data, i.e. whether
# the typevar needs a corresponding `where` being printed or not.
# It is being filled recursively here first before printing the typevar
# itself.
io = IOContext(io, :unionall_tvs => _unionall_tvs(io))
_unionall_name_tvs(io, x)

# The `:unionall_env => tv::TypeVar` io context is used to signify that
# io is happening within the unionall environment of `tv`.
show(IOContext(io, :unionall_env => x.var), x.body)
print(io, " where ")
show(io, x.var)

if isnamed(io, x.var)
print(io, " where ")
show(io, x.var)
end
end

"""
isgensym(x::Symbol)
Return whether `x` is an generated symbol, as generated by e.g.
[`gensym`](@ref).
"""
isgensym(x::Symbol) = first(String(x)) == '#'

_unionall_tvs(io::IO) = get(io, :unionall_tvs, IdDict{TypeVar,Int}())
isnamed(io::IO, tv) = get(_unionall_tvs(io), tv, 0) > 1
setnamed(io::IO, tv) = setindex!(_unionall_tvs(io), 2, tv)

function _unionall_name_tvs(io::IO, @nospecialize x::UnionAll)
# collect typevar naming candidates
tvs = TypeVar[]
y = x
while y isa UnionAll
# if it is named then this method was called already on a parent
isnamed(io, y.var) && return io
push!(tvs, y.var)
y = y.body
end

if y isa DataType
# count typevar occurrences within the body recursively
isempty(_unionall_tvs(io)) && _count_tvs!(_unionall_tvs(io), y)

for tv in reverse(collect(y.parameters))
tv isa TypeVar || continue
# might have been named above or earlier in the printing hierarchy
isnamed(io, tv) && continue

if last(tvs) == tv && (isgensym(tv.name) || (:compact => true) in io) &&
(tv.lb == Bottom || tv.ub == Any)
# safe for elision
pop!(tvs)
isempty(tvs) && return io
else
setnamed(io, tv)
end
end
end

# remaining ones need to be named, since anonymizing further nested
# occurences is disallowed, includes e.g. y::Union
foreach(tv -> isnamed(io, tv) || setnamed(io, tv), tvs)
end

@nospecialize
_count_tvs!(counts) = counts
_count_tvs!(counts, x) = counts
_count_tvs!(counts, x::TypeVar) = setindex!(counts, get(counts, x, 0) + 1, x)
_count_tvs!(counts, x::UnionAll) = _count_tvs!(counts, x.var.lb, x.var.ub, x.body)
_count_tvs!(counts, x::Union) = _count_tvs!(counts, x.a, x.b)
_count_tvs!(counts, x::DataType) = _count_tvs!(counts, x.parameters...)
_count_tvs!(counts, x1, x2, x...) = (_count_tvs!(counts, x1); _count_tvs!(counts, x2, x...))
@specialize

# Check whether 'sym' (defined in module 'parent') is visible from module 'from'
# If an object with this name exists in 'from', we need to check that it's the same binding
# and that it's not deprecated.
Expand Down Expand Up @@ -596,14 +666,19 @@ function show_datatype(io::IO, x::DataType)
print(io, "NTuple{", n, ',', x.parameters[1], "}")
else
show_type_name(io, x.name)
# Do not print the type parameters for the primary type if we are
# printing a method signature or type parameter.
# Always print the type parameter if we are printing the type directly
# since this information is still useful.

# find the first type parameter that can not be elided when
# printing
j = istuple ? n : findlast(x.parameters) do tv
return !isa(tv, TypeVar) || !isgensym(tv.name) || isnamed(io, tv) ||
in(:tv_standalone => tv, io) || tv.lb != Bottom || tv.ub != Any
end
isnothing(j) && return

print(io, '{')
for (i, p) in enumerate(x.parameters)
for (i, p) in enumerate(x.parameters[1:j])
show(io, p)
i < n && print(io, ',')
i < j && print(io, ',')
end
print(io, '}')
end
Expand Down Expand Up @@ -1753,7 +1828,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type, demangle=false, kwa
env_io = io
while isa(sig, UnionAll)
push!(tv, sig.var)
env_io = IOContext(env_io, :unionall_env => sig.var)
env_io = IOContext(env_io, :tv_standalone => sig.var)
sig = sig.body
end
sig = sig.parameters
Expand Down Expand Up @@ -1821,37 +1896,48 @@ function ismodulecall(ex::Expr)
end

function show(io::IO, tv::TypeVar)
# If we are in the `unionall_env`, the type-variable is bound
# and the type constraints are already printed.
# We don't need to print it again.
# Otherwise, the lower bound should be printed if it is not `Bottom`
# and the upper bound should be printed if it is not `Any`.
in_env = (:unionall_env => tv) in io
function show_bound(io::IO, @nospecialize(b))
parens = isa(b,UnionAll) && !print_without_params(b)
parens && print(io, "(")
show(io, b)
parens && print(io, ")")
end
lb, ub = tv.lb, tv.ub
if !in_env && lb !== Bottom
if ub === Any
show_unquoted(io, tv.name)
print(io, ">:")
show_bound(io, lb)
else

# the `:tv_standalone => tv::TypeVar` io context tells to print the typevar
# `tv` without any typebounds, this is not necessarily related to being
# within a unionall environment
(:tv_standalone => tv) in io && return show_unquoted(io, tv.name)

if (:unionall_env => tv) in io
# within unionall type argument for `tv`
if isnamed(io, tv)
# named typevars get their constraints printed after `where`
return show_unquoted(io, tv.name)
elseif lb == Bottom
# anonymous upper bound, need to show `<:Any` even if trivial
return print(io, "<:", ub)
elseif ub == Any
# anonymous non-trivial lower bound
return print(io, ">:", lb)
end
@assert false
elseif lb != Bottom && ub == Any
show_unquoted(io, tv.name)
print(io, ">:")
return show_bound(io, lb)
else
if lb != Bottom
show_bound(io, lb)
print(io, "<:")
show_unquoted(io, tv.name)
end
else
show_unquoted(io, tv.name)
if ub != Any
print(io, "<:")
show_bound(io, ub)
end
end
if !in_env && ub !== Any
print(io, "<:")
show_bound(io, ub)
end
nothing
return nothing
end

module IRShow
Expand Down Expand Up @@ -2007,7 +2093,7 @@ function dump(io::IOContext, x::DataType, n::Int, indent)
# approximately recapture the list of tvar parameterization
# that may be used by the internal fields
if isa(tparam, TypeVar)
tvar_io = IOContext(tvar_io, :unionall_env => tparam)
tvar_io = IOContext(tvar_io, :tv_standalone => tparam)
end
end
if x.name === NamedTuple_typename && !(x.parameters[1] isa Tuple)
Expand Down
2 changes: 1 addition & 1 deletion stdlib/InteractiveUtils/src/InteractiveUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ function dumpsubtypes(io::IO, x::DataType, m::Module, n::Int, indent)
tp = t
while true
show(tvar_io, tp.var)
tvar_io = IOContext(tvar_io, :unionall_env => tp.var)
tvar_io = IOContext(tvar_io, :tv_standalone => tp.var)
tp = tp.body
if isa(tp, UnionAll)
print(io, ", ")
Expand Down
44 changes: 43 additions & 1 deletion test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,8 @@ function + end
function == end
end

is_juliarepr(x) = eval(Meta.parse(repr(x))) == x

@testset "Parseable printing of types" begin
@test repr(typeof(print)) == "typeof(print)"
@test repr(typeof(Base.show_default)) == "typeof(Base.show_default)"
Expand All @@ -1498,6 +1500,46 @@ let x = TypeVar(:_), y = TypeVar(:_)
@test repr(UnionAll(z, UnionAll(x, UnionAll(y, Tuple{x,y,z})))) == "Tuple{a1,a2,a} where a2 where a1 where a"
end

@testset "unionall types" begin
X = TypeVar(gensym())
Y = TypeVar(gensym(), Ref, Ref)
x, y, z = TypeVar(:a), TypeVar(:a), TypeVar(:a)

# named typevars
@test is_juliarepr(Ref{A} where A)
@test is_juliarepr(Ref{A} where A>:Ref)
@test is_juliarepr(Ref{A} where A<:Ref)
@test is_juliarepr(Ref{A} where Ref<:A<:Ref)

# typearg order
@test is_juliarepr(UnionAll(X, Pair{X,<:Any}))
@test is_juliarepr(UnionAll(X, Pair{<:Any,X}))

# duplicates
@test is_juliarepr(UnionAll(X, Pair{X,X}))

# nesting
@test is_juliarepr(UnionAll(X, Ref{Ref{X}}))
@test is_juliarepr(Union{T, Int} where T)

# renumbered typevars with same names
@test is_juliarepr(UnionAll(z, UnionAll(x, UnionAll(y, Tuple{x,y,z}))))

# shortened typevar printing
@test repr(Ref{<:Any}) == "Ref"
@test repr(Pair{1,<:Any}) == "Pair{1}"
@test repr(Ref{<:Ref}) == "Ref{<:Ref}"
@test repr(Ref{>:Ref}) == "Ref{>:Ref}"
@test repr(Pair{<:Any,1}) == "Pair{<:Any,1}"
yname = sprint(Base.show_unquoted, Y.name)
@test repr(UnionAll(Y, Ref{Y})) == "Ref{$yname} where Ref<:$yname<:Ref"

# exception for tuples
@test is_juliarepr(Tuple)
@test is_juliarepr(Tuple{})
@test is_juliarepr(Tuple{<:Any})
end

@testset "showarg" begin
A = reshape(Vector(Int16(1):Int16(2*3*5)), 2, 3, 5)
@test summary(A) == "2×3×5 Array{Int16,3}"
Expand Down Expand Up @@ -1834,7 +1876,7 @@ end

@testset """printing "Any" is not skipped with nested arrays""" begin
@test replstr(Union{X28004,Vector}[X28004(Any[X28004(1)])], :compact => true) ==
"1-element Array{Union{X28004, Array{T,1} where T},1}:\n X(Any[X(1)])"
"1-element Array{Union{X28004, Array{<:Any,1}},1}:\n X(Any[X(1)])"
end

# Issue 25589 - Underlines in cmd printing
Expand Down

0 comments on commit f5998d0

Please sign in to comment.