@@ -180,6 +180,14 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
180
180
return None ;
181
181
}
182
182
let ( p1, p2) = self_pkgs;
183
+ // We ignore self-dependencies. They are always either trivially true or trivially false,
184
+ // as the package version implies whether the constraint will always be fulfilled or always
185
+ // violated.
186
+ // At time of writing, the public crate API only allowed a map of dependencies,
187
+ // meaning it can't hit this branch, which requires two self-dependencies.
188
+ if p1 == p2 {
189
+ return None ;
190
+ }
183
191
let dep_term = self . get ( p2) ;
184
192
// The dependency range for p2 must be the same in both case
185
193
// to be able to merge multiple p1 ranges.
@@ -381,10 +389,12 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
381
389
#[ cfg( test) ]
382
390
pub ( crate ) mod tests {
383
391
use proptest:: prelude:: * ;
392
+ use std:: cmp:: Reverse ;
393
+ use std:: collections:: BTreeMap ;
384
394
385
395
use super :: * ;
386
396
use crate :: term:: tests:: strategy as term_strat;
387
- use crate :: Ranges ;
397
+ use crate :: { OfflineDependencyProvider , Ranges , State } ;
388
398
389
399
proptest ! {
390
400
@@ -421,4 +431,66 @@ pub(crate) mod tests {
421
431
}
422
432
423
433
}
434
+
435
+ /// Check that multiple self-dependencies are supported.
436
+ ///
437
+ /// The current public API deduplicates dependencies through a map, so we test them here
438
+ /// manually.
439
+ ///
440
+ /// https://github.com/astral-sh/uv/issues/13344
441
+ #[ test]
442
+ fn package_depend_on_self ( ) {
443
+ let cases: & [ Vec < ( String , Ranges < usize > ) > ] = & [
444
+ vec ! [ ( "foo" . to_string( ) , Ranges :: full( ) ) ] ,
445
+ vec ! [
446
+ ( "foo" . to_string( ) , Ranges :: full( ) ) ,
447
+ ( "foo" . to_string( ) , Ranges :: full( ) ) ,
448
+ ] ,
449
+ vec ! [
450
+ ( "foo" . to_string( ) , Ranges :: full( ) ) ,
451
+ ( "foo" . to_string( ) , Ranges :: singleton( 1usize ) ) ,
452
+ ] ,
453
+ vec ! [
454
+ ( "foo" . to_string( ) , Ranges :: singleton( 1usize ) ) ,
455
+ ( "foo" . to_string( ) , Ranges :: from_range_bounds( 1usize ..2 ) ) ,
456
+ ( "foo" . to_string( ) , Ranges :: from_range_bounds( 1usize ..3 ) ) ,
457
+ ] ,
458
+ ] ;
459
+
460
+ for case in cases {
461
+ let mut state: State < OfflineDependencyProvider < String , Ranges < usize > > > =
462
+ State :: init ( "root" . to_string ( ) , 0 ) ;
463
+ state. unit_propagation ( state. root_package ) . unwrap ( ) ;
464
+
465
+ // Add the root package
466
+ state. add_package_version_dependencies (
467
+ state. root_package ,
468
+ 0 ,
469
+ [ ( "foo" . to_string ( ) , Ranges :: singleton ( 1usize ) ) ] ,
470
+ ) ;
471
+ state. unit_propagation ( state. root_package ) . unwrap ( ) ;
472
+
473
+ // Add a package that depends on itself twice
474
+ let ( next, _) = state
475
+ . partial_solution
476
+ . pick_highest_priority_pkg ( |_p, _r| ( 0 , Reverse ( 0 ) ) )
477
+ . unwrap ( ) ;
478
+ state. add_package_version_dependencies ( next, 1 , case. clone ( ) ) ;
479
+ state. unit_propagation ( next) . unwrap ( ) ;
480
+
481
+ assert ! ( state
482
+ . partial_solution
483
+ . pick_highest_priority_pkg( |_p, _r| ( 0 , Reverse ( 0 ) ) )
484
+ . is_none( ) ) ;
485
+
486
+ let solution: BTreeMap < String , usize > = state
487
+ . partial_solution
488
+ . extract_solution ( )
489
+ . map ( |( p, v) | ( state. package_store [ p] . clone ( ) , v) )
490
+ . collect ( ) ;
491
+ let expected = BTreeMap :: from ( [ ( "root" . to_string ( ) , 0 ) , ( "foo" . to_string ( ) , 1 ) ] ) ;
492
+
493
+ assert_eq ! ( solution, expected, "{:?}" , case) ;
494
+ }
495
+ }
424
496
}
0 commit comments