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(crate) 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(crate) 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(crate) 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 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 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 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(crate) fn negate(self) -> Self {
470 Self {
471 marker: self.marker.negate(),
472 }
473 }
474
475 #[must_use]
478 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(crate) fn and(self, other: Self) -> Self {
488 let mut marker = self.marker;
489 marker.and(other.marker);
490 Self { marker }
491 }
492
493 pub(crate) fn is_true(self) -> bool {
495 self.marker.is_true()
496 }
497
498 pub(crate) fn filter_rules(
504 self,
505 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
506 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
507 self.marker.visit_extras(|op, extra| {
508 match op {
509 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
510 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
511 _ => unreachable!(),
513 }
514 });
515 let include = raw_include
516 .into_iter()
517 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
518 .collect::<Result<Vec<_>, _>>()?;
519 let exclude = raw_exclude
520 .into_iter()
521 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
522 .collect::<Result<Vec<_>, _>>()?;
523 Ok((include, exclude))
524 }
525}
526
527impl std::fmt::Debug for ConflictMarker {
528 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
529 write!(f, "ConflictMarker({:?})", self.marker)
531 }
532}
533
534fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
536 match conflict.kind() {
537 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
538 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
539 ConflictKind::Project => encode_project(conflict.package()),
540 }
541}
542
543fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
546 let package_len = package.as_str().len();
557 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
558}
559
560fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
563 let package_len = package.as_str().len();
565 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
566}
567
568fn encode_project(package: &PackageName) -> ExtraName {
571 let package_len = package.as_str().len();
573 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
574}
575
576#[derive(Debug)]
577enum ParsedRawExtra<'a> {
578 Project { package: &'a str },
579 Extra { package: &'a str, extra: &'a str },
580 Group { package: &'a str, group: &'a str },
581}
582
583impl<'a> ParsedRawExtra<'a> {
584 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
585 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
586 let raw_extra = raw_extra.to_owned();
587 let reason = reason.into();
588 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
589 }
590
591 let raw = raw_extra.as_str();
592 let Some((kind, tail)) = raw.split_once('-') else {
593 return Err(mkerr(
594 raw_extra,
595 "expected to find leading `package`, `extra-` or `group-`",
596 ));
597 };
598 let Some((len, tail)) = tail.split_once('-') else {
599 return Err(mkerr(
600 raw_extra,
601 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
602 ));
603 };
604 let len = len.parse::<usize>().map_err(|_| {
605 mkerr(
606 raw_extra,
607 format!("found package length number `{len}`, but could not parse into integer"),
608 )
609 })?;
610 let Some((package, tail)) = tail.split_at_checked(len) else {
611 return Err(mkerr(
612 raw_extra,
613 format!(
614 "expected at least {len} bytes for package name, but found {found}",
615 found = tail.len()
616 ),
617 ));
618 };
619 match kind {
620 "project" => Ok(ParsedRawExtra::Project { package }),
621 "extra" | "group" => {
622 if !tail.starts_with('-') {
623 return Err(mkerr(
624 raw_extra,
625 format!("expected `-` after package name `{package}`"),
626 ));
627 }
628 let tail = &tail[1..];
629 if kind == "extra" {
630 Ok(ParsedRawExtra::Extra {
631 package,
632 extra: tail,
633 })
634 } else {
635 Ok(ParsedRawExtra::Group {
636 package,
637 group: tail,
638 })
639 }
640 }
641 _ => Err(mkerr(
642 raw_extra,
643 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
644 )),
645 }
646 }
647
648 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
649 let package = PackageName::from_str(self.package()).map_err(|name_error| {
650 ResolveError::InvalidValueInConflictMarker {
651 kind: "package",
652 name_error,
653 }
654 })?;
655 match self {
656 Self::Project { .. } => Ok(ConflictItem::from(package)),
657 Self::Extra { extra, .. } => {
658 let extra = ExtraName::from_str(extra).map_err(|name_error| {
659 ResolveError::InvalidValueInConflictMarker {
660 kind: "extra",
661 name_error,
662 }
663 })?;
664 Ok(ConflictItem::from((package, extra)))
665 }
666 Self::Group { group, .. } => {
667 let group = GroupName::from_str(group).map_err(|name_error| {
668 ResolveError::InvalidValueInConflictMarker {
669 kind: "group",
670 name_error,
671 }
672 })?;
673 Ok(ConflictItem::from((package, group)))
674 }
675 }
676 }
677
678 fn package(&self) -> &'a str {
679 match self {
680 Self::Project { package, .. } => package,
681 Self::Extra { package, .. } => package,
682 Self::Group { package, .. } => package,
683 }
684 }
685}
686
687pub(crate) fn resolve_activated_extras(
703 marker: MarkerTree,
704 scope_package: Option<&PackageName>,
705 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
706) -> MarkerTree {
707 if marker.is_true() || marker.is_false() {
708 return marker;
709 }
710
711 let mut transformed = MarkerTree::FALSE;
712
713 for dnf in marker.to_dnf() {
715 let mut or = MarkerTree::TRUE;
716
717 for marker in dnf {
718 let MarkerExpression::Extra {
719 ref operator,
720 ref name,
721 } = marker
722 else {
723 or.and(MarkerTree::expression(marker));
724 continue;
725 };
726
727 let Some(name) = name.as_extra() else {
728 or.and(MarkerTree::expression(marker));
729 continue;
730 };
731
732 let mut found = false;
736 for (conflict_item, conflict_marker) in known_conflicts {
737 if let Some(extra) = conflict_item.extra() {
739 let package = conflict_item.package();
740 let encoded = encode_package_extra(package, extra);
741 if encoded == *name {
742 match operator {
743 ExtraOperator::Equal => {
744 or.and(*conflict_marker);
745 found = true;
746 break;
747 }
748 ExtraOperator::NotEqual => {
749 or.and(conflict_marker.negate());
750 found = true;
751 break;
752 }
753 }
754 }
755 }
756
757 if let Some(group) = conflict_item.group() {
759 let package = conflict_item.package();
760 let encoded = encode_package_group(package, group);
761 if encoded == *name {
762 match operator {
763 ExtraOperator::Equal => {
764 or.and(*conflict_marker);
765 found = true;
766 break;
767 }
768 ExtraOperator::NotEqual => {
769 or.and(conflict_marker.negate());
770 found = true;
771 break;
772 }
773 }
774 }
775 }
776
777 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
779 let package = conflict_item.package();
780 let encoded = encode_project(package);
781 if encoded == *name {
782 match operator {
783 ExtraOperator::Equal => {
784 or.and(*conflict_marker);
785 found = true;
786 break;
787 }
788 ExtraOperator::NotEqual => {
789 or.and(conflict_marker.negate());
790 found = true;
791 break;
792 }
793 }
794 }
795 }
796 }
797
798 if !found {
800 if let Some(package) = scope_package {
801 let conflict_item = ConflictItem::from((package.clone(), name.clone()));
802 if let Some(conflict_marker) = known_conflicts.get(&conflict_item) {
803 match operator {
804 ExtraOperator::Equal => {
805 or.and(*conflict_marker);
806 found = true;
807 }
808 ExtraOperator::NotEqual => {
809 or.and(conflict_marker.negate());
810 found = true;
811 }
812 }
813 }
814 }
815 }
816
817 if !found {
820 match operator {
821 ExtraOperator::Equal => {
822 or.and(MarkerTree::FALSE);
823 }
824 ExtraOperator::NotEqual => {
825 or.and(MarkerTree::TRUE);
826 }
827 }
828 }
829 }
830
831 transformed.or(or);
832 }
833
834 transformed
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840 use std::str::FromStr;
841
842 use uv_pypi_types::ConflictSet;
843
844 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
847 let mut conflicts = Conflicts::empty();
848 for set in it {
849 conflicts.push(set);
850 }
851 conflicts
852 }
853
854 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
859 let items = it
860 .into_iter()
861 .map(|extra| (create_package("pkg"), create_extra(extra)))
862 .map(ConflictItem::from)
863 .collect::<Vec<ConflictItem>>();
864 ConflictSet::try_from(items).unwrap()
865 }
866
867 fn create_package(name: &str) -> PackageName {
869 PackageName::from_str(name).unwrap()
870 }
871
872 fn create_extra(name: &str) -> ExtraName {
874 ExtraName::from_str(name).unwrap()
875 }
876
877 fn create_extra_marker(name: &str) -> ConflictMarker {
879 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
880 }
881
882 fn create_extra_item(name: &str) -> ConflictItem {
884 ConflictItem::from((create_package("pkg"), create_extra(name)))
885 }
886
887 fn create_known_conflicts<'a>(
889 it: impl IntoIterator<Item = (&'a str, &'a str)>,
890 ) -> FxHashMap<ConflictItem, MarkerTree> {
891 it.into_iter()
892 .map(|(extra, marker)| {
893 (
894 create_extra_item(extra),
895 MarkerTree::from_str(marker).unwrap(),
896 )
897 })
898 .collect()
899 }
900
901 fn to_str(cm: ConflictMarker) -> String {
907 cm.marker
908 .try_to_string()
909 .unwrap_or_else(|| "true".to_string())
910 }
911
912 #[test]
916 fn conflicts_as_marker() {
917 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
918 let cm = ConflictMarker::from_conflicts(&conflicts);
919 assert_eq!(
920 to_str(cm),
921 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
922 );
923
924 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
925 let cm = ConflictMarker::from_conflicts(&conflicts);
926 assert_eq!(
927 to_str(cm),
928 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
929 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
930 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
931 );
932
933 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
934 let cm = ConflictMarker::from_conflicts(&conflicts);
935 assert_eq!(
936 to_str(cm),
937 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
938 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
939 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
940 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
941 );
942 let disallowed = [
955 vec!["foo", "bar"],
956 vec!["fox", "ant"],
957 vec!["foo", "fox", "bar"],
958 vec!["foo", "ant", "bar"],
959 vec!["ant", "foo", "fox"],
960 vec!["ant", "bar", "fox"],
961 vec!["foo", "bar", "fox", "ant"],
962 ];
963 for extra_names in disallowed {
964 let extras = extra_names
965 .iter()
966 .copied()
967 .map(|name| (create_package("pkg"), create_extra(name)))
968 .collect::<Vec<(PackageName, ExtraName)>>();
969 let groups = Vec::<(PackageName, GroupName)>::new();
970 assert!(
971 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
972 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
973 );
974 }
975 let allowed = [
976 vec![],
977 vec!["foo"],
978 vec!["bar"],
979 vec!["fox"],
980 vec!["ant"],
981 vec!["foo", "fox"],
982 vec!["foo", "ant"],
983 vec!["bar", "fox"],
984 vec!["bar", "ant"],
985 ];
986 for extra_names in allowed {
987 let extras = extra_names
988 .iter()
989 .copied()
990 .map(|name| (create_package("pkg"), create_extra(name)))
991 .collect::<Vec<(PackageName, ExtraName)>>();
992 let groups = Vec::<(PackageName, GroupName)>::new();
993 assert!(
994 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
995 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
996 );
997 }
998 }
999
1000 #[test]
1003 fn imbibe() {
1004 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1005 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
1006 let foo = create_extra_marker("foo");
1007 let bar = create_extra_marker("bar");
1008
1009 let mut dep_conflict_marker =
1013 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1014 assert_eq!(
1015 format!("{dep_conflict_marker:?}"),
1016 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1017 );
1018 dep_conflict_marker.imbibe(conflicts_marker);
1019 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1020 }
1021
1022 #[test]
1023 fn resolve() {
1024 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1025 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();
1026 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1027 assert_eq!(
1028 cm.try_to_string().as_deref(),
1029 Some(
1030 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1031 )
1032 );
1033
1034 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1035 .unwrap();
1036 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1037 assert_eq!(
1038 cm.try_to_string().as_deref(),
1039 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1040 );
1041
1042 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1043 .unwrap();
1044 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1045 assert!(cm.is_false());
1046 }
1047
1048 #[test]
1049 fn resolve_unencoded_package_extras() {
1050 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1051 let package = create_package("pkg");
1052
1053 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'foo'").unwrap();
1054 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1055 assert_eq!(
1056 cm.try_to_string().as_deref(),
1057 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1058 );
1059
1060 let cm = MarkerTree::from_str("python_version >= '3.10' and extra != 'foo'").unwrap();
1061 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1062 assert_eq!(
1063 cm.try_to_string().as_deref(),
1064 Some("python_full_version >= '3.10' and sys_platform != 'darwin'")
1065 );
1066
1067 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'bar'").unwrap();
1068 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1069 assert!(cm.is_false());
1070 }
1071}