diff --git a/src/iteration.jl b/src/iteration.jl index d8410ed..f279b54 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -63,8 +63,10 @@ abstract type TreeIterator{T} end _iterator_eltype(::NodeTypeUnknown) = EltypeUnknown() _iterator_eltype(::HasNodeType) = HasEltype() +Base.IteratorEltype(::Type{<:TreeIterator{Union{}}}) = throw(not_supported_exc) Base.IteratorEltype(::Type{<:TreeIterator{T}}) where {T} = _iterator_eltype(NodeType(T)) +Base.eltype(::Type{<:TreeIterator{Union{}}}) = throw(not_supported_exc) Base.eltype(::Type{<:TreeIterator{T}}) where {T} = nodetype(T) Base.eltype(ti::TreeIterator) = eltype(typeof(ti)) diff --git a/src/traits.jl b/src/traits.jl index 6a745ff..bfd77dc 100644 --- a/src/traits.jl +++ b/src/traits.jl @@ -1,3 +1,4 @@ +const not_supported_exc = ArgumentError("not supported") """ ParentLinks(::Type{T}) @@ -42,6 +43,7 @@ the tree structure and cannot be inferred through a single node. """ struct ImplicitParents <: ParentLinks; end +ParentLinks(::Type{Union{}}) = throw(not_supported_exc) ParentLinks(::Type) = ImplicitParents() ParentLinks(tree) = ParentLinks(typeof(tree)) @@ -84,6 +86,7 @@ from the tree structure. """ struct ImplicitSiblings <: SiblingLinks; end +SiblingLinks(::Type{Union{}}) = throw(not_supported_exc) SiblingLinks(::Type) = ImplicitSiblings() SiblingLinks(tree) = SiblingLinks(typeof(tree)) @@ -126,6 +129,7 @@ class of indexable trees consisting of arrays. """ struct NonIndexedChildren <: ChildIndexing end +ChildIndexing(::Type{Union{}}) = throw(not_supported_exc) ChildIndexing(::Type) = NonIndexedChildren() ChildIndexing(node) = ChildIndexing(typeof(node)) @@ -143,6 +147,7 @@ If the `childrentype` can be inferred from the type of the node alone, the type **OPTIONAL**: In most cases, [`childtype`](@ref) is used instead. If `childtype` is not defined it will fall back to `eltype ∘ childrentype`. """ +childrentype(::Type{Union{}}) = throw(not_supported_exc) childrentype(::Type{T}) where {T} = Base._return_type(children, Tuple{T}) childrentype(node) = typeof(children(node)) @@ -159,6 +164,7 @@ If `childtype` can be inferred from the type of the node alone, the type `::Type can be type-stable. If `childrentype` is defined and can be known from the node type alone, this function will fall back to `eltype(childrentype(T))`. If this gives a correct result it's not necessary to define `childtype`. """ +childtype(::Type{Union{}}) = throw(not_supported_exc) childtype(::Type{T}) where {T} = eltype(childrentype(T)) childtype(node) = eltype(childrentype(node)) @@ -172,6 +178,7 @@ traversal is type stable. **OPTIONAL**: Type inference is used to attempt to """ +childstatetype(::Type{Union{}}) = throw(not_supported_exc) childstatetype(::Type{T}) where {T} = Iterators.approx_iter_type(childrentype(T)) childstatetype(node) = childstatetype(typeof(node)) @@ -204,6 +211,7 @@ type. """ struct NodeTypeUnknown <: NodeType end +NodeType(::Type{Union{}}) = throw(not_supported_exc) NodeType(::Type) = NodeTypeUnknown() NodeType(node) = NodeType(typeof(node)) @@ -214,5 +222,6 @@ NodeType(node) = NodeType(typeof(node)) Returns a type which must be a parent type of all nodes in the tree connected to `node`. This can be used to, for example, specify the `eltype` of any `TreeIterator` on `node`. """ +nodetype(::Type{Union{}}) = throw(not_supported_exc) nodetype(::Type) = Any nodetype(node) = nodetype(typeof(node)) diff --git a/test/runtests.jl b/test/runtests.jl index 37f96c5..b6a6b89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,10 @@ using AbstractTrees, Test using Aqua +if Base.VERSION >= v"1.9" + # tests use `parentmodule(::Method)`, only supported on v1.9 and up + @testset "Traits" begin include("traits.jl") end +end @testset "Builtins" begin include("builtins.jl") end @testset "Custom tree types" begin include("trees.jl") end if Base.VERSION >= v"1.6" diff --git a/test/traits.jl b/test/traits.jl new file mode 100644 index 0000000..c06c61c --- /dev/null +++ b/test/traits.jl @@ -0,0 +1,75 @@ +module TestTraits + +using AbstractTrees +using Test + +function is_owned_by(m::Module, n::Module) + ret = false + while m != Main + if m == n + ret = true + break + end + m = parentmodule(m) + end + ret +end + +function is_owned_by(m::Method, n::Module) + is_owned_by(parentmodule(m), n) +end + +function we_own_the_method(m::Method) + is_owned_by(m, AbstractTrees) +end + +const traits = ( + ParentLinks, SiblingLinks, ChildIndexing, childrentype, childtype, AbstractTrees.childstatetype, NodeType, nodetype, +) + +const base_traits = ( + eltype, Base.IteratorEltype, +) + +struct T end + +for func ∈ traits + f = nameof(func) + @eval begin + function AbstractTrees.$f(::Type{<:T}) + # This method should not ever get called, it just serves to test dispatch/type piracy. + throw(ArgumentError("this is not the method you're looking for")) + end + end +end + +for func ∈ base_traits + f = nameof(func) + @eval begin + function Base.$f(::Type{<:AbstractTrees.TreeIterator{<:T}}) + # This method should not ever get called, it just serves to test dispatch/type piracy. + throw(ArgumentError("this is not the method you're looking for")) + end + end +end + +@testset "Traits" begin + @testset "traits should not make dependents vulnerable to commiting type piracy" begin + @testset "AbstractTrees traits" begin + @testset "func: $func" for func ∈ traits + arg = Union{} + @test_throws Exception func(arg) + @test all(we_own_the_method, methods(func, Tuple{Type{arg}})) + end + end + @testset "Base traits" begin + @testset "func: $func" for func ∈ base_traits + arg = AbstractTrees.TreeIterator{Union{}} + @test_throws Exception func(arg) + @test all(we_own_the_method, methods(func, Tuple{Type{arg}})) + end + end + end +end + +end