use std::fmt::{self, Display};
use crate::{SetRelation, VersionSet};
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Term<VS: VersionSet> {
Positive(VS),
Negative(VS),
}
impl<VS: VersionSet> Term<VS> {
pub(crate) fn any() -> Self {
Self::Negative(VS::empty())
}
pub(crate) fn empty() -> Self {
Self::Positive(VS::empty())
}
pub(crate) fn exact(version: VS::V) -> Self {
Self::Positive(VS::singleton(version))
}
pub(crate) fn is_positive(&self) -> bool {
match self {
Self::Positive(_) => true,
Self::Negative(_) => false,
}
}
pub(crate) fn negate(&self) -> Self {
match self {
Self::Positive(set) => Self::Negative(set.clone()),
Self::Negative(set) => Self::Positive(set.clone()),
}
}
pub(crate) fn contains(&self, v: &VS::V) -> bool {
match self {
Self::Positive(set) => set.contains(v),
Self::Negative(set) => !set.contains(v),
}
}
pub fn unwrap_positive(&self) -> &VS {
match self {
Self::Positive(set) => set,
Self::Negative(set) => panic!("Negative term cannot unwrap positive set: {set:?}"),
}
}
pub(crate) fn unwrap_negative(&self) -> &VS {
match self {
Self::Negative(set) => set,
Self::Positive(set) => panic!("Positive term cannot unwrap negative set: {set:?}"),
}
}
}
impl<VS: VersionSet> Term<VS> {
pub(crate) fn intersection(&self, other: &Self) -> Self {
match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)),
(Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => {
Self::Positive(n.complement().intersection(p))
}
(Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)),
}
}
pub(crate) fn is_disjoint(&self, other: &Self) -> bool {
match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => r1.is_disjoint(r2),
(Self::Negative(_), Self::Negative(_)) => false,
(Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => {
p.subset_of(n)
}
}
}
pub(crate) fn union(&self, other: &Self) -> Self {
match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.union(r2)),
(Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => {
Self::Negative(p.complement().intersection(n))
}
(Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.intersection(r2)),
}
}
#[cfg(test)]
pub(crate) fn subset_of(&self, other: &Self) -> bool {
match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => r1.subset_of(r2),
(Self::Positive(r1), Self::Negative(r2)) => r1.is_disjoint(r2),
(Self::Negative(_), Self::Positive(_)) => false,
(Self::Negative(r1), Self::Negative(r2)) => r2.subset_of(r1),
}
}
}
pub(crate) enum Relation {
Satisfied,
Contradicted,
Inconclusive,
}
impl<VS: VersionSet> Term<VS> {
#[cfg(test)]
fn satisfied_by(&self, terms_intersection: &Self) -> bool {
terms_intersection.subset_of(self)
}
#[cfg(test)]
fn contradicted_by(&self, terms_intersection: &Self) -> bool {
terms_intersection.intersection(self) == Self::empty()
}
pub(crate) fn relation_with(&self, other_terms_intersection: &Self) -> Relation {
match (self, other_terms_intersection) {
(Self::Positive(range), Self::Positive(other)) => match other.relation(range) {
SetRelation::Subset => Relation::Satisfied,
SetRelation::Disjoint => Relation::Contradicted,
SetRelation::Overlapping => Relation::Inconclusive,
},
(Self::Positive(range), Self::Negative(other)) => {
if range.subset_of(other) {
Relation::Contradicted
} else {
Relation::Inconclusive
}
}
(Self::Negative(range), Self::Positive(other)) => {
if other == &VS::empty() {
Relation::Satisfied
} else {
match other.relation(range) {
SetRelation::Subset => Relation::Contradicted,
SetRelation::Disjoint => Relation::Satisfied,
SetRelation::Overlapping => Relation::Inconclusive,
}
}
}
(Self::Negative(range), Self::Negative(other)) => {
if range.subset_of(other) {
Relation::Satisfied
} else {
Relation::Inconclusive
}
}
}
}
}
impl<VS: VersionSet> AsRef<Self> for Term<VS> {
fn as_ref(&self) -> &Self {
self
}
}
impl<VS: VersionSet + Display> Display for Term<VS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Positive(set) => write!(f, "{set}"),
Self::Negative(set) => write!(f, "Not ( {set} )"),
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use proptest::prelude::*;
use version_ranges::Ranges;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct NoDisjointRanges(Ranges<u32>);
impl Display for NoDisjointRanges {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl VersionSet for NoDisjointRanges {
type V = u32;
fn empty() -> Self {
Self(Ranges::empty())
}
fn singleton(version: Self::V) -> Self {
Self(Ranges::singleton(version))
}
fn complement(&self) -> Self {
Self(self.0.complement())
}
fn intersection(&self, other: &Self) -> Self {
Self(self.0.intersection(&other.0))
}
fn contains(&self, version: &Self::V) -> bool {
self.0.contains(version)
}
fn is_disjoint(&self, _other: &Self) -> bool {
panic!("subset-only term relations must not check disjointness")
}
}
pub fn strategy() -> impl Strategy<Value = Term<Ranges<u32>>> {
prop_oneof![
version_ranges::proptest_strategy().prop_map(Term::Negative),
version_ranges::proptest_strategy().prop_map(Term::Positive),
]
}
#[test]
fn empty_positive_intersection_satisfies_negative_term() {
let term = Term::Negative(Ranges::<u32>::singleton(1u32));
assert!(matches!(
term.relation_with(&Term::empty()),
Relation::Satisfied
));
}
#[test]
fn subset_only_relations_do_not_check_disjointness() {
let one = NoDisjointRanges::singleton(1);
let two = NoDisjointRanges::singleton(2);
assert!(matches!(
Term::Positive(one.clone()).relation_with(&Term::Negative(two.clone())),
Relation::Inconclusive
));
assert!(matches!(
Term::Negative(one).relation_with(&Term::Negative(two)),
Relation::Inconclusive
));
}
proptest! {
#[test]
fn relation_with(term1 in strategy(), term2 in strategy()) {
match term1.relation_with(&term2) {
Relation::Satisfied => assert!(term1.satisfied_by(&term2)),
Relation::Contradicted => assert!(term1.contradicted_by(&term2)),
Relation::Inconclusive => {
assert!(!term1.satisfied_by(&term2));
assert!(!term1.contradicted_by(&term2));
}
}
}
#[test]
fn positive_negative(term1 in strategy(), term2 in strategy()) {
let intersection_positive = term1.is_positive() || term2.is_positive();
let union_positive = term1.is_positive() && term2.is_positive();
assert_eq!(term1.intersection(&term2).is_positive(), intersection_positive);
assert_eq!(term1.union(&term2).is_positive(), union_positive);
}
#[test]
fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) {
let disjoint_def = r1.intersection(&r2) == Term::empty();
assert_eq!(r1.is_disjoint(&r2), disjoint_def);
}
#[test]
fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) {
let disjoint_def = r1.intersection(&r2) == r1;
assert_eq!(r1.subset_of(&r2), disjoint_def);
}
#[test]
fn union_through_intersection(r1 in strategy(), r2 in strategy()) {
let union_def = r1
.negate()
.intersection(&r2.negate())
.negate();
assert_eq!(r1.union(&r2), union_def);
}
}
}