From 26002ff8ba9410d27a78e4d87985bda5b7cb684f Mon Sep 17 00:00:00 2001 From: Milan Bouchet-Valat Date: Fri, 5 Jan 2018 17:09:29 +0100 Subject: [PATCH] Add promote_strict mechanism and use it instead of typejoin() where appropriate Introduce a mechanism similar to promote(), but used to return types which can hold exactly all values of the input types. Use it instead of typejoin() to choose an appropriate element type with collect(), map(), broadcast(), and Dict(). Add appropriate methods for Number types, as well as for Nothing/Missing so that Union{T, Nothing/Missing} is used instead of Any. Add promotion rules for Tuple and NamedTuple so that promotion is performed element by element, to allow for more precise typing of fields, potentially helping the compiler down the road. --- base/array.jl | 4 +- base/broadcast.jl | 2 +- base/dict.jl | 4 +- base/exports.jl | 3 + base/float.jl | 17 +++- base/gmp.jl | 2 +- base/int.jl | 19 ++++ base/missing.jl | 31 +++++-- base/mpfr.jl | 6 +- base/namedtuple.jl | 3 + base/promotion.jl | 211 +++++++++++++++++++++++++++++++++++++-------- base/rational.jl | 11 ++- base/set.jl | 2 +- base/tuple.jl | 64 +++++++++++++- test/arrayops.jl | 3 + test/functional.jl | 28 ++++++ test/tuple.jl | 6 ++ 17 files changed, 360 insertions(+), 56 deletions(-) diff --git a/base/array.jl b/base/array.jl index 980880fb1aea6..90273cc2ac973 100644 --- a/base/array.jl +++ b/base/array.jl @@ -572,7 +572,7 @@ function collect_to!(dest::AbstractArray{T}, itr, offs, st) where T @inbounds dest[i] = el::T i += 1 else - R = typejoin(T, S) + R = promote_strict_type(T, S) new = similar(dest, R) copyto!(new,1, dest,1, i-1) @inbounds new[i] = el @@ -595,7 +595,7 @@ function grow_to!(dest, itr, st) if S === T || S <: T push!(dest, el::T) else - new = sizehint!(empty(dest, typejoin(T, S)), length(dest)) + new = sizehint!(empty(dest, promote_strict_type(T, S)), length(dest)) if new isa AbstractSet # TODO: merge back these two branches when copy! is re-enabled for sets/vectors union!(new, dest) diff --git a/base/broadcast.jl b/base/broadcast.jl index b40aaf3854eb2..a6a610534ed49 100644 --- a/base/broadcast.jl +++ b/base/broadcast.jl @@ -507,7 +507,7 @@ end else # This element type doesn't fit in B. Allocate a new B with wider eltype, # copy over old values, and continue - newB = Base.similar(B, typejoin(eltype(B), S)) + newB = Base.similar(B, promote_strict_type(eltype(B), S)) for II in Iterators.take(iter, count) newB[II] = B[II] end diff --git a/base/dict.jl b/base/dict.jl index 3e5534885318a..826e598c659a4 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -179,7 +179,9 @@ function grow_to!(dest::AbstractDict{K,V}, itr, st) where V where K if isa(k,K) && isa(v,V) dest[k] = v else - new = empty(dest, typejoin(K,typeof(k)), typejoin(V,typeof(v))) + new = empty(dest, + promote_strict_type(K,typeof(k)), + promote_strict_type(V,typeof(v))) merge!(new, dest) new[k] = v return grow_to!(new, itr, st) diff --git a/base/exports.jl b/base/exports.jl index bbc505393a62e..1ffcd0b825667 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -869,6 +869,9 @@ export promote, promote_rule, promote_type, + promote_strict, + promote_strict_rule, + promote_strict_type, subtypes, instances, supertype, diff --git a/base/float.jl b/base/float.jl index c0f5f6a3894ba..ab429de4c7be1 100644 --- a/base/float.jl +++ b/base/float.jl @@ -44,7 +44,22 @@ A not-a-number value of type [`Float64`](@ref). """ const NaN = NaN64 -## conversions to floating-point ## +## promotions and conversions to floating-point ## + +promote_strict_rule(::Type{Float16}, ::Union{Type{Int8}, Type{UInt8}}) = Float16 +promote_strict_rule(::Type{Float32}, ::Union{Type{Int8}, Type{UInt8}, Type{Int16}, Type{UInt16}}) = Float32 +promote_strict_rule(::Type{Float64}, ::Union{Type{Int8}, Type{UInt8}, Type{Int16}, Type{UInt16}, Type{Int32}, Type{UInt32}}) = Float64 + +promote_strict_rule(::Type{Float16}, ::Union{Type{Int16}, Type{UInt16}, Type{Int32}, Type{UInt32}}) = Float32 +promote_strict_rule(::Type{Float32}, ::Union{Type{Int32}, Type{UInt32}}) = Float64 + +promote_strict_rule(::Type{Float16}, ::Union{Type{Int64}, Type{UInt64}, Type{Int128}, Type{UInt128}}) = BigFloat +promote_strict_rule(::Type{Float32}, ::Union{Type{Int64}, Type{UInt64}, Type{Int128}, Type{UInt128}}) = BigFloat +promote_strict_rule(::Type{Float64}, ::Union{Type{Int64}, Type{UInt64}, Type{Int128}, Type{UInt128}}) = BigFloat + +promote_strict_rule(::Type{Float32}, ::Type{Float16}) = Float32 +promote_strict_rule(::Type{Float64}, ::Union{Type{Float16}, Type{Float32}}) = Float64 + Float16(x::Integer) = convert(Float16, convert(Float32, x)) for t in (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128) @eval promote_rule(::Type{Float16}, ::Type{$t}) = Float16 diff --git a/base/gmp.jl b/base/gmp.jl index 37f47cc8932fa..b89c0ab6bb227 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -389,7 +389,7 @@ Float64(n::BigInt) = Float64(n, RoundNearest) Float32(n::BigInt) = Float32(n, RoundNearest) Float16(n::BigInt) = Float16(n, RoundNearest) -promote_rule(::Type{BigInt}, ::Type{<:Integer}) = BigInt +promote_strict_rule(::Type{BigInt}, ::Type{<:Integer}) = BigInt """ big(x) diff --git a/base/int.jl b/base/int.jl index fbe352b343ad9..50fbe6242cd22 100644 --- a/base/int.jl +++ b/base/int.jl @@ -560,6 +560,25 @@ end ## integer promotions ## +# promote_strict +# with same signedness or when unsigned type is smaller, promote to larger signed type +promote_strict_rule(::Type{Int16}, ::Union{Type{Int8}, Type{UInt8}}) = Int16 +promote_strict_rule(::Type{Int32}, ::Union{Type{Int16}, Type{Int8}, Type{UInt16}, Type{UInt8}}) = Int32 +promote_strict_rule(::Type{Int64}, ::Union{Type{Int16}, Type{Int32}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt8}}) = Int64 +promote_strict_rule(::Type{Int128}, ::Union{Type{Int16}, Type{Int32}, Type{Int64}, Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt64}, Type{UInt8}}) = Int128 +promote_strict_rule(::Type{UInt16}, ::Type{UInt8}) = UInt16 +promote_strict_rule(::Type{UInt32}, ::Union{Type{UInt16}, Type{UInt8}}) = UInt32 +promote_strict_rule(::Type{UInt64}, ::Union{Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt8}}) = UInt64 +promote_strict_rule(::Type{UInt128}, ::Union{Type{Int8}, Type{UInt16}, Type{UInt32}, Type{UInt64}, Type{UInt8}}) = UInt128 +# with mixed signedness when unsigned type is not smaller, promote to another signed type +promote_strict_rule(::Type{UInt8}, ::Type{Int8}) = Int16 +promote_strict_rule(::Type{UInt16}, ::Union{Type{Int16}, Type{Int8}}) = Int32 +promote_strict_rule(::Type{UInt32}, ::Union{Type{Int32}, Type{Int16}, Type{Int8}}) = Int64 +promote_strict_rule(::Type{UInt64}, ::Union{Type{Int16}, Type{Int32}, Type{Int64}, Type{Int8}}) = Int128 +promote_strict_rule(::Type{UInt128}, ::Union{Type{Int16}, Type{Int32}, Type{Int64}, Type{Int128}, Type{Int8}}) = BigInt +# TODO: what about Bool? + +# promote # with different sizes, promote to larger type promote_rule(::Type{Int16}, ::Union{Type{Int8}, Type{UInt8}}) = Int16 promote_rule(::Type{Int32}, ::Union{Type{Int16}, Type{Int8}, Type{UInt16}, Type{UInt8}}) = Int32 diff --git a/base/missing.jl b/base/missing.jl index 229c3deb49d62..9ec95b6a4ca31 100644 --- a/base/missing.jl +++ b/base/missing.jl @@ -23,12 +23,31 @@ 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 +# Both rules need to be defined for types which implement promote_rule +# but not promote_strict_rule +for (prule, ptype) in ((:promote_strict_rule, :promote_strict_type), + (:promote_rule, :promote_type)), + U in (:Nothing, :Missing) + @eval begin + $prule(::Type{$U}, ::Type{T}) where {T} = Union{T, $U} + $prule(::Type{Union{S,$U}}, ::Type{T}) where {T,S} = Union{$ptype(T, S), $U} + $prule(::Type{Any}, ::Type{$U}) = Any + $prule(::Type{$U}, ::Type{Any}) = Any + $prule(::Type{$U}, ::Type{$U}) = U + end +end + +#= # Both rules need to be defined for types which implement promote_rule +# but not promote_strict_rule +for (prule, ptype) in ((promote_strict_rule, promote_strict_type), (promote_rule, promote_type)), + U in (Nothing, Missing) + prule(::Type{U}, ::Type{T}) where {T} = Union{T, U} + prule(::Type{Union{S,U}}, ::Type{T}) where {T,S} = Union{ptype(T, S), U} + prule(::Type{Any}, ::Type{T}) where {T} = Any + prule(::Type{Any}, ::Type{U}) = Any + prule(::Type{U}, ::Type{Any}) = Any + prule(::Type{U}, ::Type{U}) = U +end =# convert(::Type{Union{T, Missing}}, x) where {T} = convert(T, x) # To fix ambiguities diff --git a/base/mpfr.jl b/base/mpfr.jl index cf54f930ef252..3e5c92c6b2947 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -261,9 +261,9 @@ Float32(x::BigFloat, r::RoundingMode) = # TODO: avoid double rounding Float16(x::BigFloat, r::RoundingMode) = convert(Float16, Float32(x, r)) -promote_rule(::Type{BigFloat}, ::Type{<:Real}) = BigFloat -promote_rule(::Type{BigInt}, ::Type{<:AbstractFloat}) = BigFloat -promote_rule(::Type{BigFloat}, ::Type{<:AbstractFloat}) = BigFloat +promote_strict_rule(::Type{BigFloat}, ::Type{<:Real}) = BigFloat +promote_strict_rule(::Type{BigInt}, ::Type{<:AbstractFloat}) = BigFloat +promote_strict_rule(::Type{BigFloat}, ::Type{<:AbstractFloat}) = BigFloat big(::Type{<:AbstractFloat}) = BigFloat diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 6606456a43462..66297666a3862 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -55,6 +55,9 @@ indexed_next(t::NamedTuple, i::Int, state) = (getfield(t, i), i+1) isempty(::NamedTuple{()}) = true isempty(::NamedTuple) = false +promote_strict_rule(::Type{NamedTuple{n, S}}, ::Type{NamedTuple{n, T}}) where {n, S, T} = + NamedTuple{n, promote_strict_type(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 9c2aa4c9eb78f..79182df475fbf 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -5,7 +5,9 @@ """ typejoin(T, S) -Compute a type that contains both `T` and `S`. + +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) @@ -128,6 +130,150 @@ function tailjoin(A, i) return t end +## strict promotion mechanism ## + +""" + promote_strict_type(type1, type2) + +Promotion refers to converting values of mixed types to a single common type. +`promote_strict_type` represents the promotion mechanism used by [`collect`](@ref) +[`map`](@ref) and [`broadcast`](@ref) when producing an array with elements of +differing types. `promote_strict_type` is guaranteed to return a type which can +xactly represent all values of either input type. Contrary to [`promote_type`](@ref), +no loss is tolerated. For example, `promote_strict_type(Int64, Float64)` returns +`Union{Float64, Int64}` since not all [`Int64`](@ref) values can be +represented exactly as `Float64` values. + +```jldoctest +julia> promote_strict_type(Int64, Float64) +Union{Float64, Int64} + +julia> promote_strict_type(Int32, Int64) +Int64 + +# FIXME: should this be Union{Float32, BigInt}? +julia> promote_strict_type(Float32, BigInt) +BigFloat + +julia> promote_strict_type(Int16, Float16) +Float16 + +# FIXME: should this be Float16? +julia> promote_type(Int64, Float16) +Union{Float16, Int64} + +julia> promote_strict_type(Int8, UInt16) +UInt16 +``` +""" +function promote_type end + +promote_strict_type() = Bottom +promote_strict_type(T) = T +promote_strict_type(T, S, U, V...) = + (@_inline_meta; promote_strict_type(T, promote_strict_type(S, U, V...))) + +promote_strict_type(::Type{Bottom}, ::Type{Bottom}) = Bottom +promote_strict_type(::Type{T}, ::Type{T}) where {T} = T +promote_strict_type(::Type{T}, ::Type{Bottom}) where {T} = T +promote_strict_type(::Type{Bottom}, ::Type{T}) where {T} = T + +function promote_strict_type(::Type{T}, ::Type{S}) where {T,S} + @_inline_meta + # Try promote_rule in both orders. Typically only one is defined, + # and there is a fallback returning Bottom below, so the common case is + # promote_strict_type(T, S) => + # promote_strict_result(T, S, result, Bottom) => + # typejoin(result, Bottom) => result + promote_strict_result(T, S, promote_strict_rule(T,S), promote_strict_rule(S,T)) +end + +""" + promote_strict_rule(type1, type2) + +Specifies what type should be used by [`promote_strict`](@ref) when given values +of types `type1` and `type2`. This function should not be called directly, +but should have definitions added to it for new types as appropriate. +""" +function promote_strict_rule end + +promote_strict_rule(::Type{<:Any}, ::Type{<:Any}) = Bottom +promote_strict_rule(::Type{Any}, ::Type) = Any + +promote_strict_result(::Type{<:Any},::Type{<:Any},::Type{T},::Type{S}) where {T,S} = + (@_inline_meta; promote_strict_type(T,S)) +# If no promote_strict rule is defined, both directions give Bottom. In that +# case use typejoin on the original types instead. +promote_strict_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = + (@_inline_meta; typejoin(T, S)) + +""" + promote_strict(xs...) + +Convert all arguments to a common type, and return them all (as a tuple). +If no arguments can be converted, an error is raised. + +# Examples +```jldoctest +julia> promote_strict(Int8(1), Float16(4.5), Float32(4.1)) +(1.0f0, 4.5f0, 4.1f0) +``` +""" +function promote_strict end + +function _promote_strict(x::T, y::S) where {T,S} + @_inline_meta + R = promote_strict_type(T, S) + return (convert(R, x), convert(R, y)) +end +promote_strict_typeof(x) = typeof(x) +promote_strict_typeof(x, xs...) = (@_inline_meta; promote_strict_type(typeof(x), promote_strict_typeof(xs...))) +function _promote_strict(x, y, z) + @_inline_meta + R = promote_strict_typeof(x, y, z) + return (convert(R, x), convert(R, y), convert(R, z)) +end +function _promote_strict(x, y, zs...) + @_inline_meta + R = promote_strict_typeof(x, y, zs...) + return (convert(R, x), convert(R, y), convert(Tuple{Vararg{R}}, zs)...) +end +# TODO: promote_strict(x::T, ys::T...) where {T} here to catch all circularities? + +promote_strict() = () +promote_strict(x) = (x,) + +function promote_strict(x, y) + @_inline_meta + px, py = _promote_strict(x, y) + not_sametype((x,y), (px,py)) + px, py +end +function promote_strict(x, y, z) + @_inline_meta + px, py, pz = _promote_strict(x, y, z) + not_sametype((x,y,z), (px,py,pz)) + px, py, pz +end +function promote_strict(x, y, z, a...) + p = _promote_strict(x, y, z, a...) + not_sametype((x, y, z, a...), p) + p +end + +promote_strict(x::T, y::T, zs::T...) where {T} = (x, y, zs...) + +not_sametype(x::T, y::T) where {T} = sametype_error(x) + +not_sametype(x, y) = nothing + +function sametype_error(input) + @_noinline_meta + error("promotion of types ", + join(map(x->string(typeof(x)), input), ", ", " and "), + " failed to change any arguments") +end + ## promotion mechanism ## """ @@ -137,8 +283,9 @@ Promotion refers to converting values of mixed types to a single common type. `promote_type` represents the default promotion behavior in Julia when operators (usually mathematical) are given arguments of differing types. `promote_type` generally tries to return a type which can at least approximate -most values of either input type without excessively widening. Some loss is -tolerated; for example, `promote_type(Int64, Float64)` returns +most values of either input type without excessively widening. Contrary to +[`promote_strict_type`] some loss is tolerated. +For example, `promote_type(Int64, Float64)` returns [`Float64`](@ref) even though strictly, not all [`Int64`](@ref) values can be represented exactly as `Float64` values. @@ -179,6 +326,8 @@ function promote_type(::Type{T}, ::Type{S}) where {T,S} # and there is a fallback returning Bottom below, so the common case is # promote_type(T, S) => # promote_result(T, S, result, Bottom) => + # promote_strict_type(T, S) => + # promote_strict_result(T, S, result, Bottom) => # typejoin(result, Bottom) => result promote_result(T, S, promote_rule(T,S), promote_rule(S,T)) end @@ -193,11 +342,14 @@ it for new types as appropriate. function promote_rule end promote_rule(::Type{<:Any}, ::Type{<:Any}) = Bottom +promote_rule(::Type{Any}, ::Type) = Any -promote_result(::Type{<:Any},::Type{<:Any},::Type{T},::Type{S}) where {T,S} = (@_inline_meta; promote_type(T,S)) +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 -# case use typejoin on the original types instead. -promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = (@_inline_meta; typejoin(T, S)) +# case use promote_strict_type on the original types instead. +promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = + (@_inline_meta; promote_strict_type(T, S)) """ promote(xs...) @@ -232,27 +384,6 @@ function _promote(x, y, zs...) end # TODO: promote(x::T, ys::T...) where {T} here to catch all circularities? -## promotions in arithmetic, etc. ## - -# Because of the promoting fallback definitions for Number, we need -# a special case for undefined promote_rule on numeric types. -# Otherwise, typejoin(T,S) is called (returning Number) so no conversion -# happens, and +(promote(x,y)...) is called again, causing a stack -# overflow. -function promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T<:Number,S<:Number} - @_inline_meta - promote_to_supertype(T, S, typejoin(T,S)) -end - -# promote numeric types T and S to typejoin(T,S) if T<:S or S<:T -# for example this makes promote_type(Integer,Real) == Real without -# promoting arbitrary pairs of numeric types to Number. -promote_to_supertype(::Type{T}, ::Type{T}, ::Type{T}) where {T<:Number} = (@_inline_meta; T) -promote_to_supertype(::Type{T}, ::Type{S}, ::Type{T}) where {T<:Number,S<:Number} = (@_inline_meta; T) -promote_to_supertype(::Type{T}, ::Type{S}, ::Type{S}) where {T<:Number,S<:Number} = (@_inline_meta; S) -promote_to_supertype(::Type{T}, ::Type{S}, ::Type) where {T<:Number,S<:Number} = - error("no promotion exists for ", T, " and ", S) - promote() = () promote(x) = (x,) @@ -276,17 +407,27 @@ end promote(x::T, y::T, zs::T...) where {T} = (x, y, zs...) -not_sametype(x::T, y::T) where {T} = sametype_error(x) - -not_sametype(x, y) = nothing +## promotions in arithmetic, etc. ## -function sametype_error(input) - @_noinline_meta - error("promotion of types ", - join(map(x->string(typeof(x)), input), ", ", " and "), - " failed to change any arguments") +# Because of the promoting fallback definitions for Number, we need +# a special case for undefined promote_rule on numeric types. +# Otherwise, typejoin(T,S) is called (returning Number) so no conversion +# happens, and +(promote(x,y)...) is called again, causing a stack +# overflow. +function promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T<:Number,S<:Number} + @_inline_meta + promote_to_supertype(T, S, typejoin(T,S)) end +# promote numeric types T and S to typejoin(T,S) if T<:S or S<:T +# for example this makes promote_type(Integer,Real) == Real without +# promoting arbitrary pairs of numeric types to Number. +promote_to_supertype(::Type{T}, ::Type{T}, ::Type{T}) where {T<:Number} = (@_inline_meta; T) +promote_to_supertype(::Type{T}, ::Type{S}, ::Type{T}) where {T<:Number,S<:Number} = (@_inline_meta; T) +promote_to_supertype(::Type{T}, ::Type{S}, ::Type{S}) where {T<:Number,S<:Number} = (@_inline_meta; S) +promote_to_supertype(::Type{T}, ::Type{S}, ::Type) where {T<:Number,S<:Number} = + error("no promotion exists for ", T, " and ", S) + +(x::Number, y::Number) = +(promote(x,y)...) *(x::Number, y::Number) = *(promote(x,y)...) -(x::Number, y::Number) = -(promote(x,y)...) diff --git a/base/rational.jl b/base/rational.jl index cbd694a676d72..ee0e0cbf7f86f 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -100,9 +100,14 @@ Rational(x::Float32) = Rational{Int}(x) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) -promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)} -promote_rule(::Type{Rational{T}}, ::Type{Rational{S}}) where {T<:Integer,S<:Integer} = Rational{promote_type(T,S)} -promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:AbstractFloat} = promote_type(T,S) +# Both rules need to be defined for types which implement promote_rule +# but not promote_strict_rule +for (prule, ptype) in ((promote_strict_rule, promote_strict_type), + (promote_rule, promote_type)) + prule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:Integer} = Rational{ptype(T,S)} + prule(::Type{Rational{T}}, ::Type{Rational{S}}) where {T<:Integer,S<:Integer} = Rational{ptype(T,S)} + prule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:AbstractFloat} = ptype(T,S) +end widen(::Type{Rational{T}}) where {T} = Rational{widen(T)} diff --git a/base/set.jl b/base/set.jl index 06cb1146963e9..d04d0d817138c 100644 --- a/base/set.jl +++ b/base/set.jl @@ -352,7 +352,7 @@ _unique_from(itr, out, seen, i) = unique_from(itr, out, seen, i) x, i = next(itr, i) S = typeof(x) if !(S === T || S <: T) - R = typejoin(S, T) + R = promote_strict_type(S, T) seenR = convert(Set{R}, seen) outR = convert(Vector{R}, out) if !in(x, seenR) diff --git a/base/tuple.jl b/base/tuple.jl index 2fa6d80d17ff7..7dc01c30e5b38 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -78,15 +78,75 @@ end eltype(t::Type{<:Tuple}) = _compute_eltype(t) function _compute_eltype(t::Type{<:Tuple}) @_pure_meta - t isa Union && return typejoin(eltype(t.a), eltype(t.b)) + t isa Union && return promote_strict_type(eltype(t.a), eltype(t.b)) t´ = unwrap_unionall(t) r = Union{} for ti in t´.parameters - r = typejoin(r, rewrap_unionall(unwrapva(ti), t)) + r = promote_strict_type(r, rewrap_unionall(unwrapva(ti), t)) end return r end +# TODO: add similar promote_rule +promote_strict_rule(a::Type{<:Tuple}, b::Type{<:Tuple}) = _compute_promote_strict(a, b) + +function _compute_promote_strict(a::Type{<:Tuple}, b::Type{<:Tuple}) + ap, bp = a.parameters, b.parameters + lar = length(ap)::Int; lbr = length(bp)::Int + if lar == 0 + return Tuple{Vararg{tail_promote_strict_type(bp,1)}} + end + if lbr == 0 + return Tuple{Vararg{tail_promote_strict_type(ap,1)}} + 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{promote_strict_type(unwrapva(ap[lar]), + tail_promote_strict_type(bp,laf))} + n = laf-1 + else + c = Vector{Any}(uninitialized, laf+1) + c[laf+1] = Vararg{tail_promote_strict_type(bp,laf+1)} + n = laf + end + elseif lbf < laf + if isvarargtype(bp[lbr]) && !bfixed + c = Vector{Any}(uninitialized, lbf) + c[lbf] = Vararg{promote_strict_type(unwrapva(bp[lbr]), + tail_promote_strict_type(ap,lbf))} + n = lbf-1 + else + c = Vector{Any}(uninitialized, lbf+1) + c[lbf+1] = Vararg{tail_promote_strict_type(ap,lbf+1)} + n = lbf + end + else + c = Vector{Any}(uninitialized, laf) + n = laf + end + for i = 1:n + ai = ap[min(i,lar)]; bi = bp[min(i,lbr)] + ci = promote_strict_type(unwrapva(ai),unwrapva(bi)) + c[i] = i == length(c) && (isvarargtype(ai) || isvarargtype(bi)) ? Vararg{ci} : ci + end + return Tuple{c...} +end + +# reduce tail_promote_strict_type over A[i:end] +function tail_promote_strict_type(A, i) + if i > length(A) + return unwrapva(A[end]) + end + t = Bottom + for j = i:length(A) + t = promote_strict_type(t, unwrapva(A[j])) + end + return t +end + # 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/test/arrayops.jl b/test/arrayops.jl index 402ae878e062a..7269c88a7ef4a 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -1205,6 +1205,9 @@ 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, 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 let x⊙y = sum([x[i]*y[i] for i=1:length(x)]) @test [1,2] ⊙ [3,4] == 11 diff --git a/test/functional.jl b/test/functional.jl index d7b9026ea61cb..454c617e0437e 100644 --- a/test/functional.jl +++ b/test/functional.jl @@ -147,3 +147,31 @@ end for n = 0:5:100-q-d 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 in (Nothing, Missing) + v = T() + 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) + @test res isa Vector{Bool} + res = @inferred map(s -> s === v, x) + @test res isa Vector{Bool} + y = Union{String, T}["a", v] + f(s::Union{Nothing, Missing}) = s + f(s::String) = s == "a" + res = @inferred 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) + @test res isa Vector{Bool} + res = @inferred map(s -> s === v, y) + @test res isa Vector{Bool} + res = collect(f(s) for s in y) + @test res isa Vector{Union{Bool, T}} + res = map(f, y) + @test res isa Vector{Union{Bool, T}} +end \ No newline at end of file diff --git a/test/tuple.jl b/test/tuple.jl index 74becec9e6401..578f4899a7df5 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -181,6 +181,12 @@ 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} + @test eltype(Tuple{Int, Missing, Float64}) === Union{Missing, Real} + @test eltype(Tuple{Int, Nothing, Float64}) === Union{Missing, Real} + @test eltype(Tuple{Int, Missing, Int8}) === Union{Missing, Int} + @test eltype(Tuple{Int, Nothing, Int8}) === Union{Missing, Int} end @testset "mapping" begin