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(crate) struct ConflictMarker {
434 marker: MarkerTree,
435}
436
437impl ConflictMarker {
438 pub(crate) const TRUE: Self = Self {
440 marker: MarkerTree::TRUE,
441 };
442
443 pub(crate) fn from_conflicts(conflicts: &Conflicts) -> Self {
445 if conflicts.is_empty() {
446 return Self::TRUE;
447 }
448 let mut marker = Self::TRUE;
449 for set in conflicts.iter() {
450 for (item1, item2) in set.iter().tuple_combinations() {
451 let pair = Self::from_conflict_item(item1)
452 .negate()
453 .or(Self::from_conflict_item(item2).negate());
454 marker = marker.and(pair);
455 }
456 }
457 marker
458 }
459
460 pub(crate) fn from_conflict_item(item: &ConflictItem) -> Self {
463 match *item.kind() {
464 ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
465 ConflictKind::Group(ref group) => Self::group(item.package(), group),
466 ConflictKind::Project => Self::project(item.package()),
467 }
468 }
469
470 fn project(package: &PackageName) -> Self {
473 let operator = uv_pep508::ExtraOperator::Equal;
474 let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
475 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
476 let marker = MarkerTree::expression(expr);
477 Self { marker }
478 }
479
480 fn extra(package: &PackageName, extra: &ExtraName) -> Self {
483 let operator = uv_pep508::ExtraOperator::Equal;
484 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_extra(package, extra));
485 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
486 let marker = MarkerTree::expression(expr);
487 Self { marker }
488 }
489
490 fn group(package: &PackageName, group: &GroupName) -> Self {
493 let operator = uv_pep508::ExtraOperator::Equal;
494 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_group(package, group));
495 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
496 let marker = MarkerTree::expression(expr);
497 Self { marker }
498 }
499
500 #[must_use]
502 pub(crate) fn negate(self) -> Self {
503 Self {
504 marker: self.marker.negate(),
505 }
506 }
507
508 #[must_use]
511 fn or(self, other: Self) -> Self {
512 let mut marker = self.marker;
513 marker.or(other.marker);
514 Self { marker }
515 }
516
517 #[must_use]
520 pub(crate) fn and(self, other: Self) -> Self {
521 let mut marker = self.marker;
522 marker.and(other.marker);
523 Self { marker }
524 }
525
526 pub(crate) fn is_true(self) -> bool {
528 self.marker.is_true()
529 }
530
531 pub(crate) fn is_constant(self) -> bool {
533 self.marker.is_true() || self.marker.is_false()
534 }
535
536 pub(crate) fn filter_rules(
542 self,
543 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
544 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
545 self.marker.visit_extras(|op, extra| {
546 match op {
547 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
548 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
549 _ => unreachable!(),
551 }
552 });
553 let include = raw_include
554 .into_iter()
555 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
556 .collect::<Result<Vec<_>, _>>()?;
557 let exclude = raw_exclude
558 .into_iter()
559 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
560 .collect::<Result<Vec<_>, _>>()?;
561 Ok((include, exclude))
562 }
563}
564
565impl std::fmt::Debug for ConflictMarker {
566 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
567 write!(f, "ConflictMarker({:?})", self.marker)
569 }
570}
571
572fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
574 match conflict.kind() {
575 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
576 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
577 ConflictKind::Project => encode_project(conflict.package()),
578 }
579}
580
581fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
584 let package_len = package.as_str().len();
595 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
596}
597
598fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
601 let package_len = package.as_str().len();
603 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
604}
605
606fn encode_project(package: &PackageName) -> ExtraName {
609 let package_len = package.as_str().len();
611 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
612}
613
614#[derive(Debug)]
615enum ParsedRawExtra<'a> {
616 Project { package: &'a str },
617 Extra { package: &'a str, extra: &'a str },
618 Group { package: &'a str, group: &'a str },
619}
620
621impl<'a> ParsedRawExtra<'a> {
622 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
623 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
624 let raw_extra = raw_extra.to_owned();
625 let reason = reason.into();
626 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
627 }
628
629 let raw = raw_extra.as_str();
630 let Some((kind, tail)) = raw.split_once('-') else {
631 return Err(mkerr(
632 raw_extra,
633 "expected to find leading `package`, `extra-` or `group-`",
634 ));
635 };
636 let Some((len, tail)) = tail.split_once('-') else {
637 return Err(mkerr(
638 raw_extra,
639 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
640 ));
641 };
642 let len = len.parse::<usize>().map_err(|_| {
643 mkerr(
644 raw_extra,
645 format!("found package length number `{len}`, but could not parse into integer"),
646 )
647 })?;
648 let Some((package, tail)) = tail.split_at_checked(len) else {
649 return Err(mkerr(
650 raw_extra,
651 format!(
652 "expected at least {len} bytes for package name, but found {found}",
653 found = tail.len()
654 ),
655 ));
656 };
657 match kind {
658 "project" => Ok(ParsedRawExtra::Project { package }),
659 "extra" | "group" => {
660 if !tail.starts_with('-') {
661 return Err(mkerr(
662 raw_extra,
663 format!("expected `-` after package name `{package}`"),
664 ));
665 }
666 let tail = &tail[1..];
667 if kind == "extra" {
668 Ok(ParsedRawExtra::Extra {
669 package,
670 extra: tail,
671 })
672 } else {
673 Ok(ParsedRawExtra::Group {
674 package,
675 group: tail,
676 })
677 }
678 }
679 _ => Err(mkerr(
680 raw_extra,
681 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
682 )),
683 }
684 }
685
686 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
687 let package = PackageName::from_str(self.package()).map_err(|name_error| {
688 ResolveError::InvalidValueInConflictMarker {
689 kind: "package",
690 name_error,
691 }
692 })?;
693 match self {
694 Self::Project { .. } => Ok(ConflictItem::from(package)),
695 Self::Extra { extra, .. } => {
696 let extra = ExtraName::from_str(extra).map_err(|name_error| {
697 ResolveError::InvalidValueInConflictMarker {
698 kind: "extra",
699 name_error,
700 }
701 })?;
702 Ok(ConflictItem::from((package, extra)))
703 }
704 Self::Group { group, .. } => {
705 let group = GroupName::from_str(group).map_err(|name_error| {
706 ResolveError::InvalidValueInConflictMarker {
707 kind: "group",
708 name_error,
709 }
710 })?;
711 Ok(ConflictItem::from((package, group)))
712 }
713 }
714 }
715
716 fn package(&self) -> &'a str {
717 match self {
718 Self::Project { package, .. } => package,
719 Self::Extra { package, .. } => package,
720 Self::Group { package, .. } => package,
721 }
722 }
723}
724
725pub(crate) fn resolve_activated_extras(
741 marker: MarkerTree,
742 scope_package: Option<&PackageName>,
743 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
744) -> MarkerTree {
745 if marker.is_true() || marker.is_false() {
746 return marker;
747 }
748
749 let mut transformed = MarkerTree::FALSE;
750
751 for dnf in marker.to_dnf() {
753 let mut or = MarkerTree::TRUE;
754
755 for marker in dnf {
756 let MarkerExpression::Extra {
757 ref operator,
758 ref name,
759 } = marker
760 else {
761 or.and(MarkerTree::expression(marker));
762 continue;
763 };
764
765 let Some(name) = name.as_extra() else {
766 or.and(MarkerTree::expression(marker));
767 continue;
768 };
769
770 let mut found = false;
774 for (conflict_item, conflict_marker) in known_conflicts {
775 if let Some(extra) = conflict_item.extra() {
777 let package = conflict_item.package();
778 let encoded = encode_package_extra(package, extra);
779 if encoded == *name {
780 match operator {
781 ExtraOperator::Equal => {
782 or.and(*conflict_marker);
783 found = true;
784 break;
785 }
786 ExtraOperator::NotEqual => {
787 or.and(conflict_marker.negate());
788 found = true;
789 break;
790 }
791 }
792 }
793 }
794
795 if let Some(group) = conflict_item.group() {
797 let package = conflict_item.package();
798 let encoded = encode_package_group(package, group);
799 if encoded == *name {
800 match operator {
801 ExtraOperator::Equal => {
802 or.and(*conflict_marker);
803 found = true;
804 break;
805 }
806 ExtraOperator::NotEqual => {
807 or.and(conflict_marker.negate());
808 found = true;
809 break;
810 }
811 }
812 }
813 }
814
815 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
817 let package = conflict_item.package();
818 let encoded = encode_project(package);
819 if encoded == *name {
820 match operator {
821 ExtraOperator::Equal => {
822 or.and(*conflict_marker);
823 found = true;
824 break;
825 }
826 ExtraOperator::NotEqual => {
827 or.and(conflict_marker.negate());
828 found = true;
829 break;
830 }
831 }
832 }
833 }
834 }
835
836 if !found {
838 if let Some(package) = scope_package {
839 let conflict_item = ConflictItem::from((package.clone(), name.clone()));
840 if let Some(conflict_marker) = known_conflicts.get(&conflict_item) {
841 match operator {
842 ExtraOperator::Equal => {
843 or.and(*conflict_marker);
844 found = true;
845 }
846 ExtraOperator::NotEqual => {
847 or.and(conflict_marker.negate());
848 found = true;
849 }
850 }
851 }
852 }
853 }
854
855 if !found {
858 match operator {
859 ExtraOperator::Equal => {
860 or.and(MarkerTree::FALSE);
861 }
862 ExtraOperator::NotEqual => {
863 or.and(MarkerTree::TRUE);
864 }
865 }
866 }
867 }
868
869 transformed.or(or);
870 }
871
872 transformed
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use std::str::FromStr;
879
880 use uv_pypi_types::ConflictSet;
881
882 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
885 let mut conflicts = Conflicts::empty();
886 for set in it {
887 conflicts.push(set);
888 }
889 conflicts
890 }
891
892 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
897 let items = it
898 .into_iter()
899 .map(|extra| (create_package("pkg"), create_extra(extra)))
900 .map(ConflictItem::from)
901 .collect::<Vec<ConflictItem>>();
902 ConflictSet::try_from(items).unwrap()
903 }
904
905 fn create_package(name: &str) -> PackageName {
907 PackageName::from_str(name).unwrap()
908 }
909
910 fn create_extra(name: &str) -> ExtraName {
912 ExtraName::from_str(name).unwrap()
913 }
914
915 fn create_extra_marker(name: &str) -> ConflictMarker {
917 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
918 }
919
920 fn create_extra_item(name: &str) -> ConflictItem {
922 ConflictItem::from((create_package("pkg"), create_extra(name)))
923 }
924
925 fn create_known_conflicts<'a>(
927 it: impl IntoIterator<Item = (&'a str, &'a str)>,
928 ) -> FxHashMap<ConflictItem, MarkerTree> {
929 it.into_iter()
930 .map(|(extra, marker)| {
931 (
932 create_extra_item(extra),
933 MarkerTree::from_str(marker).unwrap(),
934 )
935 })
936 .collect()
937 }
938
939 fn to_str(cm: ConflictMarker) -> String {
945 cm.marker
946 .try_to_string()
947 .unwrap_or_else(|| "true".to_string())
948 }
949
950 #[test]
954 fn conflicts_as_marker() {
955 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
956 let cm = ConflictMarker::from_conflicts(&conflicts);
957 assert_eq!(
958 to_str(cm),
959 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
960 );
961
962 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
963 let cm = ConflictMarker::from_conflicts(&conflicts);
964 assert_eq!(
965 to_str(cm),
966 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
967 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
968 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
969 );
970
971 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
972 let cm = ConflictMarker::from_conflicts(&conflicts);
973 assert_eq!(
974 to_str(cm),
975 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
976 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
977 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
978 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
979 );
980 let disallowed = [
993 vec!["foo", "bar"],
994 vec!["fox", "ant"],
995 vec!["foo", "fox", "bar"],
996 vec!["foo", "ant", "bar"],
997 vec!["ant", "foo", "fox"],
998 vec!["ant", "bar", "fox"],
999 vec!["foo", "bar", "fox", "ant"],
1000 ];
1001 for extra_names in disallowed {
1002 let extras = extra_names
1003 .iter()
1004 .copied()
1005 .map(|name| (create_package("pkg"), create_extra(name)))
1006 .collect::<Vec<(PackageName, ExtraName)>>();
1007 let groups = Vec::<(PackageName, GroupName)>::new();
1008 assert!(
1009 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1010 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
1011 );
1012 }
1013 let allowed = [
1014 vec![],
1015 vec!["foo"],
1016 vec!["bar"],
1017 vec!["fox"],
1018 vec!["ant"],
1019 vec!["foo", "fox"],
1020 vec!["foo", "ant"],
1021 vec!["bar", "fox"],
1022 vec!["bar", "ant"],
1023 ];
1024 for extra_names in allowed {
1025 let extras = extra_names
1026 .iter()
1027 .copied()
1028 .map(|name| (create_package("pkg"), create_extra(name)))
1029 .collect::<Vec<(PackageName, ExtraName)>>();
1030 let groups = Vec::<(PackageName, GroupName)>::new();
1031 assert!(
1032 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1033 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
1034 );
1035 }
1036 }
1037
1038 #[test]
1041 fn imbibe() {
1042 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1043 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
1044 let foo = create_extra_marker("foo");
1045 let bar = create_extra_marker("bar");
1046
1047 let mut dep_conflict_marker =
1051 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1052 assert_eq!(
1053 format!("{dep_conflict_marker:?}"),
1054 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1055 );
1056 dep_conflict_marker.imbibe(conflicts_marker);
1057 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1058 }
1059
1060 #[test]
1061 fn has_conflict_marker() {
1062 let pep508 =
1063 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1064 assert!(!UniversalMarker::from_combined(pep508).has_conflict_marker());
1065 assert!(UniversalMarker::new(pep508, create_extra_marker("foo")).has_conflict_marker());
1066 }
1067
1068 #[test]
1069 fn resolve() {
1070 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1071 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();
1072 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1073 assert_eq!(
1074 cm.try_to_string().as_deref(),
1075 Some(
1076 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1077 )
1078 );
1079
1080 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1081 .unwrap();
1082 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1083 assert_eq!(
1084 cm.try_to_string().as_deref(),
1085 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1086 );
1087
1088 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1089 .unwrap();
1090 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1091 assert!(cm.is_false());
1092 }
1093
1094 #[test]
1095 fn resolve_unencoded_package_extras() {
1096 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1097 let package = create_package("pkg");
1098
1099 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'foo'").unwrap();
1100 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1101 assert_eq!(
1102 cm.try_to_string().as_deref(),
1103 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1104 );
1105
1106 let cm = MarkerTree::from_str("python_version >= '3.10' and extra != 'foo'").unwrap();
1107 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1108 assert_eq!(
1109 cm.try_to_string().as_deref(),
1110 Some("python_full_version >= '3.10' and sys_platform != 'darwin'")
1111 );
1112
1113 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'bar'").unwrap();
1114 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1115 assert!(cm.is_false());
1116 }
1117}