diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index fd8240cba9a32..3ab012ef97bc5 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -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 diff --git a/base/errorshow.jl b/base/errorshow.jl index a4583bc819856..22deef06db09a 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -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] diff --git a/base/methodshow.jl b/base/methodshow.jl index e878660a0305e..3bf54509dc962 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -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] @@ -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 diff --git a/base/show.jl b/base/show.jl index 987a146d9ee5e..3e25e89bec81c 100644 --- a/base/show.jl +++ b/base/show.jl @@ -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. @@ -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 @@ -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 @@ -1837,12 +1924,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, "(") @@ -1850,24 +1931,41 @@ 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 + + # 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 @@ -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 diff --git a/doc/src/devdocs/types.md b/doc/src/devdocs/types.md index d8401258430f2..1ca4e0fdaf6d3 100644 --- a/doc/src/devdocs/types.md +++ b/doc/src/devdocs/types.md @@ -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 diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index c29c85500e130..38964b29c7306 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -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 @@ -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 diff --git a/stdlib/InteractiveUtils/src/InteractiveUtils.jl b/stdlib/InteractiveUtils/src/InteractiveUtils.jl index a2abf8730cd84..0b6bb46718cb1 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/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index 7bbc2b321a440..691e52d5d2fc8 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -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) diff --git a/test/errorshow.jl b/test/errorshow.jl index 04a1176c45ea9..c104c10786bec 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -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)" diff --git a/test/reflection.jl b/test/reflection.jl index 4b77f2e2ac058..54f126338e111 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -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 diff --git a/test/show.jl b/test/show.jl index ade7cb361f8d6..fa67b1787684f 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)" @@ -1490,12 +1492,60 @@ end end let x = TypeVar(:_), y = TypeVar(:_) - @test repr(UnionAll(x, UnionAll(y, Pair{x,y}))) == "Pair{_1,_2} where _2 where _1" - @test repr(UnionAll(x, UnionAll(y, Pair{UnionAll(x,Ref{x}),y}))) == "Pair{Ref{_1} where _1,_1} where _1" + @test repr(UnionAll(x, UnionAll(y, Pair{x,y}))) == "Pair" + @test repr(UnionAll(x, UnionAll(y, Pair{UnionAll(x,Ref{x}),y}))) == "Pair{Ref}" x = TypeVar(:a) y = TypeVar(:a) z = TypeVar(:a) - @test repr(UnionAll(z, UnionAll(x, UnionAll(y, Tuple{x,y,z})))) == "Tuple{a1,a2,a} where a2 where a1 where a" + @test repr(UnionAll(z, UnionAll(x, UnionAll(y, Tuple{x,y,z})))) == "Tuple{<:Any,<:Any,a} where a" +end + +@testset "unionall types" begin + X = TypeVar(gensym()) + Y = TypeVar(gensym(), Ref, Ref) + x, y, z = TypeVar(:a), TypeVar(:a), TypeVar(:a) + struct TestTVUpper{A<:Integer} end + + # 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) + @test is_juliarepr(TestTVUpper{<:Real}) + @test is_juliarepr(TestTVUpper{<:Integer}) + @test is_juliarepr(TestTVUpper{<:Signed}) + + # 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) + @test is_juliarepr(Pair{A,<:A} where A) + + # 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" + @test endswith(repr(TestTVUpper{<:Real}), "TestTVUpper{<:Real}") + @test endswith(repr(TestTVUpper{<:Integer}), "TestTVUpper") + @test endswith(repr(TestTVUpper{<:Signed}), "TestTVUpper{<:Signed}") + + # exception for tuples + @test is_juliarepr(Tuple) + @test is_juliarepr(Tuple{}) + @test is_juliarepr(Tuple{<:Any}) end @testset "showarg" begin @@ -1564,7 +1614,7 @@ end @test showstr([Float16(1)]) == "Float16[1.0]" @test showstr([[Float16(1)]]) == "Array{Float16,1}[[1.0]]" @test replstr(Real[Float16(1)]) == "1-element Array{Real,1}:\n Float16(1.0)" - @test replstr(Array{Real}[Real[1]]) == "1-element Array{Array{Real,N} where N,1}:\n [1]" + @test replstr(Array{Real}[Real[1]]) == "1-element Array{Array{Real},1}:\n [1]" # printing tuples (Issue #25042) @test replstr(fill((Int64(1), zeros(Float16, 3)), 1)) == "1-element Array{Tuple{Int64,Array{Float16,1}},1}:\n (1, [0.0, 0.0, 0.0])" @@ -1591,7 +1641,7 @@ end end # issue #25857 - @test repr([(1,),(1,2),(1,2,3)]) == "Tuple{$Int,Vararg{$Int,N} where N}[(1,), (1, 2), (1, 2, 3)]" + @test repr([(1,),(1,2),(1,2,3)]) == "Tuple{$Int,Vararg{$Int}}[(1,), (1, 2), (1, 2, 3)]" # issues #25466 & #26256 @test replstr([:A => [1]]) == "1-element Array{Pair{Symbol,Array{$Int,1}},1}:\n :A => [1]" @@ -1611,9 +1661,9 @@ end @test replstr([[1.0]=>1.0]) == "1-element Array{Pair{Array{Float64,1},Float64},1}:\n [1.0] => 1.0" # issue #28159 - @test replstr([(a=1, b=2), (a=3,c=4)]) == "2-element Array{NamedTuple{names,Tuple{$Int,$Int}} where names,1}:\n (a = 1, b = 2)\n (a = 3, c = 4)" + @test replstr([(a=1, b=2), (a=3,c=4)]) == "2-element Array{NamedTuple{<:Any,Tuple{$Int,$Int}},1}:\n (a = 1, b = 2)\n (a = 3, c = 4)" - @test replstr(Vector[Any[1]]) == "1-element Array{Array{T,1} where T,1}:\n Any[1]" + @test replstr(Vector[Any[1]]) == "1-element Array{Array{<:Any,1},1}:\n Any[1]" @test replstr(AbstractDict{Integer,Integer}[Dict{Integer,Integer}(1=>2)]) == "1-element Array{AbstractDict{Integer,Integer},1}:\n Dict(1 => 2)" @@ -1834,7 +1884,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