Start using the version ranges crate (#8667)

This commit is contained in:
konsti 2024-10-29 17:39:50 +01:00 committed by GitHub
parent a56e521179
commit 36102dbd0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 92 additions and 82 deletions

15
Cargo.lock generated
View File

@ -2498,13 +2498,14 @@ dependencies = [
[[package]] [[package]]
name = "pubgrub" name = "pubgrub"
version = "0.2.1" 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 = [ dependencies = [
"indexmap", "indexmap",
"log", "log",
"priority-queue", "priority-queue",
"rustc-hash", "rustc-hash",
"thiserror", "thiserror",
"version-ranges",
] ]
[[package]] [[package]]
@ -4885,7 +4886,6 @@ dependencies = [
"insta", "insta",
"itertools 0.13.0", "itertools 0.13.0",
"log", "log",
"pubgrub",
"regex", "regex",
"rustc-hash", "rustc-hash",
"schemars", "schemars",
@ -4901,6 +4901,7 @@ dependencies = [
"uv-normalize", "uv-normalize",
"uv-pep440", "uv-pep440",
"uv-pubgrub", "uv-pubgrub",
"version-ranges",
] ]
[[package]] [[package]]
@ -4933,9 +4934,9 @@ name = "uv-pubgrub"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"itertools 0.13.0", "itertools 0.13.0",
"pubgrub",
"thiserror", "thiserror",
"uv-pep440", "uv-pep440",
"version-ranges",
] ]
[[package]] [[package]]
@ -5376,6 +5377,14 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version-ranges"
version = "0.1.0"
source = "git+https://github.com/astral-sh/pubgrub?rev=95e1390399cdddee986b658be19587eb1fdb2d79#95e1390399cdddee986b658be19587eb1fdb2d79"
dependencies = [
"smallvec",
]
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

View File

@ -127,7 +127,8 @@ pathdiff = { version = "0.2.1" }
petgraph = { version = "0.6.5" } petgraph = { version = "0.6.5" }
platform-info = { version = "2.0.3" } platform-info = { version = "2.0.3" }
proc-macro2 = { version = "1.0.86" } 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" } quote = { version = "1.0.37" }
rayon = { version = "1.10.0" } rayon = { version = "1.10.0" }
reflink-copy = { version = "0.1.19" } reflink-copy = { version = "0.1.19" }

View File

