Skip to content

Commit d082763

Browse files
committed
Get representations working
1 parent 4dc1678 commit d082763

File tree

9 files changed

+281
-98
lines changed

9 files changed

+281
-98
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ petgraph = { version = "0.6.5" }
127127
platform-info = { version = "2.0.3" }
128128
procfs = { version = "0.17.0" , default-features = false, features = ["flate2"] }
129129
proc-macro2 = { version = "1.0.86" }
130-
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" }
131-
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" }
130+
pubgrub = { git = "https://github.com/astral-sh/pubgrub", branch = "charlie/mut" }
131+
version-ranges = { git = "https://github.com/astral-sh/pubgrub", branch = "charlie/mut" }
132132
quote = { version = "1.0.37" }
133133
rayon = { version = "1.10.0" }
134134
reflink-copy = { version = "0.1.19" }

crates/uv-pep440/src/version.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,12 @@ impl std::fmt::Display for Version {
725725
let local = if self.local().is_empty() {
726726
String::new()
727727
} else {
728-
format!("+{}", self.local().local_identifier_string())
728+
match self.local() {
729+
LocalVersionSlice::Actual(_) => {
730+
format!("+{}", self.local().local_identifier_string())
731+
}
732+
LocalVersionSlice::Sentinel => String::new(),
733+
}
729734
};
730735
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
731736
}
@@ -1389,17 +1394,18 @@ impl std::fmt::Display for Prerelease {
13891394
}
13901395
}
13911396

1392-
/// Either a sequence of local segments or [`LocalVersion::Sentinel`], an internal-only value that compares greater than all other local versions.
1397+
/// Either a sequence of local segments or [`LocalVersion::Sentinel`], an internal-only value that
1398+
/// compares greater than all other local versions.
13931399
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
13941400
#[cfg_attr(
13951401
feature = "rkyv",
13961402
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
13971403
)]
13981404
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
13991405
pub enum LocalVersion {
1400-
/// A sequence of local segments
1406+
/// A sequence of local segments.
14011407
Actual(Vec<LocalSegment>),
1402-
/// An internal-only value that compares greater to all other local versions
1408+
/// An internal-only value that compares greater to all other local versions.
14031409
Max,
14041410
}
14051411

@@ -1408,37 +1414,41 @@ pub enum LocalVersion {
14081414
pub enum LocalVersionSlice<'a> {
14091415
/// Like [`LocalVersion::Actual`]
14101416
Actual(&'a [LocalSegment]),
1411-
/// Like [`LocalVersion::Sentintel`]
1417+
/// Like [`LocalVersion::Sentinel`]
14121418
Sentinel,
14131419
}
14141420

14151421
impl LocalVersion {
1416-
/// convert into a local version slice
1422+
/// Convert the local version segments into a slice.
14171423
pub fn as_slice(&self) -> LocalVersionSlice<'_> {
14181424
match self {
14191425
LocalVersion::Actual(segments) => LocalVersionSlice::Actual(segments),
14201426
LocalVersion::Max => LocalVersionSlice::Sentinel,
14211427
}
14221428
}
14231429

1424-
/// clear the local version segments, if they exist
1430+
/// Clear the local version segments, if they exist.
14251431
pub fn clear(&mut self) {
1426-
if let Self::Actual(segments) = self {
1427-
segments.clear();
1432+
match self {
1433+
Self::Actual(segments) => segments.clear(),
1434+
Self::Max => *self = Self::Actual(Vec::new()),
14281435
}
14291436
}
14301437
}
14311438

14321439
impl LocalVersionSlice<'_> {
1433-
/// output the local version identifier string. [`LocalVersionSlice::Sentinel`] maps to `"[max-local-version]"` which is otherwise an illegal local version because `<` and `>` are not allowed
1440+
/// Output the local version identifier string.
1441+
///
1442+
/// [`LocalVersionSlice::Sentinel`] maps to `"[max]"` which is otherwise an illegal local
1443+
/// version because `[` and `]` are not allowed.
14341444
pub fn local_identifier_string(&self) -> String {
14351445
match self {
14361446
LocalVersionSlice::Actual(segments) => segments
14371447
.iter()
14381448
.map(ToString::to_string)
14391449
.collect::<Vec<String>>()
14401450
.join("."),
1441-
LocalVersionSlice::Sentinel => String::from("[max-local-version]"),
1451+
LocalVersionSlice::Sentinel => String::from("[max]"),
14421452
}
14431453
}
14441454
}

