1use petgraph::{
2 algo::toposort,
3 graph::{DiGraph, NodeIndex},
4};
5use rustc_hash::{FxHashMap, FxHashSet};
6#[cfg(feature = "schemars")]
7use std::borrow::Cow;
8use std::{collections::BTreeSet, hash::Hash, rc::Rc};
9use uv_normalize::{ExtraName, GroupName, PackageName};
10
11use crate::dependency_groups::{DependencyGroupSpecifier, DependencyGroups};
12
13#[derive(Debug, Default, Clone, Eq, PartialEq, serde::Deserialize)]
19pub struct Conflicts(Vec<ConflictSet>);
20
21impl Conflicts {
22 pub fn empty() -> Self {
26 Self::default()
27 }
28
29 pub fn push(&mut self, set: ConflictSet) {
31 self.0.push(set);
32 }
33
34 pub fn iter(&self) -> impl Iterator<Item = &'_ ConflictSet> + Clone + '_ {
36 self.0.iter()
37 }
38
39 pub fn contains<'a>(
42 &self,
43 package: &PackageName,
44 kind: impl Into<ConflictKindRef<'a>>,
45 ) -> bool {
46 let kind = kind.into();
47 self.iter().any(|set| set.contains(package, kind))
48 }
49
50 pub fn is_empty(&self) -> bool {
52 self.0.is_empty()
53 }
54
55 pub fn append(&mut self, other: &mut Self) {
58 self.0.append(&mut other.0);
59 }
60
61 pub fn expand_transitive_group_includes(
85 &mut self,
86 package: &PackageName,
87 groups: &DependencyGroups,
88 ) {
89 let mut graph = DiGraph::new();
90 let mut group_node_idxs: FxHashMap<&GroupName, NodeIndex> = FxHashMap::default();
91 let mut node_conflict_items: FxHashMap<NodeIndex, Rc<ConflictItem>> = FxHashMap::default();
92 let mut substitutions: FxHashMap<Rc<ConflictItem>, FxHashSet<Rc<ConflictItem>>> =
96 FxHashMap::default();
97
98 let mut conflict_sets: FxHashSet<ConflictSet> = FxHashSet::default();
100
101 let mut seen: FxHashSet<&GroupName> = FxHashSet::default();
103
104 for set in &self.0 {
105 conflict_sets.insert(set.clone());
106 for item in set.iter() {
107 let ConflictKind::Group(group) = &item.kind else {
108 continue;
110 };
111 if !seen.insert(group) {
112 continue;
113 }
114 let item = Rc::new(item.clone());
115 let mut canonical_items = FxHashSet::default();
116 canonical_items.insert(item.clone());
117 let node_id = graph.add_node(canonical_items);
118 group_node_idxs.insert(group, node_id);
119 node_conflict_items.insert(node_id, item.clone());
120 }
121 }
122
123 for group in groups.keys() {
125 if !seen.insert(group) {
126 continue;
127 }
128 let group_conflict_item = ConflictItem {
129 package: package.clone(),
130 kind: ConflictKind::Group(group.clone()),
131 };
132 let node_id = graph.add_node(FxHashSet::default());
133 group_node_idxs.insert(group, node_id);
134 node_conflict_items.insert(node_id, Rc::new(group_conflict_item));
135 }
136
137 for (group, specifiers) in groups {
140 if let Some(includer) = group_node_idxs.get(group) {
141 for specifier in specifiers {
142 if let DependencyGroupSpecifier::IncludeGroup { include_group } = specifier {
143 if let Some(included) = group_node_idxs.get(include_group) {
144 graph.add_edge(*included, *includer, ());
145 }
146 }
147 }
148 }
149 }
150
151 let Ok(topo_nodes) = toposort(&graph, None) else {
152 return;
153 };
154 for node in topo_nodes {
156 for neighbor_idx in graph.neighbors(node).collect::<Vec<_>>() {
157 let mut neighbor_canonical_items = Vec::new();
158 if let Some(canonical_items) = graph.node_weight(node) {
159 let neighbor_item = node_conflict_items
160 .get(&neighbor_idx)
161 .expect("ConflictItem should already be in graph")
162 .clone();
163 for canonical_item in canonical_items {
164 neighbor_canonical_items.push(canonical_item.clone());
165 substitutions
166 .entry(canonical_item.clone())
167 .or_default()
168 .insert(neighbor_item.clone());
169 }
170 }
171 graph
172 .node_weight_mut(neighbor_idx)
173 .expect("Graph node should have weight")
174 .extend(neighbor_canonical_items.into_iter());
175 }
176 }
177
178 for (canonical_item, subs) in substitutions {
183 let mut new_conflict_sets = FxHashSet::default();
184 for conflict_set in conflict_sets
185 .iter()
186 .filter(|set| set.contains_item(&canonical_item))
187 .cloned()
188 .collect::<Vec<_>>()
189 {
190 for sub in &subs {
191 let new_set = conflict_set
192 .replaced_item(&canonical_item, (**sub).clone())
193 .expect("`ConflictItem` should be in `ConflictSet`");
194 if !conflict_sets.contains(&new_set) {
195 new_conflict_sets.insert(new_set);
196 }
197 }
198 }
199 conflict_sets.extend(new_conflict_sets.into_iter());
200 }
201
202 for set in conflict_sets {
204 if !self.0.contains(&set) {
205 self.0.push(set);
206 }
207 }
208 }
209}
210
211#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
221pub struct ConflictSet {
222 set: BTreeSet<ConflictItem>,
223}
224
225impl ConflictSet {
226 pub fn pair(item1: ConflictItem, item2: ConflictItem) -> Self {
228 Self {
229 set: BTreeSet::from_iter(vec![item1, item2]),
230 }
231 }
232
233 pub fn iter(&self) -> impl Iterator<Item = &'_ ConflictItem> + Clone + '_ {
235 self.set.iter()
236 }
237
238 pub fn contains<'a>(
241 &self,
242 package: &PackageName,
243 kind: impl Into<ConflictKindRef<'a>>,
244 ) -> bool {
245 let kind = kind.into();
246 self.iter()
247 .any(|set| set.package() == package && *set.kind() == kind)
248 }
249
250 pub fn contains_item(&self, conflict_item: &ConflictItem) -> bool {
253 self.set.contains(conflict_item)
254 }
255
256 pub fn replaced_item(
258 &self,
259 old: &ConflictItem,
260 new: ConflictItem,
261 ) -> Result<Self, ConflictError> {
262 let mut new_set = self.set.clone();
263 if !new_set.contains(old) {
264 return Err(ConflictError::ReplaceMissingConflictItem);
265 }
266 new_set.remove(old);
267 new_set.insert(new);
268 Ok(Self { set: new_set })
269 }
270}
271
272impl<'de> serde::Deserialize<'de> for ConflictSet {
273 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
274 where
275 D: serde::Deserializer<'de>,
276 {
277 let set = Vec::<ConflictItem>::deserialize(deserializer)?;
278 Self::try_from(set).map_err(serde::de::Error::custom)
279 }
280}
281
282impl TryFrom<Vec<ConflictItem>> for ConflictSet {
283 type Error = ConflictError;
284
285 fn try_from(items: Vec<ConflictItem>) -> Result<Self, ConflictError> {
286 match items.len() {
287 0 => return Err(ConflictError::ZeroItems),
288 1 => return Err(ConflictError::OneItem),
289 _ => {}
290 }
291 Ok(Self {
292 set: BTreeSet::from_iter(items),
293 })
294 }
295}
296
297#[derive(
302 Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize, serde::Serialize,
303)]
304#[serde(
305 deny_unknown_fields,
306 try_from = "ConflictItemWire",
307 into = "ConflictItemWire"
308)]
309pub struct ConflictItem {
310 package: PackageName,
311 kind: ConflictKind,
312}
313
314impl ConflictItem {
315 pub fn package(&self) -> &PackageName {
317 &self.package
318 }
319
320 pub fn kind(&self) -> &ConflictKind {
324 &self.kind
325 }
326
327 pub fn extra(&self) -> Option<&ExtraName> {
329 self.kind.extra()
330 }
331
332 pub fn group(&self) -> Option<&GroupName> {
334 self.kind.group()
335 }
336
337 pub fn as_ref(&self) -> ConflictItemRef<'_> {
339 ConflictItemRef {
340 package: self.package(),
341 kind: self.kind.as_ref(),
342 }
343 }
344}
345
346impl From<PackageName> for ConflictItem {
347 fn from(package: PackageName) -> Self {
348 let kind = ConflictKind::Project;
349 Self { package, kind }
350 }
351}
352
353impl From<(PackageName, ExtraName)> for ConflictItem {
354 fn from((package, extra): (PackageName, ExtraName)) -> Self {
355 let kind = ConflictKind::Extra(extra);
356 Self { package, kind }
357 }
358}
359
360impl From<(PackageName, GroupName)> for ConflictItem {
361 fn from((package, group): (PackageName, GroupName)) -> Self {
362 let kind = ConflictKind::Group(group);
363 Self { package, kind }
364 }
365}
366
367#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
372pub struct ConflictItemRef<'a> {
373 package: &'a PackageName,
374 kind: ConflictKindRef<'a>,
375}
376
377impl<'a> ConflictItemRef<'a> {
378 pub fn package(&self) -> &'a PackageName {
380 self.package
381 }
382
383 pub fn kind(&self) -> ConflictKindRef<'a> {
387 self.kind
388 }
389
390 pub fn extra(&self) -> Option<&'a ExtraName> {
392 self.kind.extra()
393 }
394
395 pub fn group(&self) -> Option<&'a GroupName> {
397 self.kind.group()
398 }
399
400 pub fn to_owned(&self) -> ConflictItem {
402 ConflictItem {
403 package: self.package().clone(),
404 kind: self.kind.to_owned(),
405 }
406 }
407}
408
409impl<'a> From<&'a PackageName> for ConflictItemRef<'a> {
410 fn from(package: &'a PackageName) -> Self {
411 let kind = ConflictKindRef::Project;
412 Self { package, kind }
413 }
414}
415
416impl<'a> From<(&'a PackageName, &'a ExtraName)> for ConflictItemRef<'a> {
417 fn from((package, extra): (&'a PackageName, &'a ExtraName)) -> Self {
418 let kind = ConflictKindRef::Extra(extra);
419 ConflictItemRef { package, kind }
420 }
421}
422
423impl<'a> From<(&'a PackageName, &'a GroupName)> for ConflictItemRef<'a> {
424 fn from((package, group): (&'a PackageName, &'a GroupName)) -> Self {
425 let kind = ConflictKindRef::Group(group);
426 ConflictItemRef { package, kind }
427 }
428}
429
430impl hashbrown::Equivalent<ConflictItem> for ConflictItemRef<'_> {
431 fn equivalent(&self, key: &ConflictItem) -> bool {
432 key.as_ref() == *self
433 }
434}
435
436#[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
440#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
441pub enum ConflictKind {
442 Extra(ExtraName),
443 Group(GroupName),
444 Project,
445}
446
447impl ConflictKind {
448 pub fn extra(&self) -> Option<&ExtraName> {
451 match self {
452 Self::Extra(extra) => Some(extra),
453 Self::Group(_) | Self::Project => None,
454 }
455 }
456
457 pub fn group(&self) -> Option<&GroupName> {
460 match self {
461 Self::Group(group) => Some(group),
462 Self::Extra(_) | Self::Project => None,
463 }
464 }
465
466 pub fn as_ref(&self) -> ConflictKindRef<'_> {
468 match self {
469 Self::Extra(extra) => ConflictKindRef::Extra(extra),
470 Self::Group(group) => ConflictKindRef::Group(group),
471 Self::Project => ConflictKindRef::Project,
472 }
473 }
474}
475
476#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
480pub enum ConflictKindRef<'a> {
481 Extra(&'a ExtraName),
482 Group(&'a GroupName),
483 Project,
484}
485
486impl<'a> ConflictKindRef<'a> {
487 pub fn extra(&self) -> Option<&'a ExtraName> {
490 match self {
491 Self::Extra(extra) => Some(extra),
492 Self::Group(_) | Self::Project => None,
493 }
494 }
495
496 pub fn group(&self) -> Option<&'a GroupName> {
499 match self {
500 Self::Group(group) => Some(group),
501 Self::Extra(_) | Self::Project => None,
502 }
503 }
504
505 pub fn to_owned(&self) -> ConflictKind {
507 match self {
508 Self::Extra(extra) => ConflictKind::Extra((*extra).clone()),
509 Self::Group(group) => ConflictKind::Group((*group).clone()),
510 Self::Project => ConflictKind::Project,
511 }
512 }
513}
514
515impl<'a> From<&'a ExtraName> for ConflictKindRef<'a> {
516 fn from(extra: &'a ExtraName) -> Self {
517 Self::Extra(extra)
518 }
519}
520
521impl<'a> From<&'a GroupName> for ConflictKindRef<'a> {
522 fn from(group: &'a GroupName) -> Self {
523 Self::Group(group)
524 }
525}
526
527impl PartialEq<ConflictKind> for ConflictKindRef<'_> {
528 fn eq(&self, other: &ConflictKind) -> bool {
529 other.as_ref() == *self
530 }
531}
532
533impl<'a> PartialEq<ConflictKindRef<'a>> for ConflictKind {
534 fn eq(&self, other: &ConflictKindRef<'a>) -> bool {
535 self.as_ref() == *other
536 }
537}
538
539impl hashbrown::Equivalent<ConflictKind> for ConflictKindRef<'_> {
540 fn equivalent(&self, key: &ConflictKind) -> bool {
541 key.as_ref() == *self
542 }
543}
544
545#[derive(Debug, thiserror::Error)]
547pub enum ConflictError {
548 #[error("Each set of conflicts must have at least two entries, but found none")]
550 ZeroItems,
551 #[error("Each set of conflicts must have at least two entries, but found only one")]
553 OneItem,
554 #[error("Expected `package` field in conflicting entry")]
560 MissingPackage,
561 #[error("Expected `package`, `extra` or `group` field in conflicting entry")]
563 MissingPackageAndExtraAndGroup,
564 #[error("Expected one of `extra` or `group` in conflicting entry, but found both")]
566 FoundExtraAndGroup,
567 #[error("Expected `ConflictSet` to contain `ConflictItem` to replace")]
568 ReplaceMissingConflictItem,
569}
570
571#[derive(Debug, Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
581#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
582pub struct SchemaConflicts(Vec<SchemaConflictSet>);
583
584impl SchemaConflicts {
585 pub fn to_conflicts_with_package_name(&self, package: &PackageName) -> Conflicts {
593 let mut conflicting = Conflicts::empty();
594 for tool_uv_set in &self.0 {
595 let mut set = vec![];
596 for item in &tool_uv_set.0 {
597 let package = item.package.clone().unwrap_or_else(|| package.clone());
598 set.push(ConflictItem {
599 package: package.clone(),
600 kind: item.kind.clone(),
601 });
602 }
603 let set = ConflictSet::try_from(set).unwrap();
607 conflicting.push(set);
608 }
609 conflicting
610 }
611}
612
613#[derive(Debug, Default, Clone, Eq, PartialEq, serde::Serialize)]
620#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
621pub struct SchemaConflictSet(Vec<SchemaConflictItem>);
622
623#[derive(
630 Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord, serde::Deserialize, serde::Serialize,
631)]
632#[serde(
633 deny_unknown_fields,
634 try_from = "ConflictItemWire",
635 into = "ConflictItemWire"
636)]
637pub struct SchemaConflictItem {
638 package: Option<PackageName>,
639 kind: ConflictKind,
640}
641
642#[cfg(feature = "schemars")]
643impl schemars::JsonSchema for SchemaConflictItem {
644 fn schema_name() -> Cow<'static, str> {
645 Cow::Borrowed("SchemaConflictItem")
646 }
647
648 fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
649 <ConflictItemWire as schemars::JsonSchema>::json_schema(generator)
650 }
651}
652
653impl<'de> serde::Deserialize<'de> for SchemaConflictSet {
654 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
655 where
656 D: serde::Deserializer<'de>,
657 {
658 let items = Vec::<SchemaConflictItem>::deserialize(deserializer)?;
659 Self::try_from(items).map_err(serde::de::Error::custom)
660 }
661}
662
663impl TryFrom<Vec<SchemaConflictItem>> for SchemaConflictSet {
664 type Error = ConflictError;
665
666 fn try_from(items: Vec<SchemaConflictItem>) -> Result<Self, ConflictError> {
667 match items.len() {
668 0 => return Err(ConflictError::ZeroItems),
669 1 => return Err(ConflictError::OneItem),
670 _ => {}
671 }
672 Ok(Self(items))
673 }
674}
675
676#[derive(Debug, serde::Deserialize, serde::Serialize)]
681#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
682struct ConflictItemWire {
683 #[serde(default)]
684 package: Option<PackageName>,
685 #[serde(default)]
686 extra: Option<ExtraName>,
687 #[serde(default)]
688 group: Option<GroupName>,
689}
690
691impl TryFrom<ConflictItemWire> for ConflictItem {
692 type Error = ConflictError;
693
694 fn try_from(wire: ConflictItemWire) -> Result<Self, ConflictError> {
695 let Some(package) = wire.package else {
696 return Err(ConflictError::MissingPackage);
697 };
698 match (wire.extra, wire.group) {
699 (Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
700 (None, None) => Ok(Self::from(package)),
701 (Some(extra), None) => Ok(Self::from((package, extra))),
702 (None, Some(group)) => Ok(Self::from((package, group))),
703 }
704 }
705}
706
707impl From<ConflictItem> for ConflictItemWire {
708 fn from(item: ConflictItem) -> Self {
709 match item.kind {
710 ConflictKind::Extra(extra) => Self {
711 package: Some(item.package),
712 extra: Some(extra),
713 group: None,
714 },
715 ConflictKind::Group(group) => Self {
716 package: Some(item.package),
717 extra: None,
718 group: Some(group),
719 },
720 ConflictKind::Project => Self {
721 package: Some(item.package),
722 extra: None,
723 group: None,
724 },
725 }
726 }
727}
728
729impl TryFrom<ConflictItemWire> for SchemaConflictItem {
730 type Error = ConflictError;
731
732 fn try_from(wire: ConflictItemWire) -> Result<Self, ConflictError> {
733 let package = wire.package;
734 match (wire.extra, wire.group) {
735 (Some(_), Some(_)) => Err(ConflictError::FoundExtraAndGroup),
736 (None, None) => {
737 let Some(package) = package else {
738 return Err(ConflictError::MissingPackageAndExtraAndGroup);
739 };
740 Ok(Self {
741 package: Some(package),
742 kind: ConflictKind::Project,
743 })
744 }
745 (Some(extra), None) => Ok(Self {
746 package,
747 kind: ConflictKind::Extra(extra),
748 }),
749 (None, Some(group)) => Ok(Self {
750 package,
751 kind: ConflictKind::Group(group),
752 }),
753 }
754 }
755}
756
757impl From<SchemaConflictItem> for ConflictItemWire {
758 fn from(item: SchemaConflictItem) -> Self {
759 match item.kind {
760 ConflictKind::Extra(extra) => Self {
761 package: item.package,
762 extra: Some(extra),
763 group: None,
764 },
765 ConflictKind::Group(group) => Self {
766 package: item.package,
767 extra: None,
768 group: Some(group),
769 },
770 ConflictKind::Project => Self {
771 package: item.package,
772 extra: None,
773 group: None,
774 },
775 }
776 }
777}
778
779#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
795pub struct Inference {
796 pub included: bool,
797 pub item: ConflictItem,
798}