-
Notifications
You must be signed in to change notification settings - Fork 4
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
Multiple Inheritance #43
base: main
Are you sure you want to change the base?
Changes from all commits
bad0f6a
3d64d25
ac11070
54210a4
1dfc7f2
d8dcbb1
692b95b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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,)}} | ||||||||||
Comment on lines
+11
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in case we define more interfaces in the future and the second parameter is a union, this seems more robust? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also why not just test |
||||||||||
|
||||||||||
@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() | ||||||||||
|
||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,10 @@ | |
""" | ||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not qualified to review the exact implem but there are lots of codecov misses here |
||
inherits(::Type{<:Interface}, obj) = false | ||
|
||
""" | ||
@implements(interface, objtype, test_objects) | ||
|
@@ -39,6 +42,25 @@ | |
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,21 +80,44 @@ | |
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 | ||
nothing | ||
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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ | |
|
||
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 @@ | |
|
||
mandatory_keys(T::Type{<:Interface}, args...) = keys(components(T).mandatory) | ||
|
||
function _flatten_inheritance(::Type{T}) where T | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here for the codecov misses, is it due to macros? |
||
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 @@ | |
@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))") | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,10 +72,9 @@ | |
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")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does that mean? |
||
# 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 @@ | |
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 @@ | |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recently discovered |
||
# If T implements it, test that | ||
if implements(interface, T) | ||
interface, test(interface, T; show, kw...) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interface definitions are becoming fairly long to put everything on a single line.
I remember we had discussed defining the documentation somewhere else and just giving the macro a
String
variable, but for some reason it didn't work?