Skip to content

Commit

Permalink
IOContext: introduce the :typeinfo property
Browse files Browse the repository at this point in the history
Before, the :compact property was conflating 2 concepts:
1) are we low on screen space?
2) can we skip printing individual type information for elements in a collection?

Now, :compact controls only 1), while :typeinfo controls 2).
Cf. #22981 for context and discussion. Credit to Stefan Karpinski for the
formulation of the design implemented here.
  • Loading branch information
rfourquet committed Dec 7, 2017
1 parent 9b72a2c commit 8a0c710
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 112 deletions.
244 changes: 168 additions & 76 deletions base/arrayshow.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

# methods related to array printing

# Printing a value requires to take into account the :typeinfo property
# from the IO context; this property encodes (as a type) the type information
# that is supposed to have already been displayed concerning this value,
# so that redundancy can be avoided. For example, when printing an array of
# `Float16` values, the header "Float16" will be printed, and the values
# can simply be printed with the decimal representations:
# show(Float16(1)) -> "Float16(1.0)"
# show([Float16(1)]) -> "Float16[1.0]" (instead of "Float16[Float16(1.0)]")
# Similarly:
# show([[Float16(1)]]) -> "Array{Float16}[[1.0]]" (instead of "Array{Float16}[Float16[1.0]]")
#
# The array printing methods here can be grouped into two categories (and are annotated as such):
# 1) "typeinfo aware" : these are "API boundaries" functions, which will read the typeinfo
# property from the context, and pass down to their value an updated property
# according to its eltype; at each layer of nesting, only one "typeinfo aware"
# function must be called;
# 2) "typeinfo agnostic": these are helper functions used by the first category; hence
# they don't manipulate the typeinfo property, and let the printing routines
# for their elements read directly the property set by their callers
#
# Non-annotated functions are even lower level (e.g. print_matrix_row), so they fall
# by default into category 2.
#
# The basic organization of this file is
# 1) printing with `display`
# 2) printing with `show`
# 3) Logic for displaying type information


## printing with `display`

"""
Unexported convenience function used in body of `replace_in_print_matrix`
methods. By default returns a string of the same width as original with a
Expand Down Expand Up @@ -101,6 +136,7 @@ function print_matrix_vdots(io::IO, vdots::AbstractString,
end
end

# typeinfo agnostic
"""
print_matrix(io::IO, mat, pre, sep, post, hdots, vdots, ddots, hmod, vmod)
Expand Down Expand Up @@ -216,8 +252,9 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
end
end

# typeinfo agnostic
# n-dimensional arrays
function show_nd(io::IO, a::AbstractArray, print_matrix, label_slices)
function show_nd(io::IO, a::AbstractArray, print_matrix::Function, label_slices::Bool)
limit::Bool = get(io, :limit, false)
if isempty(a)
return
Expand Down Expand Up @@ -261,15 +298,63 @@ function show_nd(io::IO, a::AbstractArray, print_matrix, label_slices)
end
end

# print_array: main helper functions for _display
# typeinfo agnostic

# 0-dimensional arrays
print_array(io::IO, X::AbstractArray{T,0} where T) =
isassigned(X) ? show(io, X[]) :
print(io, undef_ref_str)

print_array(io::IO, X::AbstractVecOrMat) = print_matrix(io, X)

print_array(io::IO, X::AbstractArray) = show_nd(io, X, print_matrix, true)

# typeinfo aware
# implements: show(io::IO, ::MIME"text/plain", X::AbstractArray)
function _display(io::IO, X::AbstractArray)
# 0) compute new IOContext
if !haskey(io, :compact) && length(indices(X, 2)) > 1
io = IOContext(io, :compact => true)
end
if get(io, :limit, false) && eltype(X) === Method
# override usual show method for Vector{Method}: don't abbreviate long lists
io = IOContext(io, :limit => false)
end
# we assume this function is always called from top-level, i.e. that it's not nested
# within another "show" method; hence we always print the summary, without
# checking for current :typeinfo (this could be changed in the future)
io = IOContext(io, :typeinfo => eltype(X))

# 1) print summary info
summary(io, X)
isempty(X) && return
print(io, ":")
if get(io, :limit, false) && displaysize(io)[1]-4 <= 0
return print(io, "")
else
println(io)
end

