1use std::borrow::Borrow;
2use std::collections::BTreeSet;
3use std::str::FromStr;
4
5use itertools::Itertools;
6use rustc_hash::FxHashMap;
7
8use uv_normalize::{ExtraName, GroupName, PackageName};
9use uv_pep508::{ExtraOperator, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree};
10use uv_pypi_types::{ConflictItem, ConflictKind, Conflicts, Inference};
11
12use crate::ResolveError;
13
14#[derive(Default, Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
27pub struct UniversalMarker {
28 marker: MarkerTree,
77 pep508: MarkerTree,
82}
83
84impl UniversalMarker {
85 pub(crate) const TRUE: Self = Self {
87 marker: MarkerTree::TRUE,
88 pep508: MarkerTree::TRUE,
89 };
90
91 pub(crate) const FALSE: Self = Self {
93 marker: MarkerTree::FALSE,
94 pep508: MarkerTree::FALSE,
95 };
96
97 pub(crate) fn new(mut pep508_marker: MarkerTree, conflict_marker: ConflictMarker) -> Self {
99 pep508_marker.and(conflict_marker.marker);
100 Self::from_combined(pep508_marker)
101 }
102
103 pub(crate) fn from_combined(marker: MarkerTree) -> Self {
106 Self {
107 marker,
108 pep508: marker.without_extras(),
109 }
110 }
111
112 pub(crate) fn or(&mut self, other: Self) {
116 self.marker.or(other.marker);
117 self.pep508.or(other.pep508);
118 }
119
120 pub(crate) fn and(&mut self, other: Self) {
124 self.marker.and(other.marker);
125 self.pep508.and(other.pep508);
126 }
127
128 pub(crate) fn imbibe(&mut self, conflicts: ConflictMarker) {
135 let self_marker = self.marker;
136 self.marker = conflicts.marker;
137 self.marker.implies(self_marker);
138 self.pep508 = self.marker.without_extras();
139 }
140
141 pub(crate) fn unify_inference_sets(&mut self, conflict_sets: &[BTreeSet<Inference>]) {
143 let mut previous_marker = None;
144
145 for conflict_set in conflict_sets {
146 let mut marker = self.marker;
147 for inference in conflict_set {
148 let extra = encode_conflict_item(&inference.item);
149
150 marker = if inference.included {
151 marker.simplify_extras_with(|candidate| *candidate == extra)
152 } else {
153 marker.simplify_not_extras_with(|candidate| *candidate == extra)
154 };
155 }
156 if let Some(previous_marker) = &previous_marker {
157 if previous_marker != &marker {
158 return;
159 }
160 } else {
161 previous_marker = Some(marker);
162 }
163 }
164
165 if let Some(all_branches_marker) = previous_marker {
166 self.marker = all_branches_marker;
167 self.pep508 = self.marker.without_extras();
168 }
169 }
170
171 pub(crate) fn assume_conflict_item(&mut self, item: &ConflictItem) {
176 match *item.kind() {
177 ConflictKind::Extra(ref extra) => self.assume_extra(item.package(), extra),
178 ConflictKind::Group(ref group) => self.assume_group(item.package(), group),
179 ConflictKind::Project => self.assume_project(item.package()),
180 }
181 self.pep508 = self.marker.without_extras();
182 }
183
184 pub(crate) fn assume_not_conflict_item(&mut self, item: &ConflictItem) {
190 match *item.kind() {
191 ConflictKind::Extra(ref extra) => self.assume_not_extra(item.package(), extra),
192 ConflictKind::Group(ref group) => self.assume_not_group(item.package(), group),
193 ConflictKind::Project => self.assume_not_project(item.package()),
194 }
195 self.pep508 = self.marker.without_extras();
196 }
197
198 fn assume_project(&mut self, package: &PackageName) {
204 let extra = encode_project(package);
205 self.marker = self
206 .marker
207 .simplify_extras_with(|candidate| *candidate == extra);
208 self.pep508 = self.marker.without_extras();
209 }
210
211 fn assume_not_project(&mut self, package: &PackageName) {
217 let extra = encode_project(package);
218 self.marker = self
219 .marker
220 .simplify_not_extras_with(|candidate| *candidate == extra);
221 self.pep508 = self.marker.without_extras();
222 }
223
224 fn assume_extra(&mut self, package: &PackageName, extra: &ExtraName) {
229 let extra = encode_package_extra(package, extra);
230 self.marker = self
231 .marker
232 .simplify_extras_with(|candidate| *candidate == extra);
233 self.pep508 = self.marker.without_extras();
234 }
235
236 fn assume_not_extra(&mut self, package: &PackageName, extra: &ExtraName) {
241 let extra = encode_package_extra(package, extra);
242 self.marker = self
243 .marker
244 .simplify_not_extras_with(|candidate| *candidate == extra);
245 self.pep508 = self.marker.without_extras();
246 }
247
248 fn assume_group(&mut self, package: &PackageName, group: &GroupName) {
253 let extra = encode_package_group(package, group);
254 self.marker = self
255 .marker
256 .simplify_extras_with(|candidate| *candidate == extra);
257 self.pep508 = self.marker.without_extras();
258 }
259
260 fn assume_not_group(&mut self, package: &PackageName, group: &GroupName) {
265 let extra = encode_package_group(package, group);
266 self.marker = self
267 .marker
268 .simplify_not_extras_with(|candidate| *candidate == extra);
269 self.pep508 = self.marker.without_extras();
270 }
271
272 pub(crate) fn is_true(self) -> bool {
274 self.marker.is_true()
275 }
276
277 pub(crate) fn is_false(self) -> bool {
279 self.marker.is_false()
280 }
281
282 pub(crate) fn is_disjoint(self, other: Self) -> bool {
287 self.marker.is_disjoint(other.marker)
288 }
289
290 pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool {
296 self.marker.evaluate(env, &[])
297 }
298
299 pub(crate) fn evaluate<P, E, G>(
306 self,
307 env: &MarkerEnvironment,
308 projects: impl Iterator<Item = P>,
309 extras: impl Iterator<Item = (P, E)>,
310 groups: impl Iterator<Item = (P, G)>,
311 ) -> bool
312 where
313 P: Borrow<PackageName>,
314 E: Borrow<ExtraName>,
315 G: Borrow<GroupName>,
316 {
317 let projects = projects.map(|package| encode_project(package.borrow()));
318 let extras =
319 extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
320 let groups =
321 groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
322 self.marker.evaluate(
323 env,
324 &projects
325 .chain(extras)
326 .chain(groups)
327 .collect::<Vec<ExtraName>>(),
328 )
329 }
330
331 pub(crate) fn evaluate_only_extras<P, E, G>(self, extras: &[(P, E)], groups: &[(P, G)]) -> bool
333 where
334 P: Borrow<PackageName>,
335 E: Borrow<ExtraName>,
336 G: Borrow<GroupName>,
337 {
338 let extras = extras
339 .iter()
340 .map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
341 let groups = groups
342 .iter()
343 .map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
344 self.marker
345 .evaluate_only_extras(&extras.chain(groups).collect::<Vec<ExtraName>>())
346 }
347
348 pub fn combined(self) -> MarkerTree {
351 self.marker
352 }
353
354 pub fn pep508(self) -> MarkerTree {
363 self.pep508
364 }
365
366 pub(crate) fn conflict(self) -> ConflictMarker {
378 ConflictMarker {
379 marker: self.marker.only_extras(),
380 }
381 }
382}
383
384impl std::fmt::Debug for UniversalMarker {
385 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
386 std::fmt::Debug::fmt(&self.marker, f)
387 }
388}
389
390#[derive(Default, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
395pub struct ConflictMarker {
396 marker: MarkerTree,
397}
398
399impl ConflictMarker {
400 pub const TRUE: Self = Self {
402 marker: MarkerTree::TRUE,
403 };
404
405 pub const FALSE: Self = Self {
407 marker: MarkerTree::FALSE,
408 };
409
410 pub fn from_conflicts(conflicts: &Conflicts) -> Self {
412 if conflicts.is_empty() {
413 return Self::TRUE;
414 }
415 let mut marker = Self::TRUE;
416 for set in conflicts.iter() {
417 for (item1, item2) in set.iter().tuple_combinations() {
418 let pair = Self::from_conflict_item(item1)
419 .negate()
420 .or(Self::from_conflict_item(item2).negate());
421 marker = marker.and(pair);
422 }
423 }
424 marker
425 }
426
427 pub fn from_conflict_item(item: &ConflictItem) -> Self {
430 match *item.kind() {
431 ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
432 ConflictKind::Group(ref group) => Self::group(item.package(), group),
433 ConflictKind::Project => Self::project(item.package()),
434 }
435 }
436
437 pub fn project(package: &PackageName) -> Self {
440 let operator = uv_pep508::ExtraOperator::Equal;
441 let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
442 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
443 let marker = MarkerTree::expression(expr);
444 Self { marker }
445 }
446
447 pub fn extra(package: &PackageName, extra: &ExtraName) -> Self {
450 let operator = uv_pep508::ExtraOperator::Equal;
451 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_extra(package, extra));
452 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
453 let marker = MarkerTree::expression(expr);
454 Self { marker }
455 }
456
457 pub fn group(package: &PackageName, group: &GroupName) -> Self {
460 let operator = uv_pep508::ExtraOperator::Equal;
461 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_group(package, group));
462 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
463 let marker = MarkerTree::expression(expr);
464 Self { marker }
465 }
466
467 #[must_use]
469 pub fn negate(self) -> Self {
470 Self {
471 marker: self.marker.negate(),
472 }
473 }
474
475 #[must_use]
478 pub fn or(self, other: Self) -> Self {
479 let mut marker = self.marker;
480 marker.or(other.marker);
481 Self { marker }
482 }
483
484 #[must_use]
487 pub fn and(self, other: Self) -> Self {
488 let mut marker = self.marker;
489 marker.and(other.marker);
490 Self { marker }
491 }
492
493 #[must_use]
499 pub fn implies(self, other: Self) -> Self {
500 let mut marker = self.marker;
501 marker.implies(other.marker);
502 Self { marker }
503 }
504
505 pub fn is_true(self) -> bool {
507 self.marker.is_true()
508 }
509
510 pub fn is_false(self) -> bool {
512 self.marker.is_false()
513 }
514
515 pub(crate) fn filter_rules(
521 self,
522 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
523 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
524 self.marker.visit_extras(|op, extra| {
525 match op {
526 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
527 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
528 _ => unreachable!(),
530 }
531 });
532 let include = raw_include
533 .into_iter()
534 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
535 .collect::<Result<Vec<_>, _>>()?;
536 let exclude = raw_exclude
537 .into_iter()
538 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
539 .collect::<Result<Vec<_>, _>>()?;
540 Ok((include, exclude))
541 }
542}
543
544impl std::fmt::Debug for ConflictMarker {
545 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
546 write!(f, "ConflictMarker({:?})", self.marker)
548 }
549}
550
551fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
553 match conflict.kind() {
554 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
555 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
556 ConflictKind::Project => encode_project(conflict.package()),
557 }
558}
559
560fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
563 let package_len = package.as_str().len();
574 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
575}
576
577fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
580 let package_len = package.as_str().len();
582 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
583}
584
585fn encode_project(package: &PackageName) -> ExtraName {
588 let package_len = package.as_str().len();
590 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
591}
592
593#[derive(Debug)]
594enum ParsedRawExtra<'a> {
595 Project { package: &'a str },
596 Extra { package: &'a str, extra: &'a str },
597 Group { package: &'a str, group: &'a str },
598}
599
600impl<'a> ParsedRawExtra<'a> {
601 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
602 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
603 let raw_extra = raw_extra.to_owned();
604 let reason = reason.into();
605 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
606 }
607
608 let raw = raw_extra.as_str();
609 let Some((kind, tail)) = raw.split_once('-') else {
610 return Err(mkerr(
611 raw_extra,
612 "expected to find leading `package`, `extra-` or `group-`",
613 ));
614 };
615 let Some((len, tail)) = tail.split_once('-') else {
616 return Err(mkerr(
617 raw_extra,
618 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
619 ));
620 };
621 let len = len.parse::<usize>().map_err(|_| {
622 mkerr(
623 raw_extra,
624 format!("found package length number `{len}`, but could not parse into integer"),
625 )
626 })?;
627 let Some((package, tail)) = tail.split_at_checked(len) else {
628 return Err(mkerr(
629 raw_extra,
630 format!(
631 "expected at least {len} bytes for package name, but found {found}",
632 found = tail.len()
633 ),
634 ));
635 };
636 match kind {
637 "project" => Ok(ParsedRawExtra::Project { package }),
638 "extra" | "group" => {
639 if !tail.starts_with('-') {
640 return Err(mkerr(
641 raw_extra,
642 format!("expected `-` after package name `{package}`"),
643 ));
644 }
645 let tail = &tail[1..];
646 if kind == "extra" {
647 Ok(ParsedRawExtra::Extra {
648 package,
649 extra: tail,
650 })
651 } else {
652 Ok(ParsedRawExtra::Group {
653 package,
654 group: tail,
655 })
656 }
657 }
658 _ => Err(mkerr(
659 raw_extra,
660 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
661 )),
662 }
663 }
664
665 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
666 let package = PackageName::from_str(self.package()).map_err(|name_error| {
667 ResolveError::InvalidValueInConflictMarker {
668 kind: "package",
669 name_error,
670 }
671 })?;
672 match self {
673 Self::Project { .. } => Ok(ConflictItem::from(package)),
674 Self::Extra { extra, .. } => {
675 let extra = ExtraName::from_str(extra).map_err(|name_error| {
676 ResolveError::InvalidValueInConflictMarker {
677 kind: "extra",
678 name_error,
679 }
680 })?;
681 Ok(ConflictItem::from((package, extra)))
682 }
683 Self::Group { group, .. } => {
684 let group = GroupName::from_str(group).map_err(|name_error| {
685 ResolveError::InvalidValueInConflictMarker {
686 kind: "group",
687 name_error,
688 }
689 })?;
690 Ok(ConflictItem::from((package, group)))
691 }
692 }
693 }
694
695 fn package(&self) -> &'a str {
696 match self {
697 Self::Project { package, .. } => package,
698 Self::Extra { package, .. } => package,
699 Self::Group { package, .. } => package,
700 }
701 }
702}
703
704pub(crate) fn resolve_conflicts(
714 marker: MarkerTree,
715 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
716) -> MarkerTree {
717 if marker.is_true() || marker.is_false() {
718 return marker;
719 }
720
721 let mut transformed = MarkerTree::FALSE;
722
723 for dnf in marker.to_dnf() {
725 let mut or = MarkerTree::TRUE;
726
727 for marker in dnf {
728 let MarkerExpression::Extra {
729 ref operator,
730 ref name,
731 } = marker
732 else {
733 or.and(MarkerTree::expression(marker));
734 continue;
735 };
736
737 let Some(name) = name.as_extra() else {
738 or.and(MarkerTree::expression(marker));
739 continue;
740 };
741
742 let mut found = false;
746 for (conflict_item, conflict_marker) in known_conflicts {
747 if let Some(extra) = conflict_item.extra() {
749 let package = conflict_item.package();
750 let encoded = encode_package_extra(package, extra);
751 if encoded == *name {
752 match operator {
753 ExtraOperator::Equal => {
754 or.and(*conflict_marker);
755 found = true;
756 break;
757 }
758 ExtraOperator::NotEqual => {
759 or.and(conflict_marker.negate());
760 found = true;
761 break;
762 }
763 }
764 }
765 }
766
767 if let Some(group) = conflict_item.group() {
769 let package = conflict_item.package();
770 let encoded = encode_package_group(package, group);
771 if encoded == *name {
772 match operator {
773 ExtraOperator::Equal => {
774 or.and(*conflict_marker);
775 found = true;
776 break;
777 }
778 ExtraOperator::NotEqual => {
779 or.and(conflict_marker.negate());
780 found = true;
781 break;
782 }
783 }
784 }
785 }
786
787 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
789 let package = conflict_item.package();
790 let encoded = encode_project(package);
791 if encoded == *name {
792 match operator {
793 ExtraOperator::Equal => {
794 or.and(*conflict_marker);
795 found = true;
796 break;
797 }
798 ExtraOperator::NotEqual => {
799 or.and(conflict_marker.negate());
800 found = true;
801 break;
802 }
803 }
804 }
805 }
806 }
807
808 if !found {
811 match operator {
812 ExtraOperator::Equal => {
813 or.and(MarkerTree::FALSE);
814 }
815 ExtraOperator::NotEqual => {
816 or.and(MarkerTree::TRUE);
817 }
818 }
819 }
820 }
821
822 transformed.or(or);
823 }
824
825 transformed
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use std::str::FromStr;
832
833 use uv_pypi_types::ConflictSet;
834
835 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
838 let mut conflicts = Conflicts::empty();
839 for set in it {
840 conflicts.push(set);
841 }
842 conflicts
843 }
844
845 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
850 let items = it
851 .into_iter()
852 .map(|extra| (create_package("pkg"), create_extra(extra)))
853 .map(ConflictItem::from)
854 .collect::<Vec<ConflictItem>>();
855 ConflictSet::try_from(items).unwrap()
856 }
857
858 fn create_package(name: &str) -> PackageName {
860 PackageName::from_str(name).unwrap()
861 }
862
863 fn create_extra(name: &str) -> ExtraName {
865 ExtraName::from_str(name).unwrap()
866 }
867
868 fn create_extra_marker(name: &str) -> ConflictMarker {
870 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
871 }
872
873 fn create_extra_item(name: &str) -> ConflictItem {
875 ConflictItem::from((create_package("pkg"), create_extra(name)))
876 }
877
878 fn create_known_conflicts<'a>(
880 it: impl IntoIterator<Item = (&'a str, &'a str)>,
881 ) -> FxHashMap<ConflictItem, MarkerTree> {
882 it.into_iter()
883 .map(|(extra, marker)| {
884 (
885 create_extra_item(extra),
886 MarkerTree::from_str(marker).unwrap(),
887 )
888 })
889 .collect()
890 }
891
892 fn to_str(cm: ConflictMarker) -> String {
898 cm.marker
899 .try_to_string()
900 .unwrap_or_else(|| "true".to_string())
901 }
902
903 #[test]
907 fn conflicts_as_marker() {
908 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
909 let cm = ConflictMarker::from_conflicts(&conflicts);
910 assert_eq!(
911 to_str(cm),
912 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
913 );
914
915 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
916 let cm = ConflictMarker::from_conflicts(&conflicts);
917 assert_eq!(
918 to_str(cm),
919 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
920 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
921 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
922 );
923
924 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
925 let cm = ConflictMarker::from_conflicts(&conflicts);
926 assert_eq!(
927 to_str(cm),
928 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
929 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
930 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
931 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
932 );
933 let disallowed = [
946 vec!["foo", "bar"],
947 vec!["fox", "ant"],
948 vec!["foo", "fox", "bar"],
949 vec!["foo", "ant", "bar"],
950 vec!["ant", "foo", "fox"],
951 vec!["ant", "bar", "fox"],
952 vec!["foo", "bar", "fox", "ant"],
953 ];
954 for extra_names in disallowed {
955 let extras = extra_names
956 .iter()
957 .copied()
958 .map(|name| (create_package("pkg"), create_extra(name)))
959 .collect::<Vec<(PackageName, ExtraName)>>();
960 let groups = Vec::<(PackageName, GroupName)>::new();
961 assert!(
962 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
963 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
964 );
965 }
966 let allowed = [
967 vec![],
968 vec!["foo"],
969 vec!["bar"],
970 vec!["fox"],
971 vec!["ant"],
972 vec!["foo", "fox"],
973 vec!["foo", "ant"],
974 vec!["bar", "fox"],
975 vec!["bar", "ant"],
976 ];
977 for extra_names in allowed {
978 let extras = extra_names
979 .iter()
980 .copied()
981 .map(|name| (create_package("pkg"), create_extra(name)))
982 .collect::<Vec<(PackageName, ExtraName)>>();
983 let groups = Vec::<(PackageName, GroupName)>::new();
984 assert!(
985 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
986 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
987 );
988 }
989 }
990
991 #[test]
994 fn imbibe() {
995 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
996 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
997 let foo = create_extra_marker("foo");
998 let bar = create_extra_marker("bar");
999
1000 let mut dep_conflict_marker =
1004 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1005 assert_eq!(
1006 format!("{dep_conflict_marker:?}"),
1007 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1008 );
1009 dep_conflict_marker.imbibe(conflicts_marker);
1010 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1011 }
1012
1013 #[test]
1014 fn resolve() {
1015 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1016 let cm = MarkerTree::from_str("(python_version >= '3.10' and extra == 'extra-3-pkg-foo') or (python_version < '3.10' and extra != 'extra-3-pkg-foo')").unwrap();
1017 let cm = resolve_conflicts(cm, &known_conflicts);
1018 assert_eq!(
1019 cm.try_to_string().as_deref(),
1020 Some(
1021 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1022 )
1023 );
1024
1025 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1026 .unwrap();
1027 let cm = resolve_conflicts(cm, &known_conflicts);
1028 assert_eq!(
1029 cm.try_to_string().as_deref(),
1030 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1031 );
1032
1033 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1034 .unwrap();
1035 let cm = resolve_conflicts(cm, &known_conflicts);
1036 assert!(cm.is_false());
1037 }
1038}