Skip to content

Commit 67b766a

Browse files
committed
Add ConformanceTests._implements trait
... to allow fine control over which things to test in the conformance tests and which not.
1 parent e877196 commit 67b766a

File tree

9 files changed

+93
-0
lines changed

9 files changed

+93
-0
lines changed

src/ConformanceTests.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,63 @@ It is supposed to be implemented in the src file of the respective type.
4343
function generate_element end
4444

4545

46+
###############################################################################
47+
#
48+
# `implements` trait
49+
#
50+
###############################################################################
51+
52+
# Calling `_implements(T, f)` checks whether a "sensible" method for the unary
53+
# function `f` is implemented for inputs of type `T`. The argument order is
54+
# meant to be similar to e.g. `isa`, and thus indicates `T implements f`.
55+
#
56+
# For example, `_implements(MyRingElem, is_unit)` should return true if
57+
# invoking `is_unit` on elements of type `MyRingElem` is supported.
58+
#
59+
# The generic fallback uses `hasmethod`. However, this may return `true` in
60+
# cases where it shouldn't, as we often provide generic methods for that rely
61+
# on other methods being implemented -- either for the same type, or for types
62+
# derived from it. For example the `is_nilpotent(::PolyElem{T})` method needs
63+
# `is_nilpotent(::T)` in order to work.
64+
#
65+
# To reflect this, additional `_implements` methods need to be provided.
66+
# We currently do this for at least the following functions:
67+
# - factor
68+
# - is_irreducible
69+
# - is_nilpotent
70+
# - is_squarefree
71+
# - is_unit
72+
# - is_zero_divisor
73+
#
74+
_implements(::Type{T}, f::Any) where {T} = hasmethod(f, Tuple{T})
75+
76+
# Alternatively, the first argument can be a concrete object. By default we
77+
# then redispatch to the type based version. But one may also choose to
78+
# implement custom methods for this: certain operations will only work for
79+
# *some* instances. E.g. for `Z/nZ` it may happen that for `n` a prime we can
80+
# perform a certain operation, but not if `n` is composite.
81+
#
82+
# In that case the recommendation is that `_implements` invoked on the type
83+
# returns `false`, but invoked on a concrete instance of a type, it may use
84+
# specifics of the instance to also return `true` if appropriate.
85+
function _implements(x::T, f::Any) where {T}
86+
@assert !(x isa Type) # paranoia
87+
return _implements(T, f)
88+
end
89+
90+
# helper for `_implements` which checks if `f` has a method explicitly for
91+
# a concrete type `T` (i.e. not a generic method that can be specialized to `T`
92+
# but really one that is implement for `T` and `T` only).
93+
function _implements_directly(::Type{T}, f::Any) where {T}
94+
isconcretetype(T) || return false # TODO: drop this?
95+
meth = methods(f, Tuple{T})
96+
# TODO: deal with type parameters: if `T` is `FreeAssociativeAlgebraElem{ZZRingElem}`
97+
# and `f` has a method for `FreeAssociativeAlgebraElem` then we should still consider
98+
# this a match.
99+
return any(m -> m.sig == Tuple{typeof(f), T}, meth)
100+
end
101+
102+
46103
###############################################################################
47104
#
48105
# The following function stubs' actual implementations are in the folder `ext/TestExt/`.

src/FreeAssociativeAlgebra.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ function is_unit(a::FreeAssociativeAlgebraElem{T}) where T
136136
end
137137
end
138138

139+
ConformanceTests._implements(::Type{FreeAssociativeAlgebraElem{T}}, f::typeof(is_unit)) where T = is_domain_type(T) || _implements_directly(T, f)
140+
139141
###############################################################################
140142
#
141143
# Hashing

src/MPoly.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,8 @@ function is_unit(f::T) where {T <: MPolyRingElem}
437437
return constant_term_is_unit # handles the case that there is no constant term
438438
end
439439

440+
ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
441+
440442
function content(a::MPolyRingElem{T}) where T <: RingElement
441443
z = zero(coefficient_ring(a))
442444
for c in coefficients(a)
@@ -452,12 +454,16 @@ function is_nilpotent(f::T) where {T <: MPolyRingElem}
452454
return all(is_nilpotent, coefficients(f))
453455
end
454456

457+
ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
458+
455459

456460
function is_zero_divisor(x::MPolyRingElem{T}) where T <: RingElement
457461
is_domain_type(T) && return is_zero(x)
458462
return is_zero_divisor(content(x))
459463
end
460464

465+
ConformanceTests._implements(::Type{MPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)
466+
461467
function is_zero_divisor_with_annihilator(a::MPolyRingElem{T}) where T <: RingElement
462468
f, b = is_zero_divisor_with_annihilator(content(a))
463469
return f, parent(a)(b)

src/MatRing.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,15 @@ is_unit(a::MatRingElem{T}) where T <: RingElement = is_unit(det(a))
5454

5555
is_unit(a::MatRingElem{T}) where T <: FieldElement = rank(a) == degree(a)
5656

57+
ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_unit)) where {T <: RingElement} = _implements(T, is_unit)
58+
5759
# proof over a commutative ring: use adj(A)*A = det(A)*I = A*adj(A)
5860
is_zero_divisor(a::MatRingElem{T}) where T <: RingElement = is_zero_divisor(det(a))
5961