# 2) show actual content
print_array(io, X)
end


## printing with `show`

### non-Vector arrays

# _show_nonempty & _show_empty: main helper functions for show(io, X)
# typeinfo agnostic

"""
`print_matrix_repr(io, X)` prints matrix X with opening and closing square brackets.
`_show_nonempty(io, X::AbstractMatrix, prefix)` prints matrix X with opening and closing square brackets,
preceded by `prefix`, supposed to encode the type of the elements.
"""
function print_matrix_repr(io, X::AbstractArray)
function _show_nonempty(io::IO, X::AbstractMatrix, prefix::String)
@assert !isempty(X)
limit = get(io, :limit, false)::Bool
compact, prefix = array_eltype_show_how(X)
if compact && !haskey(io, :compact)
io = IOContext(io, :compact => compact)
end
indr, indc = indices(X,1), indices(X,2)
nr, nc = length(indr), length(indc)
rdots, cdots = false, false
Expand Down Expand Up @@ -310,86 +395,93 @@ function print_matrix_repr(io, X::AbstractArray)
print(io, "]")
end

show(io::IO, X::AbstractArray) = showarray(io, X, true)

repremptyarray(io::IO, X::Array{T}) where {T} = print(io, "Array{$T}(", join(size(X),','), ')')
repremptyarray(io, X) = nothing # by default, we don't know this constructor
_show_nonempty(io::IO, X::AbstractArray, prefix::String) =
show_nd(io, X, (io, slice) -> _show_nonempty(io, slice, prefix), false)

function showarray(io::IO, X::AbstractArray, repr::Bool = true; header = true)
if repr && ndims(X) == 1
return show_vector(io, X, "[", "]")
end
if !haskey(io, :compact) && length(indices(X, 2)) > 1
io = IOContext(io, :compact => true)
end
if !repr && get(io, :limit, false) && eltype(X) === Method
# override usual show method for Vector{Method}: don't abbreviate long lists
io = IOContext(io, :limit => false)
end
(!repr && header) && summary(io, X)
if !isempty(X)
if !repr && header
print(io, ":")
if get(io, :limit, false) && displaysize(io)[1]-4 <= 0
return print(io, "")
else
println(io)
end
end
if ndims(X) == 0
if isassigned(X)
return show(io, X[])
else
return print(io, undef_ref_str)
end
end
if repr
if ndims(X) <= 2
print_matrix_repr(io, X)
else
show_nd(io, X, print_matrix_repr, false)
end
else
punct = (" ", " ", "")
if ndims(X) <= 2
print_matrix(io, X, punct...)
else
show_nd(io, X,
(io, slice) -> print_matrix(io, slice, punct...),
!repr)
end
end
elseif repr
repremptyarray(io, X)
end
end
# a specific call path is used to show vectors (show_vector)
_show_nonempty(::IO, ::AbstractVector, ::String) =
error("_show_nonempty(::IO, ::AbstractVector, ::String) is not implemented")

# returns compact, prefix
function array_eltype_show_how(X)
e = eltype(X)
if print_without_params(e)
str = string(unwrap_unionall(e).name) # Print "Array" rather than "Array{T,N}"
else
str = string(e)
end
# Types hard-coded here are those which are created by default for a given syntax
(_isleaftype(e),
(!isempty(X) && (e===Float64 || e===Int || e===Char || e===String) ? "" : str))
_show_nonempty(io::IO, X::AbstractArray{T,0} where T, prefix::String) = print_array(io, X)

# NOTE: it's not clear how this method could use the :typeinfo attribute
_show_empty(io::IO, X::Array{T}) where {T} = print(io, "Array{$T}(", join(size(X),','), ')')
_show_empty(io, X) = nothing # by default, we don't know this constructor

# typeinfo aware (necessarily)
function show(io::IO, X::AbstractArray)
@assert ndims(X) != 1
prefix = typeinfo_prefix(io, X)
io = IOContext(io, :typeinfo => eltype(X), :compact => true)
isempty(X) ?
_show_empty(io, X) :
_show_nonempty(io, X, prefix)
end

function show_vector(io::IO, v, opn, cls)
compact, prefix = array_eltype_show_how(v)
### Vector arrays

