diff --git a/BaseInterfaces/src/BaseInterfaces.jl b/BaseInterfaces/src/BaseInterfaces.jl index 5b966bf..40d254f 100644 --- a/BaseInterfaces/src/BaseInterfaces.jl +++ b/BaseInterfaces/src/BaseInterfaces.jl @@ -9,13 +9,13 @@ export Interfaces export ArrayInterface, DictInterface, IterationInterface, SetInterface include("interfaces/iteration.jl") -include("interfaces/dict.jl") include("interfaces/set.jl") +include("interfaces/dict.jl") include("interfaces/array.jl") include("implementations/iteration.jl") -include("implementations/dict.jl") include("implementations/set.jl") +include("implementations/dict.jl") include("implementations/array.jl") end diff --git a/BaseInterfaces/src/implementations/iteration.jl b/BaseInterfaces/src/implementations/iteration.jl index 422a353..5268fcf 100644 --- a/BaseInterfaces/src/implementations/iteration.jl +++ b/BaseInterfaces/src/implementations/iteration.jl @@ -1,8 +1,3 @@ - -@implements IterationInterface{(:reverse,:indexing)} UnitRange [1:5, -2:2] -@implements IterationInterface{(:reverse,:indexing)} StepRange [1:2:10, 20:-10:-20] -@implements IterationInterface{(:reverse,:indexing)} Array [[1, 2, 3, 4], [:a :b; :c :d]] -@implements IterationInterface{(:reverse,:indexing)} Tuple [(1, 2, 3, 4)] @static if VERSION >= v"1.9.0" @implements IterationInterface{(:reverse,:indexing)} NamedTuple [(a=1, b=2, c=3, d=4)] else @@ -13,12 +8,3 @@ end @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{(: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 WeakKeyDict - diff --git a/BaseInterfaces/src/interfaces/array.jl b/BaseInterfaces/src/interfaces/array.jl index 0d3289a..b755548 100644 --- a/BaseInterfaces/src/interfaces/array.jl +++ b/BaseInterfaces/src/interfaces/array.jl @@ -201,4 +201,4 @@ array_components = (; _wrappertype(A) = Base.typename(typeof(A)).wrapper -@interface ArrayInterface AbstractArray array_components "Base Julia AbstractArray interface" +@interface ArrayInterface <: IterationInterface{(:reverse,:indexing)} AbstractArray array_components "Base Julia AbstractArray interface" diff --git a/BaseInterfaces/src/interfaces/dict.jl b/BaseInterfaces/src/interfaces/dict.jl index 8053e76..f309905 100644 --- a/BaseInterfaces/src/interfaces/dict.jl +++ b/BaseInterfaces/src/interfaces/dict.jl @@ -1,5 +1,5 @@ -@interface DictInterface AbstractDict ( # <: CollectionInterface +@interface DictInterface <: IterationInterface{(:reverse,)} AbstractDict ( # <: CollectionInterface mandatory = (; iterate = "AbstractDict follows the IterationInterface" => a -> Interfaces.test(IterationInterface, a.d; show=false) && first(iterate(a.d)) isa Pair, eltype = "eltype is a Pair" => a -> eltype(a.d) <: Pair, diff --git a/BaseInterfaces/src/interfaces/set.jl b/BaseInterfaces/src/interfaces/set.jl index b8d754e..6d88e5d 100644 --- a/BaseInterfaces/src/interfaces/set.jl +++ b/BaseInterfaces/src/interfaces/set.jl @@ -67,4 +67,4 @@ set_components = (; ) ) -@interface SetInterface AbstractSet set_components "The `AbstractSet` interface" +@interface SetInterface <: IterationInterface AbstractSet set_components "The `AbstractSet` interface" diff --git a/BaseInterfaces/test/runtests.jl b/BaseInterfaces/test/runtests.jl index 65bbc5f..81bdeeb 100644 --- a/BaseInterfaces/test/runtests.jl +++ b/BaseInterfaces/test/runtests.jl @@ -6,6 +6,22 @@ using Test @implements SetInterface{(:empty,:emptymutable,:hasfastin,:intersect,:union,:sizehint!)} Test.GenericSet [Test.GenericSet(Set((1, 2)))] @implements DictInterface Test.GenericDict [Arguments(d=Test.GenericDict(Dict(:a => 1, :b => 2)), k=:c, v=3)] +@testset "inheritance" begin + # Inherited interfaces are stored in the second parameter of the supertype + @test supertype(ArrayInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse, :indexing)}} + @test supertype(DictInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse,)}} + + @test Interfaces.implements(ArrayInterface, Array) + @test Interfaces.implements(IterationInterface, Array) + @test !Interfaces.implements(DictInterface, Array) + @test !Interfaces.implements(SetInterface, Array) + @test Interfaces.implements(IterationInterface{(:reverse,:indexing)}, Array) + + @test Interfaces.implements(IterationInterface, Dict) + @test !Interfaces.implements(IterationInterface{:indexing}, Dict) + @test !Interfaces.implements(ArrayInterface, Dict) +end + # Test all interfaces @test Interfaces.test() diff --git a/src/implements.jl b/src/implements.jl index fc6e4b7..54436f6 100644 --- a/src/implements.jl +++ b/src/implements.jl @@ -16,7 +16,10 @@ all the mandatory components of the interace are implemented. """ function implements end implements(T::Type{<:Interface}, obj) = implements(T, typeof(obj)) -implements(::Type{<:Interface}, obj::Type) = false +implements(T::Type{<:Interface}, obj::Type) = inherits(T, obj) + +function inherits end +inherits(::Type{<:Interface}, obj) = false """ @implements(interface, objtype, test_objects) @@ -39,6 +42,25 @@ using BaseInterfaces, Interfaces macro implements(interface, objtype, test_objects) _implements_inner(interface, objtype, test_objects) end + +inherited_type(::Type{<:Interface{<:Any,Inherits}}) where Inherits = Inherits +inherited_basetype(::Type{T}) where T = basetypeof(inherited_type(T)) + +inherited_optional_keys(::Type{<:Interface{Optional}}) where Optional = Optional +function inherited_optional_keys(::Type{T}) where T<:Union + map(propertynames(T)) do pn + inherited_optional_keys(getproperty(T, pn)) + end +end +inherited_optional_keys(::Type) = () + +function inherited_interfaces(::Type{T}) where T <: Union + map(propertynames(T)) do pn + t = getproperty(T, pn) + inherited_optional_keys(t) + end +end + function _implements_inner(interface, objtype, test_objects; show=false) if interface isa Expr && interface.head == :curly interfacetype = interface.args[1] @@ -58,8 +80,16 @@ function _implements_inner(interface, objtype, test_objects; show=false) end # Define a `implements` trait stating that `objtype` implements `interface` $Interfaces.implements(::Type{<:$interfacetype}, ::Type{<:$objtype}) = true + # Define implements with user-specified `Options` to check $Interfaces.implements(T::Type{<:$interfacetype{Options}}, O::Type{<:$objtype}) where Options = $Interfaces._all_in(Options, $Interfaces.optional_keys(T, O)) + # Define a method using `inherited_basetype` to generate the type that + # will dispatch when another Interface inherits this Interface. + function $Interfaces.inherits(::Type{T}, ::Type{<:$objtype}) where {T<:$Interfaces.inherited_basetype($interfacetype)} + implementation_keys = $Interfaces.inherited_optional_keys($Interfaces.inherited_type($interfacetype)) + user_keys = $Interfaces._as_tuple($Interfaces._user_optional_keys(T)) + return all(map(in(implementation_keys), user_keys)) + end # Define which optional components the object implements $Interfaces.optional_keys(::Type{<:$interfacetype}, ::Type{<:$objtype}) = $optional_keys $Interfaces.test_objects(::Type{<:$interfacetype}, ::Type{<:$objtype}) = $test_objects @@ -67,12 +97,27 @@ function _implements_inner(interface, objtype, test_objects; show=false) end |> esc end +_user_optional_keys(::Type{<:Interface{Options}}) where Options = Options +_user_optional_keys(::Type{<:Interface}) = () + _all_in(items::Tuple, collection) = all(map(in(collection), items)) _all_in(item::Symbol, collection) = in(item, collection) struct Implemented{T<:Interface} end struct NotImplemented{T<:Interface} end +function basetypeof(::Type{T}) where T + if T isa Union + types = map(propertynames(T)) do pn + t = getproperty(T, pn) + getfield(parentmodule(t), nameof(t)) + end + Union{types...} + else + getfield(parentmodule(T), nameof(T)) + end +end + """ implemented_trait(T::Type{<:Interface}, obj) implemented_trait(T::Type{<:Interface{Option}}, obj) diff --git a/src/interface.jl b/src/interface.jl index 6c0cd82..d89c17c 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -6,7 +6,7 @@ Abstract supertype for all Interfaces.jl interfaces. Components is an `Tuple` of `Symbol`. """ -abstract type Interface{Components} end +abstract type Interface{Components,Inherits} end """ optional_keys(T::Type{<:Interface}, O::Type) @@ -21,6 +21,24 @@ optional_keys(T::Type{<:Interface}) = keys(components(T).optional) mandatory_keys(T::Type{<:Interface}, args...) = keys(components(T).mandatory) +function _flatten_inheritance(::Type{T}) where T + t = if T isa Union + types = map(propertynames(T)) do pn + _flatten_inheritance(getproperty(T, pn)) + end + Union{T,types...} + else + T + end + return t +end +function _flatten_inheritance( + ::Type{T} +) where T<:Interface{Options,Inherited} where {Options,Inherited} + t = Inherited <: Nothing ? T : Union{T,Inherited} + return t +end + """ test_objects(T::Type{<:Interface}, O::Type) @@ -67,29 +85,51 @@ description = "A description of the interface" @interface MyInterface Any components description ``` """ -macro interface(interface::Symbol, type, components, description) +macro interface(interface_expr, type, components, description) + _error(interface_expr) = throw(ArgumentError("$interface_expr not recognised as an interface type.")) + + interface_expr = if interface_expr isa Symbol + interface_type = interface_expr + :(abstract type $interface_type{Components,Inherited} <: $Interfaces.Interface{Components,Nothing} end) + else + interface_expr.head == :<: || _error(interface_expr) + interface_type = interface_expr.args[1] + inherits_expr = interface_expr.args[2] + if inherits_expr isa Expr && inherits_expr.head == :curly + inherits_type = inherits_expr + :(abstract type $interface_type{Components,Inherited} <: $Interfaces.Interface{Components,$Interfaces._flatten_inheritance($inherits_type)} end) + elseif inherits_expr isa Symbol + inherits_type = inherits_expr + :(abstract type $interface_type{Components,Inherited} <: $Interfaces.Interface{Components,$inherits_type} end) + else + _error(interface_expr) + end + end + quote @assert $type isa Type @assert $components isa NamedTuple{(:mandatory,:optional)} @assert $description isa String # Define the interface type (should it be concrete?) - abstract type $interface{Components} <: $Interfaces.Interface{Components} end + $interface_expr # Define the interface component methods - $Interfaces.requiredtype(::Type{<:$interface}) = $type - $Interfaces.components(::Type{<:$interface}) = $components - $Interfaces.description(::Type{<:$interface}) = $description + $Interfaces.requiredtype(::Type{<:$interface_type}) = $type + $Interfaces.components(::Type{<:$interface_type}) = $components + $Interfaces.description(::Type{<:$interface_type}) = $description # Generate a docstring for the interface let description=$description, - interfacesym=$(QuoteNode(interface)), - m_keys=$Interfaces.mandatory_keys($interface), - o_keys=$Interfaces.optional_keys($interface) + interface_sym=$(QuoteNode(interface_type)), + m_keys=$Interfaces.mandatory_keys($interface_type), + o_keys=$Interfaces.optional_keys($interface_type) @doc """ - $(" ") $interfacesym + $(" ") $interface_sym An Interfaces.jl `Interface` with mandatory components `$m_keys` and optional components `$o_keys`. $description - """ $interface + """ $interface_type end end |> esc end + +_namedtuple_expr_err() = error("components must be defined in-line in the macro with both `mandatory` and `optional` fields, not passed as a variable. E.g. (; mandatory=(; x=x_predicate), optional=(; y=y_predicate))") diff --git a/src/test.jl b/src/test.jl index 2d4549f..f1bdb0a 100644 --- a/src/test.jl +++ b/src/test.jl @@ -72,10 +72,9 @@ test(mod::Module; kw...) = _test_module_implements(Any, mod; kw...) test(T::Type{<:Interface}; kw...) = _test_module_implements(Type{_check_no_options(T)}, nothing; kw...) -function _check_no_options(T) - T isa UnionAll || throw(ArgumentError("Interface options not accepted for more than one implementation")) - return T -end +_check_no_options(T::Type) = T +_check_no_options(::Type{<:Interface{Keys}}) where Keys = + throw(ArgumentError("Interface options not accepted for more than one implementation")) # Here we test all the `implements` methods in `methodlist` that were defined in `mod`. # Basically we are using the `implements` method table as the global state of all # available implementations. @@ -99,7 +98,11 @@ function _test_module_implements(T, mod; show=true, kw...) t = b.parameters[2].var.ub t isa UnionAll || return nothing, true - interface = t.body.name.wrapper + if t.body isa UnionAll + interface = t.body.body.name.wrapper + else + interface = t.body.name.wrapper + end implementation = b.parameters[3].var.ub implementation == Any && return nothing, true @@ -141,7 +144,7 @@ function test(::Type{T}; show=true, kw...) where T results = map(methodlist) do m t = m.sig.parameters[2].var.ub t isa UnionAll || return true - interface = t.body.name.wrapper + interface = t.body.body.name.wrapper # If T implements it, test that if implements(interface, T) interface, test(interface, T; show, kw...)