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