Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better type promottion #923

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/Dimensions/dimension.jl
Original file line number Diff line number Diff line change
Expand Up @@ -554,3 +554,20 @@ mean(A; dims=Ti)
@dim Ti TimeDim "Time"

const Time = Ti # For some backwards compat

function Base.convert(::Type{D1}, dim::D2) where {D1<:Dimension{T},D2} where T
basetypeof(D2) <: basetypeof(D1) ||
throw(ArgumentError("Cannot convert $D1 to $D2"))
rebuild(dim, convert(T, val(dim)))
end

function Base.promote_rule(
::Type{D1}, ::Type{D2}
) where {D1<:Dimension{T1},D2<:Dimension{T2}} where {T1,T2}
T = promote_type(T1, T2)
if basetypeof(D1) == basetypeof(D2)
basetypeof(D1){T}
else
Dimension{T}
end
end
79 changes: 68 additions & 11 deletions src/Lookups/lookup_arrays.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""
Lookup

Expand Down Expand Up @@ -159,11 +158,15 @@ NoLookup() = NoLookup(AutoValues())

rebuild(l::NoLookup; data=parent(l), kw...) = NoLookup(data)

Base.convert(::Type{L1}, lookup::Lookup) where {L1<:NoLookup{A}} where A =
NoLookup(convert(A, axes(lookup, 1)))

# Used in @d broadcasts
struct Length1NoLookup <: AbstractNoLookup end
Length1NoLookup(::AbstractVector) = Length1NoLookup()

rebuild(l::Length1NoLookup; kw...) = Length1NoLookup()

Base.parent(::Length1NoLookup) = Base.OneTo(1)

"""
Expand All @@ -176,7 +179,7 @@ is provided by this package.
`AbstractSampled` must have `order`, `span` and `sampling` fields,
or a `rebuild` method that accepts them as keyword arguments.
"""
abstract type AbstractSampled{T,O<:Order,Sp<:Span,Sa<:Sampling} <: Aligned{T,O} end
abstract type AbstractSampled{T,A,O<:Order,Sp<:Span,Sa<:Sampling,M} <: Aligned{T,O} end

span(lookup::AbstractSampled) = lookup.span
sampling(lookup::AbstractSampled) = lookup.sampling
Expand All @@ -192,6 +195,24 @@ function Base.:(==)(l1::AbstractSampled, l2::AbstractSampled)
parent(l1) == parent(l2)
end

function Base.promote_rule(
::Type{S1}, ::Type{S2}
) where {S1<:AbstractSampled{T1,A1,O1,Sp1,Sa1,M1},
S2<:AbstractSampled{T2,A2,O2,Sp2,Sa2,M2}
} where {T1,A1,O1,Sp1,Sa1,M1,T2,A2,O2,Sp2,Sa2,M2}
A = promote_type(A1, A2)
T = eltype(A)
O = promote_type(O1, O2)
Sp = promote_type(Sp1, Sp2)
Sa = promote_type(Sa1, Sa2)
M = promote_type(M1, M2)
if basetypeof(S1) == basetypeof(S2)
basetypeof(S1){T,A,O,Sp,Sa,M}
else
AbstractSampled{T,A,O,Sp,Sa,M}
end
end

