1- use std:: collections:: { BTreeMap , BTreeSet } ;
1+ use std:: collections:: { BTreeMap , BTreeSet , Bound } ;
22use std:: fmt:: Formatter ;
33use std:: sync:: Arc ;
44
55use indexmap:: IndexSet ;
6- use pubgrub:: { DefaultStringReporter , DerivationTree , Derived , External , Range , Reporter } ;
6+ use pubgrub:: {
7+ DefaultStringReporter , DerivationTree , Derived , External , Range , Ranges , Reporter , Term ,
8+ } ;
79use rustc_hash:: FxHashMap ;
810
911use crate :: candidate_selector:: CandidateSelector ;
@@ -19,7 +21,7 @@ use uv_distribution_types::{
1921 BuiltDist , IndexCapabilities , IndexLocations , IndexUrl , InstalledDist , SourceDist ,
2022} ;
2123use uv_normalize:: PackageName ;
22- use uv_pep440:: Version ;
24+ use uv_pep440:: { LocalVersionSlice , Version } ;
2325use uv_pep508:: MarkerTree ;
2426use 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 {
0 commit comments