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 has_conflict_marker(self) -> bool {
288 self.marker != self.pep508
289 }
290
291 pub(crate) fn is_disjoint(self, other: Self) -> bool {
296 self.marker.is_disjoint(other.marker)
297 }
298
299 pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool {
305 self.marker.evaluate(env, &[])
306 }
307
308 pub(crate) fn evaluate<P, E, G>(
315 self,
316 env: &MarkerEnvironment,
317 projects: impl Iterator<Item = P>,
318 extras: impl Iterator<Item = (P, E)>,
319 groups: impl Iterator<Item = (P, G)>,
320 ) -> bool
321 where
322 P: Borrow<PackageName>,
323 E: Borrow<ExtraName>,
324 G: Borrow<GroupName>,
325 {
326 let projects = projects.map(|package| encode_project(package.borrow()));
327 let extras =
328 extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
329 let groups =
330 groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
331 self.marker.evaluate(
332 env,
333 &projects
334 .chain(extras)
335 .chain(groups)
336 .collect::<Vec<ExtraName>>(),
337 )
338 }
339
340 pub(crate) fn evaluate_only_extras<P, E, G>(self, extras: &[(P, E)], groups: &[(P, G)]) -> bool
342 where
343 P: Borrow<PackageName>,
344 E: Borrow<ExtraName>,
345 G: Borrow<GroupName>,
346 {
347 let extras = extras
348 .iter()
349 .map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
350 let groups = groups
351 .iter()
352 .map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
353 self.marker
354 .evaluate_only_extras(&extras.chain(groups).collect::<Vec<ExtraName>>())
355 }
356
357 pub fn combined(self) -> MarkerTree {
360 self.marker
361 }
362
363 pub(crate) fn pep508(self) -> MarkerTree {
372 self.pep508
373 }
374
375 pub(crate) fn conflict(self) -> ConflictMarker {
387 ConflictMarker {
388 marker: self.marker.only_extras(),
389 }
390 }
391
392 pub(crate) fn conflict_for_environment(self, env: &MarkerEnvironment) -> ConflictMarker {
399 let mut remaining = MarkerTree::FALSE;
400
401 'conjunctions: for conjunction in self.marker.to_dnf() {
402 let mut conflict = MarkerTree::TRUE;
403 for expression in conjunction {
404 match expression {
405 expression @ MarkerExpression::Extra { .. } => {
406 conflict.and(MarkerTree::expression(expression));
407 }
408 expression => {
409 if !MarkerTree::expression(expression).evaluate(env, &[]) {
410 continue 'conjunctions;
411 }
412 }
413 }
414 }
415 remaining.or(conflict);
416 }
417
418 ConflictMarker { marker: remaining }
419 }
420}
421
422impl std::fmt::Debug for UniversalMarker {
423 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
424 std::fmt::Debug::fmt(&self.marker, f)
425 }
426}
427
428#[derive(Default, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
433pub struct ConflictMarker {
434 marker: MarkerTree,
435}
436
437impl ConflictMarker {
438 pub const TRUE: Self = Self {
440 marker: MarkerTree::TRUE,
441 };
442
443 pub const FALSE: Self = Self {
445 marker: MarkerTree::FALSE,
446 };
447
448 pub(crate) fn from_conflicts(conflicts: &Conflicts) -> Self {
450 if conflicts.is_empty() {
451 return Self::TRUE;
452 }
453 let mut marker = Self::TRUE;
454 for set in conflicts.iter() {
455 for (item1, item2) in set.iter().tuple_combinations() {
456 let pair = Self::from_conflict_item(item1)
457 .negate()
458 .or(Self::from_conflict_item(item2).negate());
459 marker = marker.and(pair);
460 }
461 }
462 marker
463 }
464
465 pub(crate) fn from_conflict_item(item: &ConflictItem) -> Self {
468 match *item.kind() {
469 ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
470 ConflictKind::Group(ref group) => Self::group(item.package(), group),
471 ConflictKind::Project => Self::project(item.package()),
472 }
473 }
474
475 fn project(package: &PackageName) -> Self {
478 let operator = uv_pep508::ExtraOperator::Equal;
479 let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
480 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
481 let marker = MarkerTree::expression(expr);
482 Self { marker }
483 }
484
485 fn extra(package: &PackageName, extra: &ExtraName) -> Self {
488 let operator = uv_pep508::ExtraOperator::Equal;
489 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_extra(package, extra));
490 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
491 let marker = MarkerTree::expression(expr);
492 Self { marker }
493 }
494
495 fn group(package: &PackageName, group: &GroupName) -> Self {
498 let operator = uv_pep508::ExtraOperator::Equal;
499 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_group(package, group));
500 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
501 let marker = MarkerTree::expression(expr);
502 Self { marker }
503 }
504
505 #[must_use]
507 pub(crate) fn negate(self) -> Self {
508 Self {
509 marker: self.marker.negate(),
510 }
511 }
512
513 #[must_use]
516 fn or(self, other: Self) -> Self {
517 let mut marker = self.marker;
518 marker.or(other.marker);
519 Self { marker }
520 }
521
522 #[must_use]
525 pub(crate) fn and(self, other: Self) -> Self {
526 let mut marker = self.marker;
527 marker.and(other.marker);
528 Self { marker }
529 }
530
531 pub(crate) fn is_true(self) -> bool {
533 self.marker.is_true()
534 }
535
536 pub(crate) fn is_constant(self) -> bool {
538 self.marker.is_true() || self.marker.is_false()
539 }
540
541 pub(crate) fn filter_rules(
547 self,
548 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
549 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
550 self.marker.visit_extras(|op, extra| {
551 match op {
552 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
553 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
554 _ => unreachable!(),
556 }
557 });
558 let include = raw_include
559 .into_iter()
560 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
561 .collect::<Result<Vec<_>, _>>()?;
562 let exclude = raw_exclude
563 .into_iter()
564 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
565 .collect::<Result<Vec<_>, _>>()?;
566 Ok((include, exclude))
567 }
568}
569
570impl std::fmt::Debug for ConflictMarker {
571 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
572 write!(f, "ConflictMarker({:?})", self.marker)
574 }
575}
576
577fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
579 match conflict.kind() {
580 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
581 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
582 ConflictKind::Project => encode_project(conflict.package()),
583 }
584}
585
586fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
589 let package_len = package.as_str().len();
600 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
601}
602
603fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
606 let package_len = package.as_str().len();
608 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
609}
610
611fn encode_project(package: &PackageName) -> ExtraName {
614 let package_len = package.as_str().len();
616 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
617}
618
619#[derive(Debug)]
620enum ParsedRawExtra<'a> {
621 Project { package: &'a str },
622 Extra { package: &'a str, extra: &'a str },
623 Group { package: &'a str, group: &'a str },
624}
625
626impl<'a> ParsedRawExtra<'a> {
627 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
628 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
629 let raw_extra = raw_extra.to_owned();
630 let reason = reason.into();
631 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
632 }
633
634 let raw = raw_extra.as_str();
635 let Some((kind, tail)) = raw.split_once('-') else {
636 return Err(mkerr(
637 raw_extra,
638 "expected to find leading `package`, `extra-` or `group-`",
639 ));
640 };
641 let Some((len, tail)) = tail.split_once('-') else {
642 return Err(mkerr(
643 raw_extra,
644 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
645 ));
646 };
647 let len = len.parse::<usize>().map_err(|_| {
648 mkerr(
649 raw_extra,
650 format!("found package length number `{len}`, but could not parse into integer"),
651 )
652 })?;
653 let Some((package, tail)) = tail.split_at_checked(len) else {
654 return Err(mkerr(
655 raw_extra,
656 format!(
657 "expected at least {len} bytes for package name, but found {found}",
658 found = tail.len()
659 ),
660 ));
661 };
662 match kind {
663 "project" => Ok(ParsedRawExtra::Project { package }),
664 "extra" | "group" => {
665 if !tail.starts_with('-') {
666 return Err(mkerr(
667 raw_extra,
668 format!("expected `-` after package name `{package}`"),
669 ));
670 }
671 let tail = &tail[1..];
672 if kind == "extra" {
673 Ok(ParsedRawExtra::Extra {
674 package,
675 extra: tail,
676 })
677 } else {
678 Ok(ParsedRawExtra::Group {
679 package,
680 group: tail,
681 })
682 }
683 }
684 _ => Err(mkerr(
685 raw_extra,
686 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
687 )),
688 }
689 }
690
691 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
692 let package = PackageName::from_str(self.package()).map_err(|name_error| {
693 ResolveError::InvalidValueInConflictMarker {
694 kind: "package",
695 name_error,
696 }
697 })?;
698 match self {
699 Self::Project { .. } => Ok(ConflictItem::from(package)),
700 Self::Extra { extra, .. } => {
701 let extra = ExtraName::from_str(extra).map_err(|name_error| {
702 ResolveError::InvalidValueInConflictMarker {
703 kind: "extra",
704 name_error,
705 }
706 })?;
707 Ok(ConflictItem::from((package, extra)))
708 }
709 Self::Group { group, .. } => {
710 let group = GroupName::from_str(group).map_err(|name_error| {
711 ResolveError::InvalidValueInConflictMarker {
712 kind: "group",
713 name_error,
714 }
715 })?;
716 Ok(ConflictItem::from((package, group)))
717 }
718 }
719 }
720
721 fn package(&self) -> &'a str {
722 match self {
723 Self::Project { package, .. } => package,
724 Self::Extra { package, .. } => package,
725 Self::Group { package, .. } => package,
726 }
727 }
728}
729
730pub(crate) fn resolve_activated_extras(
746 marker: MarkerTree,
747 scope_package: Option<&PackageName>,
748 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
749) -> MarkerTree {
750 if marker.is_true() || marker.is_false() {
751 return marker;
752 }
753
754 let mut transformed = MarkerTree::FALSE;
755
756 for dnf in marker.to_dnf() {
758 let mut or = MarkerTree::TRUE;
759
760 for marker in dnf {
761 let MarkerExpression::Extra {
762 ref operator,
763 ref name,
764 } = marker
765 else {
766 or.and(MarkerTree::expression(marker));
767 continue;
768 };
769
770 let Some(name) = name.as_extra() else {
771 or.and(MarkerTree::expression(marker));
772 continue;
773 };
774
775 let mut found = false;
779 for (conflict_item, conflict_marker) in known_conflicts {
780 if let Some(extra) = conflict_item.extra() {
782 let package = conflict_item.package();
783 let encoded = encode_package_extra(package, extra);
784 if encoded == *name {
785 match operator {
786 ExtraOperator::Equal => {
787 or.and(*conflict_marker);
788 found = true;
789 break;
790 }
791 ExtraOperator::NotEqual => {
792 or.and(conflict_marker.negate());
793 found = true;
794 break;
795 }
796 }
797 }
798 }
799
800 if let Some(group) = conflict_item.group() {
802 let package = conflict_item.package();
803 let encoded = encode_package_group(package, group);
804 if encoded == *name {
805 match operator {
806 ExtraOperator::Equal => {
807 or.and(*conflict_marker);
808 found = true;
809 break;
810 }
811 ExtraOperator::NotEqual => {
812 or.and(conflict_marker.negate());
813 found = true;
814 break;
815 }
816 }
817 }
818 }
819
820 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
822 let package = conflict_item.package();
823 let encoded = encode_project(package);
824 if encoded == *name {
825 match operator {
826 ExtraOperator::Equal => {
827 or.and(*conflict_marker);
828 found = true;
829 break;
830 }
831 ExtraOperator::NotEqual => {
832 or.and(conflict_marker.negate());
833 found = true;
834 break;
835 }
836 }
837 }
838 }
839 }
840
841 if !found {
843 if let Some(package) = scope_package {
844 let conflict_item = ConflictItem::from((package.clone(), name.clone()));
845 if let Some(conflict_marker) = known_conflicts.get(&conflict_item) {
846 match operator {
847 ExtraOperator::Equal => {
848 or.and(*conflict_marker);
849 found = true;
850 }
851 ExtraOperator::NotEqual => {
852 or.and(conflict_marker.negate());
853 found = true;
854 }
855 }
856 }
857 }
858 }
859
860 if !found {
863 match operator {
864 ExtraOperator::Equal => {
865 or.and(MarkerTree::FALSE);
866 }
867 ExtraOperator::NotEqual => {
868 or.and(MarkerTree::TRUE);
869 }
870 }
871 }
872 }
873
874 transformed.or(or);
875 }
876
877 transformed
878}
879
880#[cfg(test)]
881mod tests {
882 use super::*;
883 use std::str::FromStr;
884
885 use uv_pypi_types::ConflictSet;
886
887 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
890 let mut conflicts = Conflicts::empty();
891 for set in it {
892 conflicts.push(set);
893 }
894 conflicts
895 }
896
897 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
902 let items = it
903 .into_iter()
904 .map(|extra| (create_package("pkg"), create_extra(extra)))
905 .map(ConflictItem::from)
906 .collect::<Vec<ConflictItem>>();
907 ConflictSet::try_from(items).unwrap()
908 }
909
910 fn create_package(name: &str) -> PackageName {
912 PackageName::from_str(name).unwrap()
913 }
914
915 fn create_extra(name: &str) -> ExtraName {
917 ExtraName::from_str(name).unwrap()
918 }
919
920 fn create_extra_marker(name: &str) -> ConflictMarker {
922 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
923 }
924
925 fn create_extra_item(name: &str) -> ConflictItem {
927 ConflictItem::from((create_package("pkg"), create_extra(name)))
928 }
929
930 fn create_known_conflicts<'a>(
932 it: impl IntoIterator<Item = (&'a str, &'a str)>,
933 ) -> FxHashMap<ConflictItem, MarkerTree> {
934 it.into_iter()
935 .map(|(extra, marker)| {
936 (
937 create_extra_item(extra),
938 MarkerTree::from_str(marker).unwrap(),
939 )
940 })
941 .collect()
942 }
943
944 fn to_str(cm: ConflictMarker) -> String {
950 cm.marker
951 .try_to_string()
952 .unwrap_or_else(|| "true".to_string())
953 }
954
955 #[test]
959 fn conflicts_as_marker() {
960 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
961 let cm = ConflictMarker::from_conflicts(&conflicts);
962 assert_eq!(
963 to_str(cm),
964 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
965 );
966
967 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
968 let cm = ConflictMarker::from_conflicts(&conflicts);
969 assert_eq!(
970 to_str(cm),
971 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
972 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
973 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
974 );
975
976 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
977 let cm = ConflictMarker::from_conflicts(&conflicts);
978 assert_eq!(
979 to_str(cm),
980 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
981 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
982 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
983 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
984 );
985 let disallowed = [
998 vec!["foo", "bar"],
999 vec!["fox", "ant"],
1000 vec!["foo", "fox", "bar"],
1001 vec!["foo", "ant", "bar"],
1002 vec!["ant", "foo", "fox"],
1003 vec!["ant", "bar", "fox"],
1004 vec!["foo", "bar", "fox", "ant"],
1005 ];
1006 for extra_names in disallowed {
1007 let extras = extra_names
1008 .iter()
1009 .copied()
1010 .map(|name| (create_package("pkg"), create_extra(name)))
1011 .collect::<Vec<(PackageName, ExtraName)>>();
1012 let groups = Vec::<(PackageName, GroupName)>::new();
1013 assert!(
1014 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1015 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
1016 );
1017 }
1018 let allowed = [
1019 vec![],
1020 vec!["foo"],
1021 vec!["bar"],
1022 vec!["fox"],
1023 vec!["ant"],
1024 vec!["foo", "fox"],
1025 vec!["foo", "ant"],
1026 vec!["bar", "fox"],
1027 vec!["bar", "ant"],
1028 ];
1029 for extra_names in allowed {
1030 let extras = extra_names
1031 .iter()
1032 .copied()
1033 .map(|name| (create_package("pkg"), create_extra(name)))
1034 .collect::<Vec<(PackageName, ExtraName)>>();
1035 let groups = Vec::<(PackageName, GroupName)>::new();
1036 assert!(
1037 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1038 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
1039 );
1040 }
1041 }
1042
1043 #[test]
1046 fn imbibe() {
1047 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1048 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
1049 let foo = create_extra_marker("foo");
1050 let bar = create_extra_marker("bar");
1051
1052 let mut dep_conflict_marker =
1056 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1057 assert_eq!(
1058 format!("{dep_conflict_marker:?}"),
1059 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1060 );
1061 dep_conflict_marker.imbibe(conflicts_marker);
1062 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1063 }
1064
1065 #[test]
1066 fn has_conflict_marker() {
1067 let pep508 =
1068 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1069 assert!(!UniversalMarker::from_combined(pep508).has_conflict_marker());
1070 assert!(UniversalMarker::new(pep508, create_extra_marker("foo")).has_conflict_marker());
1071 }
1072
1073 #[test]
1074 fn resolve() {
1075 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1076 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();
1077 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1078 assert_eq!(
1079 cm.try_to_string().as_deref(),
1080 Some(
1081 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1082 )
1083 );
1084
1085 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1086 .unwrap();
1087 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1088 assert_eq!(
1089 cm.try_to_string().as_deref(),
1090 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1091 );
1092
1093 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1094 .unwrap();
1095 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1096 assert!(cm.is_false());
1097 }
1098
1099 #[test]
1100 fn resolve_unencoded_package_extras() {
1101 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1102 let package = create_package("pkg");
1103
1104 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'foo'").unwrap();
1105 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1106 assert_eq!(
1107 cm.try_to_string().as_deref(),
1108 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1109 );
1110
1111 let cm = MarkerTree::from_str("python_version >= '3.10' and extra != 'foo'").unwrap();
1112 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1113 assert_eq!(
1114 cm.try_to_string().as_deref(),
1115 Some("python_full_version >= '3.10' and sys_platform != 'darwin'")
1116 );
1117
1118 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'bar'").unwrap();
1119 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1120 assert!(cm.is_false());
1121 }
1122}