Skip to content

Commit

Permalink
Support promoting parameters of Tuple and NamedTuple, add promotion f…
Browse files Browse the repository at this point in the history
…or Nothing

Reuse the typejoin() logic.
  • Loading branch information
nalimilan committed Jan 14, 2018
1 parent 36b271f commit 8d90c23
Show file tree
Hide file tree
Showing 16 changed files with 135 additions and 47 deletions.
18 changes: 12 additions & 6 deletions base/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions base/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 30 additions & 21 deletions base/promotion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,67 @@
"""
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
end
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
Expand All @@ -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...}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions base/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ function union!(s::Set{T}, itr) where T
s
end


"""
intersect(s, itrs...)
∩(s, itrs...)
Expand Down
5 changes: 5 additions & 0 deletions base/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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{}) = ()
Expand Down
2 changes: 1 addition & 1 deletion base/twiceprecision.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion stdlib/Test/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
1 change: 1 addition & 0 deletions test/ambiguous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}))
Expand Down
2 changes: 1 addition & 1 deletion test/arrayops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions test/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 2 additions & 2 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
15 changes: 7 additions & 8 deletions test/functional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
Expand Down
10 changes: 10 additions & 0 deletions test/missing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 29 additions & 1 deletion test/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
@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)])
23 changes: 23 additions & 0 deletions test/tuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 8d90c23

Please sign in to comment.