Skip to content

Commit

Permalink
fill out array inteface (#40)
Browse files Browse the repository at this point in the history
* add more tests to array interface

* separate out logical indexing as optional

* fix more iteration and set interfaces

* update doml

* fix compat

* dont test IdentityUnitRange
  • Loading branch information
rafaqz authored Feb 4, 2024
1 parent 1ac3367 commit 596de23
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 36 deletions.
1 change: 1 addition & 0 deletions BaseInterfaces/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Interfaces = "85a1e053-f937-4924-92a5-1367d23b7b87"
[compat]
julia = "1.6"
Interfaces = "0.3"
Test = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
2 changes: 2 additions & 0 deletions BaseInterfaces/src/BaseInterfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module BaseInterfaces

using Interfaces

export Interfaces

export ArrayInterface, DictInterface, IterationInterface, SetInterface

include("iteration.jl")
Expand Down
91 changes: 83 additions & 8 deletions BaseInterfaces/src/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,107 @@ _ndims(::AbstractArray{<:Any,N}) where N = N

array_components = (;
mandatory = (;
type = A -> A isa AbstractArray,
eltype = (
A -> eltype(A) isa Type,
A -> eltype(A) == _eltype(A),
# Everything else is tested later
),
ndims = (
A -> ndims(A) isa Int,
A -> ndims(A) == _ndims(A),
# Everything else is tested later
),
size = (
"size(A) returns a tuple of Integer" => A -> size(A) isa NTuple{<:Any,Integer},
"length of size(A) matches ndims(A)" => A -> length(size(A)) == ndims(A),
),
getindex = (
"Can index with begin/firstinex" => A -> A[begin] isa eltype(A),
"Can index with begin/firstindex" => A -> A[begin] isa eltype(A),
"Can index with end/lastindex" => A -> A[end] isa eltype(A),
"Can index with all indices in `eachindex(A)`" => A -> all(x -> A[x] isa eltype(A), eachindex(A)),
"Can index with multiple dimensions" => A -> A[map(first, axes(A))...] isa eltype(A),
"Can use trailing ones" => A -> A[map(first, axes(A))..., 1, 1, 1] isa eltype(A),
"Can index with CartesianIndex" => A -> A[CartesianIndex(map(first, axes(A))...)] isa eltype(A),
"Can use trailing ones in CartesianIndex" => A -> A[CartesianIndex(map(first, axes(A))..., 1, 1, 1)] isa eltype(A),
),
indexstyle = "IndexStyle returns IndexCartesian or IndexLinear" => A -> IndexStyle(A) in (IndexCartesian(), IndexLinear()),
"Can index with Int for multiple dimensions" => A -> A[map(first, axes(A))...] isa eltype(A),
"Can index with Int for multiple dimensions and trailing ones" => A -> A[map(first, axes(A))..., 1, 1, 1] isa eltype(A),
"Can index with Int for multiple dimensions and trailing colons" => A -> size(A[map(first, axes(A))..., :, :, :]) == (1, 1, 1),
"Can index with CartesianIndex" =>
A -> A[CartesianIndex(map(first, axes(A))...)] isa eltype(A),
"Can index with CartesianIndex and trailing ones" =>
A -> A[CartesianIndex(map(first, axes(A))...), 1, 1, 1] isa eltype(A),
"Can index with CartesianIndices" =>
A -> size(A[CartesianIndices(map(a -> first(a):last(a), axes(A)))]) == size(A),
"Can index with CartesianIndices and trailing ones" =>
A -> size(A[CartesianIndices(map(a -> first(a):last(a), axes(A))), 1, 1, 1]) == size(A),
"Can index with CartesianIndices and trailing colons" =>
A -> size(A[CartesianIndices(map(a -> first(a):last(a), axes(A))), :, :, :]) == (size(A)..., 1, 1, 1),
"Can index with UnitRange" =>
A -> size(A[map(a -> first(a):last(a), axes(A))...]) == size(A),
"Can index with UnitRange and trailing ones" =>
A -> size(A[map(a -> first(a):last(a), axes(A))..., 1, 1, 1]) == size(A),
"Can index with UnitRange and trailing colons" =>
A -> size(A[map(a -> first(a):last(a), axes(A))..., :, :, :]) == (size(A)..., 1, 1, 1),
"Can index with StepRange" =>
A -> size(A[map(a -> first(a):2:last(a), axes(A))...]) == map(a -> length(first(a):2:last(a)), axes(A)),
"Can index with StepRange and trailing ones" =>
A -> size(A[map(a -> first(a):2:last(a), axes(A))..., 1, 1, 1]) == map(a -> length(first(a):2:last(a)), axes(A)),
"Can index with StepRange and trailing colons" =>
A -> size(A[map(a -> first(a):2:last(a), axes(A))..., :, :, :]) == (map(a -> length(first(a):2:last(a)), axes(A))..., 1, 1, 1),
"Can index with a Vector of Int" => A -> begin
i = [first(axes(A, 1)):2:last(axes(A, 1))...]
res = A[i, ntuple(_ -> 1, ndims(A) - 1)...]
size(res) == (length(i),)
end,
"Can index with a Vector of Int32" => A -> begin
i = Int32[first(axes(A, 1)):2:last(axes(A, 1))...]
res = A[i, ntuple(_ -> 1, ndims(A) - 1)...]
size(res) == (length(i),)
end,
"Can index with a Vector of Int with trailing ones" => A -> begin
i = [first(axes(A, 1)):2:last(axes(A, 1))...]
res = A[i, ntuple(_ -> 1, ndims(A) + 1)...]
size(res) == (length(i),)
end,
"Can index with a Vector of Int with trailing colons" => A -> begin
i = [first(axes(A, 1)):2:last(axes(A, 1))...]
res = A[i, ntuple(_ -> :, ndims(A) + 1)...]
size(res) == (length(i), ntuple(i -> size(A, i + 1), ndims(A) + 1)...)
end,
),
indexstyle = "IndexStyle returns IndexCartesian or IndexLinear" =>
A -> IndexStyle(A) in (IndexCartesian(), IndexLinear()),
),
# TODO implement all the optional conditions
optional = (;
logical = (
"Can index with logical indices" => A -> begin
l = [iseven(i) for i in 1:size(A, 1)]
size(A[l, ntuple(_ -> 1, ndims(A) - 1)...]) == (count(l),)
end,
"Can index with logical indices and trailing ones" => A -> begin
l = [iseven(i) for i in 1:size(A, 1)]
size(A[l, ntuple(_ -> 1, ndims(A) + 1)...]) == (count(l),)
end,
"Can index with logical indices and trailing colons" => A -> begin
l = [iseven(i) for i in 1:size(A, 1)]
size(A[l, ntuple(_ -> :, ndims(A) + 1)...]) == (count(l), ntuple(i -> size(A, i + 1), ndims(A) + 1)...)
end,
"Can index with multidimensional logical indices" => A -> begin
l = map(CartesianIndices(A)) do I
iseven(prod(Tuple(I)))
end
size(A[l]) == (count(l),)
end,
"Can index with multidimensional logical indices and trailing ones" => A -> begin
l = map(CartesianIndices(A)) do I
iseven(prod(Tuple(I)))
end
size(A[l, 1, 1, 1]) == (count(l),)
end,
"Can index with multidimensional logical indices and trailing colons" => A -> begin
l = map(CartesianIndices(A)) do I
iseven(prod(Tuple(I)))
end
size(A[l, :, :, :]) == (count(l), 1, 1, 1)
end,
),
setindex! = (
A -> length(A) > 1 || throw(ArgumentError("Test arrays must have more than one element to test setindex!")),
"setindex! can write the first to the last element" =>
Expand Down
51 changes: 33 additions & 18 deletions BaseInterfaces/src/implementations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@

# @implements ArrayInterface Base.LogicalIndex # No getindex

@implements ArrayInterface{:logical} UnitRange [2:10]
@implements ArrayInterface{:logical} StepRange [2:1:10]
@implements ArrayInterface{:logical} Base.OneTo [Base.OneTo(10)]

@implements ArrayInterface UnitRange [2:10]
@implements ArrayInterface StepRange [2:1:10]
@implements ArrayInterface Base.OneTo [Base.OneTo(10)]
@implements ArrayInterface Base.Slice [Base.Slice(100:150)]
@implements ArrayInterface Base.CodeUnits [codeunits("abcde")]
@implements ArrayInterface Base.IdentityUnitRange [Base.IdentityUnitRange(100:150)]
@implements ArrayInterface{(:setindex!,:similar_type,:similar_eltype,:similar_size)} Array [[3, 2], ['a' 'b'; 'n' 'm']]
@implements ArrayInterface{(:setindex!,:similar_type,:similar_size)} BitArray [BitArray([false true; true false])]
@implements ArrayInterface{:setindex!} SubArray [view([7, 2], 1:2)]
@implements ArrayInterface{:setindex!} PermutedDimsArray [PermutedDimsArray([7 2], (2, 1))]
@implements ArrayInterface{:setindex!} Base.ReshapedArray [reshape(view([7, 2], 1:2), 2, 1)]
# @implements ArrayInterface Base.Slice [Base.Slice(100:150)]
# These are breaking unreliably in CI. No idea how this can work sometimes and not others...
#
# Testing ArrayInterface is implemented for Base.IdentityUnitRange
# InterfaceError: test for ArrayInterface :getindex 19 "Can index with a Vector of Int32" threw a BoundsError
# For test object Base.IdentityUnitRange(100:150):

# Error During Test at /home/runner/work/Interfaces.jl/Interfaces.jl/BaseInterfaces/test/runtests.jl:10
# Test threw exception
# Expression: Interfaces.test()
# BoundsError: attempt to access 51-element Base.IdentityUnitRange{UnitRange{Int64}} with indices 100:150 at index [100]
# Stacktrace:
# @implements ArrayInterface Base.IdentityUnitRange [Base.IdentityUnitRange(100:150)]
@implements ArrayInterface{:logical} Base.CodeUnits [codeunits("abcde")]
@implements ArrayInterface{(:logical,:setindex!,:similar_type,:similar_eltype,:similar_size)} Array [[3, 2], ['a' 'b'; 'n' 'm']]
@implements ArrayInterface{(:logical,:setindex!,:similar_type,:similar_size)} BitArray [BitArray([false true; true false])]
@implements ArrayInterface{(:logical,:setindex!)} SubArray [view([7, 2], 1:2)]
@implements ArrayInterface{(:logical,:setindex!)} PermutedDimsArray [PermutedDimsArray([7 2], (2, 1))]
@implements ArrayInterface{(:logical,:setindex!)} Base.ReshapedArray [reshape(view([7, 2], 1:2), 2, 1)]

@implements DictInterface{:setindex!} Dict [Arguments(d=Dict(:a => 1, :b => 2), k=:c, v=3)]
@implements DictInterface{:setindex!} IdDict [Arguments(d=IdDict(:a => 1, :b => 2), k=:c, v=3)]
Expand All @@ -34,19 +45,23 @@ end
else
@implements IterationInterface{:indexing} NamedTuple [(a=1, b=2, c=3, d=4)] # No reverse on 1.6
end
# @implements IterationInterface{(:reverse,:indexing)} String
# @implements IterationInterface{(:reverse,:indexing)} Pair
# @implements IterationInterface{(:reverse,:indexing)} Number
# @implements IterationInterface{(:reverse,:indexing)} Base.EachLine
@implements IterationInterface{(:reverse,:indexing)} String ["abcdefg"]
@implements IterationInterface{(:reverse,:indexing)} Pair [:a => 2]
@implements IterationInterface Number [1, 1.0, 1.0f0, UInt(8), false]
@implements IterationInterface{:reverse} Base.Generator [(i for i in 1:5), (i for i in 1:5)]
# @implements IterationInterface Set
# @implements IterationInterface BitSet
# @implements IterationInterface{(:reverse,:indexing)} Base.EachLine [eachline(joinpath(dirname(pathof(BaseInterfaces)), "implementations.jl"))]

@implements IterationInterface Set [Set((1, 2, 3, 4))]
@implements IterationInterface BitSet [BitSet((1, 2, 3, 4))]
@implements IterationInterface Dict [Dict("a" => 2, :b => 3.0)]
@implements IterationInterface Base.EnvDict [Arguments(d=Base.EnvDict())]
@implements IterationInterface Base.ImmutableDict [Arguments(d=Base.ImmutableDict(:a => 1, :b => 2))]
# @implements IterationInterface IdDict
# @implements IterationInterface Dict
# @implements IterationInterface WeakKeyDict

# TODO add grouping to reduce the number of options
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} Set [Set((1, 2))]
@implements SetInterface{(:copy,:empty,:emptymutable,:hasfastin,:setdiff,:intersect,:union,:empty!,:delete!,:push!,:copymutable,:sizehint!)} BitSet [BitSet((1, 2))]
@implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Base.KeySet [Base.KeySet(Dict(:a=>1, :b=>2))]
@implements SetInterface{(:empty,:hasfastin,:intersect,:union,:sizehint!)} Base.IdSet (s = Base.IdSet(); push!(s, "a"); push!(s, "b"); [s])

9 changes: 4 additions & 5 deletions BaseInterfaces/src/iteration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ EltypeUnknown() (none)
# that implement the interface.
mandatory = (
iterate = (
x -> !isempty(x),
x -> !isnothing(iterate(x)),
x -> !isnothing(iterate(iterate(x))),
x -> iterate(x) isa Tuple,
x -> iterate(x, last(iterate(x))) isa Tuple,
"test iterator is not empty" => x -> !isempty(x),
"iterate does not return `nothing`" => x -> !isnothing(iterate(x)),
"iterate returns a Tuple" => x -> iterate(x) isa Tuple{<:Any,<:Any},
"iterate returns a Tuple" => x -> iterate(x, last(iterate(x))) isa Union{Nothing,Tuple{<:Any,<:Any}},
),
isiterable = x -> Base.isiterable(typeof(x)),
eltype = x -> begin
Expand Down
3 changes: 3 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ authors = ["Rafael Schouten <rafaelschouten@gmail.com>"]
version = "0.3.0"

[compat]
Aqua = "0.8"
Documenter = "1"
Test = "1"
julia = "1.6"

[extras]
Expand Down
6 changes: 3 additions & 3 deletions src/implements.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ implements(T::Type{<:Interface}, obj) = implements(T, typeof(obj))
implements(::Type{<:Interface}, obj::Type) = false

"""
@implements(interface, objtype)
@implements(interface, objtype, test_objects)
Declare that an interface implements an interface, or multipleinterfaces.
Expand All @@ -32,8 +32,8 @@ Here we implement the IterationInterface for Base julia, indicating with
`(:indexing, :reverse)` that our object can be indexed and works with `Iterators.reverse`:
```julia
using BaseInterfaces
@implements BaseInterfaces.IterationInterface{(:indexing,:reverse)} MyObject
using BaseInterfaces, Interfaces
@implements BaseInterfaces.IterationInterface{(:indexing,:reverse)} MyObject [MyObject(1:10), MyObject(10:-1:1)]
```
"""
macro implements(interface, objtype, test_objects)
Expand Down
4 changes: 2 additions & 2 deletions src/test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,9 @@ function _test(T::Type{<:Interface}, O::Type, objs::TestObjectWrapper;
mandatory_results = _test(T, components(T).mandatory, objs)
optional_results = _test(T, optional, objs)
if show
_showresults(stdout, mandatory_results, "Mandatory components")
_showresults(stdout, mandatory_results, "\nMandatory components")
if !isempty(optional_results)
_showresults(stdout, optional_results, "Optional components")
_showresults(stdout, optional_results, "\nOptional components")
end
end
return all(_bool(mandatory_results)) && all(_bool(optional_results))
Expand Down

0 comments on commit 596de23

Please sign in to comment.