diff --git a/base/missing.jl b/base/missing.jl index 8166ade8d2401..8804b1b1ffc0f 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -23,12 +23,18 @@ nonmissingtype(::Type{Missing}) = Union{} nonmissingtype(::Type{T}) where {T} = T nonmissingtype(::Type{Any}) = Any -promote_rule(::Type{Missing}, ::Type{T}) where {T} = Union{T, Missing} -promote_rule(::Type{Union{S,Missing}}, ::Type{T}) where {T,S} = Union{promote_type(T, S), Missing} -promote_rule(::Type{Any}, ::Type{T}) where {T} = Any -promote_rule(::Type{Any}, ::Type{Missing}) = Any -promote_rule(::Type{Missing}, ::Type{Any}) = Any -promote_rule(::Type{Missing}, ::Type{Missing}) = Missing +for U in (:Nothing, :Missing) + @eval begin + promote_rule(::Type{$U}, ::Type{T}) where {T} = Union{T, $U} + promote_rule(::Type{Union{S,$U}}, ::Type{T}) where {T,S} = Union{promote_type(T, S), $U} + promote_rule(::Type{Any}, ::Type{$U}) = Any + promote_rule(::Type{$U}, ::Type{Any}) = Any + promote_rule(::Type{$U}, ::Type{$U}) = U + end +end +promote_rule(::Type{Union{Nothing, Missing}}, ::Type{Any}) = Any +promote_rule(::Type{Union{Nothing, Missing}}, ::Type{T}) where {T} = + Union{Nothing, Missing, T} convert(::Type{Union{T, Missing}}, x) where {T} = convert(T, x) # To fix ambiguities diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 6606456a43462..353a8d4623325 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -55,6 +55,12 @@ indexed_next(t::NamedTuple, i::Int, state) = (getfield(t, i), i+1) isempty(::NamedTuple{()}) = true isempty(::NamedTuple) = false +promote_rule(::Type{NamedTuple{n, S}}, ::Type{NamedTuple{n, T}}) where {n, S, T} = + NamedTuple{n, promote_type(S, T)} + +promote_join(::Type{NamedTuple{n, S}}, ::Type{NamedTuple{n, T}}) where {n, S, T} = + NamedTuple{n, promote_join(S, T)} + convert(::Type{NamedTuple{names,T}}, nt::NamedTuple{names,T}) where {names,T} = nt convert(::Type{NamedTuple{names}}, nt::NamedTuple{names}) where {names} = nt diff --git a/base/promotion.jl b/base/promotion.jl index ba5ee0fd74e4e..9f59ce38a2adf 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -5,30 +5,35 @@ """ typejoin(T, S) -Return the closest common ancestor of `T` and `S`, i.e. a type which -contains both of them. + +Return the closest common ancestor of `T` and `S`, i.e. the narrowest type from which +they both inherit. """ typejoin() = (@_pure_meta; Bottom) typejoin(@nospecialize(t)) = (@_pure_meta; t) typejoin(@nospecialize(t), ts...) = (@_pure_meta; typejoin(t, typejoin(ts...))) -function typejoin(@nospecialize(a), @nospecialize(b)) +typejoin(@nospecialize(a), @nospecialize(b)) = join_types(a, b, typejoin) + +function join_types(@nospecialize(a), @nospecialize(b), f::Function) @_pure_meta if a <: b return b elseif b <: a return a elseif isa(a,UnionAll) - return UnionAll(a.var, typejoin(a.body, b)) + return UnionAll(a.var, join_types(a.body, b, f)) elseif isa(b,UnionAll) - return UnionAll(b.var, typejoin(a, b.body)) + return UnionAll(b.var, join_types(a, b.body, f)) elseif isa(a,TypeVar) - return typejoin(a.ub, b) + return f(a.ub, b) elseif isa(b,TypeVar) - return typejoin(a, b.ub) + return f(a, b.ub) elseif isa(a,Union) - return typejoin(typejoin(a.a,a.b), b) + a′ = f(a.a,a.b) + return a′ === a ? typejoin(a, b) : f(a′, b) elseif isa(b,Union) - return typejoin(a, typejoin(b.a,b.b)) + b′ = f(b.a,b.b) + return b′ === b ? typejoin(a, b) : f(a, b′) elseif a <: Tuple if !(b <: Tuple) return Any @@ -36,31 +41,31 @@ function typejoin(@nospecialize(a), @nospecialize(b)) ap, bp = a.parameters, b.parameters lar = length(ap)::Int; lbr = length(bp)::Int if lar == 0 - return Tuple{Vararg{tailjoin(bp,1)}} + return Tuple{Vararg{tailjoin(bp,1,f)}} end if lbr == 0 - return Tuple{Vararg{tailjoin(ap,1)}} + return Tuple{Vararg{tailjoin(ap,1,f)}} end laf, afixed = full_va_len(ap) lbf, bfixed = full_va_len(bp) if laf < lbf if isvarargtype(ap[lar]) && !afixed c = Vector{Any}(uninitialized, laf) - c[laf] = Vararg{typejoin(unwrapva(ap[lar]), tailjoin(bp,laf))} + c[laf] = Vararg{f(unwrapva(ap[lar]), tailjoin(bp,laf,f))} n = laf-1 else c = Vector{Any}(uninitialized, laf+1) - c[laf+1] = Vararg{tailjoin(bp,laf+1)} + c[laf+1] = Vararg{tailjoin(bp,laf+1,f)} n = laf end elseif lbf < laf if isvarargtype(bp[lbr]) && !bfixed c = Vector{Any}(uninitialized, lbf) - c[lbf] = Vararg{typejoin(unwrapva(bp[lbr]), tailjoin(ap,lbf))} + c[lbf] = Vararg{f(unwrapva(bp[lbr]), tailjoin(ap,lbf,f))} n = lbf-1 else c = Vector{Any}(uninitialized, lbf+1) - c[lbf+1] = Vararg{tailjoin(ap,lbf+1)} + c[lbf+1] = Vararg{tailjoin(ap,lbf+1,f)} n = lbf end else @@ -69,7 +74,7 @@ function typejoin(@nospecialize(a), @nospecialize(b)) end for i = 1:n ai = ap[min(i,lar)]; bi = bp[min(i,lbr)] - ci = typejoin(unwrapva(ai),unwrapva(bi)) + ci = f(unwrapva(ai),unwrapva(bi)) c[i] = i == length(c) && (isvarargtype(ai) || isvarargtype(bi)) ? Vararg{ci} : ci end return Tuple{c...} @@ -110,10 +115,10 @@ Compute a type that contains both `T` and `S`, which could be either a parent of both types, or a `Union` if appropriate. Falls back to [`typejoin`](@ref). """ -promote_join(@nospecialize(a), @nospecialize(b)) = typejoin(a, b) +promote_join(@nospecialize(a), @nospecialize(b)) = join_types(a, b, promote_join) promote_join(::Type{Nothing}, ::Type{T}) where {T} = isconcrete(T) ? Union{T, Nothing} : Any -promote_join(::Type{T}, ::Type{Void}) where {T} = +promote_join(::Type{T}, ::Type{Nothing}) where {T} = isconcrete(T) ? Union{T, Nothing} : Any promote_join(::Type{Missing}, ::Type{T}) where {T} = isconcrete(T) ? Union{T, Missing} : Any @@ -138,14 +143,14 @@ function full_va_len(p) return length(p)::Int, true end -# reduce typejoin over A[i:end] -function tailjoin(A, i) +# reduce join_types over A[i:end] +function tailjoin(A, i, f::Function) if i > length(A) return unwrapva(A[end]) end t = Bottom for j = i:length(A) - t = typejoin(t, unwrapva(A[j])) + t = f(t, unwrapva(A[j])) end return t end @@ -215,6 +220,10 @@ it for new types as appropriate. function promote_rule end promote_rule(::Type{<:Any}, ::Type{<:Any}) = Bottom +# To fix ambiguities +promote_rule(::Type{Any}, ::Type{<:Any}) = Any +promote_rule(::Type{<:Any}, ::Type{Any}) = Any +promote_rule(::Type{Any}, ::Type{Any}) = Any promote_result(::Type{<:Any},::Type{<:Any},::Type{T},::Type{S}) where {T,S} = (@_inline_meta; promote_type(T,S)) # If no promote_rule is defined, both directions give Bottom. In that diff --git a/base/set.jl b/base/set.jl index 28f288f1aef35..f748a4e97f364 100644 --- a/base/set.jl +++ b/base/set.jl @@ -142,6 +142,7 @@ function union!(s::Set{T}, itr) where T s end + """ intersect(s, itrs...) ∩(s, itrs...) diff --git a/base/tuple.jl b/base/tuple.jl index 22e4bd249eb26..2833a18b802a2 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -87,6 +87,11 @@ function _compute_eltype(t::Type{<:Tuple}) return r end +# promotion + +promote_rule(::Type{S}, ::Type{T}) where {S<:Tuple, T<:Tuple} = + join_types(S, T, promote_type) + # version of tail that doesn't throw on empty tuples (used in array indexing) safe_tail(t::Tuple) = tail(t) safe_tail(t::Tuple{}) = () diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index bc47ea3f2bb6d..305d3a8c507cd 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -237,7 +237,7 @@ eltype(::Type{TwicePrecision{T}}) where {T} = T promote_rule(::Type{TwicePrecision{R}}, ::Type{TwicePrecision{S}}) where {R,S} = TwicePrecision{promote_type(R,S)} -promote_rule(::Type{TwicePrecision{R}}, ::Type{S}) where {R,S} = +promote_rule(::Type{TwicePrecision{R}}, ::Type{S}) where {R,S<:Number} = TwicePrecision{promote_type(R,S)} (::Type{T})(x::TwicePrecision) where {T<:Number} = T(x.hi + x.lo)::T diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 12eb63e0386c3..4231f410c3650 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -107,7 +107,7 @@ let fails = @testset NoThrowTestSet begin @test isapprox(1 / 2, 2 / 1, atol=1 / 1) @test isapprox(1 - 2, 2 - 1; atol=1 - 1) # Fail - function keyword splatting - k = [(:atol, 0), (:nans, true)] + k = Any[(:atol, 0), (:nans, true)] @test isapprox(1, 2; k...) # Error - unexpected pass @test_broken true diff --git a/test/abstractarray.jl b/test/abstractarray.jl index ccbbfd3f67b79..ac4a2afac5eeb 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -625,7 +625,7 @@ function test_cat(::Type{TestAbstractArray}) @test @inferred(vcat([1.0, 2.0], 3))::Array{Float64,1} == [1.0, 2.0, 3.0] @test @inferred(vcat(["a"], "b"))::Vector{String} == ["a", "b"] - @test @inferred(vcat((1,), (2.0,)))::Vector{Tuple{Real}} == [(1,), (2.0,)] + @test @inferred(vcat((1,), (2.0,)))::Vector{Tuple{Float64}} == [(1,), (2.0,)] end function test_ind2sub(::Type{TestAbstractArray}) diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 1654f88360215..77c64bacfa86c 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -289,6 +289,7 @@ end pop!(need_to_handle_undef_sparam, which(Base.cat, (Any, SparseArrays._TypedDenseConcatGroup{T} where T))) pop!(need_to_handle_undef_sparam, which(Base.float, Tuple{AbstractArray{Union{Missing, T},N} where {T, N}})) pop!(need_to_handle_undef_sparam, which(Base.convert, Tuple{Type{Union{Missing, T}} where T, Any})) + pop!(need_to_handle_undef_sparam, which(Base.promote_rule, Tuple{Type{Union{Nothing, S}} where S, Type{T} where T})) pop!(need_to_handle_undef_sparam, which(Base.promote_rule, Tuple{Type{Union{Missing, S}} where S, Type{T} where T})) pop!(need_to_handle_undef_sparam, which(Base.zero, Tuple{Type{Union{Missing, T}} where T})) pop!(need_to_handle_undef_sparam, which(Base.one, Tuple{Type{Union{Missing, T}} where T})) diff --git a/test/arrayops.jl b/test/arrayops.jl index 9082863fbaa10..b681c3d7ebfbc 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1206,7 +1206,7 @@ end @test isequal([1,2,3], [a for (a,b) in enumerate(2:4)]) @test isequal([2,3,4], [b for (a,b) in enumerate(2:4)]) - @test [s for s in Union{String, Void}["a", nothing]] isa Vector{Union{String, Void}} + @test [s for s in Union{String, Nothing}["a", nothing]] isa Vector{Union{String, Nothing}} @test [s for s in Union{String, Missing}["a", missing]] isa Vector{Union{String, Missing}} @testset "comprehension in let-bound function" begin diff --git a/test/bitarray.jl b/test/bitarray.jl index 214484dcebc7f..cbfc41994913e 100644 --- a/test/bitarray.jl +++ b/test/bitarray.jl @@ -2,7 +2,7 @@ using Base: findprevnot, findnextnot -tc(r1::NTuple{N,Any}, r2::NTuple{N,Any}) where {N} = all(x->tc(x...), [zip(r1,r2)...]) +tc(r1::NTuple{N,Any}, r2::NTuple{N,Any}) where {N} = all(x->tc(x...), Any[zip(r1,r2)...]) tc(r1::BitArray{N}, r2::Union{BitArray{N},Array{Bool,N}}) where {N} = true tc(r1::Transpose{Bool,BitVector}, r2::Union{Transpose{Bool,BitVector},Transpose{Bool,Vector{Bool}}}) = true tc(r1::T, r2::T) where {T} = true @@ -854,10 +854,10 @@ timesofar("unary arithmetic") @check_bit_operation broadcast(-, u1, b2) Matrix{UInt8} @check_bit_operation broadcast(*, u1, b2) Matrix{UInt8} - for (x1,t1) = [(f1, Float64), - (ci1, Complex{Int}), - (cu1, Complex{UInt8}), - (cf1, ComplexF64)] + for (x1,t1) = Any[(f1, Float64), + (ci1, Complex{Int}), + (cu1, Complex{UInt8}), + (cf1, ComplexF64)] @check_bit_operation broadcast(+, x1, b2) Matrix{t1} @check_bit_operation broadcast(-, x1, b2) Matrix{t1} @check_bit_operation broadcast(*, x1, b2) Matrix{t1} diff --git a/test/core.jl b/test/core.jl index 7001daa5f4bb4..f478c10982c81 100644 --- a/test/core.jl +++ b/test/core.jl @@ -125,8 +125,8 @@ end @test typejoin(Tuple{Vararg{Int,2}}, Tuple{Int,Int,Int}) === Tuple{Int,Int,Vararg{Int}} @test typejoin(Tuple{Vararg{Int,2}}, Tuple{Vararg{Int}}) === Tuple{Vararg{Int}} -# promote_join returns a Union only with Void/Missing combined with concrete types -for T in (Void, Missing) +# promote_join returns a Union only with Nothing/Missing combined with concrete types +for T in (Nothing, Missing) @test Base.promote_join(Int, Float64) === Real @test Base.promote_join(Int, T) === Union{Int, T} @test Base.promote_join(T, String) === Union{T, String} diff --git a/test/functional.jl b/test/functional.jl index 2410094633c55..a42d001f72e6f 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -151,27 +151,26 @@ end for p = 100-q-d-n if p < n < d < q] == [(50,30,15,5), (50,30,20,0), (50,40,10,0), (75,20,5,0)] -@testset "map/collect return type on generators with $T" for (T, v) in ((Void, nothing), - (Missing, missing)) +@testset "map/collect return type on generators with $T" for T in (Nothing, Missing) x = ["a", "b"] res = @inferred collect(s for s in x) @test res isa Vector{String} res = @inferred map(identity, x) @test res isa Vector{String} - res = @inferred collect(s === v for s in x) + res = @inferred collect(s isa T for s in x) @test res isa Vector{Bool} - res = @inferred map(s -> s === v, x) + res = @inferred map(s -> s isa T, x) @test res isa Vector{Bool} - y = Union{String, T}["a", v] - f(s::Union{Void, Missing}) = s + y = Union{String, T}["a", T()] + f(s::Union{Nothing, Missing}) = s f(s::String) = s == "a" res = collect(s for s in y) @test res isa Vector{Union{String, T}} res = map(identity, y) @test res isa Vector{Union{String, T}} - res = @inferred collect(s === v for s in y) + res = @inferred collect(s isa T for s in y) @test res isa Vector{Bool} - res = @inferred map(s -> s === v, y) + res = @inferred map(s -> s isa T, y) @test res isa Vector{Bool} res = collect(f(s) for s in y) @test res isa Vector{Union{Bool, T}} diff --git a/test/missing.jl b/test/missing.jl index 960edeaaf74ff..69d1f88bbb678 100644 --- a/test/missing.jl +++ b/test/missing.jl @@ -40,6 +40,16 @@ end @test_broken promote_type(Union{Nothing, Missing, Int}, Float64) == Any end +@testset "promotion in various contexts" for T in (Nothing, Missing) + @test collect(v for v in (1, T())) isa Vector{Union{Int,T}} + @test map(identity, Any[1, T()]) isa Vector{Union{Int,T}} + @test broadcast(identity, Any[1, T()]) isa Vector{Union{Int,T}} + @test unique((1, T())) isa Vector{Union{Int,T}} + + @test map(ismissing, Any[1, missing]) isa Vector{Bool} + @test broadcast(ismissing, Any[1, missing]) isa BitVector +end + @testset "comparison operators" begin @test (missing == missing) === missing @test (1 == missing) === missing diff --git a/test/namedtuple.jl b/test/namedtuple.jl index df5e5d274e3f3..8d910136be8cf 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -212,4 +212,32 @@ abstr_nt_22194_3() @test @inferred find(equalto(1), (a=1, b=2)) == [:a] @test @inferred find(equalto(1), (a=1, b=1)) == [:a, :b] @test @inferred isempty(find(equalto(1), NamedTuple())) -@test @inferred isempty(find(equalto(1), (a=2, b=3))) \ No newline at end of file +@test @inferred isempty(find(equalto(1), (a=2, b=3))) + +# Test promotion + +@test promote_type(NamedTuple{(:a,),Tuple{Int}}, + NamedTuple{(:a,),Tuple{Float64}}) === + NamedTuple{(:a,),Tuple{Float64}} +@test promote_type(NamedTuple{(:a,:b),Tuple{Int,Float64}}, + NamedTuple{(:a,:b),Tuple{Float64,Int}}) === + NamedTuple{(:a, :b),Tuple{Float64, Float64}} +@test promote_type(NamedTuple{(:a,:b),Tuple{Int,String}}, + NamedTuple{(:a,:b),Tuple{Float64,Int}}) === + NamedTuple{(:a,:b),Tuple{Float64,Any}} + +for T in (Nothing, Missing) + @test promote_type(NamedTuple{(:a,:b),Tuple{Int,Int}}, + NamedTuple{(:a,:b),Tuple{T,Union{Float64,T}}}) === + NamedTuple{(:a,:b),Tuple{Union{Int, T},Union{Float64,T}}} + + x = [(a=1, b=T()), (a=1, b=2)] + @test x isa Vector{NamedTuple{(:a,:b),Tuple{Int,Union{T,Int}}}} + + y = map(v -> (a=v.a, b=v.b), [(a=1, b=T()), (a=1, b=2)]) + @test y isa Vector{NamedTuple{(:a,:b),Tuple{Int,Union{T,Int}}}} + @test isequal(x, y) +end +y = map(v -> (a=v.a, b=v.a + v.b), [(a=1, b=missing), (a=1, b=2)]) +@test y isa Vector{NamedTuple{(:a,:b),Tuple{Int,Union{Missing,Int}}}} +@test isequal(y, [(a=1, b=missing), (a=1, b=3)]) \ No newline at end of file diff --git a/test/tuple.jl b/test/tuple.jl index f7f5d0260da31..2eab4c0ddaa6a 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -181,6 +181,29 @@ end typejoin(Int, AbstractFloat, Bool) @test eltype(Union{Tuple{Int, Float64}, Tuple{Vararg{Bool}}}) === typejoin(Int, Float64, Bool) + @test eltype(Tuple{Int, Missing}) === Union{Missing, Int} + @test eltype(Tuple{Int, Nothing}) === Union{Nothing, Int} +end + +@testset "promotion" begin + @test promote_type(Tuple{Int}, Tuple{Float64}) === Tuple{Float64} + @test promote_type(Tuple{Int,Float64}, Tuple{Float64,Int}) === Tuple{Float64,Float64} + @test promote_type(Tuple{Int,String}, Tuple{Float64,Int}) === Tuple{Float64,Any} + + for T in (Nothing, Missing) + @test promote_type(Tuple{Int,Int}, Tuple{T,Union{T,Float64}}) === + Tuple{Union{Int,T},Union{Float64,T}} + + x = [(1, T()), (1, 2)] + @test x isa Vector{Tuple{Int,Union{Int,T}}} + + y = map(v -> (v[1], v[2]), [(1, T()), (1, 2)]) + @test y isa Vector{Tuple{Int,Union{T,Int}}} + @test isequal(x, y) + end + y = map(v -> (v[1], v[1] + v[2]), [(1, missing), (1, 2)]) + @test y isa Vector{Tuple{Int,Union{Missing,Int}}} + @test isequal(y, [(1, missing), (1, 3)]) end @testset "mapping" begin