From ae52c98750c83a646fda624ca16a82d680751dd6 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Sun, 8 Oct 2017 12:17:00 +0200 Subject: [PATCH] IOContex: introduce the :typeinfo property [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. --- base/arrayshow.jl | 218 +++++++++++++++++++++++++++++--------------- base/grisu/grisu.jl | 11 ++- base/precompile.jl | 6 +- base/replutil.jl | 4 +- base/set.jl | 11 +-- examples/ModInts.jl | 2 +- 6 files changed, 158 insertions(+), 94 deletions(-) diff --git a/base/arrayshow.jl b/base/arrayshow.jl index 601cf11e8be698..f5473d8aa7028d 100644 --- a/base/arrayshow.jl +++ b/base/arrayshow.jl @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/base/grisu/grisu.jl b/base/grisu/grisu.jl index 64942d2775ba0f..2cd64ce234f1d6 100644 --- a/base/grisu/grisu.jl +++ b/base/grisu/grisu.jl @@ -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 diff --git a/base/precompile.jl b/base/precompile.jl index 2ccd8321388289..05f6285491d8cf 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -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}}) @@ -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}}) diff --git a/base/replutil.jl b/base/replutil.jl index 7373367414e355..774697266f1af3 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -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 @@ -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 diff --git a/base/set.jl b/base/set.jl index a50f1fbc187dc6..8d0304f3236530 100644 --- a/base/set.jl +++ b/base/set.jl @@ -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) diff --git a/examples/ModInts.jl b/examples/ModInts.jl index 59fde002d26c52..0354d2e8bd8306 100644 --- a/examples/ModInts.jl +++ b/examples/ModInts.jl @@ -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)