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 if conflicts.marker.is_true() {
136 return;
137 }
138 let self_marker = self.marker;
139 self.marker = conflicts.marker;
140 self.marker.implies(self_marker);
141 self.pep508 = self.marker.without_extras();
142 }
143
144 pub(crate) fn unify_inference_sets(&mut self, conflict_sets: &[BTreeSet<Inference>]) {
146 let mut previous_marker = None;
147
148 for conflict_set in conflict_sets {
149 let mut marker = self.marker;
150 for inference in conflict_set {
151 let extra = encode_conflict_item(&inference.item);
152
153 marker = if inference.included {
154 marker.simplify_extras_with(|candidate| *candidate == extra)
155 } else {
156 marker.simplify_not_extras_with(|candidate| *candidate == extra)
157 };
158 }
159 if let Some(previous_marker) = &previous_marker {
160 if previous_marker != &marker {
161 return;
162 }
163 } else {
164 previous_marker = Some(marker);
165 }
166 }
167
168 if let Some(all_branches_marker) = previous_marker {
169 self.marker = all_branches_marker;
170 self.pep508 = self.marker.without_extras();
171 }
172 }
173
174 pub(crate) fn assume_conflict_item(&mut self, item: &ConflictItem) {
179 match *item.kind() {
180 ConflictKind::Extra(ref extra) => self.assume_extra(item.package(), extra),
181 ConflictKind::Group(ref group) => self.assume_group(item.package(), group),
182 ConflictKind::Project => self.assume_project(item.package()),
183 }
184 }
185
186 pub(crate) fn assume_not_conflict_item(&mut self, item: &ConflictItem) {
192 match *item.kind() {
193 ConflictKind::Extra(ref extra) => self.assume_not_extra(item.package(), extra),
194 ConflictKind::Group(ref group) => self.assume_not_group(item.package(), group),
195 ConflictKind::Project => self.assume_not_project(item.package()),
196 }
197 }
198
199 fn assume_project(&mut self, package: &PackageName) {
205 let extra = encode_project(package);
206 self.marker = self
207 .marker
208 .simplify_extras_with(|candidate| *candidate == extra);
209 self.pep508 = self.marker.without_extras();
210 }
211
212 fn assume_not_project(&mut self, package: &PackageName) {
218 let extra = encode_project(package);
219 self.marker = self
220 .marker
221 .simplify_not_extras_with(|candidate| *candidate == extra);
222 self.pep508 = self.marker.without_extras();
223 }
224
225 fn assume_extra(&mut self, package: &PackageName, extra: &ExtraName) {
230 let extra = encode_package_extra(package, extra);
231 self.marker = self
232 .marker
233 .simplify_extras_with(|candidate| *candidate == extra);
234 self.pep508 = self.marker.without_extras();
235 }
236
237 fn assume_not_extra(&mut self, package: &PackageName, extra: &ExtraName) {
242 let extra = encode_package_extra(package, extra);
243 self.marker = self
244 .marker
245 .simplify_not_extras_with(|candidate| *candidate == extra);
246 self.pep508 = self.marker.without_extras();
247 }
248
249 fn assume_group(&mut self, package: &PackageName, group: &GroupName) {
254 let extra = encode_package_group(package, group);
255 self.marker = self
256 .marker
257 .simplify_extras_with(|candidate| *candidate == extra);
258 self.pep508 = self.marker.without_extras();
259 }
260
261 fn assume_not_group(&mut self, package: &PackageName, group: &GroupName) {
266 let extra = encode_package_group(package, group);
267 self.marker = self
268 .marker
269 .simplify_not_extras_with(|candidate| *candidate == extra);
270 self.pep508 = self.marker.without_extras();
271 }
272
273 pub(crate) fn is_true(self) -> bool {
275 self.marker.is_true()
276 }
277
278 pub(crate) fn is_false(self) -> bool {
280 self.marker.is_false()
281 }
282
283 pub(crate) fn has_conflict_marker(self) -> bool {
289 self.marker != self.pep508
290 }
291
292 pub(crate) fn is_disjoint(self, other: Self) -> bool {
297 self.marker.is_disjoint(other.marker)
298 }
299
300 pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool {
306 self.marker.evaluate(env, &[])
307 }
308
309 pub(crate) fn evaluate<P, E, G>(
316 self,
317 env: &MarkerEnvironment,
318 projects: impl Iterator<Item = P>,
319 extras: impl Iterator<Item = (P, E)>,
320 groups: impl Iterator<Item = (P, G)>,
321 ) -> bool
322 where
323 P: Borrow<PackageName>,
324 E: Borrow<ExtraName>,
325 G: Borrow<GroupName>,
326 {
327 let projects = projects.map(|package| encode_project(package.borrow()));
328 let extras =
329 extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
330 let groups =
331 groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
332 self.marker.evaluate(
333 env,
334 &projects
335 .chain(extras)
336 .chain(groups)
337 .collect::<Vec<ExtraName>>(),
338 )
339 }
340
341 pub(crate) fn evaluate_only_extras<P, E, G>(self, extras: &[(P, E)], groups: &[(P, G)]) -> bool
343 where
344 P: Borrow<PackageName>,
345 E: Borrow<ExtraName>,
346 G: Borrow<GroupName>,
347 {
348 let extras = extras
349 .iter()
350 .map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
351 let groups = groups
352 .iter()
353 .map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
354 self.marker
355 .evaluate_only_extras(&extras.chain(groups).collect::<Vec<ExtraName>>())
356 }
357
358 pub fn combined(self) -> MarkerTree {
361 self.marker
362 }
363
364 pub(crate) fn pep508(self) -> MarkerTree {
373 self.pep508
374 }
375
376 pub(crate) fn conflict(self) -> ConflictMarker {
388 ConflictMarker {
389 marker: self.marker.only_extras(),
390 }
391 }
392
393 pub(crate) fn conflict_for_environment(self, env: &MarkerEnvironment) -> ConflictMarker {
400 let mut remaining = MarkerTree::FALSE;
401
402 'conjunctions: for conjunction in self.marker.to_dnf() {
403 let mut conflict = MarkerTree::TRUE;
404 for expression in conjunction {
405 match expression {
406 expression @ MarkerExpression::Extra { .. } => {
407 conflict.and(MarkerTree::expression(expression));
408 }
409 expression => {
410 if !MarkerTree::expression(expression).evaluate(env, &[]) {
411 continue 'conjunctions;
412 }
413 }
414 }
415 }
416 remaining.or(conflict);
417 }
418
419 ConflictMarker { marker: remaining }
420 }
421}
422
423impl std::fmt::Debug for UniversalMarker {
424 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
425 std::fmt::Debug::fmt(&self.marker, f)
426 }
427}
428
429#[derive(Default, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
434pub(crate) struct ConflictMarker {
435 marker: MarkerTree,
436}
437
438impl ConflictMarker {
439 pub(crate) const TRUE: Self = Self {
441 marker: MarkerTree::TRUE,
442 };
443
444 pub(crate) fn from_conflicts(conflicts: &Conflicts) -> Self {
446 if conflicts.is_empty() {
447 return Self::TRUE;
448 }
449 let mut marker = Self::TRUE;
450 for set in conflicts.iter() {
451 for (item1, item2) in set.iter().tuple_combinations() {
452 let pair = Self::from_conflict_item(item1)
453 .negate()
454 .or(Self::from_conflict_item(item2).negate());
455 marker = marker.and(pair);
456 }
457 }
458 marker
459 }
460
461 pub(crate) fn from_conflict_item(item: &ConflictItem) -> Self {
464 match *item.kind() {
465 ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
466 ConflictKind::Group(ref group) => Self::group(item.package(), group),
467 ConflictKind::Project => Self::project(item.package()),
468 }
469 }
470
471 fn project(package: &PackageName) -> Self {
474 let operator = uv_pep508::ExtraOperator::Equal;
475 let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
476 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
477 let marker = MarkerTree::expression(expr);
478 Self { marker }
479 }
480
481 fn extra(package: &PackageName, extra: &ExtraName) -> Self {
484 let operator = uv_pep508::ExtraOperator::Equal;
485 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_extra(package, extra));
486 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
487 let marker = MarkerTree::expression(expr);
488 Self { marker }
489 }
490
491 fn group(package: &PackageName, group: &GroupName) -> Self {
494 let operator = uv_pep508::ExtraOperator::Equal;
495 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_group(package, group));
496 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
497 let marker = MarkerTree::expression(expr);
498 Self { marker }
499 }
500
501 #[must_use]
503 pub(crate) fn negate(self) -> Self {
504 Self {
505 marker: self.marker.negate(),
506 }
507 }
508
509 #[must_use]
512 fn or(self, other: Self) -> Self {
513 let mut marker = self.marker;
514 marker.or(other.marker);
515 Self { marker }
516 }
517
518 #[must_use]
521 pub(crate) fn and(self, other: Self) -> Self {
522 let mut marker = self.marker;
523 marker.and(other.marker);
524 Self { marker }
525 }
526
527 pub(crate) fn is_true(self) -> bool {
529 self.marker.is_true()
530 }
531
532 pub(crate) fn is_constant(self) -> bool {
534 self.marker.is_true() || self.marker.is_false()
535 }
536
537 pub(crate) fn filter_rules(
543 self,
544 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
545 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
546 self.marker.visit_extras(|op, extra| {
547 match op {
548 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
549 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
550 _ => unreachable!(),
552 }
553 });
554 let include = raw_include
555 .into_iter()
556 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
557 .collect::<Result<Vec<_>, _>>()?;
558 let exclude = raw_exclude
559 .into_iter()
560 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
561 .collect::<Result<Vec<_>, _>>()?;
562 Ok((include, exclude))
563 }
564}
565
566impl std::fmt::Debug for ConflictMarker {
567 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
568 write!(f, "ConflictMarker({:?})", self.marker)
570 }
571}
572
573fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
575 match conflict.kind() {
576 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
577 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
578 ConflictKind::Project => encode_project(conflict.package()),
579 }
580}
581
582fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
585 let package_len = package.as_str().len();
596 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
597}
598
599fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
602 let package_len = package.as_str().len();
604 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
605}
606
607fn encode_project(package: &PackageName) -> ExtraName {
610 let package_len = package.as_str().len();
612 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
613}
614
615#[derive(Debug)]
616enum ParsedRawExtra<'a> {
617 Project { package: &'a str },
618 Extra { package: &'a str, extra: &'a str },
619 Group { package: &'a str, group: &'a str },
620}
621
622impl<'a> ParsedRawExtra<'a> {
623 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
624 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
625 let raw_extra = raw_extra.to_owned();
626 let reason = reason.into();
627 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
628 }
629
630 let raw = raw_extra.as_str();
631 let Some((kind, tail)) = raw.split_once('-') else {
632 return Err(mkerr(
633 raw_extra,
634 "expected to find leading `package`, `extra-` or `group-`",
635 ));
636 };
637 let Some((len, tail)) = tail.split_once('-') else {
638 return Err(mkerr(
639 raw_extra,
640 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
641 ));
642 };
643 let len = len.parse::<usize>().map_err(|_| {
644 mkerr(
645 raw_extra,
646 format!("found package length number `{len}`, but could not parse into integer"),
647 )
648 })?;
649 let Some((package, tail)) = tail.split_at_checked(len) else {
650 return Err(mkerr(
651 raw_extra,
652 format!(
653 "expected at least {len} bytes for package name, but found {found}",
654 found = tail.len()
655 ),
656 ));
657 };
658 match kind {
659 "project" => Ok(ParsedRawExtra::Project { package }),
660 "extra" | "group" => {
661 if !tail.starts_with('-') {
662 return Err(mkerr(
663 raw_extra,
664 format!("expected `-` after package name `{package}`"),
665 ));
666 }
667 let tail = &tail[1..];
668 if kind == "extra" {
669 Ok(ParsedRawExtra::Extra {
670 package,
671 extra: tail,
672 })
673 } else {
674 Ok(ParsedRawExtra::Group {
675 package,
676 group: tail,
677 })
678 }
679 }
680 _ => Err(mkerr(
681 raw_extra,
682 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
683 )),
684 }
685 }
686
687 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
688 let package = PackageName::from_str(self.package()).map_err(|name_error| {
689 ResolveError::InvalidValueInConflictMarker {
690 kind: "package",
691 name_error,
692 }
693 })?;
694 match self {
695 Self::Project { .. } => Ok(ConflictItem::from(package)),
696 Self::Extra { extra, .. } => {
697 let extra = ExtraName::from_str(extra).map_err(|name_error| {
698 ResolveError::InvalidValueInConflictMarker {
699 kind: "extra",
700 name_error,
701 }
702 })?;
703 Ok(ConflictItem::from((package, extra)))
704 }
705 Self::Group { group, .. } => {
706 let group = GroupName::from_str(group).map_err(|name_error| {
707 ResolveError::InvalidValueInConflictMarker {
708 kind: "group",
709 name_error,
710 }
711 })?;
712 Ok(ConflictItem::from((package, group)))
713 }
714 }
715 }
716
717 fn package(&self) -> &'a str {
718 match self {
719 Self::Project { package, .. } => package,
720 Self::Extra { package, .. } => package,
721 Self::Group { package, .. } => package,
722 }
723 }
724}
725
726pub(crate) fn resolve_activated_extras(
742 marker: MarkerTree,
743 scope_package: Option<&PackageName>,
744 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
745) -> MarkerTree {
746 if marker.is_true() || marker.is_false() {
747 return marker;
748 }
749
750 let mut transformed = MarkerTree::FALSE;
751
752 for dnf in marker.to_dnf() {
754 let mut or = MarkerTree::TRUE;
755
756 for marker in dnf {
757 let MarkerExpression::Extra {
758 ref operator,
759 ref name,
760 } = marker
761 else {
762 or.and(MarkerTree::expression(marker));
763 continue;
764 };
765
766 let Some(name) = name.as_extra() else {
767 or.and(MarkerTree::expression(marker));
768 continue;
769 };
770
771 let mut found = false;
775 for (conflict_item, conflict_marker) in known_conflicts {
776 if let Some(extra) = conflict_item.extra() {
778 let package = conflict_item.package();
779 let encoded = encode_package_extra(package, extra);
780 if encoded == *name {
781 match operator {
782 ExtraOperator::Equal => {
783 or.and(*conflict_marker);
784 found = true;
785 break;
786 }
787 ExtraOperator::NotEqual => {
788 or.and(conflict_marker.negate());
789 found = true;
790 break;
791 }
792 }
793 }
794 }
795
796 if let Some(group) = conflict_item.group() {
798 let package = conflict_item.package();
799 let encoded = encode_package_group(package, group);
800 if encoded == *name {
801 match operator {
802 ExtraOperator::Equal => {
803 or.and(*conflict_marker);
804 found = true;
805 break;
806 }
807 ExtraOperator::NotEqual => {
808 or.and(conflict_marker.negate());
809 found = true;
810 break;
811 }
812 }
813 }
814 }
815
816 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
818 let package = conflict_item.package();
819 let encoded = encode_project(package);
820 if encoded == *name {
821 match operator {
822 ExtraOperator::Equal => {
823 or.and(*conflict_marker);
824 found = true;
825 break;
826 }
827 ExtraOperator::NotEqual => {
828 or.and(conflict_marker.negate());
829 found = true;
830 break;
831 }
832 }
833 }
834 }
835 }
836
837 if !found {
839 if let Some(package) = scope_package {
840 let conflict_item = ConflictItem::from((package.clone(), name.clone()));
841 if let Some(conflict_marker) = known_conflicts.get(&conflict_item) {
842 match operator {
843 ExtraOperator::Equal => {
844 or.and(*conflict_marker);
845 found = true;
846 }
847 ExtraOperator::NotEqual => {
848 or.and(conflict_marker.negate());
849 found = true;
850 }
851 }
852 }
853 }
854 }
855
856 if !found {
859 match operator {
860 ExtraOperator::Equal => {
861 or.and(MarkerTree::FALSE);
862 }
863 ExtraOperator::NotEqual => {
864 or.and(MarkerTree::TRUE);
865 }
866 }
867 }
868 }
869
870 transformed.or(or);
871 }
872
873 transformed
874}
875
876#[cfg(test)]
877mod tests {
878 use super::*;
879 use std::str::FromStr;
880
881 use uv_pypi_types::ConflictSet;
882
883 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
886 let mut conflicts = Conflicts::empty();
887 for set in it {
888 conflicts.push(set);
889 }
890 conflicts
891 }
892
893 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
898 let items = it
899 .into_iter()
900 .map(|extra| (create_package("pkg"), create_extra(extra)))
901 .map(ConflictItem::from)
902 .collect::<Vec<ConflictItem>>();
903 ConflictSet::try_from(items).unwrap()
904 }
905
906 fn create_package(name: &str) -> PackageName {
908 PackageName::from_str(name).unwrap()
909 }
910
911 fn create_extra(name: &str) -> ExtraName {
913 ExtraName::from_str(name).unwrap()
914 }
915
916 fn create_extra_marker(name: &str) -> ConflictMarker {
918 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
919 }
920
921 fn create_extra_item(name: &str) -> ConflictItem {
923 ConflictItem::from((create_package("pkg"), create_extra(name)))
924 }
925
926 fn create_known_conflicts<'a>(
928 it: impl IntoIterator<Item = (&'a str, &'a str)>,
929 ) -> FxHashMap<ConflictItem, MarkerTree> {
930 it.into_iter()
931 .map(|(extra, marker)| {
932 (
933 create_extra_item(extra),
934 MarkerTree::from_str(marker).unwrap(),
935 )
936 })
937 .collect()
938 }
939
940 fn to_str(cm: ConflictMarker) -> String {
946 cm.marker
947 .try_to_string()
948 .unwrap_or_else(|| "true".to_string())
949 }
950
951 #[test]
955 fn conflicts_as_marker() {
956 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
957 let cm = ConflictMarker::from_conflicts(&conflicts);
958 assert_eq!(
959 to_str(cm),
960 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
961 );
962
963 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
964 let cm = ConflictMarker::from_conflicts(&conflicts);
965 assert_eq!(
966 to_str(cm),
967 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
968 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
969 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
970 );
971
972 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
973 let cm = ConflictMarker::from_conflicts(&conflicts);
974 assert_eq!(
975 to_str(cm),
976 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
977 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
978 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
979 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
980 );
981 let disallowed = [
994 vec!["foo", "bar"],
995 vec!["fox", "ant"],
996 vec!["foo", "fox", "bar"],
997 vec!["foo", "ant", "bar"],
998 vec!["ant", "foo", "fox"],
999 vec!["ant", "bar", "fox"],
1000 vec!["foo", "bar", "fox", "ant"],
1001 ];
1002 for extra_names in disallowed {
1003 let extras = extra_names
1004 .iter()
1005 .copied()
1006 .map(|name| (create_package("pkg"), create_extra(name)))
1007 .collect::<Vec<(PackageName, ExtraName)>>();
1008 let groups = Vec::<(PackageName, GroupName)>::new();
1009 assert!(
1010 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1011 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
1012 );
1013 }
1014 let allowed = [
1015 vec![],
1016 vec!["foo"],
1017 vec!["bar"],
1018 vec!["fox"],
1019 vec!["ant"],
1020 vec!["foo", "fox"],
1021 vec!["foo", "ant"],
1022 vec!["bar", "fox"],
1023 vec!["bar", "ant"],
1024 ];
1025 for extra_names in allowed {
1026 let extras = extra_names
1027 .iter()
1028 .copied()
1029 .map(|name| (create_package("pkg"), create_extra(name)))
1030 .collect::<Vec<(PackageName, ExtraName)>>();
1031 let groups = Vec::<(PackageName, GroupName)>::new();
1032 assert!(
1033 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1034 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
1035 );
1036 }
1037 }
1038
1039 #[test]
1042 fn imbibe() {
1043 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1044 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
1045 let foo = create_extra_marker("foo");
1046 let bar = create_extra_marker("bar");
1047
1048 let mut dep_conflict_marker =
1052 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1053 assert_eq!(
1054 format!("{dep_conflict_marker:?}"),
1055 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1056 );
1057 dep_conflict_marker.imbibe(conflicts_marker);
1058 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1059 }
1060
1061 #[test]
1062 fn imbibe_true() {
1063 let pep508 =
1064 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1065 let mut marker = UniversalMarker::new(pep508, create_extra_marker("foo"));
1066 let expected = marker;
1067
1068 marker.imbibe(ConflictMarker::TRUE);
1069
1070 assert_eq!(marker, expected);
1071 }
1072
1073 #[test]
1074 fn has_conflict_marker() {
1075 let pep508 =
1076 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1077 assert!(!UniversalMarker::from_combined(pep508).has_conflict_marker());
1078 assert!(UniversalMarker::new(pep508, create_extra_marker("foo")).has_conflict_marker());
1079 }
1080
1081 #[test]
1082 fn resolve() {
1083 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1084 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();
1085 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1086 assert_eq!(
1087 cm.try_to_string().as_deref(),
1088 Some(
1089 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1090 )
1091 );
1092
1093 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1094 .unwrap();
1095 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1096 assert_eq!(
1097 cm.try_to_string().as_deref(),
1098 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1099 );
1100
1101 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1102 .unwrap();
1103 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1104 assert!(cm.is_false());
1105 }
1106
1107 #[test]
1108 fn resolve_unencoded_package_extras() {
1109 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1110 let package = create_package("pkg");
1111
1112 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'foo'").unwrap();
1113 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1114 assert_eq!(
1115 cm.try_to_string().as_deref(),
1116 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1117 );
1118
1119 let cm = MarkerTree::from_str("python_version >= '3.10' and extra != 'foo'").unwrap();
1120 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1121 assert_eq!(
1122 cm.try_to_string().as_deref(),
1123 Some("python_full_version >= '3.10' and sys_platform != 'darwin'")
1124 );
1125
1126 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'bar'").unwrap();
1127 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1128 assert!(cm.is_false());
1129 }
1130}