for f in (:getindex, :view, :dotview)
@eval begin
# span may need its step size or bounds updated
Expand Down Expand Up @@ -306,7 +327,7 @@ A = ones(x, y)
20 1.0 1.0 1.0 1.0
```
"""
struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,O,Sp,Sa}
struct Sampled{T,A<:AbstractVector{T},O,Sp,Sa,M} <: AbstractSampled{T,A,O,Sp,Sa,M}
data::A
order::O
span::Sp
Expand All @@ -326,6 +347,17 @@ function rebuild(l::Sampled;
Sampled(data, order, span, sampling, metadata)
end

function Base.convert(
::Type{S1}, lookup::AbstractSampled
) where {S1<:Sampled{T,A,O,Sp,Sa,M}} where {T,A,O,Sp,Sa,M}
Sampled(convert(A, parent(lookup));
order=order(lookup),
span=span(lookup),
sampling=sampling(lookup),
metadata=convert(M, metadata(lookup)),
)
end

# These are used to specialise dispatch:
# When Cycling, we need to modify any `Selector`. After that
# we switch to `NotCycling` and use `AbstractSampled` fallbacks.
Expand All @@ -342,7 +374,7 @@ An abstract supertype for cyclic lookups.

These are `AbstractSampled` lookups that are cyclic for `Selectors`.
"""
abstract type AbstractCyclic{X,T,O,Sp,Sa} <: AbstractSampled{T,O,Sp,Sa} end
abstract type AbstractCyclic{X,T,A,O,Sp,Sa,M} <: AbstractSampled{T,A,O,Sp,Sa,M} end

cycle(l::AbstractCyclic) = l.cycle
cycle_status(l::AbstractCyclic) = l.cycle_status
Expand Down Expand Up @@ -418,7 +450,7 @@ $SAMPLED_ARGUMENTS_DOC
leap years breaking correct date cycling of a single year. If you actually need this behaviour,
please make a GitHub issue.
"""
struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,O,Sp,Sa}
struct Cyclic{X,T,A<:AbstractVector{T},O,Sp,Sa,M,C} <: AbstractCyclic{X,T,A,O,Sp,Sa,M}
data::A
order::O
span::Sp
Expand Down Expand Up @@ -464,18 +496,31 @@ But this can easily be extended, all methods are defined for `AbstractCategorica
All `AbstractCategorical` must provide a `rebuild`
method with `data`, `order` and `metadata` keyword arguments.
"""
abstract type AbstractCategorical{T,O} <: Aligned{T,O} end
abstract type AbstractCategorical{T,A,O,M} <: Aligned{T,O} end

order(lookup::AbstractCategorical) = lookup.order
metadata(lookup::AbstractCategorical) = lookup.metadata

const CategoricalEltypes = Union{AbstractChar,Symbol,AbstractString}

function Base.:(==)(l1::AbstractCategorical, l2::AbstractCategorical)
order(l1) == order(l2) && parent(l1) == parent(l2)
end

function Base.promote_rule(::Type{S1}, ::Type{S2}) where {
S1<:AbstractCategorical{T1,A1,O1,M1}, S2<:AbstractCategorical{T2,A2,O2,M2}
} where {T1,A1,O1,M1,T2,A2,O2,M2}
T = promote_type(T1, T2)
A = promote_type(A1, A2)
O = promote_type(O1, O2)
M = promote_type(M1, M2)
AbstractCategorical{T,A,O,M}
end

function Adapt.adapt_structure(to, l::AbstractCategorical)
rebuild(l; data=Adapt.adapt(to, parent(l)), metadata=NoMetadata())
end


"""
Categorical <: AbstractCategorical