# typeinfo aware
# NOTE: v is not constrained to be a vector, as this function can work with iterables
# in general (it's used e.g. by show(::IO, ::Set))
function show_vector(io::IO, v, opn='[', cls=']')
print(io, typeinfo_prefix(io, v))
# directly or indirectly, the context now knows about eltype(v)
io = IOContext(io, :typeinfo => eltype(v), :compact => true)
limited = get(io, :limit, false)
if compact && !haskey(io, :compact)
io = IOContext(io, :compact => compact)
end
print(io, prefix)
if limited && _length(v) > 20
inds = indices1(v)
show_delim_array(io, v, opn, ",", "", false, inds[1], inds[1]+9)
print(io, " \u2026 ")
print(io, " ")
show_delim_array(io, v, "", ",", cls, false, inds[end-9], inds[end])
else
show_delim_array(io, v, opn, ",", cls, false)
end
end

show(io::IO, X::AbstractVector) = show_vector(io, X)


## Logic for displaying type information

# given type `typeinfo` extracted from context, assuming a collection
# is being displayed, deduce the elements type; in spirit this is
# similar to `eltype`, but in some cases this would lead to incomplete
# information: assume we are at the top level, and no typeinfo is set,
# and that it is deduced to be typeinfo=Any by default, and consider
# printing X = Any[1]; to know if the eltype of X is already displayed,
# we would compare eltype(X) to eltype(typeinfo) == Any, and deduce
# that we don't need to print X's eltype because it's already known by
# the context, which is wrong; even if default value of typeinfo is
# not set to Any, then the problem would be similar one layer below
# when printing an array like Any[Any[1]]; hence we must treat Any
# specially
function typeinfo_eltype(typeinfo::Type)::Union{Type,Void}
if typeinfo == Any
# the current context knows nothing about what is being displayed, not even
# whether it's a collection or scalar
nothing
else
# we assume typeinfo refers to a collection-like type, whose
# eltype meaningfully represents what the context knows about
# the eltype of the object currently being displayed
eltype(typeinfo)
end
end

# X not constrained, can be any iterable (cf. show_vector)
function typeinfo_prefix(io::IO, X)
typeinfo = get(io, :typeinfo, Any)::Type
@assert X isa typeinfo "$(typeof(X)) is not a subtype of $typeinfo"
# what the context already knows about the eltype of X:
eltype_ctx = typeinfo_eltype(typeinfo)
eltype_X = eltype(X)
# Types hard-coded here are those which are created by default for a given syntax
if eltype_X == eltype_ctx || !isempty(X) && eltype_X in (Float64, Int, Char, String)
""
elseif print_without_params(eltype_X)
string(unwrap_unionall(eltype_X).name) # Print "Array" rather than "Array{T,N}"
else
string(eltype_X)
end
end
12 changes: 9 additions & 3 deletions base/grisu/grisu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,21 @@ function Base.show(io::IO, x::Union{Float64,Float32})
if get(io, :compact, false)
_show(io, x, PRECISION, 6, x isa Float64, true)
else
_show(io, x, SHORTEST, 0, true, false)
_show(io, x, SHORTEST, 0, get(io, :typeinfo, Any) !== typeof(x), false)
end
end

function Base.show(io::IO, x::Float16)
if get(io, :compact, false)
hastypeinfo = Float16 === get(io, :typeinfo, Any)
# if hastypeinfo, the printing would be more compact using `SHORTEST`
# while still retaining all the information
# BUT: we want to print all digits in `show`, not in display, so we rely
# on the :compact property to make the decision
# (cf. https://github.com/JuliaLang/julia/pull/24651#issuecomment-345535687)
if get(io, :compact, false) && !hastypeinfo
_show(io, x, PRECISION, 5, false, true)
else
_show(io, x, SHORTEST, 0, true, false)
_show(io, x, SHORTEST, 0, !hastypeinfo, false)
end
end

