Skip to content

Commit

Permalink
IOContex: introduce the :typeinfo property
Browse files Browse the repository at this point in the history
[ci skip]

TODO: remove [ci skip] when tests updated, and adjust PR reference below

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?

Cf. # 22981 for context and discussion. Credit to Stefan Karpinski for the
formulation of the design implemented here.
  • Loading branch information
rfourquet committed Nov 20, 2017
1 parent 710e04d commit ae52c98
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 94 deletions.
218 changes: 142 additions & 76 deletions base/arrayshow.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
# 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;
# 2) "typeinfo agnostic": this 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 @@ -58,6 +92,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 @@ -173,8 +208,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 @@ -218,15 +254,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
print(io, summary(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 & _show_empty: main helper function for show(io, X)
# typeinfo agnostic

"""
`print_matrix_repr(io, X)` prints matrix X with opening and closing square brackets.
`_show(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(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 @@ -267,86 +351,68 @@ 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(io::IO, X::AbstractArray, prefix::String) =
show_nd(io, X, (io, slice) -> _show(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) && print(io, summary(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(::IO, ::AbstractVector, ::String) = error("_show(::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(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(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, " \u2026 ") # i.e. " … "
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

# X not constrained, can be any iterable (cf. show_vector)
function typeinfo_prefix(io::IO, X)
typeinfo = get(io, :typeinfo, Any)::Union{DataType,UnionAll}
@assert X isa typeinfo
# what the context already knows about the eltype of X:
# (could use a more specific tool than eltype, maybe with _isleaftype)
eltype_ctx = 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
11 changes: 8 additions & 3 deletions base/grisu/grisu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,20 @@ 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 will be more compact using `SHORTEST`
# 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 @@ -1602,7 +1602,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 @@ -1614,9 +1613,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), 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 @@ -28,14 +28,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
2 changes: 1 addition & 1 deletion examples/ModInts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct ModInt{n} <: Integer
end

Base.show(io::IO, k::ModInt{n}) where {n} =
print(io, get(io, :compact, false) ? k.k : "$(k.k) mod $n")
print(io, Base.typeinfo_context(io) == typeof(k) ? k.k : "$(k.k) mod $n")

(+)(a::ModInt{n}, b::ModInt{n}) where {n} = ModInt{n}(a.k+b.k)
(-)(a::ModInt{n}, b::ModInt{n}) where {n} = ModInt{n}(a.k-b.k)
Expand Down

0 comments on commit ae52c98

Please sign in to comment.