Expand Down Expand Up @@ -533,10 +578,22 @@ function rebuild(l::Categorical;
Categorical(data, order, metadata)
end

function Base.:(==)(l1::AbstractCategorical, l2::AbstractCategorical)
order(l1) == order(l2) && parent(l1) == parent(l2)
function Base.promote_rule(::Type{S1}, ::Type{S2}) where {
S1<:Categorical{T1,A1,O1,M1}, S2<:Categorical{T2,A2,O2,M2}
} where {T1,A1,O1,M1,T2,A2,O2,M2}
T = promote_type(T1, T2)
A = promote_type(A1, A2)
O = promote_type(O1, O2)
M = promote_type(M1, M2)
Categorical{T,A,O,M}
end
function Base.convert(::Type{S1}, lookup::AbstractCategorical) where {
S1<:Categorical{T,A,O,M}
} where {T,A,O,M}
Categorical(convert(A, parent(lookup));
order=order(lookup), metadata=convert(M, metadata(lookup)),
)
end


"""
Unaligned <: Lookup
Expand Down Expand Up @@ -971,4 +1028,4 @@ function promote_first(a1::AbstractArray, as::AbstractArray...)
end

return convert(C, a1)
end
end
15 changes: 15 additions & 0 deletions src/Lookups/lookup_traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ val(span::Regular) = span.step

Base.step(span::Regular) = span.step
Base.:(==)(l1::Regular, l2::Regular) = val(l1) == val(l2)
Base.promote_rule(::Type{<:Regular{T1}}, ::Type{<:Regular{T2}}) where {T1,T2}=
Regular{promote_type(T1, T2)}
Base.convert(::Type{<:Regular{T1}}, span::Regular{T2}) where {T1,T2} =
Regular(convert(T1, val(span)))

"""
Irregular <: Span
Expand All @@ -256,6 +260,13 @@ bounds(span::Irregular) = span.bounds
val(span::Irregular) = span.bounds

Base.:(==)(l1::Irregular, l2::Irregular) = val(l1) == val(l2)
function Base.promote_rule(
::Type{<:Irregular{<:Tuple{T1,T2}}}, ::Type{<:Irregular{<:Tuple{T3,T4}}}
) where {T1,T2,T3,T4}
Irregular{Tuple{promote_type(T1, T3), promote_type(T2, T4)}}
end
Base.convert(::Type{Irregular{Tuple{T1,T2}}}, s::Irregular{Tuple{<:Any,<:Any}}) where {T1,T2} =
Irregular(convert(Tuple{T1,T2}, val(s)))

"""
Explicit(bounds::AbstractMatrix)
Expand All @@ -272,6 +283,10 @@ Explicit() = Explicit(AutoBounds())

val(span::Explicit) = span.val
Base.:(==)(l1::Explicit, l2::Explicit) = val(l1) == val(l2)
Base.promote_rule(::Type{<:Explicit{<:B1}}, ::Type{<:Explicit{<:B2}}) where {B1,B2} =
Explicit{promote_type(B1, B2)}
Base.convert(::Type{<:Explicit{<:B1}}, ::Explicit{<:B2}) where {B1,B2} =
Explicit{promote_type(B1, B2)}

Adapt.adapt_structure(to, s::Explicit) = Explicit(Adapt.adapt_structure(to, val(s)))

Expand Down
61 changes: 42 additions & 19 deletions src/Lookups/metadata.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

"""
AbstractMetadata{X,T}
AbstractMetadata{X,K,V,T}

Abstract supertype for all metadata wrappers.

Expand All @@ -11,11 +11,15 @@ or simply saving data back to the same file type with identical metadata.
Using a wrapper instead of `Dict` or `NamedTuple` also lets us pass metadata
objects to [`set`](@ref) without ambiguity about where to put them.
"""
abstract type AbstractMetadata{X,T} end
abstract type AbstractMetadata{X,K,V,T} <: AbstractDict{K,V} end

const _MetadataContents = Union{AbstractDict,NamedTuple}
const MetadataContents = Union{AbstractDict,NamedTuple}
const DefaultDict = Dict{Symbol,Any}
const AllMetadata = Union{AbstractMetadata,AbstractDict}

valtype(::AbstractMetadata{<:Any,<:Any,<:Any,T}) where T = T
valtype(::Type{<:AbstractMetadata{<:Any,<:Any,<:Any,T}})where T = T

Base.get(m::AbstractMetadata, args...) = get(val(m), args...)
Base.getindex(m::AbstractMetadata, key) = getindex(val(m), key)
Base.setindex!(m::AbstractMetadata, x, key) = setindex!(val(m), x, key)
Expand All @@ -38,20 +42,24 @@ Base.:(==)(m1::AbstractMetadata, m2::AbstractMetadata) = m1 isa typeof(m2) && va
General [`Metadata`](@ref) object. The `X` type parameter
categorises the metadata for method dispatch, if required.
"""
struct Metadata{X,T<:_MetadataContents} <: AbstractMetadata{X,T}
struct Metadata{X,T<:MetadataContents,K,V} <: AbstractMetadata{X,T,K,V}
val::T
end
Metadata(val::T) where {T<:_MetadataContents} = Metadata{Nothing,T}(val)
Metadata{X}(val::T) where {X,T<:_MetadataContents} = Metadata{X,T}(val)
Metadata{X,T}(val::T) where {X,T<:NamedTuple} =
Metadata{X,T,Symbol,Any}(val)
Metadata{X,T}(val::T) where {X,T<:AbstractDict{K,V}} where {K,V} =
Metadata{X,T,K,V}(val)
Metadata(val::T) where {T<:MetadataContents} = Metadata{Nothing,T}(val)
Metadata{X}(val::T) where {X,T<:MetadataContents} = Metadata{X,T}(val)

# NamedTuple/Dict constructor
# We have to combine these because the no-arg method is overwritten by empty kw.
function (::Type{M})(ps...; kw...) where M <: Metadata
if length(ps) > 0 && length(kw) > 0
throw(ArgumentError("Metadata can be constructed with args of Pair to make a Dict, or kw for a NamedTuple. But not both."))
end
length(kw) > 0 ? M((; kw...)) : M(Dict(ps...))
(::Type{M})(p1::Pair, ps::Pair...) where M <: Metadata = M(Dict(p1, ps...))
function (::Type{M})(; kw...) where M <: Metadata
M((; kw...))
end
Metadata() = Metadata(DefaultDict())
Metadata{X}() where X = Metadata{X}(DefaultDict())
Metadata{X,T}() where {X,T} = Metadata{X,T}(T())

ConstructionBase.constructorof(::Type{<:Metadata{X}}) where {X} = Metadata{X}

Expand All @@ -74,14 +82,28 @@ Indicates an object has no metadata. But unlike using `nothing`,
returning the fallback argument. `keys` returns `()` while `haskey`
always returns `false`.
"""
struct NoMetadata <: AbstractMetadata{Nothing,NamedTuple{(),Tuple{}}} end
struct NoMetadata <: AbstractMetadata{Nothing,Dict{Symbol,Any},Symbol,Any} end

val(m::NoMetadata) = NamedTuple()

Base.keys(::NoMetadata) = ()
Base.haskey(::NoMetadata, args...) = false
Base.get(::NoMetadata, key, fallback) = fallback
Base.length(::NoMetadata) = 0
Base.convert(::Type{NoMetadata}, s::Union{NamedTuple,AbstractMetadata,AbstractDict}) =
NoMetadata()

Base.convert(::Type{Metadata}, ::NoMetadata) = Metadata()
Base.convert(::Type{Metadata}, m::MetadataContents) = Metadata(m)
Base.convert(::Type{Metadata{X}}, m::MetadataContents) where X = Metadata{X}(m)
Base.convert(::Type{Metadata{X,T}}, m::AbstractDict) where {X,T<:AbstractDict} =
Metadata{X,T}(T(m))
Base.convert(::Type{Metadata{X,T}}, m::NamedTuple) where {X,T<:AbstractDict} =
Metadata{X,T}(T(metadatadict(m)))
Base.convert(::Type{Metadata{X,T}}, m::NamedTuple) where {X,T<:NamedTuple} =
Metadata{X,T}(T(m))
Base.convert(::Type{Metadata{X,T}}, m::AbstractDict) where {X,T<:NamedTuple} =
Metadata{X,T}(T(pairs(metadatadict(m))))


function Base.show(io::IO, mime::MIME"text/plain", metadata::Metadata{N}) where N
print(io, "Metadata")
Expand All @@ -96,12 +118,13 @@ end

# Metadata utils

function metadatadict(dict)
symboldict = Dict{Symbol,Any}()
for (k, v) in dict
metadatadict(dict) = metadatadict(DefaultDict, dict)
function metadatadict(::Type{T}, dict) where T
symboldict = T()
for (k, v) in pairs(dict)
symboldict[Symbol(k)] = v
end
symboldict
return symboldict
end

metadata(x) = NoMetadata()
Expand All @@ -110,4 +133,4 @@ units(x) = units(metadata(x))
units(m::NoMetadata) = nothing
units(m::Nothing) = nothing
units(m::Metadata) = get(m, :units, nothing)
units(m::AbstractDict) = get(m, :units, nothing)
units(m::AbstractDict) = get(m, :units, nothing)
30 changes: 28 additions & 2 deletions src/array/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Base.checkbounds(::Type{Bool}, A::AbstractBasicDimArray, d1::IDim, dims::IDim...
Base.checkbounds(A::AbstractBasicDimArray, d1::IDim, dims::IDim...) =
Base.checkbounds(A, dims2indices(A, (d1, dims...))...)


"""
AbstractDimArray <: AbstractBasicArray

Expand Down Expand Up @@ -464,7 +465,7 @@ function DimArray(A::AbstractDimArray;
)
DimArray(data, dims; refdims, name, metadata)
end
DimArray{T}(A::AbstractDimArray; kw...) where T = DimArray(convert.(T, A))
DimArray{T}(A::AbstractDimArray; kw...) where T = DimArray(convert.(T, A); kw...)
DimArray{T}(A::AbstractDimArray{T}; kw...) where T = DimArray(A; kw...)
# We collect other kinds of AbstractBasicDimArray
# to avoid complicated nesting of dims
Expand All @@ -485,7 +486,7 @@ function DimArray(f::Function, dim::Dimension; name=Symbol(nameof(f), "(", name(
DimArray(f.(val(dim)), (dim,); name)
end

DimArray(itr::Base.Generator; kwargs...) = rebuild(collect(itr); kwargs...)
DimArray(itr::Base.Generator; kw...) = rebuild(collect(itr); kw...)

const DimVector = DimArray{T,1} where T
const DimMatrix = DimArray{T,2} where T
Expand All @@ -501,6 +502,31 @@ DimMatrix(A::AbstractMatrix, args...; kw...) = DimArray(A, args...; kw...)
Base.convert(::Type{DimArray}, A::AbstractDimArray) = DimArray(A)
Base.convert(::Type{DimArray{T}}, A::AbstractDimArray) where {T} = DimArray{T}(A)

function Base.convert(
::Type{<:DimArray{T,N,DT,RT,AT,NaT,MeT}}, a::DimArray{S,N}
) where {T,N,S,DT,RT,AT,NaT,MeT}
rebuild(a;
data=convert(AT, parent(a)),
dims=convert(DT, dims(a)),
name=convert(NaT, name(a)),
refdims=RT <: Tuple{} ? () : convert(RT, refdims(a)),
metadata=convert(MeT, metadata(a)),
)
end

# Promote element types
function Base.promote_rule(
a::Type{<:DimArray{T,N,DT,RT,AT,NaT,MeT}}, b::Type{<:DimArray{S,N,DS,RS,AS,NaS,MeS}}
) where {T,S,N,DT,DS,RT,RS,AT,AS,NaT,NaS,MeT,MeS}
A = promote_type(AT, AS)
TS = eltype(A)
D = promote_type(DT, DS)
R = RT <: Tuple{} || RS <: Tuple{} ? Tuple{} : promote_type(RT, RS)
Na = promote_type(NaT, NaS)
M = promote_type(MeT, MeS)
return DimArray{TS,N,D,R,A,Na,M}
end

checkdims(A::AbstractArray{<:Any,N}, dims::Tuple) where N = checkdims(N, dims)
checkdims(::Type{<:AbstractArray{<:Any,N}}, dims::Tuple) where N = checkdims(N, dims)
checkdims(n::Integer, dims::Tuple) = length(dims) == n || _dimlengtherror(n, length(dims))
Expand Down
Loading
Loading