crates/uv-pep440/src/version_ranges.rs

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
use version_ranges::Ranges;
44

5-
use crate::{LocalVersion, Operator, Prerelease, Version, VersionSpecifier, VersionSpecifiers};
5+
use crate::{
6+
LocalVersion, LocalVersionSlice, Operator, Prerelease, Version, VersionSpecifier,
7+
VersionSpecifiers,
8+
};
69

710
impl From<VersionSpecifiers> for Ranges<Version> {
811
/// Convert [`VersionSpecifiers`] to a PubGrub-compatible version range, using PEP 440
@@ -23,13 +26,13 @@ impl From<VersionSpecifier> for Ranges<Version> {
2326
let VersionSpecifier { operator, version } = specifier;
2427
match operator {
2528
Operator::Equal => match version.local() {
26-
crate::LocalVersionSlice::Actual(&[]) => {
29+
LocalVersionSlice::Actual(&[]) => {
2730
let low = version;
2831
let high = low.clone().with_local(LocalVersion::Max);
2932
Ranges::between(low, high)
3033
}
31-
crate::LocalVersionSlice::Actual(_) => Ranges::singleton(version),
32-
crate::LocalVersionSlice::Sentinel => unreachable!(
34+
LocalVersionSlice::Actual(_) => Ranges::singleton(version),
35+
LocalVersionSlice::Sentinel => unreachable!(
3336
"found `LocalVersionSlice::Sentinel`, which should be an internal-only value"
3437
),
3538
},
@@ -146,34 +149,21 @@ pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version
146149
match operator {
147150
Operator::Equal => {
148151
let version = version.only_release();
149-
match version.local() {
150-
crate::LocalVersionSlice::Actual(&[]) => {
151-
let low = version;
152-
let high = low.clone().with_local(LocalVersion::Max);
153-
Ranges::between(low, high)
154-
}
155-
crate::LocalVersionSlice::Actual(_) => Ranges::singleton(version),
156-
crate::LocalVersionSlice::Sentinel => unreachable!(
157-
"found `LocalVersionSlice::Sentinel`, which should be an internal-only value"
158-
),
159-
}
152+
Ranges::singleton(version)
160153
}
161154
Operator::ExactEqual => {
162155
let version = version.only_release();
163156
Ranges::singleton(version)
164157
}
165-
Operator::NotEqual => release_specifier_to_range(VersionSpecifier {
166-
operator: Operator::Equal,
167-
version,
168-
})
169-
.complement(),
158+
Operator::NotEqual => {
159+
let version = version.only_release();
160+
Ranges::singleton(version).complement()
161+
}
170162
Operator::TildeEqual => {
171163
let [rest @ .., last, _] = version.release() else {
172164
unreachable!("~= must have at least two segments");
173165
};
174-
let upper =
175-
// greater release includes local
176-
Version::new(rest.iter().chain([&(last + 1)]));
166+
let upper = Version::new(rest.iter().chain([&(last + 1)]));
177167
let version = version.only_release();
178168
Ranges::from_range_bounds(version..upper)
179169
}
@@ -183,7 +173,7 @@ pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version
183173
}
184174
Operator::LessThanEqual => {
185175
let version = version.only_release();
186-
Ranges::lower_than(version.with_local(LocalVersion::Max))
176+
Ranges::lower_than(version)
187177
}
188178
Operator::GreaterThan => {
189179
let version = version.only_release();

crates/uv-resolver/src/error.rs

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use std::collections::{BTreeMap, BTreeSet};
1+
use std::collections::{BTreeMap, BTreeSet, Bound};
22
use std::fmt::Formatter;
33
use std::sync::Arc;
44

55
use indexmap::IndexSet;
6-
use pubgrub::{DefaultStringReporter, DerivationTree, Derived, External, Range, Reporter};
6+
use pubgrub::{
7+
DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
8+
};
79
use rustc_hash::FxHashMap;
810

911
use crate::candidate_selector::CandidateSelector;
@@ -19,7 +21,7 @@ use uv_distribution_types::{
1921
BuiltDist, IndexCapabilities, IndexLocations, IndexUrl, InstalledDist, SourceDist,
2022
};
2123
use uv_normalize::PackageName;
22-
use uv_pep440::Version;
24+
use uv_pep440::{LocalVersionSlice, Version};
2325
use uv_pep508::MarkerTree;
2426
use uv_static::EnvVars;
2527

@@ -210,6 +212,157 @@ impl NoSolutionError {
210212
.expect("derivation tree should contain at least one external term")
211213
}
212214

215+
/// Simplifies the version ranges on any incompatibilities to remove the `[max]` sentinel.
216+
///
217+
/// The `[max]` sentinel is used to represent the maximum local version of a package, to
218+
/// implement PEP 440 semantics for local version equality. For example, `1.0.0+foo` needs to
219+
/// satisfy `==1.0.0`.
220+
pub(crate) fn simplify_local_version_segments(
221+
mut derivation_tree: ErrorTree,
222+
) -> Option<ErrorTree> {
223+
/// Remove local versions sentinels (`+[max]`) from the given version ranges.
224+
fn strip_sentinel(versions: &mut Ranges<Version>) {
225+
versions.iter_mut().for_each(|(lower, upper)| {
226+
match (&lower, &upper) {
227+
(Bound::Unbounded, Bound::Unbounded) => {}
228+
(Bound::Unbounded, Bound::Included(v)) => {
229+
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
230+
if v.local() == LocalVersionSlice::Sentinel {
231+
*upper = Bound::Included(v.clone().without_local());
232+
}
233+
}
234+
(Bound::Unbounded, Bound::Excluded(v)) => {
235+
// `<1.0.0+[max]` is equivalent to `<1.0.0`
236+
if v.local() == LocalVersionSlice::Sentinel {
237+
*upper = Bound::Excluded(v.clone().without_local());
238+
}
239+
}
240+
(Bound::Included(v), Bound::Unbounded) => {
241+
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
242+
if v.local() == LocalVersionSlice::Sentinel {
243+
*lower = Bound::Excluded(v.clone().without_local());
244+
}
245+
}
246+
(Bound::Included(v), Bound::Included(b)) => {
247+
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
248+
if v.local() == LocalVersionSlice::Sentinel {
249+
*lower = Bound::Excluded(v.clone().without_local());
250+
}
251+
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
252+
if b.local() == LocalVersionSlice::Sentinel {
253+
*upper = Bound::Included(b.clone().without_local());
254+
}
255+
}
256+
(Bound::Included(v), Bound::Excluded(b)) => {
257+
// `>=1.0.0+[max]` is equivalent to `>1.0.0`
258+
if v.local() == LocalVersionSlice::Sentinel {
259+
*lower = Bound::Excluded(v.clone().without_local());
260+
}
261+
// `<1.0.0+[max]` is equivalent to `<1.0.0`
262+
if b.local() == LocalVersionSlice::Sentinel {
263+
*upper = Bound::Included(b.clone().without_local());
264+
}
265+
}
266+
(Bound::Excluded(v), Bound::Unbounded) => {
267+
// `>1.0.0+[max]` is equivalent to `>1.0.0`
268+
if v.local() == LocalVersionSlice::Sentinel {
269+
*lower = Bound::Excluded(v.clone().without_local());
270+
}
271+
}
272+
(Bound::Excluded(v), Bound::Included(b)) => {
273+
// `>1.0.0+[max]` is equivalent to `>1.0.0`
274+
if v.local() == LocalVersionSlice::Sentinel {
275+
*lower = Bound::Excluded(v.clone().without_local());
276+
}
277+
// `<=1.0.0+[max]` is equivalent to `<=1.0.0`
278+
if b.local() == LocalVersionSlice::Sentinel {
279+
*upper = Bound::Included(b.clone().without_local());
280+
}
281+
}
282+
(Bound::Excluded(v), Bound::Excluded(b)) => {
283+
// `>1.0.0+[max]` is equivalent to `>1.0.0`
284+
if v.local() == LocalVersionSlice::Sentinel {
285+
*lower = Bound::Excluded(v.clone().without_local());
286+
}
287+
// `<1.0.0+[max]` is equivalent to `<1.0.0`
288+
if b.local() == LocalVersionSlice::Sentinel {
289+
*upper = Bound::Excluded(b.clone().without_local());
290+
}
291+
}
292+
}
293+
});
294+
}
295+
296+
/// Returns `true` if the range appears to be, e.g., `>1.0.0, <1.0.0+[max]`.
297+
fn is_sentinel(versions: &mut Ranges<Version>) -> bool {
298+
versions.iter().all(|(lower, upper)| {
299+
let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
300+
return false;
301+
};
302+
if lower.local() == LocalVersionSlice::Sentinel {
303+
return false;
304+
}
305+
if upper.local() != LocalVersionSlice::Sentinel {
306+
return false;
307+
}
308+
*lower == upper.clone().without_local()
309+
})
310+
}
311+
312+
match derivation_tree {
313+
DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
314+
DerivationTree::External(External::NoVersions(_, ref mut versions)) => {
315+
if is_sentinel(versions) {
316+
None
317+
} else {
318+
strip_sentinel(versions);
319+
Some(derivation_tree)
320+
}
321+
}
322+
DerivationTree::External(External::FromDependencyOf(
323+
_,
324+
ref mut versions1,
325+
_,
326+
ref mut versions2,
327+
)) => {
328+
strip_sentinel(versions1);
329+
strip_sentinel(versions2);
330+
Some(derivation_tree)
331+
}
332+
DerivationTree::External(External::Custom(_, ref mut versions, _)) => {
333+
strip_sentinel(versions);
334+
Some(derivation_tree)
335+
}
336+
DerivationTree::Derived(mut derived) => {
337+
let cause1 = Self::simplify_local_version_segments((*derived.cause1).clone());
338+
let cause2 = Self::simplify_local_version_segments((*derived.cause2).clone());
339+
match (cause1, cause2) {
340+
(Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
341+
cause1: Arc::new(cause1),
342+
cause2: Arc::new(cause2),
343+
terms: std::mem::take(&mut derived.terms)
344+
.into_iter()
345+
.map(|(pkg, mut term)| {
346+
match &mut term {
347+
Term::Positive(versions) => {
348+
strip_sentinel(versions);
349+
}
350+
Term::Negative(versions) => {
351+
strip_sentinel(versions);
352+
}
353+
}
354+
(pkg, term)
355+
})
356+
.collect(),
357+
shared_id: derived.shared_id,
358+
})),
359+
(Some(cause), None) | (None, Some(cause)) => Some(cause),
360+
_ => None,
361+
}
362+
}
363+
}
364+
}
365+
213366
/// Initialize a [`NoSolutionHeader`] for this error.
214367
pub fn header(&self) -> NoSolutionHeader {
215368
NoSolutionHeader::new(self.markers.clone())
@@ -238,6 +391,7 @@ impl std::fmt::Display for NoSolutionError {
238391
display_tree(&tree, "Resolver derivation tree before reduction");
239392
}
240393

394+
// simplify_local_version_segments(&mut tree);
241395
collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
242396

243397
if self.workspace_members.len() == 1 {

crates/uv-resolver/src/pubgrub/report.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,16 @@ impl PubGrubReportFormatter<'_> {
496496
}
497497
}
498498

499+
// fn simplify_locals<'a>(
500+
// &self,
501+
// set: &'a Range<Version>,
502+
// ) -> Cow<'a, Range<Version>> {
503+
// // for (lower, upper) in set.iter() {
504+
// //
505+
// // }
506+
// Cow::Owned(Range::from((Bound::Included(Version::new([0])), Bound::Excluded(Version::new([1])))))
507+
// }
508+
499509
/// Generate the [`PubGrubHints`] for a derivation tree.
500510
///
501511
/// The [`PubGrubHints`] help users resolve errors by providing additional context or modifying

0 commit comments

Comments
 (0)