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`) and they are not
referenced from elsewhere in the type hierarchy.

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 30, 2020
1 parent 84dd4dd commit ddd6afb
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 56 deletions.
2 changes: 1 addition & 1 deletion base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2320,7 +2320,7 @@ arguments accepted by varargs methods (see the section on [Varargs Functions](@r
# Examples
```jldoctest
julia> mytupletype = Tuple{AbstractString,Vararg{Int}}
Tuple{AbstractString,Vararg{Int64,N} where N}
Tuple{AbstractString,Vararg{Int64}}
julia> isa(("1",), mytupletype)
true
Expand Down
2 changes: 1 addition & 1 deletion base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,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, html=false)
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 @@ -112,7 +112,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
172 changes: 135 additions & 37 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::TypeVar) = get(_unionall_tvs(io), tv, 0) > 1
setnamed(io::IO, tv::TypeVar) = 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 && (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.lb, x.ub))
_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,12 +666,29 @@ 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.

if !istuple
# find out up until which parameter we need to print typevars
y = x.name.wrapper
n, j = 0, 0
while y isa UnionAll
j += 1
tv = x.parameters[j]
if !isa(tv, TypeVar) || isnamed(io, tv) ||
in(:tv_standalone => tv, io) || !in(:unionall_env => tv, io) ||
# can only elide when typevar constraints are
# the same as defined in the parametric type itself
isa(tv.lb, TypeVar) || isa(tv.ub, TypeVar) ||
!(tv.lb == y.var.lb) || !(tv.ub == y.var.ub)
n = j
end
y = y.body
end
n == 0 && return
end

print(io, '{')
for (i, p) in enumerate(x.parameters)
for (i, p) in enumerate(x.parameters[1:n])
show(io, p)
i < n && print(io, ',')
end
Expand Down Expand Up @@ -1780,7 +1867,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 @@ -1837,37 +1924,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 @@ -2027,19 +2125,19 @@ end

# Types
function dump(io::IOContext, x::DataType, n::Int, indent)
print(io, x)
tvar_io = io
for tparam in x.parameters
# approximately recapture the list of tvar parameterization
# that may be used by the internal fields
if isa(tparam, TypeVar)
tvar_io = IOContext(tvar_io, :tv_standalone => tparam)
end
end
print(tvar_io, x)
if x !== Any
print(io, " <: ", supertype(x))
print(tvar_io, " <: ", supertype(x))
end
if n > 0 && !(x <: Tuple) && !x.abstract
tvar_io::IOContext = io
for tparam in x.parameters
# 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)
end
end
if x.name === NamedTuple_typename && !(x.parameters[1] isa Tuple)
# named tuple type with unknown field names
return
Expand Down
2 changes: 1 addition & 1 deletion doc/src/devdocs/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ julia> Tuple
Tuple
julia> Tuple.parameters
svec(Vararg{Any,N} where N)
svec(Vararg{Any})
```

Unlike other types, tuple types are covariant in their parameters, so this definition permits
Expand Down
4 changes: 2 additions & 2 deletions doc/src/manual/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ of trailing elements:

```jldoctest
julia> mytupletype = Tuple{AbstractString,Vararg{Int}}
Tuple{AbstractString,Vararg{Int64,N} where N}
Tuple{AbstractString,Vararg{Int64}}
julia> isa(("1",), mytupletype)
true
Expand Down Expand Up @@ -1089,7 +1089,7 @@ consider the two types created by the following declarations:

```jldoctest
julia> const T1 = Array{Array{T,1} where T, 1}
Array{Array{T,1} where T,1}
Array{Array{<:Any,1},1}
julia> const T2 = Array{Array{T,1}, 1} where T
Array{Array{T,1},1} where T
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
2 changes: 1 addition & 1 deletion stdlib/InteractiveUtils/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ end
Base.getindex(A::Stable, i) = A.A[i]
Base.getindex(A::Unstable, i) = A.A[i]

tag = "ARRAY{FLOAT64,N}"
tag = "ARRAY{FLOAT64}"
@test warntype_hastag(getindex, Tuple{Unstable{Float64},Int}, tag)
@test !warntype_hastag(getindex, Tuple{Stable{Float64,2},Int}, tag)
@test warntype_hastag(getindex, Tuple{Stable{Float64},Int}, tag)
Expand Down
2 changes: 1 addition & 1 deletion test/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ let err_str,
@test startswith(sprint(show, which(FunctionLike(), Tuple{})),
"(::$(curmod_prefix)FunctionLike)() in $curmod_str at $sp:$(method_defs_lineno + 7)")
@test startswith(sprint(show, which(StructWithUnionAllMethodDefs{<:Integer}, (Any,))),
"($(curmod_prefix)StructWithUnionAllMethodDefs{T} where T<:Integer)(x)")
"($(curmod_prefix)StructWithUnionAllMethodDefs{<:Integer})(x)")
@test repr("text/plain", FunctionLike()) == "(::$(curmod_prefix)FunctionLike) (generic function with 1 method)"
@test repr("text/plain", Core.arraysize) == "arraysize (built-in function)"

Expand Down
2 changes: 1 addition & 1 deletion test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ let ex = :(a + b)
end
foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N} = nothing
@test startswith(string(first(methods(foo13825))),
"foo13825(::Array{T,N}, ::Array, ::Array{T,1} where T)")
"foo13825(::Array{T,N}, ::Array, ::Array{<:Any,1})")

mutable struct TLayout
x::Int8
Expand Down
Loading

0 comments on commit ddd6afb

Please sign in to comment.