@ -29,7 +29,6 @@ uv-pubgrub = { workspace = true }
boxcar = { workspace = true } boxcar = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
pubgrub = { workspace = true }
regex = { workspace = true } regex = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true } schemars = { workspace = true, optional = true }
@ -40,6 +39,7 @@ thiserror = { workspace = true }
tracing = { workspace = true, optional = true } tracing = { workspace = true, optional = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
version-ranges = { workspace = true }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.40.0" } insta = { version = "1.40.0" }

View File

@ -51,12 +51,12 @@ use std::sync::Mutex;
use std::sync::MutexGuard; use std::sync::MutexGuard;
use itertools::Either; use itertools::Either;
use pubgrub::Range;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
use uv_pep440::Operator; use uv_pep440::Operator;
use uv_pep440::{Version, VersionSpecifier}; use uv_pep440::{Version, VersionSpecifier};
use uv_pubgrub::PubGrubSpecifier; use uv_pubgrub::PubGrubSpecifier;
use version_ranges::Ranges;
use crate::marker::MarkerValueExtra; use crate::marker::MarkerValueExtra;
use crate::ExtraOperator; use crate::ExtraOperator;
@ -403,7 +403,7 @@ impl InternerGuard<'_> {
}); });
return self.create_node(node.var.clone(), children); 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() { if py_range.is_empty() {
// Oops, the bounds imply there is nothing that can match, // Oops, the bounds imply there is nothing that can match,
// so we always evaluate to false. // so we always evaluate to false.
@ -428,12 +428,12 @@ impl InternerGuard<'_> {
// are known to be satisfied. // are known to be satisfied.
let &(ref first_range, first_node_id) = new.first().unwrap(); let &(ref first_range, first_node_id) = new.first().unwrap();
let first_upper = first_range.bounding_range().unwrap().1; 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); *new.first_mut().unwrap() = (clipped, first_node_id);
let &(ref last_range, last_node_id) = new.last().unwrap(); let &(ref last_range, last_node_id) = new.last().unwrap();
let last_lower = last_range.bounding_range().unwrap().0; 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); *new.last_mut().unwrap() = (clipped, last_node_id);
self.create_node(node.var.clone(), Edges::Version { edges: new }) self.create_node(node.var.clone(), Edges::Version { edges: new })
@ -459,7 +459,7 @@ impl InternerGuard<'_> {
return i; 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() { if py_range.is_empty() {
// Oops, the bounds imply there is nothing that can match, // Oops, the bounds imply there is nothing that can match,
// so we always evaluate to false. // so we always evaluate to false.
@ -521,14 +521,14 @@ impl InternerGuard<'_> {
// adjacent ranges map to the same node, which would not be // adjacent ranges map to the same node, which would not be
// a canonical representation. // a canonical representation.
if exclude_node_id == first_node_id { 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); *new.first_mut().unwrap() = (clipped, first_node_id);
} else { } 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); *new.first_mut().unwrap() = (clipped, first_node_id);
let py_range_lower = 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))); 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 // same reasoning applies here: to maintain a canonical
// representation. // representation.
if exclude_node_id == last_node_id { 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); *new.last_mut().unwrap() = (clipped, last_node_id);
} else { } 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); *new.last_mut().unwrap() = (clipped, last_node_id);
let py_range_upper = 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)); 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 // 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. // interval without gaps. Additionally, there are at least two edges in the set.
Version { Version {
edges: SmallVec<(Range<Version>, NodeId)>, edges: SmallVec<(Ranges<Version>, NodeId)>,
}, },
// The edges of a string variable, representing a disjoint set of ranges that cover // The edges of a string variable, representing a disjoint set of ranges that cover
// the output space. // the output space.
@ -696,7 +696,7 @@ pub(crate) enum Edges {
// Invariant: All ranges are simple, meaning they can be represented by a bounded // 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. // interval without gaps. Additionally, there are at least two edges in the set.
String { String {
edges: SmallVec<(Range<String>, NodeId)>, edges: SmallVec<(Ranges<String>, NodeId)>,
}, },
// The edges of a boolean variable, representing the values `true` (the `high` child) // The edges of a boolean variable, representing the values `true` (the `high` child)
// and `false` (the `low` child). // and `false` (the `low` child).
@ -727,13 +727,13 @@ impl Edges {
/// This function will panic for the `In` and `Contains` marker operators, which /// This function will panic for the `In` and `Contains` marker operators, which
/// should be represented as separate boolean variables. /// should be represented as separate boolean variables.
fn from_string(operator: MarkerOperator, value: String) -> Edges { fn from_string(operator: MarkerOperator, value: String) -> Edges {
let range: Range<String> = match operator { let range: Ranges<String> = match operator {
MarkerOperator::Equal => Range::singleton(value), MarkerOperator::Equal => Ranges::singleton(value),
MarkerOperator::NotEqual => Range::singleton(value).complement(), MarkerOperator::NotEqual => Ranges::singleton(value).complement(),
MarkerOperator::GreaterThan => Range::strictly_higher_than(value), MarkerOperator::GreaterThan => Ranges::strictly_higher_than(value),
MarkerOperator::GreaterEqual => Range::higher_than(value), MarkerOperator::GreaterEqual => Ranges::higher_than(value),
MarkerOperator::LessThan => Range::strictly_lower_than(value), MarkerOperator::LessThan => Ranges::strictly_lower_than(value),
MarkerOperator::LessEqual => Range::lower_than(value), MarkerOperator::LessEqual => Ranges::lower_than(value),
MarkerOperator::TildeEqual => unreachable!("string comparisons with ~= are ignored"), MarkerOperator::TildeEqual => unreachable!("string comparisons with ~= are ignored"),
_ => unreachable!("`in` and `contains` are treated as boolean variables"), _ => 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`. /// Only for use when the `key` is a `PythonVersion`. Normalizes to `PythonFullVersion`.
fn from_python_versions(versions: Vec<Version>, negated: bool) -> Result<Edges, NodeId> { fn from_python_versions(versions: Vec<Version>, negated: bool) -> Result<Edges, NodeId> {
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 // TODO(zanieb): We need to make sure this is performant, repeated unions like this do not
// seem efficient. // seem efficient.
@ -779,12 +779,12 @@ impl Edges {
/// Returns an [`Edges`] where values in the given range are `true`. /// Returns an [`Edges`] where values in the given range are `true`.
fn from_versions(versions: &Vec<Version>, negated: bool) -> Edges { fn from_versions(versions: &Vec<Version>, 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 // TODO(zanieb): We need to make sure this is performant, repeated unions like this do not
// seem efficient. // seem efficient.
for version in versions { for version in versions {
range = range.union(&Range::singleton(version.clone())); range = range.union(&Ranges::singleton(version.clone()));
} }
if negated { if negated {
@ -797,7 +797,7 @@ impl Edges {
} }
/// Returns an [`Edges`] where values in the given range are `true`. /// Returns an [`Edges`] where values in the given range are `true`.
fn from_range<T>(range: &Range<T>) -> SmallVec<(Range<T>, NodeId)> fn from_range<T>(range: &Ranges<T>) -> SmallVec<(Ranges<T>, NodeId)>
where where
T: Ord + Clone, T: Ord + Clone,
{ {
@ -805,13 +805,13 @@ impl Edges {
// Add the `true` edges. // Add the `true` edges.
for (start, end) in range.iter() { 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)); edges.push((range, NodeId::TRUE));
} }
// Add the `false` edges. // Add the `false` edges.
for (start, end) in range.complement().iter() { 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)); 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 /// 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. /// this should not occur in practice because `requires-python` bounds are global.
fn apply_ranges<T>( fn apply_ranges<T>(
left_edges: &SmallVec<(Range<T>, NodeId)>, left_edges: &SmallVec<(Ranges<T>, NodeId)>,
left_parent: NodeId, left_parent: NodeId,
right_edges: &SmallVec<(Range<T>, NodeId)>, right_edges: &SmallVec<(Ranges<T>, NodeId)>,
right_parent: NodeId, right_parent: NodeId,
mut apply: impl FnMut(NodeId, NodeId) -> NodeId, mut apply: impl FnMut(NodeId, NodeId) -> NodeId,
) -> SmallVec<(Range<T>, NodeId)> ) -> SmallVec<(Ranges<T>, NodeId)>
where where
T: Clone + Ord, T: Clone + Ord,
{ {
@ -968,9 +968,9 @@ impl Edges {
// Returns `true` if all intersecting ranges in two range maps are disjoint. // Returns `true` if all intersecting ranges in two range maps are disjoint.
fn is_disjoint_ranges<T>( fn is_disjoint_ranges<T>(
left_edges: &SmallVec<(Range<T>, NodeId)>, left_edges: &SmallVec<(Ranges<T>, NodeId)>,
left_parent: NodeId, left_parent: NodeId,
right_edges: &SmallVec<(Range<T>, NodeId)>, right_edges: &SmallVec<(Ranges<T>, NodeId)>,
right_parent: NodeId, right_parent: NodeId,
interner: &mut InternerGuard<'_>, interner: &mut InternerGuard<'_>,
) -> bool ) -> bool
@ -1179,7 +1179,7 @@ fn python_version_to_full_version(specifier: VersionSpecifier) -> Result<Version
} }
/// Compares the start of two ranges that are known to be disjoint. /// Compares the start of two ranges that are known to be disjoint.
fn compare_disjoint_range_start<T>(range1: &Range<T>, range2: &Range<T>) -> Ordering fn compare_disjoint_range_start<T>(range1: &Ranges<T>, range2: &Ranges<T>) -> Ordering
where where
T: Ord, T: Ord,
{ {
@ -1199,7 +1199,7 @@ where
} }
/// Returns `true` if two disjoint ranges can be conjoined seamlessly without introducing a gap. /// Returns `true` if two disjoint ranges can be conjoined seamlessly without introducing a gap.
fn can_conjoin<T>(range1: &Range<T>, range2: &Range<T>) -> bool fn can_conjoin<T>(range1: &Ranges<T>, range2: &Ranges<T>) -> bool
where where
T: Ord + Clone, T: Ord + Clone,
{ {

View File

@ -3,9 +3,9 @@ use std::ops::Bound;
use indexmap::IndexMap; use indexmap::IndexMap;
use itertools::Itertools; use itertools::Itertools;
use pubgrub::Range;
use rustc_hash::FxBuildHasher; use rustc_hash::FxBuildHasher;
use uv_pep440::{Version, VersionSpecifier}; use uv_pep440::{Version, VersionSpecifier};
use version_ranges::Ranges;
use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind};
@ -280,17 +280,17 @@ fn simplify(dnf: &mut Vec<Vec<MarkerExpression>>) {
/// Merge any edges that lead to identical subtrees into a single range. /// Merge any edges that lead to identical subtrees into a single range.
pub(crate) fn collect_edges<'a, T>( pub(crate) fn collect_edges<'a, T>(
map: impl ExactSizeIterator<Item = (&'a Range<T>, MarkerTree)>, map: impl ExactSizeIterator<Item = (&'a Ranges<T>, MarkerTree)>,
) -> IndexMap<MarkerTree, Range<T>, FxBuildHasher> ) -> IndexMap<MarkerTree, Ranges<T>, FxBuildHasher>
where where
T: Ord + Clone + 'a, 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 { for (range, tree) in map {
// OK because all ranges are guaranteed to be non-empty. // OK because all ranges are guaranteed to be non-empty.
let (start, end) = range.bounding_range().unwrap(); let (start, end) = range.bounding_range().unwrap();
// Combine the ranges. // Combine the ranges.
let range = Range::from_range_bounds((start.cloned(), end.cloned())); let range = Ranges::from_range_bounds((start.cloned(), end.cloned()));
paths paths
.entry(tree) .entry(tree)
.and_modify(|union| *union = union.union(&range)) .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 /// For example, `os_name < 'Linux' or os_name > 'Linux'` can be simplified to
/// `os_name != 'Linux'`. /// `os_name != 'Linux'`.
fn range_inequality<T>(range: &Range<T>) -> Option<Vec<&T>> fn range_inequality<T>(range: &Ranges<T>) -> Option<Vec<&T>>
where where
T: Ord + Clone + fmt::Debug, 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 /// For example, `python_full_version < '3.8' or python_full_version >= '3.9'` can be simplified to
/// `python_full_version != '3.8.*'`. /// `python_full_version != '3.8.*'`.
fn star_range_inequality(range: &Range<Version>) -> Option<VersionSpecifier> { fn star_range_inequality(range: &Ranges<Version>) -> Option<VersionSpecifier> {
let (b1, b2) = range.iter().collect_tuple()?; let (b1, b2) = range.iter().collect_tuple()?;
match (b1, b2) { match (b1, b2) {

View File

@ -5,10 +5,10 @@ use std::ops::{Bound, Deref};
use std::str::FromStr; use std::str::FromStr;
use itertools::Itertools; use itertools::Itertools;
use pubgrub::Range;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use uv_normalize::ExtraName; use uv_normalize::ExtraName;
use uv_pep440::{Version, VersionParseError, VersionSpecifier}; use uv_pep440::{Version, VersionParseError, VersionSpecifier};
use version_ranges::Ranges;
use crate::cursor::Cursor; use crate::cursor::Cursor;
use crate::marker::parse; use crate::marker::parse;
@ -1396,7 +1396,7 @@ pub enum MarkerTreeKind<'a> {
pub struct VersionMarkerTree<'a> { pub struct VersionMarkerTree<'a> {
id: NodeId, id: NodeId,
key: MarkerValueVersion, key: MarkerValueVersion,
map: &'a [(Range<Version>, NodeId)], map: &'a [(Ranges<Version>, NodeId)],
} }
impl VersionMarkerTree<'_> { impl VersionMarkerTree<'_> {
@ -1406,7 +1406,7 @@ impl VersionMarkerTree<'_> {
} }
/// The edges of this node, corresponding to possible output ranges of the given variable. /// The edges of this node, corresponding to possible output ranges of the given variable.
pub fn edges(&self) -> impl ExactSizeIterator<Item = (&Range<Version>, MarkerTree)> + '_ { pub fn edges(&self) -> impl ExactSizeIterator<Item = (&Ranges<Version>, MarkerTree)> + '_ {
self.map self.map
.iter() .iter()
.map(|(range, node)| (range, MarkerTree(node.negate(self.id)))) .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
@ -1432,7 +1432,7 @@ impl Ord for VersionMarkerTree<'_> {
pub struct StringMarkerTree<'a> { pub struct StringMarkerTree<'a> {
id: NodeId, id: NodeId,
key: MarkerValueString, key: MarkerValueString,
map: &'a [(Range<String>, NodeId)], map: &'a [(Ranges<String>, NodeId)],
} }
impl StringMarkerTree<'_> { impl StringMarkerTree<'_> {
@ -1442,7 +1442,7 @@ impl StringMarkerTree<'_> {
} }
/// The edges of this node, corresponding to possible output ranges of the given variable. /// The edges of this node, corresponding to possible output ranges of the given variable.
pub fn children(&self) -> impl ExactSizeIterator<Item = (&Range<String>, MarkerTree)> { pub fn children(&self) -> impl ExactSizeIterator<Item = (&Ranges<String>, MarkerTree)> {
self.map self.map
.iter() .iter()
.map(|(range, node)| (range, MarkerTree(node.negate(self.id)))) .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))

View File

@ -15,4 +15,4 @@ uv-pep440 = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
pubgrub = { workspace = true } version-ranges = { workspace = true }

View File

@ -1,8 +1,8 @@
use std::ops::Bound; use std::ops::Bound;
use itertools::Itertools; use itertools::Itertools;
use pubgrub::Range;
use thiserror::Error; use thiserror::Error;
use version_ranges::Ranges;
use uv_pep440::{Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers}; 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. /// A range of versions that can be used to satisfy a requirement.
#[derive(Debug)] #[derive(Debug)]
pub struct PubGrubSpecifier(Range<Version>); pub struct PubGrubSpecifier(Ranges<Version>);
impl PubGrubSpecifier { impl PubGrubSpecifier {
/// Returns an iterator over the bounds of the [`PubGrubSpecifier`]. /// Returns an iterator over the bounds of the [`PubGrubSpecifier`].
@ -22,19 +22,19 @@ impl PubGrubSpecifier {
self.0.iter() 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>)> { pub fn bounding_range(&self) -> Option<(Bound<&Version>, Bound<&Version>)> {
self.0.bounding_range() self.0.bounding_range()
} }
} }
impl From<Range<Version>> for PubGrubSpecifier { impl From<Ranges<Version>> for PubGrubSpecifier {
fn from(range: Range<Version>) -> Self { fn from(range: Ranges<Version>) -> Self {
PubGrubSpecifier(range) PubGrubSpecifier(range)
} }
} }
impl From<PubGrubSpecifier> for Range<Version> { impl From<PubGrubSpecifier> for Ranges<Version> {
/// Convert a PubGrub specifier to a range of versions. /// Convert a PubGrub specifier to a range of versions.
fn from(specifier: PubGrubSpecifier) -> Self { fn from(specifier: PubGrubSpecifier) -> Self {
specifier.0 specifier.0
@ -50,7 +50,7 @@ impl PubGrubSpecifier {
let range = specifiers let range = specifiers
.iter() .iter()
.map(Self::from_pep440_specifier) .map(Self::from_pep440_specifier)
.fold_ok(Range::full(), |range, specifier| { .fold_ok(Ranges::full(), |range, specifier| {
range.intersection(&specifier.into()) range.intersection(&specifier.into())
})?; })?;
Ok(Self(range)) Ok(Self(range))
@ -64,15 +64,15 @@ impl PubGrubSpecifier {
let ranges = match specifier.operator() { let ranges = match specifier.operator() {
Operator::Equal => { Operator::Equal => {
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::singleton(version) Ranges::singleton(version)
} }
Operator::ExactEqual => { Operator::ExactEqual => {
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::singleton(version) Ranges::singleton(version)
} }
Operator::NotEqual => { Operator::NotEqual => {
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::singleton(version).complement() Ranges::singleton(version).complement()
} }
Operator::TildeEqual => { Operator::TildeEqual => {
let [rest @ .., last, _] = specifier.version().release() else { let [rest @ .., last, _] = specifier.version().release() else {
@ -82,38 +82,38 @@ impl PubGrubSpecifier {
.with_epoch(specifier.version().epoch()) .with_epoch(specifier.version().epoch())
.with_dev(Some(0)); .with_dev(Some(0));
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::from_range_bounds(version..upper) Ranges::from_range_bounds(version..upper)
} }
Operator::LessThan => { Operator::LessThan => {
let version = specifier.version().clone(); let version = specifier.version().clone();
if version.any_prerelease() { if version.any_prerelease() {
Range::strictly_lower_than(version) Ranges::strictly_lower_than(version)
} else { } else {
// Per PEP 440: "The exclusive ordered comparison <V MUST NOT allow a // Per PEP 440: "The exclusive ordered comparison <V MUST NOT allow a
// pre-release of the specified version unless the specified version is itself a // pre-release of the specified version unless the specified version is itself a
// pre-release." // pre-release."
Range::strictly_lower_than(version.with_min(Some(0))) Ranges::strictly_lower_than(version.with_min(Some(0)))
} }
} }
Operator::LessThanEqual => { Operator::LessThanEqual => {
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::lower_than(version) Ranges::lower_than(version)
} }
Operator::GreaterThan => { Operator::GreaterThan => {
// Per PEP 440: "The exclusive ordered comparison >V MUST NOT allow a post-release of // 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." // the given version unless V itself is a post release."
let version = specifier.version().clone(); let version = specifier.version().clone();
if let Some(dev) = version.dev() { 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() { } 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 { } else {
Range::strictly_higher_than(version.with_max(Some(0))) Ranges::strictly_higher_than(version.with_max(Some(0)))
} }
} }
Operator::GreaterThanEqual => { Operator::GreaterThanEqual => {
let version = specifier.version().clone(); let version = specifier.version().clone();
Range::higher_than(version) Ranges::higher_than(version)
} }
Operator::EqualStar => { Operator::EqualStar => {
let low = specifier.version().clone().with_dev(Some(0)); let low = specifier.version().clone().with_dev(Some(0));
@ -130,7 +130,7 @@ impl PubGrubSpecifier {
*release.last_mut().unwrap() += 1; *release.last_mut().unwrap() += 1;
high = high.with_release(release); high = high.with_release(release);
} }
Range::from_range_bounds(low..high) Ranges::from_range_bounds(low..high)
} }
Operator::NotEqualStar => { Operator::NotEqualStar => {
let low = specifier.version().clone().with_dev(Some(0)); let low = specifier.version().clone().with_dev(Some(0));
@ -147,7 +147,7 @@ impl PubGrubSpecifier {
*release.last_mut().unwrap() += 1; *release.last_mut().unwrap() += 1;
high = high.with_release(release); 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 let range = specifiers
.iter() .iter()
.map(Self::from_release_specifier) .map(Self::from_release_specifier)
.fold_ok(Range::full(), |range, specifier| { .fold_ok(Ranges::full(), |range, specifier| {
range.intersection(&specifier.into()) range.intersection(&specifier.into())
})?; })?;
Ok(Self(range)) Ok(Self(range))
@ -194,15 +194,15 @@ impl PubGrubSpecifier {
let ranges = match specifier.operator() { let ranges = match specifier.operator() {
Operator::Equal => { Operator::Equal => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::singleton(version) Ranges::singleton(version)
} }
Operator::ExactEqual => { Operator::ExactEqual => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::singleton(version) Ranges::singleton(version)
} }
Operator::NotEqual => { Operator::NotEqual => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::singleton(version).complement() Ranges::singleton(version).complement()
} }
Operator::TildeEqual => { Operator::TildeEqual => {
let [rest @ .., last, _] = specifier.version().release() else { let [rest @ .., last, _] = specifier.version().release() else {
@ -210,23 +210,23 @@ impl PubGrubSpecifier {
}; };
let upper = Version::new(rest.iter().chain([&(last + 1)])); let upper = Version::new(rest.iter().chain([&(last + 1)]));
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::from_range_bounds(version..upper) Ranges::from_range_bounds(version..upper)
} }
Operator::LessThan => { Operator::LessThan => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::strictly_lower_than(version) Ranges::strictly_lower_than(version)
} }
Operator::LessThanEqual => { Operator::LessThanEqual => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::lower_than(version) Ranges::lower_than(version)
} }
Operator::GreaterThan => { Operator::GreaterThan => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::strictly_higher_than(version) Ranges::strictly_higher_than(version)
} }
Operator::GreaterThanEqual => { Operator::GreaterThanEqual => {
let version = specifier.version().only_release(); let version = specifier.version().only_release();
Range::higher_than(version) Ranges::higher_than(version)
} }
Operator::EqualStar => { Operator::EqualStar => {
let low = specifier.version().only_release(); let low = specifier.version().only_release();
@ -237,7 +237,7 @@ impl PubGrubSpecifier {
high = high.with_release(release); high = high.with_release(release);
high high
}; };
Range::from_range_bounds(low..high) Ranges::from_range_bounds(low..high)
} }
Operator::NotEqualStar => { Operator::NotEqualStar => {
let low = specifier.version().only_release(); let low = specifier.version().only_release();
@ -248,7 +248,7 @@ impl PubGrubSpecifier {
high = high.with_release(release); high = high.with_release(release);
high high
}; };
Range::from_range_bounds(low..high).complement() Ranges::from_range_bounds(low..high).complement()
} }
}; };
Ok(Self(ranges)) Ok(Self(ranges))