diff --git a/src/experimental/helpers.rs b/src/experimental/helpers.rs
new file mode 100644
index 00000000..5a867907
--- /dev/null
+++ b/src/experimental/helpers.rs
@@ -0,0 +1,355 @@
+//! Helpers structs.
+
+use std::fmt::{self, Display};
+use std::iter::repeat_n;
+use std::num::NonZeroU64;
+use std::rc::Rc;
+
+use crate::experimental::{VersionIndex, VersionSet};
+
+/// Package allowing more than 63 versions.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct Pkg
{
+ pkg: P,
+ quotient: u64,
+ count: u64,
+}
+
+impl
Pkg
{
+ /// Get the inner package.
+ pub fn pkg(&self) -> &P {
+ &self.pkg
+ }
+}
+
+impl Display for Pkg {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} (q={})", self.pkg, self.quotient)
+ }
+}
+
+/// Virtual package ensuring package unicity.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct VirtualPkg
{
+ pkg: P,
+ quotient: u64,
+ count: u64,
+}
+
+impl
VirtualPkg
{
+ /// Get the inner package.
+ pub fn pkg(&self) -> &P {
+ &self.pkg
+ }
+}
+
+impl Display for VirtualPkg {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "VirtualPkg({}, q={}, c={})",
+ self.pkg, self.quotient, self.count
+ )
+ }
+}
+
+/// Virtual package dependency allowing more than 63 versions.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct VirtualDep
{
+ pkg: P,
+ version_indices: Rc<[VersionSet]>,
+ offset: u64,
+ quotient: u64,
+}
+
+impl
VirtualDep
{
+ /// Get the inner package.
+ pub fn pkg(&self) -> &P {
+ &self.pkg
+ }
+}
+
+impl Display for VirtualDep {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let vc = self
+ .version_indices
+ .iter()
+ .map(|vs| vs.count())
+ .sum::();
+
+ write!(
+ f,
+ "VirtualDep({}, vc={vc}, o={}, q={})",
+ self.pkg, self.offset, self.quotient
+ )
+ }
+}
+
+/// Package wrapper used to allow more than 63 versions per package.
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub enum PackageVersionWrapper {
+ /// Package allowing more than 63 versions
+ Pkg(Pkg),
+ /// Virtual package ensuring package unicity
+ VirtualPkg(VirtualPkg
),
+ /// Virtual package dependency allowing more than 63 versions
+ VirtualDep(VirtualDep
),
+}
+
+impl Display for PackageVersionWrapper {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Pkg(p) => p.fmt(f),
+ Self::VirtualPkg(vp) => vp.fmt(f),
+ Self::VirtualDep(vd) => vd.fmt(f),
+ }
+ }
+}
+
+impl PackageVersionWrapper {
+ /// Create a new package.
+ pub fn new_pkg(
+ pkg: P,
+ true_version_index: u64,
+ version_count: NonZeroU64,
+ ) -> (Self, VersionIndex) {
+ (
+ Self::Pkg(Pkg {
+ pkg,
+ quotient: true_version_index / VersionIndex::MAX,
+ count: (version_count.get() - 1) / VersionIndex::MAX,
+ }),
+ VersionIndex::new((true_version_index % VersionIndex::MAX) as u8).unwrap(),
+ )
+ }
+
+ /// Create a new package dependency with no versions.
+ pub fn new_empty_dep(pkg: P) -> (Self, VersionSet) {
+ (
+ Self::Pkg(Pkg {
+ pkg,
+ quotient: 0,
+ count: 0,
+ }),
+ VersionSet::empty(),
+ )
+ }
+
+ /// Create a new package dependency at the specified version.
+ pub fn new_singleton_dep(
+ pkg: P,
+ true_version_index: u64,
+ version_count: u64,
+ ) -> (Self, VersionSet) {
+ match NonZeroU64::new(version_count) {
+ Some(version_count) => {
+ assert!(true_version_index < version_count.get());
+ let (this, v) = Self::new_pkg(pkg, true_version_index, version_count);
+ (this, VersionSet::singleton(v))
+ }
+ None => Self::new_empty_dep(pkg),
+ }
+ }
+
+ /// Create a new package dependency at the specified versions.
+ pub fn new_dep(
+ pkg: P,
+ true_version_indices: impl IntoIterator- ,
+ version_count: u64,
+ ) -> (Self, VersionSet) {
+ let Some(nz_version_count) = NonZeroU64::new(version_count) else {
+ return Self::new_empty_dep(pkg);
+ };
+ if version_count <= VersionIndex::MAX {
+ let mut set = VersionSet::empty();
+ for true_version_index in true_version_indices {
+ assert!(true_version_index < version_count);
+ let v = VersionIndex::new(true_version_index as u8).unwrap();
+ set = set.union(VersionSet::singleton(v));
+ }
+ return (
+ Self::Pkg(Pkg {
+ pkg,
+ quotient: 0,
+ count: (version_count - 1) / VersionIndex::MAX,
+ }),
+ set,
+ );
+ }
+
+ let mut true_version_indices = true_version_indices.into_iter();
+
+ let Some(first) = true_version_indices.next() else {
+ return Self::new_empty_dep(pkg);
+ };
+ assert!(first < version_count);
+
+ let Some(second) = true_version_indices.next() else {
+ let (d, vs) = Self::new_pkg(pkg, first, nz_version_count);
+ return (d, VersionSet::singleton(vs));
+ };
+ assert!(second < version_count);
+
+ let mut version_indices = Rc::from_iter(repeat_n(
+ VersionSet::empty(),
+ (1 + (version_count - 1) / VersionIndex::MAX) as usize,
+ ));
+ let versions_slice = Rc::make_mut(&mut version_indices);
+
+ for true_version_index in [first, second].into_iter().chain(true_version_indices) {
+ assert!(true_version_index < version_count);
+ let index = (true_version_index / VersionIndex::MAX) as usize;
+ let v = VersionIndex::new((true_version_index % VersionIndex::MAX) as u8).unwrap();
+ let set = versions_slice.get_mut(index).unwrap();
+ *set = set.union(VersionSet::singleton(v));
+ }
+
+ let offset = 0;
+ let quotient = VersionIndex::MAX.pow(version_count.ilog(VersionIndex::MAX) - 1);
+ let version_set = Self::dep_version_set(&version_indices, offset, quotient);
+
+ let this = Self::VirtualDep(VirtualDep {
+ pkg,
+ version_indices,
+ offset,
+ quotient,
+ });
+
+ (this, version_set)
+ }
+
+ /// Clone and replace the package of this wrapper.
+ pub fn replace_pkg(&self, new_pkg: T) -> PackageVersionWrapper {
+ match *self {
+ Self::Pkg(Pkg {
+ pkg: _,
+ quotient,
+ count,
+ }) => PackageVersionWrapper::Pkg(Pkg {
+ pkg: new_pkg,
+ quotient,
+ count,
+ }),
+ Self::VirtualPkg(VirtualPkg {
+ pkg: _,
+ quotient,
+ count,
+ }) => PackageVersionWrapper::VirtualPkg(VirtualPkg {
+ pkg: new_pkg,
+ quotient,
+ count,
+ }),
+ Self::VirtualDep(VirtualDep {
+ pkg: _,
+ ref version_indices,
+ offset,
+ quotient,
+ }) => PackageVersionWrapper::VirtualDep(VirtualDep {
+ pkg: new_pkg,
+ version_indices: version_indices.clone(),
+ offset,
+ quotient,
+ }),
+ }
+ }
+
+ /// Get the inner package if existing.
+ pub fn inner_pkg(&self) -> Option<&P> {
+ match self {
+ Self::Pkg(Pkg { pkg, .. }) => Some(pkg),
+ _ => None,
+ }
+ }
+
+ /// Get the inner package if existing.
+ pub fn inner(&self, version_index: VersionIndex) -> Option<(&P, u64)> {
+ match self {
+ Self::Pkg(Pkg { pkg, quotient, .. }) => Some((
+ pkg,
+ quotient * VersionIndex::MAX + version_index.get() as u64,
+ )),
+ _ => None,
+ }
+ }
+
+ /// Get the inner package if existing.
+ pub fn into_inner(self, version_index: VersionIndex) -> Option<(P, u64)> {
+ match self {
+ Self::Pkg(Pkg { pkg, quotient, .. }) => Some((
+ pkg,
+ quotient * VersionIndex::MAX + version_index.get() as u64,
+ )),
+ _ => None,
+ }
+ }
+
+ /// Get the wrapper virtual dependency if existing.
+ pub fn dependency(&self, version_index: VersionIndex) -> Option<(Self, VersionSet)> {
+ match *self {
+ Self::Pkg(Pkg {
+ ref pkg,
+ quotient,
+ count,
+ })
+ | Self::VirtualPkg(VirtualPkg {
+ ref pkg,
+ quotient,
+ count,
+ }) => {
+ if count == 0 {
+ None
+ } else {
+ Some((
+ Self::VirtualPkg(VirtualPkg {
+ pkg: pkg.clone(),
+ quotient: quotient / VersionIndex::MAX,
+ count: count / VersionIndex::MAX,
+ }),
+ VersionSet::singleton(
+ VersionIndex::new((quotient % VersionIndex::MAX) as u8).unwrap(),
+ ),
+ ))
+ }
+ }
+ Self::VirtualDep(VirtualDep {
+ ref pkg,
+ ref version_indices,
+ offset,
+ quotient,
+ }) => {
+ let offset = offset + version_index.get() as u64 * quotient;
+ if quotient == 1 {
+ return Some((
+ Self::Pkg(Pkg {
+ pkg: pkg.clone(),
+ quotient: offset,
+ count: (version_indices.len() - 1) as u64,
+ }),
+ version_indices[offset as usize],
+ ));
+ }
+ let quotient = quotient / VersionIndex::MAX;
+ let version_set = Self::dep_version_set(version_indices, offset, quotient);
+
+ let this = Self::VirtualDep(VirtualDep {
+ pkg: pkg.clone(),
+ version_indices: version_indices.clone(),
+ offset,
+ quotient,
+ });
+
+ Some((this, version_set))
+ }
+ }
+ }
+
+ fn dep_version_set(sets: &[VersionSet], offset: u64, quotient: u64) -> VersionSet {
+ sets[offset as usize..]
+ .chunks(quotient as usize)
+ .take(VersionIndex::MAX as usize)
+ .enumerate()
+ .filter(|&(_, sets)| sets.iter().any(|&vs| vs != VersionSet::empty()))
+ .map(|(i, _)| VersionSet::singleton(VersionIndex::new(i as u8).unwrap()))
+ .fold(VersionSet::empty(), |acc, vs| acc.union(vs))
+ }
+}
diff --git a/src/experimental/mod.rs b/src/experimental/mod.rs
new file mode 100644
index 00000000..7f7fab5e
--- /dev/null
+++ b/src/experimental/mod.rs
@@ -0,0 +1,6 @@
+pub mod helpers;
+mod term;
+mod version;
+
+pub use term::Term;
+pub use version::{VersionIndex, VersionSet};
diff --git a/src/experimental/term.rs b/src/experimental/term.rs
new file mode 100644
index 00000000..4125b9dd
--- /dev/null
+++ b/src/experimental/term.rs
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: MPL-2.0
+
+//! A term is the fundamental unit of operation of the PubGrub algorithm.
+//! It is a positive or negative expression regarding a set of versions.
+
+use std::fmt::{self, Display};
+
+use crate::experimental::{VersionIndex, VersionSet};
+
+/// A positive or negative expression regarding a set of versions.
+///
+/// `Term::positive(vs)` and `Term::negative(vs.complement())` are not equivalent:
+/// * `Term::positive(vs)` is satisfied if the package is selected AND the selected version is in `vs`.
+/// * `Term::negative(vs.complement())` is satisfied if the package is not selected OR the selected version is in `vs`.
+///
+/// A positive term in the partial solution requires a version to be selected, but a negative term
+/// allows for a solution that does not have that package selected.
+/// Specifically, `Term::positive(VersionSet::empty())` means that there was a conflict
+/// (we need to select a version for the package but can't pick any),
+/// while `Term::negative(VersionSet::full())` would mean it is fine as long as we don't select the package.
+///
+/// Like [`VersionSet`], this is implemented as a `u64` bitset,
+/// where the first bit is set if the unselected package is allowed (for a negative term),
+/// and the others bits are set if the package selected at the corresponding version index is allowed.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct Term(u64);
+
+impl Term {
+ /// Construct a positive `Term`.
+ /// For example, `[1, 2]` is a positive expression
+ /// that is evaluated true if a version with index 1 or 2 is selected.
+ #[inline]
+ pub(crate) fn positive(vs: VersionSet) -> Self {
+ Self(vs.0)
+ }
+
+ /// Construct a negative `Term`.
+ /// For example, `not [3, 5]` is a negative expression
+ /// that is evaluated true if a version with index 3 or 5 is selected
+ /// or if no version is selected at all.
+ #[inline]
+ pub(crate) fn negative(vs: VersionSet) -> Self {
+ Self(!vs.0)
+ }
+
+ /// A term that is always true.
+ #[inline]
+ pub(crate) fn any() -> Self {
+ Self(!0)
+ }
+
+ /// A term that is never true.
+ #[inline]
+ pub(crate) fn empty() -> Self {
+ Self(0)
+ }
+
+ /// A positive term containing exactly that version.
+ #[inline]
+ pub(crate) fn exact(version_index: VersionIndex) -> Self {
+ Self::positive(VersionSet::singleton(version_index))
+ }
+
+ /// Simply check if a term is positive.
+ #[inline]
+ pub fn is_positive(self) -> bool {
+ self.0 & 1 == 0
+ }
+
+ /// Simply check if a term is negative.
+ #[inline]
+ pub fn is_negative(self) -> bool {
+ self.0 & 1 != 0
+ }
+
+ /// Negate a term.
+ /// Evaluation of a negated term always returns
+ /// the opposite of the evaluation of the original one.
+ #[inline]
+ pub(crate) fn negate(self) -> Self {
+ Self(!self.0)
+ }
+
+ /// Get the inner version set.
+ #[inline]
+ pub fn version_set(self) -> VersionSet {
+ if self.is_positive() {
+ VersionSet(self.0)
+ } else {
+ VersionSet(!self.0)
+ }
+ }
+
+ /// Evaluate a term regarding a given choice of version.
+ #[inline]
+ pub(crate) fn contains(self, v: VersionIndex) -> bool {
+ self.0 & VersionSet::singleton(v).0 != 0
+ }
+
+ /// Unwrap the set contained in a positive term.
+ /// Will panic if used on a negative set.
+ #[inline]
+ pub(crate) fn unwrap_positive(self) -> VersionSet {
+ if self.is_positive() {
+ VersionSet(self.0)
+ } else {
+ panic!("Negative term cannot unwrap positive set")
+ }
+ }
+
+ /// Unwrap the set contained in a negative term.
+ /// Will panic if used on a positive set.
+ #[inline]
+ pub(crate) fn unwrap_negative(self) -> VersionSet {
+ if self.is_negative() {
+ VersionSet(!self.0)
+ } else {
+ panic!("Positive term cannot unwrap negative set")
+ }
+ }
+
+ /// Compute the intersection of two terms.
+ /// The intersection is negative (unselected package is allowed)
+ /// if all terms are negative.
+ #[inline]
+ pub(crate) fn intersection(self, other: Self) -> Self {
+ Self(self.0 & other.0)
+ }
+
+ /// Compute the union of two terms.
+ /// If at least one term is negative, the union is also negative (unselected package is allowed).
+ #[inline]
+ pub(crate) fn union(self, other: Self) -> Self {
+ Self(self.0 | other.0)
+ }
+
+ /// Check whether two terms are mutually exclusive.
+ #[inline]
+ pub(crate) fn is_disjoint(self, other: Self) -> bool {
+ self.0 & other.0 == 0
+ }
+
+ /// Indicate if this term is a subset of another term.
+ /// Just like for sets, we say that t1 is a subset of t2
+ /// if and only if t1 ∩ t2 = t1.
+ #[inline]
+ pub(crate) fn subset_of(self, other: Self) -> bool {
+ self.0 & other.0 == self.0
+ }
+
+ /// Check if a set of terms satisfies or contradicts a given term.
+ /// Otherwise the relation is inconclusive.
+ #[inline]
+ pub(crate) fn relation_with(self, other_terms_intersection: Self) -> Relation {
+ if other_terms_intersection.subset_of(self) {
+ Relation::Satisfied
+ } else if other_terms_intersection.is_disjoint(self) {
+ Relation::Contradicted
+ } else {
+ Relation::Inconclusive
+ }
+ }
+}
+
+/// Describe a relation between a set of terms S and another term t.
+///
+/// As a shorthand, we say that a term v
+/// satisfies or contradicts a term t if {v} satisfies or contradicts it.
+pub(crate) enum Relation {
+ /// We say that a set of terms S "satisfies" a term t
+ /// if t must be true whenever every term in S is true.
+ Satisfied,
+ /// Conversely, S "contradicts" t if t must be false
+ /// whenever every term in S is true.
+ Contradicted,
+ /// If neither of these is true we say that S is "inconclusive" for t.
+ Inconclusive,
+}
+
+impl Display for Term {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.is_negative() {
+ write!(f, "Not ( ")?;
+ }
+
+ let mut list = f.debug_list();
+ for v in self.version_set().iter() {
+ list.entry(&v.get());
+ }
+ list.finish()?;
+
+ if self.is_negative() {
+ write!(f, " )")?;
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+pub mod tests {
+ use proptest::prelude::*;
+
+ use super::*;
+
+ impl Term {
+ /// Check if a set of terms satisfies this term.
+ ///
+ /// We say that a set of terms S "satisfies" a term t
+ /// if t must be true whenever every term in S is true.
+ ///
+ /// It turns out that this can also be expressed with set operations:
+ /// S satisfies t if and only if ⋂ S ⊆ t
+ fn satisfied_by(self, terms_intersection: Self) -> bool {
+ terms_intersection.subset_of(self)
+ }
+
+ /// Check if a set of terms contradicts this term.
+ ///
+ /// We say that a set of terms S "contradicts" a term t
+ /// if t must be false whenever every term in S is true.
+ ///
+ /// It turns out that this can also be expressed with set operations:
+ /// S contradicts t if and only if ⋂ S is disjoint with t
+ /// S contradicts t if and only if (⋂ S) ⋂ t = ∅
+ fn contradicted_by(self, terms_intersection: Self) -> bool {
+ terms_intersection.intersection(self) == Self::empty()
+ }
+ }
+
+ pub fn strategy() -> impl Strategy {
+ any::().prop_map(Term)
+ }
+
+ proptest! {
+ /// Testing relation
+ #[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));
+ }
+ }
+ }
+
+ /// Ensure that we don't wrongly convert between positive and negative ranges
+ #[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);
+ }
+ }
+}
diff --git a/src/experimental/version.rs b/src/experimental/version.rs
new file mode 100644
index 00000000..c76d6c73
--- /dev/null
+++ b/src/experimental/version.rs
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: MPL-2.0
+
+/// Type for identifying a version index.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(transparent)]
+pub struct VersionIndex(u8);
+
+impl VersionIndex {
+ /// Maximum possible version index.
+ pub const MAX: u64 = (u64::BITS - 1) as u64;
+
+ /// Constructor for a version index.
+ #[inline]
+ pub fn new(v: u8) -> Option {
+ if v < Self::MAX as u8 {
+ Some(Self(v))
+ } else {
+ None
+ }
+ }
+
+ /// Get the inner version index.
+ #[inline]
+ pub fn get(self) -> u8 {
+ self.0
+ }
+}
+
+/// Type for identifying a set of version indices.
+///
+/// This is implemented as a `u64` bitset which can represent up to 63 versions.
+/// The first bit is kept unset to leave space for the positive/negative bit of [`Term`](super::Term).
+///
+/// See the [helpers](super::helpers) module to support more than 63 versions by using a wrapper package.
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
+#[repr(transparent)]
+pub struct VersionSet(pub(crate) u64);
+
+impl VersionSet {
+ /// Constructor for an empty set containing no version index.
+ #[inline]
+ pub fn empty() -> Self {
+ Self(0)
+ }
+
+ /// Constructor for the set containing all version indices.
+ #[inline]
+ pub fn full() -> Self {
+ Self(u64::MAX & (!1))
+ }
+
+ /// Constructor for a set containing exactly one version index.
+ #[inline]
+ pub fn singleton(v: VersionIndex) -> Self {
+ Self(2 << v.0)
+ }
+
+ /// Compute the complement of this set.
+ #[inline]
+ pub fn complement(self) -> Self {
+ Self((!self.0) & (!1))
+ }
+
+ /// Compute the intersection with another set.
+ #[inline]
+ pub fn intersection(self, other: Self) -> Self {
+ Self(self.0 & other.0)
+ }
+
+ /// Compute the union with another set.
+ #[inline]
+ pub fn union(self, other: Self) -> Self {
+ Self(self.0 | other.0)
+ }
+
+ /// Evaluate membership of a version index in this set.
+ #[inline]
+ pub fn contains(self, v: VersionIndex) -> bool {
+ self.intersection(Self::singleton(v)) != Self::empty()
+ }
+
+ /// Whether the set has no overlapping version indices.
+ #[inline]
+ pub fn is_disjoint(self, other: Self) -> bool {
+ self.intersection(other) == Self::empty()
+ }
+
+ /// Whether all version indices of `self` are contained in `other`.
+ #[inline]
+ pub fn subset_of(self, other: Self) -> bool {
+ self == self.intersection(other)
+ }
+
+ /// Get an iterator over the version indices contained in the set.
+ #[inline]
+ pub fn iter(self) -> impl Iterator
- {
+ (0..VersionIndex::MAX)
+ .filter(move |v| self.0 & (2 << v) != 0)
+ .map(|v| VersionIndex(v as u8))
+ }
+
+ /// Get the first version index of the set.
+ #[inline]
+ pub fn first(self) -> Option {
+ if self != Self::empty() {
+ Some(VersionIndex((self.0 >> 1).trailing_zeros() as u8))
+ } else {
+ None
+ }
+ }
+
+ /// Get the last version index of the set.
+ #[inline]
+ pub fn last(self) -> Option {
+ if self != Self::empty() {
+ let v = VersionIndex::MAX - (self.0 >> 1).leading_zeros() as u64;
+ Some(VersionIndex(v as u8))
+ } else {
+ None
+ }
+ }
+
+ /// Count the number of version indices contained in the set.
+ #[inline]
+ pub fn count(self) -> usize {
+ self.0.count_ones() as usize
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index cc1c943f..14ee1276 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -211,6 +211,8 @@
#![warn(missing_docs)]
mod error;
+#[allow(unused)]
+mod experimental;
mod package;
mod provider;
mod report;