diff --git a/Cargo.lock b/Cargo.lock index c3e38ba11422..01d76f13d4b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2498,13 +2498,14 @@ dependencies = [ [[package]] name = "pubgrub" version = "0.2.1" -source = "git+https://github.com/astral-sh/pubgrub?rev=7243f4faf8e54837aa8a401a18406e7173de4ad5#7243f4faf8e54837aa8a401a18406e7173de4ad5" +source = "git+https://github.com/astral-sh/pubgrub?rev=95e1390399cdddee986b658be19587eb1fdb2d79#95e1390399cdddee986b658be19587eb1fdb2d79" dependencies = [ "indexmap", "log", "priority-queue", "rustc-hash", "thiserror", + "version-ranges", ] [[package]] @@ -4885,7 +4886,6 @@ dependencies = [ "insta", "itertools 0.13.0", "log", - "pubgrub", "regex", "rustc-hash", "schemars", @@ -4901,6 +4901,7 @@ dependencies = [ "uv-normalize", "uv-pep440", "uv-pubgrub", + "version-ranges", ] [[package]] @@ -4933,9 +4934,9 @@ name = "uv-pubgrub" version = "0.0.1" dependencies = [ "itertools 0.13.0", - "pubgrub", "thiserror", "uv-pep440", + "version-ranges", ] [[package]] @@ -5376,6 +5377,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version-ranges" +version = "0.1.0" +source = "git+https://github.com/astral-sh/pubgrub?rev=95e1390399cdddee986b658be19587eb1fdb2d79#95e1390399cdddee986b658be19587eb1fdb2d79" +dependencies = [ + "smallvec", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 3f4a71dca1c6..8b03754a9fd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,8 @@ pathdiff = { version = "0.2.1" } petgraph = { version = "0.6.5" } platform-info = { version = "2.0.3" } proc-macro2 = { version = "1.0.86" } -pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "7243f4faf8e54837aa8a401a18406e7173de4ad5" } +pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" } +version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" } quote = { version = "1.0.37" } rayon = { version = "1.10.0" } reflink-copy = { version = "0.1.19" } diff --git a/crates/uv-pep508/Cargo.toml b/crates/uv-pep508/Cargo.toml index a5d5dbc71b7c..4ba89682b318 100644 --- a/crates/uv-pep508/Cargo.toml +++ b/crates/uv-pep508/Cargo.toml @@ -29,7 +29,6 @@ uv-pubgrub = { workspace = true } boxcar = { workspace = true } indexmap = { workspace = true } itertools = { workspace = true } -pubgrub = { workspace = true } regex = { workspace = true } rustc-hash = { workspace = true } schemars = { workspace = true, optional = true } @@ -40,6 +39,7 @@ thiserror = { workspace = true } tracing = { workspace = true, optional = true } unicode-width = { workspace = true } url = { workspace = true, features = ["serde"] } +version-ranges = { workspace = true } [dev-dependencies] insta = { version = "1.40.0" } diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index f44ec54c94cb..e41a49b5497a 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -51,12 +51,12 @@ use std::sync::Mutex; use std::sync::MutexGuard; use itertools::Either; -use pubgrub::Range; use rustc_hash::FxHashMap; use std::sync::LazyLock; use uv_pep440::Operator; use uv_pep440::{Version, VersionSpecifier}; use uv_pubgrub::PubGrubSpecifier; +use version_ranges::Ranges; use crate::marker::MarkerValueExtra; use crate::ExtraOperator; @@ -403,7 +403,7 @@ impl InternerGuard<'_> { }); return self.create_node(node.var.clone(), children); }; - let py_range = Range::from_range_bounds((py_lower.cloned(), py_upper.cloned())); + let py_range = Ranges::from_range_bounds((py_lower.cloned(), py_upper.cloned())); if py_range.is_empty() { // Oops, the bounds imply there is nothing that can match, // so we always evaluate to false. @@ -428,12 +428,12 @@ impl InternerGuard<'_> { // are known to be satisfied. let &(ref first_range, first_node_id) = new.first().unwrap(); let first_upper = first_range.bounding_range().unwrap().1; - let clipped = Range::from_range_bounds((Bound::Unbounded, first_upper.cloned())); + let clipped = Ranges::from_range_bounds((Bound::Unbounded, first_upper.cloned())); *new.first_mut().unwrap() = (clipped, first_node_id); let &(ref last_range, last_node_id) = new.last().unwrap(); let last_lower = last_range.bounding_range().unwrap().0; - let clipped = Range::from_range_bounds((last_lower.cloned(), Bound::Unbounded)); + let clipped = Ranges::from_range_bounds((last_lower.cloned(), Bound::Unbounded)); *new.last_mut().unwrap() = (clipped, last_node_id); self.create_node(node.var.clone(), Edges::Version { edges: new }) @@ -459,7 +459,7 @@ impl InternerGuard<'_> { return i; } - let py_range = Range::from_range_bounds((py_lower.cloned(), py_upper.cloned())); + let py_range = Ranges::from_range_bounds((py_lower.cloned(), py_upper.cloned())); if py_range.is_empty() { // Oops, the bounds imply there is nothing that can match, // so we always evaluate to false. @@ -521,14 +521,14 @@ impl InternerGuard<'_> { // adjacent ranges map to the same node, which would not be // a canonical representation. if exclude_node_id == first_node_id { - let clipped = Range::from_range_bounds((Bound::Unbounded, first_upper.cloned())); + let clipped = Ranges::from_range_bounds((Bound::Unbounded, first_upper.cloned())); *new.first_mut().unwrap() = (clipped, first_node_id); } else { - let clipped = Range::from_range_bounds((py_lower.cloned(), first_upper.cloned())); + let clipped = Ranges::from_range_bounds((py_lower.cloned(), first_upper.cloned())); *new.first_mut().unwrap() = (clipped, first_node_id); let py_range_lower = - Range::from_range_bounds((py_lower.cloned(), Bound::Unbounded)); + Ranges::from_range_bounds((py_lower.cloned(), Bound::Unbounded)); new.insert(0, (py_range_lower.complement(), NodeId::FALSE.negate(i))); } } @@ -539,14 +539,14 @@ impl InternerGuard<'_> { // same reasoning applies here: to maintain a canonical // representation. if exclude_node_id == last_node_id { - let clipped = Range::from_range_bounds((last_lower.cloned(), Bound::Unbounded)); + let clipped = Ranges::from_range_bounds((last_lower.cloned(), Bound::Unbounded)); *new.last_mut().unwrap() = (clipped, last_node_id); } else { - let clipped = Range::from_range_bounds((last_lower.cloned(), py_upper.cloned())); + let clipped = Ranges::from_range_bounds((last_lower.cloned(), py_upper.cloned())); *new.last_mut().unwrap() = (clipped, last_node_id); let py_range_upper = - Range::from_range_bounds((Bound::Unbounded, py_upper.cloned())); + Ranges::from_range_bounds((Bound::Unbounded, py_upper.cloned())); new.push((py_range_upper.complement(), exclude_node_id)); } } @@ -688,7 +688,7 @@ pub(crate) enum Edges { // Invariant: All ranges are simple, meaning they can be represented by a bounded // interval without gaps. Additionally, there are at least two edges in the set. Version { - edges: SmallVec<(Range, NodeId)>, + edges: SmallVec<(Ranges, NodeId)>, }, // The edges of a string variable, representing a disjoint set of ranges that cover // the output space. @@ -696,7 +696,7 @@ pub(crate) enum Edges { // Invariant: All ranges are simple, meaning they can be represented by a bounded // interval without gaps. Additionally, there are at least two edges in the set. String { - edges: SmallVec<(Range, NodeId)>, + edges: SmallVec<(Ranges, NodeId)>, }, // The edges of a boolean variable, representing the values `true` (the `high` child) // and `false` (the `low` child). @@ -727,13 +727,13 @@ impl Edges { /// This function will panic for the `In` and `Contains` marker operators, which /// should be represented as separate boolean variables. fn from_string(operator: MarkerOperator, value: String) -> Edges { - let range: Range = match operator { - MarkerOperator::Equal => Range::singleton(value), - MarkerOperator::NotEqual => Range::singleton(value).complement(), - MarkerOperator::GreaterThan => Range::strictly_higher_than(value), - MarkerOperator::GreaterEqual => Range::higher_than(value), - MarkerOperator::LessThan => Range::strictly_lower_than(value), - MarkerOperator::LessEqual => Range::lower_than(value), + let range: Ranges = match operator { + MarkerOperator::Equal => Ranges::singleton(value), + MarkerOperator::NotEqual => Ranges::singleton(value).complement(), + MarkerOperator::GreaterThan => Ranges::strictly_higher_than(value), + MarkerOperator::GreaterEqual => Ranges::higher_than(value), + MarkerOperator::LessThan => Ranges::strictly_lower_than(value), + MarkerOperator::LessEqual => Ranges::lower_than(value), MarkerOperator::TildeEqual => unreachable!("string comparisons with ~= are ignored"), _ => unreachable!("`in` and `contains` are treated as boolean variables"), }; @@ -756,7 +756,7 @@ impl Edges { /// /// Only for use when the `key` is a `PythonVersion`. Normalizes to `PythonFullVersion`. fn from_python_versions(versions: Vec, negated: bool) -> Result { - let mut range = Range::empty(); + let mut range = Ranges::empty(); // TODO(zanieb): We need to make sure this is performant, repeated unions like this do not // seem efficient. @@ -779,12 +779,12 @@ impl Edges { /// Returns an [`Edges`] where values in the given range are `true`. fn from_versions(versions: &Vec, negated: bool) -> Edges { - let mut range = Range::empty(); + let mut range = Ranges::empty(); // TODO(zanieb): We need to make sure this is performant, repeated unions like this do not // seem efficient. for version in versions { - range = range.union(&Range::singleton(version.clone())); + range = range.union(&Ranges::singleton(version.clone())); } if negated { @@ -797,7 +797,7 @@ impl Edges { } /// Returns an [`Edges`] where values in the given range are `true`. - fn from_range(range: &Range) -> SmallVec<(Range, NodeId)> + fn from_range(range: &Ranges) -> SmallVec<(Ranges, NodeId)> where T: Ord + Clone, { @@ -805,13 +805,13 @@ impl Edges { // Add the `true` edges. for (start, end) in range.iter() { - let range = Range::from_range_bounds((start.clone(), end.clone())); + let range = Ranges::from_range_bounds((start.clone(), end.clone())); edges.push((range, NodeId::TRUE)); } // Add the `false` edges. for (start, end) in range.complement().iter() { - let range = Range::from_range_bounds((start.clone(), end.clone())); + let range = Ranges::from_range_bounds((start.clone(), end.clone())); edges.push((range, NodeId::FALSE)); } @@ -891,12 +891,12 @@ impl Edges { /// In that case, we drop any ranges that do not exist in the domain of both edges. Note that /// this should not occur in practice because `requires-python` bounds are global. fn apply_ranges( - left_edges: &SmallVec<(Range, NodeId)>, + left_edges: &SmallVec<(Ranges, NodeId)>, left_parent: NodeId, - right_edges: &SmallVec<(Range, NodeId)>, + right_edges: &SmallVec<(Ranges, NodeId)>, right_parent: NodeId, mut apply: impl FnMut(NodeId, NodeId) -> NodeId, - ) -> SmallVec<(Range, NodeId)> + ) -> SmallVec<(Ranges, NodeId)> where T: Clone + Ord, { @@ -968,9 +968,9 @@ impl Edges { // Returns `true` if all intersecting ranges in two range maps are disjoint. fn is_disjoint_ranges( - left_edges: &SmallVec<(Range, NodeId)>, + left_edges: &SmallVec<(Ranges, NodeId)>, left_parent: NodeId, - right_edges: &SmallVec<(Range, NodeId)>, + right_edges: &SmallVec<(Ranges, NodeId)>, right_parent: NodeId, interner: &mut InternerGuard<'_>, ) -> bool @@ -1179,7 +1179,7 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result(range1: &Range, range2: &Range) -> Ordering +fn compare_disjoint_range_start(range1: &Ranges, range2: &Ranges) -> Ordering where T: Ord, { @@ -1199,7 +1199,7 @@ where } /// Returns `true` if two disjoint ranges can be conjoined seamlessly without introducing a gap. -fn can_conjoin(range1: &Range, range2: &Range) -> bool +fn can_conjoin(range1: &Ranges, range2: &Ranges) -> bool where T: Ord + Clone, { diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 3c10d9a9e5c8..82230f373cb3 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -3,9 +3,9 @@ use std::ops::Bound; use indexmap::IndexMap; use itertools::Itertools; -use pubgrub::Range; use rustc_hash::FxBuildHasher; use uv_pep440::{Version, VersionSpecifier}; +use version_ranges::Ranges; use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; @@ -280,17 +280,17 @@ fn simplify(dnf: &mut Vec>) { /// Merge any edges that lead to identical subtrees into a single range. pub(crate) fn collect_edges<'a, T>( - map: impl ExactSizeIterator, MarkerTree)>, -) -> IndexMap, FxBuildHasher> + map: impl ExactSizeIterator, MarkerTree)>, +) -> IndexMap, FxBuildHasher> where T: Ord + Clone + 'a, { - let mut paths: IndexMap<_, Range<_>, FxBuildHasher> = IndexMap::default(); + let mut paths: IndexMap<_, Ranges<_>, FxBuildHasher> = IndexMap::default(); for (range, tree) in map { // OK because all ranges are guaranteed to be non-empty. let (start, end) = range.bounding_range().unwrap(); // Combine the ranges. - let range = Range::from_range_bounds((start.cloned(), end.cloned())); + let range = Ranges::from_range_bounds((start.cloned(), end.cloned())); paths .entry(tree) .and_modify(|union| *union = union.union(&range)) @@ -305,7 +305,7 @@ where /// /// For example, `os_name < 'Linux' or os_name > 'Linux'` can be simplified to /// `os_name != 'Linux'`. -fn range_inequality(range: &Range) -> Option> +fn range_inequality(range: &Ranges) -> Option> where T: Ord + Clone + fmt::Debug, { @@ -329,7 +329,7 @@ where /// /// For example, `python_full_version < '3.8' or python_full_version >= '3.9'` can be simplified to /// `python_full_version != '3.8.*'`. -fn star_range_inequality(range: &Range) -> Option { +fn star_range_inequality(range: &Ranges) -> Option { let (b1, b2) = range.iter().collect_tuple()?; match (b1, b2) { diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 677ce64038d2..db31e50e2f16 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -5,10 +5,10 @@ use std::ops::{Bound, Deref}; use std::str::FromStr; use itertools::Itertools; -use pubgrub::Range; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use uv_normalize::ExtraName; use uv_pep440::{Version, VersionParseError, VersionSpecifier}; +use version_ranges::Ranges; use crate::cursor::Cursor; use crate::marker::parse; @@ -1396,7 +1396,7 @@ pub enum MarkerTreeKind<'a> { pub struct VersionMarkerTree<'a> { id: NodeId, key: MarkerValueVersion, - map: &'a [(Range, NodeId)], + map: &'a [(Ranges, NodeId)], } impl VersionMarkerTree<'_> { @@ -1406,7 +1406,7 @@ impl VersionMarkerTree<'_> { } /// The edges of this node, corresponding to possible output ranges of the given variable. - pub fn edges(&self) -> impl ExactSizeIterator, MarkerTree)> + '_ { + pub fn edges(&self) -> impl ExactSizeIterator, MarkerTree)> + '_ { self.map .iter() .map(|(range, node)| (range, MarkerTree(node.negate(self.id)))) @@ -1432,7 +1432,7 @@ impl Ord for VersionMarkerTree<'_> { pub struct StringMarkerTree<'a> { id: NodeId, key: MarkerValueString, - map: &'a [(Range, NodeId)], + map: &'a [(Ranges, NodeId)], } impl StringMarkerTree<'_> { @@ -1442,7 +1442,7 @@ impl StringMarkerTree<'_> { } /// The edges of this node, corresponding to possible output ranges of the given variable. - pub fn children(&self) -> impl ExactSizeIterator, MarkerTree)> { + pub fn children(&self) -> impl ExactSizeIterator, MarkerTree)> { self.map .iter() .map(|(range, node)| (range, MarkerTree(node.negate(self.id)))) diff --git a/crates/uv-pubgrub/Cargo.toml b/crates/uv-pubgrub/Cargo.toml index 8c975311221f..ec44c72b4f7c 100644 --- a/crates/uv-pubgrub/Cargo.toml +++ b/crates/uv-pubgrub/Cargo.toml @@ -15,4 +15,4 @@ uv-pep440 = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } -pubgrub = { workspace = true } +version-ranges = { workspace = true } diff --git a/crates/uv-pubgrub/src/lib.rs b/crates/uv-pubgrub/src/lib.rs index bf07a3639475..5c19f185a352 100644 --- a/crates/uv-pubgrub/src/lib.rs +++ b/crates/uv-pubgrub/src/lib.rs @@ -1,8 +1,8 @@ use std::ops::Bound; use itertools::Itertools; -use pubgrub::Range; use thiserror::Error; +use version_ranges::Ranges; use uv_pep440::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers}; @@ -14,7 +14,7 @@ pub enum PubGrubSpecifierError { /// A range of versions that can be used to satisfy a requirement. #[derive(Debug)] -pub struct PubGrubSpecifier(Range); +pub struct PubGrubSpecifier(Ranges); impl PubGrubSpecifier { /// Returns an iterator over the bounds of the [`PubGrubSpecifier`]. @@ -22,19 +22,19 @@ impl PubGrubSpecifier { self.0.iter() } - /// Return the bounding [`Range`] of the [`PubGrubSpecifier`]. + /// Return the bounding [`Ranges`] of the [`PubGrubSpecifier`]. pub fn bounding_range(&self) -> Option<(Bound<&Version>, Bound<&Version>)> { self.0.bounding_range() } } -impl From> for PubGrubSpecifier { - fn from(range: Range) -> Self { +impl From> for PubGrubSpecifier { + fn from(range: Ranges) -> Self { PubGrubSpecifier(range) } } -impl From for Range { +impl From for Ranges { /// Convert a PubGrub specifier to a range of versions. fn from(specifier: PubGrubSpecifier) -> Self { specifier.0 @@ -50,7 +50,7 @@ impl PubGrubSpecifier { let range = specifiers .iter() .map(Self::from_pep440_specifier) - .fold_ok(Range::full(), |range, specifier| { + .fold_ok(Ranges::full(), |range, specifier| { range.intersection(&specifier.into()) })?; Ok(Self(range)) @@ -64,15 +64,15 @@ impl PubGrubSpecifier { let ranges = match specifier.operator() { Operator::Equal => { let version = specifier.version().clone(); - Range::singleton(version) + Ranges::singleton(version) } Operator::ExactEqual => { let version = specifier.version().clone(); - Range::singleton(version) + Ranges::singleton(version) } Operator::NotEqual => { let version = specifier.version().clone(); - Range::singleton(version).complement() + Ranges::singleton(version).complement() } Operator::TildeEqual => { let [rest @ .., last, _] = specifier.version().release() else { @@ -82,38 +82,38 @@ impl PubGrubSpecifier { .with_epoch(specifier.version().epoch()) .with_dev(Some(0)); let version = specifier.version().clone(); - Range::from_range_bounds(version..upper) + Ranges::from_range_bounds(version..upper) } Operator::LessThan => { let version = specifier.version().clone(); if version.any_prerelease() { - Range::strictly_lower_than(version) + Ranges::strictly_lower_than(version) } else { // Per PEP 440: "The exclusive ordered comparison { let version = specifier.version().clone(); - Range::lower_than(version) + Ranges::lower_than(version) } Operator::GreaterThan => { // Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of // the given version unless V itself is a post release." let version = specifier.version().clone(); if let Some(dev) = version.dev() { - Range::higher_than(version.with_dev(Some(dev + 1))) + Ranges::higher_than(version.with_dev(Some(dev + 1))) } else if let Some(post) = version.post() { - Range::higher_than(version.with_post(Some(post + 1))) + Ranges::higher_than(version.with_post(Some(post + 1))) } else { - Range::strictly_higher_than(version.with_max(Some(0))) + Ranges::strictly_higher_than(version.with_max(Some(0))) } } Operator::GreaterThanEqual => { let version = specifier.version().clone(); - Range::higher_than(version) + Ranges::higher_than(version) } Operator::EqualStar => { let low = specifier.version().clone().with_dev(Some(0)); @@ -130,7 +130,7 @@ impl PubGrubSpecifier { *release.last_mut().unwrap() += 1; high = high.with_release(release); } - Range::from_range_bounds(low..high) + Ranges::from_range_bounds(low..high) } Operator::NotEqualStar => { let low = specifier.version().clone().with_dev(Some(0)); @@ -147,7 +147,7 @@ impl PubGrubSpecifier { *release.last_mut().unwrap() += 1; high = high.with_release(release); } - Range::from_range_bounds(low..high).complement() + Ranges::from_range_bounds(low..high).complement() } }; @@ -171,7 +171,7 @@ impl PubGrubSpecifier { let range = specifiers .iter() .map(Self::from_release_specifier) - .fold_ok(Range::full(), |range, specifier| { + .fold_ok(Ranges::full(), |range, specifier| { range.intersection(&specifier.into()) })?; Ok(Self(range)) @@ -194,15 +194,15 @@ impl PubGrubSpecifier { let ranges = match specifier.operator() { Operator::Equal => { let version = specifier.version().only_release(); - Range::singleton(version) + Ranges::singleton(version) } Operator::ExactEqual => { let version = specifier.version().only_release(); - Range::singleton(version) + Ranges::singleton(version) } Operator::NotEqual => { let version = specifier.version().only_release(); - Range::singleton(version).complement() + Ranges::singleton(version).complement() } Operator::TildeEqual => { let [rest @ .., last, _] = specifier.version().release() else { @@ -210,23 +210,23 @@ impl PubGrubSpecifier { }; let upper = Version::new(rest.iter().chain([&(last + 1)])); let version = specifier.version().only_release(); - Range::from_range_bounds(version..upper) + Ranges::from_range_bounds(version..upper) } Operator::LessThan => { let version = specifier.version().only_release(); - Range::strictly_lower_than(version) + Ranges::strictly_lower_than(version) } Operator::LessThanEqual => { let version = specifier.version().only_release(); - Range::lower_than(version) + Ranges::lower_than(version) } Operator::GreaterThan => { let version = specifier.version().only_release(); - Range::strictly_higher_than(version) + Ranges::strictly_higher_than(version) } Operator::GreaterThanEqual => { let version = specifier.version().only_release(); - Range::higher_than(version) + Ranges::higher_than(version) } Operator::EqualStar => { let low = specifier.version().only_release(); @@ -237,7 +237,7 @@ impl PubGrubSpecifier { high = high.with_release(release); high }; - Range::from_range_bounds(low..high) + Ranges::from_range_bounds(low..high) } Operator::NotEqualStar => { let low = specifier.version().only_release(); @@ -248,7 +248,7 @@ impl PubGrubSpecifier { high = high.with_release(release); high }; - Range::from_range_bounds(low..high).complement() + Ranges::from_range_bounds(low..high).complement() } }; Ok(Self(ranges))