use std::fmt::{self, Display};
use crate::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(crate) 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)),
}
}
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 {
if other_terms_intersection.subset_of(self) {
Relation::Satisfied
} else if self.is_disjoint(other_terms_intersection) {
Relation::Contradicted
} 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;
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),
]
}
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);
}
}
}