Expand Down
6 changes: 2 additions & 4 deletions base/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,6 @@ precompile(Tuple{typeof(Base.indexed_next), Tuple{Array{Int64, 1}, Void}, Int64,
precompile(Tuple{typeof(Base.setdiff), Array{Int64, 1}, Array{Int64, 1}})
precompile(Tuple{typeof(Base.Multimedia.display), Array{Int64, 1}})
precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}, Int64})
precompile(Tuple{typeof(Base.array_eltype_show_how), Array{Int64, 1}})
precompile(Tuple{typeof(Base.summary), Array{Int64, 1}, Tuple{Base.OneTo{Int64}}})
precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}, Int64, Int64})
precompile(Tuple{typeof(Base.isassigned), Array{Int64, 1}})
Expand All @@ -1608,9 +1607,8 @@ precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal},
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal}, String, String})
precompile(Tuple{typeof(Base.print), Base.IOContext{Base.Terminals.TTYTerminal}, String, String, Char})
precompile(Tuple{typeof(Base.show_vector), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String, String})
precompile(Tuple{typeof(Base.print_matrix_repr), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}})
precompile(Tuple{typeof(Base.show_nd), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, typeof(Base.print_matrix_repr), Bool})
precompile(Tuple{typeof(Base.repremptyarray), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}})
precompile(Tuple{typeof(Base._show_nonemtpy), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String})
precompile(Tuple{typeof(Base._show_empty), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}})
precompile(Tuple{typeof(Base.print_matrix), Base.IOContext{Base.Terminals.TTYTerminal}, Array{Int64, 1}, String, String, String})
precompile(Tuple{typeof(Base.getindex), Base.ImmutableDict{Symbol, Any}, Symbol})
precompile(Tuple{typeof(Base.vcat), Base.OneTo{Int64}})
Expand Down
4 changes: 2 additions & 2 deletions base/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function show(io::IO, ::MIME"text/plain", t::Task)
end
end

show(io::IO, ::MIME"text/plain", X::AbstractArray) = showarray(io, X, false)
show(io::IO, ::MIME"text/plain", X::AbstractArray) = _display(io, X)
show(io::IO, ::MIME"text/plain", r::AbstractRange) = show(io, r) # always use the compact form for printing ranges

# display something useful even for strings containing arbitrary
Expand All @@ -146,7 +146,7 @@ function show(io::IO, ::MIME"text/plain", s::String)
show(io, s)
else
println(io, sizeof(s), "-byte String of invalid UTF-8 data:")
showarray(io, Vector{UInt8}(s), false; header=false)
print_array(io, Vector{UInt8}(s))
end
end

Expand Down
11 changes: 3 additions & 8 deletions base/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,9 @@ similar(s::Set{T}) where {T} = Set{T}()
similar(s::Set, T::Type) = Set{T}()

function show(io::IO, s::Set)
print(io, "Set")
if isempty(s)
print(io, "{", eltype(s), "}()")
return
end
print(io, "(")
show_vector(io, s, "[", "]")
print(io, ")")
print(io, "Set(")
show_vector(io, s)
print(io, ')')
end

isempty(s::Set) = isempty(s.dict)
Expand Down
9 changes: 8 additions & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ The following properties are in common use:
- `:displaysize`: A `Tuple{Int,Int}` giving the size in rows and columns to use for text
output. This can be used to override the display size for called functions, but to
get the size of the screen use the `displaysize` function.
- `:typeinfo`: a `Type` characterizing the information already printed
concerning the type of the object about to be displayed. This is mainly useful when
displaying a collection of objects of the same type, so that redundant type information
can be avoided (e.g. `[Float16(0)]` can be shown as "Float16[0.0]" instead
of "Float16[Float16(0.0)]" : while displaying the elements of the array, the `:typeinfo`
property will be set to `Float16`).
# Examples
```jldoctest
Expand Down Expand Up @@ -133,7 +139,8 @@ function show_default(io::IO, @nospecialize(x))
nb = sizeof(x)
if nf != 0 || nb == 0
if !show_circular(io, x)
recur_io = IOContext(io, Pair{Symbol,Any}(:SHOWN_SET, x))
recur_io = IOContext(io, Pair{Symbol,Any}(:SHOWN_SET, x),
Pair{Symbol,Any}(:typeinfo, Any))
for i in 1:nf
f = fieldname(t, i)
if !isdefined(x, f)
Expand Down
Loading

0 comments on commit 8a0c710

Please sign in to comment.