6062
is_zero_divisor(a::MatRingElem{T}) where T <: FieldElement = rank(a) != degree(a)
6163

64+
ConformanceTests._implements(::Type{MatRingElem{T}}, ::typeof(is_zero_divisor)) where {T <: RingElement} = _implements(T, is_zero_divisor)
65+
6266
function is_zero_divisor_with_annihilator(a::MatRingElem{T}) where T <: RingElement
6367
f, b = is_zero_divisor_with_annihilator(det(a))
6468
throw(NotImplementedError(:adj, a)) #return f, b*adj(A)

src/NCRings.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ function is_nilpotent(a::T) where {T <: NCRingElement}
175175
throw(NotImplementedError(:is_nilpotent, a))
176176
end
177177

178+
ConformanceTests._implements(::Type{T}, f::typeof(is_nilpotent)) where {T <: NCRingElement} = is_domain_type(T) || _implements_directly(T, f)
179+
178180

179181
###############################################################################
180182
#

src/Poly.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ function is_unit(f::T) where {T <: PolyRingElem}
243243
return true
244244
end
245245

246+
ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
247+
246248
function is_nilpotent(f::T) where {T <: PolyRingElem}
247249
# Makes essential use of the fact that sum of 2 nilpotents is nilpotent.
248250
# This is true when the coeffs are commutative.
@@ -252,9 +254,12 @@ function is_nilpotent(f::T) where {T <: PolyRingElem}
252254
return all(is_nilpotent, coefficients(f))
253255
end
254256

257+
ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
255258

256259
is_zero_divisor(a::PolynomialElem) = is_zero_divisor(content(a))
257260

261+
ConformanceTests._implements(::Type{PolynomialElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)
262+
258263
function is_zero_divisor_with_annihilator(a::PolyRingElem{T}) where T <: RingElement
259264
f, b = is_zero_divisor_with_annihilator(content(a))
260265
return f, parent(a)(b)

src/algorithms/GenericFunctions.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ function is_zero_divisor(a::T) where T <: RingElement
429429
return is_zero(a) && !is_trivial(parent(a))
430430
end
431431

432+
ConformanceTests._implements(::Type{T}, f::typeof(is_zero_divisor)) where {T} = is_domain_type(T) || _implements_directly(T, f)
433+
432434
@doc raw"""
433435
is_zero_divisor_with_annihilator(a::T) where T <: RingElement
434436
@@ -456,6 +458,8 @@ function factor(a)
456458
throw(NotImplementedError(:factor, a))
457459
end
458460

461+
ConformanceTests._implements(::Type{T}, f::typeof(factor)) where {T} = _implements_directly(T, f)
462+
459463
@doc raw"""
460464
factor_squarefree(a::T) where T <: RingElement -> Fac{T}
461465
@@ -466,6 +470,8 @@ function factor_squarefree(a)
466470
throw(NotImplementedError(:factor_squarefree, a))
467471
end
468472

473+
ConformanceTests._implements(::Type{T}, f::typeof(factor_squarefree)) where {T} = _implements_directly(T, f)
474+
469475
@doc raw"""
470476
is_irreducible(a::RingElement)
471477
@@ -479,6 +485,8 @@ function is_irreducible(a)
479485
return length(af) == 1 && all(isone, values(af.fac))
480486
end
481487

488+
ConformanceTests._implements(::Type{T}, ::typeof(is_irreducible)) where {T} = _implements(T, is_unit) && _implements(T, factor)
489+
482490
@doc raw"""
483491
is_squarefree(a::RingElement)
484492
@@ -493,3 +501,4 @@ function is_squarefree(a)
493501
return all(isone, values(af.fac))
494502
end
495503

504+
ConformanceTests._implements(::Type{T}, ::typeof(is_squarefree)) where {T} = _implements(T, is_unit) && _implements(T, factor_squarefree)

src/algorithms/LaurentPoly.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ function is_nilpotent(f::T) where {T <: LaurentPolyRingElem}
159159
return is_nilpotent(f.poly);
160160
end
161161

162+
ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
163+
164+
ConformanceTests._implements(::Type{LaurentPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
165+
162166

163167
###############################################################################
164168
#

src/generic/LaurentMPoly.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,18 @@ function is_unit(f::T) where {T <: LaurentMPolyRingElem}
126126
return unit_seen
127127
end
128128

129+
ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_unit)) where T = _implements(T, is_unit) && _implements(T, is_nilpotent)
129130

130131
function is_nilpotent(f::T) where {T <: LaurentMPolyRingElem}
131132
return is_nilpotent(f.mpoly);
132133
end
133134

135+
ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_nilpotent)) where T = _implements(T, is_nilpotent)
134136

135137
is_zero_divisor(p::LaurentMPolyWrap) = is_zero_divisor(p.mpoly)
136138

139+
ConformanceTests._implements(::Type{LaurentMPolyRingElem{T}}, ::typeof(is_zero_divisor)) where T = _implements(T, is_zero_divisor)
140+
137141
function is_zero_divisor_with_annihilator(p::LaurentMPolyWrap)
138142
f, b = is_zero_divisor_with_annihilator(p.mpoly)
139143
return f, LaurentMPolyWrap(parent(p), b)

0 commit comments

Comments
 (0)