diff --git a/base/errorshow.jl b/base/errorshow.jl index d611d1132c2e7d..84af9cf9e914a9 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -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] diff --git a/base/methodshow.jl b/base/methodshow.jl index 6f9c46f929100c..2e12f368c5e482 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -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] @@ -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 diff --git a/base/show.jl b/base/show.jl index 77adf0454894f4..623c3ff7c9efac 100644 --- a/base/show.jl +++ b/base/show.jl @@ -529,11 +529,70 @@ function show(io::IO, @nospecialize(x::Type)) end end + io = IOContext(io, :unionall_tvs => tvars(io)) + + _unionall_name_tvs(io, x) 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 + +tvars(io::IO) = get(io, :unionall_tvs, IdDict{TypeVar,Int}()) + +isgensym(x::Symbol) = first(String(x)) == '#' +isnamed(io::IO, tv) = get(tvars(io), tv, 0) > 1 +setnamed(io::IO, tv) = setindex!(tvars(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(tvars(io)) && _count_tvs!(tvars(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. @@ -596,14 +655,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) || 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 @@ -1753,7 +1817,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 @@ -1821,12 +1885,6 @@ 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, "(") @@ -1834,24 +1892,38 @@ function show(io::IO, tv::TypeVar) 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 + + (: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 @@ -2007,7 +2079,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) diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index 48bb13b961a6fb..c77c20cfb29549 100644 --- a/stdlib/InteractiveUtils/src/InteractiveUtils.jl +++ b/stdlib/InteractiveUtils/src/InteractiveUtils.jl @@ -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, ", ") diff --git a/test/show.jl b/test/show.jl index ade7cb361f8d65..a41d735480e628 100644 --- a/test/show.jl +++ b/test/show.jl @@ -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)" @@ -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}" @@ -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