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
84#[derive(Debug)]
87pub(crate) struct ActivatedConflictItems(Vec<ExtraName>);
88
89impl ActivatedConflictItems {
90 pub(crate) fn new<P, E, G>(
94 projects: impl Iterator<Item = P>,
95 extras: impl Iterator<Item = (P, E)>,
96 groups: impl Iterator<Item = (P, G)>,
97 ) -> Self
98 where
99 P: Borrow<PackageName>,
100 E: Borrow<ExtraName>,
101 G: Borrow<GroupName>,
102 {
103 let projects = projects.map(|package| encode_project(package.borrow()));
104 let extras =
105 extras.map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
106 let groups =
107 groups.map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
108 Self(projects.chain(extras).chain(groups).collect())
109 }
110}
111
112impl UniversalMarker {
113 pub(crate) const TRUE: Self = Self {
115 marker: MarkerTree::TRUE,
116 pep508: MarkerTree::TRUE,
117 };
118
119 pub(crate) const FALSE: Self = Self {
121 marker: MarkerTree::FALSE,
122 pep508: MarkerTree::FALSE,
123 };
124
125 pub(crate) fn new(mut pep508_marker: MarkerTree, conflict_marker: ConflictMarker) -> Self {
127 pep508_marker = pep508_marker.and(conflict_marker.marker);
128 Self::from_combined(pep508_marker)
129 }
130
131 pub(crate) fn from_combined(marker: MarkerTree) -> Self {
134 Self {
135 marker,
136 pep508: marker.without_extras(),
137 }
138 }
139
140 pub(crate) fn or(&mut self, other: Self) {
144 self.marker = self.marker.or(other.marker);
145 self.pep508 = self.pep508.or(other.pep508);
146 }
147
148 pub(crate) fn and(&mut self, other: Self) {
152 self.marker = self.marker.and(other.marker);
153 self.pep508 = self.pep508.and(other.pep508);
154 }
155
156 pub(crate) fn imbibe(&mut self, conflicts: ConflictMarker) {
163 if conflicts.marker.is_true() {
164 return;
165 }
166 let self_marker = self.marker;
167 self.marker = conflicts.marker;
168 self.marker = self.marker.implies(self_marker);
169 self.pep508 = self.marker.without_extras();
170 }
171
172 pub(crate) fn unify_inference_sets(&mut self, conflict_sets: &[BTreeSet<Inference>]) {
174 let mut previous_marker = None;
175
176 for conflict_set in conflict_sets {
177 let mut marker = self.marker;
178 for inference in conflict_set {
179 let extra = encode_conflict_item(&inference.item);
180
181 marker = if inference.included {
182 marker.simplify_extras_with(|candidate| *candidate == extra)
183 } else {
184 marker.simplify_not_extras_with(|candidate| *candidate == extra)
185 };
186 }
187 if let Some(previous_marker) = &previous_marker {
188 if previous_marker != &marker {
189 return;
190 }
191 } else {
192 previous_marker = Some(marker);
193 }
194 }
195
196 if let Some(all_branches_marker) = previous_marker {
197 self.marker = all_branches_marker;
198 self.pep508 = self.marker.without_extras();
199 }
200 }
201
202 pub(crate) fn assume_conflict_item(&mut self, item: &ConflictItem) {
207 match *item.kind() {
208 ConflictKind::Extra(ref extra) => self.assume_extra(item.package(), extra),
209 ConflictKind::Group(ref group) => self.assume_group(item.package(), group),
210 ConflictKind::Project => self.assume_project(item.package()),
211 }
212 }
213
214 pub(crate) fn assume_not_conflict_item(&mut self, item: &ConflictItem) {
220 match *item.kind() {
221 ConflictKind::Extra(ref extra) => self.assume_not_extra(item.package(), extra),
222 ConflictKind::Group(ref group) => self.assume_not_group(item.package(), group),
223 ConflictKind::Project => self.assume_not_project(item.package()),
224 }
225 }
226
227 fn assume_project(&mut self, package: &PackageName) {
233 let extra = encode_project(package);
234 self.marker = self
235 .marker
236 .simplify_extras_with(|candidate| *candidate == extra);
237 self.pep508 = self.marker.without_extras();
238 }
239
240 fn assume_not_project(&mut self, package: &PackageName) {
246 let extra = encode_project(package);
247 self.marker = self
248 .marker
249 .simplify_not_extras_with(|candidate| *candidate == extra);
250 self.pep508 = self.marker.without_extras();
251 }
252
253 fn assume_extra(&mut self, package: &PackageName, extra: &ExtraName) {
258 let extra = encode_package_extra(package, extra);
259 self.marker = self
260 .marker
261 .simplify_extras_with(|candidate| *candidate == extra);
262 self.pep508 = self.marker.without_extras();
263 }
264
265 fn assume_not_extra(&mut self, package: &PackageName, extra: &ExtraName) {
270 let extra = encode_package_extra(package, extra);
271 self.marker = self
272 .marker
273 .simplify_not_extras_with(|candidate| *candidate == extra);
274 self.pep508 = self.marker.without_extras();
275 }
276
277 fn assume_group(&mut self, package: &PackageName, group: &GroupName) {
282 let extra = encode_package_group(package, group);
283 self.marker = self
284 .marker
285 .simplify_extras_with(|candidate| *candidate == extra);
286 self.pep508 = self.marker.without_extras();
287 }
288
289 fn assume_not_group(&mut self, package: &PackageName, group: &GroupName) {
294 let extra = encode_package_group(package, group);
295 self.marker = self
296 .marker
297 .simplify_not_extras_with(|candidate| *candidate == extra);
298 self.pep508 = self.marker.without_extras();
299 }
300
301 pub(crate) fn is_true(self) -> bool {
303 self.marker.is_true()
304 }
305
306 pub(crate) fn is_false(self) -> bool {
308 self.marker.is_false()
309 }
310
311 pub(crate) fn has_conflict_marker(self) -> bool {
317 self.marker != self.pep508
318 }
319
320 pub(crate) fn is_disjoint(self, other: Self) -> bool {
325 self.marker.is_disjoint(other.marker)
326 }
327
328 pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool {
334 self.marker.evaluate(env, &[])
335 }
336
337 pub(crate) fn evaluate<P, E, G>(
344 self,
345 env: &MarkerEnvironment,
346 projects: impl Iterator<Item = P>,
347 extras: impl Iterator<Item = (P, E)>,
348 groups: impl Iterator<Item = (P, G)>,
349 ) -> bool
350 where
351 P: Borrow<PackageName>,
352 E: Borrow<ExtraName>,
353 G: Borrow<GroupName>,
354 {
355 let activated = ActivatedConflictItems::new(projects, extras, groups);
356 self.evaluate_activated(env, &activated)
357 }
358
359 pub(crate) fn evaluate_activated(
361 self,
362 env: &MarkerEnvironment,
363 activated: &ActivatedConflictItems,
364 ) -> bool {
365 self.marker.evaluate(env, &activated.0)
366 }
367
368 pub(crate) fn evaluate_only_extras<P, E, G>(self, extras: &[(P, E)], groups: &[(P, G)]) -> bool
370 where
371 P: Borrow<PackageName>,
372 E: Borrow<ExtraName>,
373 G: Borrow<GroupName>,
374 {
375 let extras = extras
376 .iter()
377 .map(|(package, extra)| encode_package_extra(package.borrow(), extra.borrow()));
378 let groups = groups
379 .iter()
380 .map(|(package, group)| encode_package_group(package.borrow(), group.borrow()));
381 self.marker
382 .evaluate_only_extras(&extras.chain(groups).collect::<Vec<ExtraName>>())
383 }
384
385 pub fn combined(self) -> MarkerTree {
388 self.marker
389 }
390
391 pub(crate) fn pep508(self) -> MarkerTree {
400 self.pep508
401 }
402
403 pub(crate) fn conflict(self) -> ConflictMarker {
415 ConflictMarker {
416 marker: self.marker.only_extras(),
417 }
418 }
419
420 pub(crate) fn conflict_for_environment(self, env: &MarkerEnvironment) -> ConflictMarker {
427 let mut remaining = MarkerTree::FALSE;
428
429 'conjunctions: for conjunction in self.marker.to_dnf() {
430 let mut conflict = MarkerTree::TRUE;
431 for expression in conjunction {
432 match expression {
433 expression @ MarkerExpression::Extra { .. } => {
434 conflict = conflict.and(MarkerTree::expression(expression));
435 }
436 expression => {
437 if !MarkerTree::expression(expression).evaluate(env, &[]) {
438 continue 'conjunctions;
439 }
440 }
441 }
442 }
443 remaining = remaining.or(conflict);
444 }
445
446 ConflictMarker { marker: remaining }
447 }
448}
449
450impl std::fmt::Debug for UniversalMarker {
451 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
452 std::fmt::Debug::fmt(&self.marker, f)
453 }
454}
455
456#[derive(Default, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
461pub(crate) struct ConflictMarker {
462 marker: MarkerTree,
463}
464
465impl ConflictMarker {
466 pub(crate) const TRUE: Self = Self {
468 marker: MarkerTree::TRUE,
469 };
470
471 pub(crate) fn from_conflicts(conflicts: &Conflicts) -> Self {
473 if conflicts.is_empty() {
474 return Self::TRUE;
475 }
476 let mut marker = Self::TRUE;
477 for set in conflicts.iter() {
478 for (item1, item2) in set.iter().tuple_combinations() {
479 let pair = Self::from_conflict_item(item1)
480 .negate()
481 .or(Self::from_conflict_item(item2).negate());
482 marker = marker.and(pair);
483 }
484 }
485 marker
486 }
487
488 pub(crate) fn from_conflict_item(item: &ConflictItem) -> Self {
491 match *item.kind() {
492 ConflictKind::Extra(ref extra) => Self::extra(item.package(), extra),
493 ConflictKind::Group(ref group) => Self::group(item.package(), group),
494 ConflictKind::Project => Self::project(item.package()),
495 }
496 }
497
498 fn project(package: &PackageName) -> Self {
501 let operator = uv_pep508::ExtraOperator::Equal;
502 let name = uv_pep508::MarkerValueExtra::Extra(encode_project(package));
503 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
504 let marker = MarkerTree::expression(expr);
505 Self { marker }
506 }
507
508 fn extra(package: &PackageName, extra: &ExtraName) -> Self {
511 let operator = uv_pep508::ExtraOperator::Equal;
512 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_extra(package, extra));
513 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
514 let marker = MarkerTree::expression(expr);
515 Self { marker }
516 }
517
518 fn group(package: &PackageName, group: &GroupName) -> Self {
521 let operator = uv_pep508::ExtraOperator::Equal;
522 let name = uv_pep508::MarkerValueExtra::Extra(encode_package_group(package, group));
523 let expr = uv_pep508::MarkerExpression::Extra { operator, name };
524 let marker = MarkerTree::expression(expr);
525 Self { marker }
526 }
527
528 #[must_use]
530 pub(crate) fn negate(self) -> Self {
531 Self {
532 marker: self.marker.negate(),
533 }
534 }
535
536 #[must_use]
539 fn or(self, other: Self) -> Self {
540 Self {
541 marker: self.marker.or(other.marker),
542 }
543 }
544
545 #[must_use]
548 pub(crate) fn and(self, other: Self) -> Self {
549 Self {
550 marker: self.marker.and(other.marker),
551 }
552 }
553
554 pub(crate) fn is_true(self) -> bool {
556 self.marker.is_true()
557 }
558
559 pub(crate) fn is_constant(self) -> bool {
561 self.marker.is_true() || self.marker.is_false()
562 }
563
564 pub(crate) fn filter_rules(
570 self,
571 ) -> Result<(Vec<ConflictItem>, Vec<ConflictItem>), ResolveError> {
572 let (mut raw_include, mut raw_exclude) = (vec![], vec![]);
573 self.marker.visit_extras(|op, extra| {
574 match op {
575 MarkerOperator::Equal => raw_include.push(extra.to_owned()),
576 MarkerOperator::NotEqual => raw_exclude.push(extra.to_owned()),
577 _ => unreachable!(),
579 }
580 });
581 let include = raw_include
582 .into_iter()
583 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
584 .collect::<Result<Vec<_>, _>>()?;
585 let exclude = raw_exclude
586 .into_iter()
587 .map(|extra| ParsedRawExtra::parse(&extra).and_then(|parsed| parsed.to_conflict_item()))
588 .collect::<Result<Vec<_>, _>>()?;
589 Ok((include, exclude))
590 }
591}
592
593impl std::fmt::Debug for ConflictMarker {
594 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
595 write!(f, "ConflictMarker({:?})", self.marker)
597 }
598}
599
600fn encode_conflict_item(conflict: &ConflictItem) -> ExtraName {
602 match conflict.kind() {
603 ConflictKind::Extra(extra) => encode_package_extra(conflict.package(), extra),
604 ConflictKind::Group(group) => encode_package_group(conflict.package(), group),
605 ConflictKind::Project => encode_project(conflict.package()),
606 }
607}
608
609fn encode_package_extra(package: &PackageName, extra: &ExtraName) -> ExtraName {
612 let package_len = package.as_str().len();
623 ExtraName::from_owned(format!("extra-{package_len}-{package}-{extra}")).unwrap()
624}
625
626fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
629 let package_len = package.as_str().len();
631 ExtraName::from_owned(format!("group-{package_len}-{package}-{group}")).unwrap()
632}
633
634fn encode_project(package: &PackageName) -> ExtraName {
637 let package_len = package.as_str().len();
639 ExtraName::from_owned(format!("project-{package_len}-{package}")).unwrap()
640}
641
642#[derive(Debug)]
643enum ParsedRawExtra<'a> {
644 Project { package: &'a str },
645 Extra { package: &'a str, extra: &'a str },
646 Group { package: &'a str, group: &'a str },
647}
648
649impl<'a> ParsedRawExtra<'a> {
650 fn parse(raw_extra: &'a ExtraName) -> Result<Self, ResolveError> {
651 fn mkerr(raw_extra: &ExtraName, reason: impl Into<String>) -> ResolveError {
652 let raw_extra = raw_extra.to_owned();
653 let reason = reason.into();
654 ResolveError::InvalidExtraInConflictMarker { reason, raw_extra }
655 }
656
657 let raw = raw_extra.as_str();
658 let Some((kind, tail)) = raw.split_once('-') else {
659 return Err(mkerr(
660 raw_extra,
661 "expected to find leading `package`, `extra-` or `group-`",
662 ));
663 };
664 let Some((len, tail)) = tail.split_once('-') else {
665 return Err(mkerr(
666 raw_extra,
667 "expected to find `{number}-` after leading `package-`, `extra-` or `group-`",
668 ));
669 };
670 let len = len.parse::<usize>().map_err(|_| {
671 mkerr(
672 raw_extra,
673 format!("found package length number `{len}`, but could not parse into integer"),
674 )
675 })?;
676 let Some((package, tail)) = tail.split_at_checked(len) else {
677 return Err(mkerr(
678 raw_extra,
679 format!(
680 "expected at least {len} bytes for package name, but found {found}",
681 found = tail.len()
682 ),
683 ));
684 };
685 match kind {
686 "project" => Ok(ParsedRawExtra::Project { package }),
687 "extra" | "group" => {
688 if !tail.starts_with('-') {
689 return Err(mkerr(
690 raw_extra,
691 format!("expected `-` after package name `{package}`"),
692 ));
693 }
694 let tail = &tail[1..];
695 if kind == "extra" {
696 Ok(ParsedRawExtra::Extra {
697 package,
698 extra: tail,
699 })
700 } else {
701 Ok(ParsedRawExtra::Group {
702 package,
703 group: tail,
704 })
705 }
706 }
707 _ => Err(mkerr(
708 raw_extra,
709 format!("unrecognized kind `{kind}` (must be `extra` or `group`)"),
710 )),
711 }
712 }
713
714 fn to_conflict_item(&self) -> Result<ConflictItem, ResolveError> {
715 let package = PackageName::from_str(self.package()).map_err(|name_error| {
716 ResolveError::InvalidValueInConflictMarker {
717 kind: "package",
718 name_error,
719 }
720 })?;
721 match self {
722 Self::Project { .. } => Ok(ConflictItem::from(package)),
723 Self::Extra { extra, .. } => {
724 let extra = ExtraName::from_str(extra).map_err(|name_error| {
725 ResolveError::InvalidValueInConflictMarker {
726 kind: "extra",
727 name_error,
728 }
729 })?;
730 Ok(ConflictItem::from((package, extra)))
731 }
732 Self::Group { group, .. } => {
733 let group = GroupName::from_str(group).map_err(|name_error| {
734 ResolveError::InvalidValueInConflictMarker {
735 kind: "group",
736 name_error,
737 }
738 })?;
739 Ok(ConflictItem::from((package, group)))
740 }
741 }
742 }
743
744 fn package(&self) -> &'a str {
745 match self {
746 Self::Project { package, .. } => package,
747 Self::Extra { package, .. } => package,
748 Self::Group { package, .. } => package,
749 }
750 }
751}
752
753pub(crate) fn resolve_activated_extras(
769 marker: MarkerTree,
770 scope_package: Option<&PackageName>,
771 known_conflicts: &FxHashMap<ConflictItem, MarkerTree>,
772) -> MarkerTree {
773 if marker.is_true() || marker.is_false() {
774 return marker;
775 }
776
777 let mut transformed = MarkerTree::FALSE;
778
779 for dnf in marker.to_dnf() {
781 let mut or = MarkerTree::TRUE;
782
783 for marker in dnf {
784 let MarkerExpression::Extra {
785 ref operator,
786 ref name,
787 } = marker
788 else {
789 or = or.and(MarkerTree::expression(marker));
790 continue;
791 };
792
793 let Some(name) = name.as_extra() else {
794 or = or.and(MarkerTree::expression(marker));
795 continue;
796 };
797
798 let mut found = false;
802 for (conflict_item, conflict_marker) in known_conflicts {
803 if let Some(extra) = conflict_item.extra() {
805 let package = conflict_item.package();
806 let encoded = encode_package_extra(package, extra);
807 if encoded == *name {
808 match operator {
809 ExtraOperator::Equal => {
810 or = or.and(*conflict_marker);
811 found = true;
812 break;
813 }
814 ExtraOperator::NotEqual => {
815 or = or.and(conflict_marker.negate());
816 found = true;
817 break;
818 }
819 }
820 }
821 }
822
823 if let Some(group) = conflict_item.group() {
825 let package = conflict_item.package();
826 let encoded = encode_package_group(package, group);
827 if encoded == *name {
828 match operator {
829 ExtraOperator::Equal => {
830 or = or.and(*conflict_marker);
831 found = true;
832 break;
833 }
834 ExtraOperator::NotEqual => {
835 or = or.and(conflict_marker.negate());
836 found = true;
837 break;
838 }
839 }
840 }
841 }
842
843 if conflict_item.extra().is_none() && conflict_item.group().is_none() {
845 let package = conflict_item.package();
846 let encoded = encode_project(package);
847 if encoded == *name {
848 match operator {
849 ExtraOperator::Equal => {
850 or = or.and(*conflict_marker);
851 found = true;
852 break;
853 }
854 ExtraOperator::NotEqual => {
855 or = or.and(conflict_marker.negate());
856 found = true;
857 break;
858 }
859 }
860 }
861 }
862 }
863
864 if !found {
866 if let Some(package) = scope_package {
867 let conflict_item = ConflictItem::from((package.clone(), name.clone()));
868 if let Some(conflict_marker) = known_conflicts.get(&conflict_item) {
869 match operator {
870 ExtraOperator::Equal => {
871 or = or.and(*conflict_marker);
872 found = true;
873 }
874 ExtraOperator::NotEqual => {
875 or = or.and(conflict_marker.negate());
876 found = true;
877 }
878 }
879 }
880 }
881 }
882
883 if !found {
886 match operator {
887 ExtraOperator::Equal => {
888 or = or.and(MarkerTree::FALSE);
889 }
890 ExtraOperator::NotEqual => {
891 or = or.and(MarkerTree::TRUE);
892 }
893 }
894 }
895 }
896
897 transformed = transformed.or(or);
898 }
899
900 transformed
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906 use std::str::FromStr;
907
908 use uv_pep508::MarkerEnvironmentBuilder;
909 use uv_pypi_types::ConflictSet;
910
911 fn create_conflicts(it: impl IntoIterator<Item = ConflictSet>) -> Conflicts {
914 let mut conflicts = Conflicts::empty();
915 for set in it {
916 conflicts.push(set);
917 }
918 conflicts
919 }
920
921 fn create_set<'a>(it: impl IntoIterator<Item = &'a str>) -> ConflictSet {
926 let items = it
927 .into_iter()
928 .map(|extra| (create_package("pkg"), create_extra(extra)))
929 .map(ConflictItem::from)
930 .collect::<Vec<ConflictItem>>();
931 ConflictSet::try_from(items).unwrap()
932 }
933
934 fn create_package(name: &str) -> PackageName {
936 PackageName::from_str(name).unwrap()
937 }
938
939 fn create_extra(name: &str) -> ExtraName {
941 ExtraName::from_str(name).unwrap()
942 }
943
944 fn marker_environment() -> MarkerEnvironment {
946 MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
947 implementation_name: "cpython",
948 implementation_version: "3.12.0",
949 os_name: "posix",
950 platform_machine: "arm64",
951 platform_python_implementation: "CPython",
952 platform_release: "23.0.0",
953 platform_system: "Darwin",
954 platform_version: "test",
955 python_full_version: "3.12.0",
956 python_version: "3.12",
957 sys_platform: "darwin",
958 })
959 .expect("valid marker environment")
960 }
961
962 fn create_extra_marker(name: &str) -> ConflictMarker {
964 ConflictMarker::extra(&create_package("pkg"), &create_extra(name))
965 }
966
967 fn create_extra_item(name: &str) -> ConflictItem {
969 ConflictItem::from((create_package("pkg"), create_extra(name)))
970 }
971
972 fn create_known_conflicts<'a>(
974 it: impl IntoIterator<Item = (&'a str, &'a str)>,
975 ) -> FxHashMap<ConflictItem, MarkerTree> {
976 it.into_iter()
977 .map(|(extra, marker)| {
978 (
979 create_extra_item(extra),
980 MarkerTree::from_str(marker).unwrap(),
981 )
982 })
983 .collect()
984 }
985
986 fn to_str(cm: ConflictMarker) -> String {
992 cm.marker
993 .try_to_string()
994 .unwrap_or_else(|| "true".to_string())
995 }
996
997 #[test]
1000 fn activated_conflict_items_encode_every_kind() {
1001 let package = create_package("project");
1002 let extra = create_extra("feature");
1003 let group = GroupName::from_str("dev").expect("valid group name");
1004 let marker = UniversalMarker::new(
1005 MarkerTree::TRUE,
1006 ConflictMarker::project(&package)
1007 .and(ConflictMarker::extra(&package, &extra))
1008 .and(ConflictMarker::group(&package, &group)),
1009 );
1010 let env = marker_environment();
1011
1012 let activated = ActivatedConflictItems::new(
1013 [&package].into_iter(),
1014 [(&package, &extra)].into_iter(),
1015 [(&package, &group)].into_iter(),
1016 );
1017 assert!(marker.evaluate_activated(&env, &activated));
1018
1019 let without_group = ActivatedConflictItems::new(
1020 [&package].into_iter(),
1021 [(&package, &extra)].into_iter(),
1022 std::iter::empty::<(&PackageName, &GroupName)>(),
1023 );
1024 assert!(!marker.evaluate_activated(&env, &without_group));
1025 }
1026
1027 #[test]
1031 fn conflicts_as_marker() {
1032 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1033 let cm = ConflictMarker::from_conflicts(&conflicts);
1034 assert_eq!(
1035 to_str(cm),
1036 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1037 );
1038
1039 let conflicts = create_conflicts([create_set(["foo", "bar", "baz"])]);
1040 let cm = ConflictMarker::from_conflicts(&conflicts);
1041 assert_eq!(
1042 to_str(cm),
1043 "(extra != 'extra-3-pkg-baz' and extra != 'extra-3-pkg-foo') \
1044 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo') \
1045 or (extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-baz')",
1046 );
1047
1048 let conflicts = create_conflicts([create_set(["foo", "bar"]), create_set(["fox", "ant"])]);
1049 let cm = ConflictMarker::from_conflicts(&conflicts);
1050 assert_eq!(
1051 to_str(cm),
1052 "(extra != 'extra-3-pkg-bar' and extra != 'extra-3-pkg-fox') or \
1053 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-foo') or \
1054 (extra != 'extra-3-pkg-ant' and extra != 'extra-3-pkg-bar') or \
1055 (extra == 'extra-3-pkg-bar' and extra != 'extra-3-pkg-foo' and extra != 'extra-3-pkg-fox')",
1056 );
1057 let disallowed = [
1070 vec!["foo", "bar"],
1071 vec!["fox", "ant"],
1072 vec!["foo", "fox", "bar"],
1073 vec!["foo", "ant", "bar"],
1074 vec!["ant", "foo", "fox"],
1075 vec!["ant", "bar", "fox"],
1076 vec!["foo", "bar", "fox", "ant"],
1077 ];
1078 for extra_names in disallowed {
1079 let extras = extra_names
1080 .iter()
1081 .copied()
1082 .map(|name| (create_package("pkg"), create_extra(name)))
1083 .collect::<Vec<(PackageName, ExtraName)>>();
1084 let groups = Vec::<(PackageName, GroupName)>::new();
1085 assert!(
1086 !UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1087 "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
1088 );
1089 }
1090 let allowed = [
1091 vec![],
1092 vec!["foo"],
1093 vec!["bar"],
1094 vec!["fox"],
1095 vec!["ant"],
1096 vec!["foo", "fox"],
1097 vec!["foo", "ant"],
1098 vec!["bar", "fox"],
1099 vec!["bar", "ant"],
1100 ];
1101 for extra_names in allowed {
1102 let extras = extra_names
1103 .iter()
1104 .copied()
1105 .map(|name| (create_package("pkg"), create_extra(name)))
1106 .collect::<Vec<(PackageName, ExtraName)>>();
1107 let groups = Vec::<(PackageName, GroupName)>::new();
1108 assert!(
1109 UniversalMarker::new(MarkerTree::TRUE, cm).evaluate_only_extras(&extras, &groups),
1110 "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
1111 );
1112 }
1113 }
1114
1115 #[test]
1118 fn imbibe() {
1119 let conflicts = create_conflicts([create_set(["foo", "bar"])]);
1120 let conflicts_marker = ConflictMarker::from_conflicts(&conflicts);
1121 let foo = create_extra_marker("foo");
1122 let bar = create_extra_marker("bar");
1123
1124 let mut dep_conflict_marker =
1128 UniversalMarker::new(MarkerTree::TRUE, foo.negate().or(bar.negate()));
1129 assert_eq!(
1130 format!("{dep_conflict_marker:?}"),
1131 "extra != 'extra-3-pkg-foo' or extra != 'extra-3-pkg-bar'"
1132 );
1133 dep_conflict_marker.imbibe(conflicts_marker);
1134 assert_eq!(format!("{dep_conflict_marker:?}"), "true");
1135 }
1136
1137 #[test]
1138 fn imbibe_true() {
1139 let pep508 =
1140 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1141 let mut marker = UniversalMarker::new(pep508, create_extra_marker("foo"));
1142 let expected = marker;
1143
1144 marker.imbibe(ConflictMarker::TRUE);
1145
1146 assert_eq!(marker, expected);
1147 }
1148
1149 #[test]
1150 fn has_conflict_marker() {
1151 let pep508 =
1152 MarkerTree::from_str("sys_platform == 'darwin'").expect("valid marker expression");
1153 assert!(!UniversalMarker::from_combined(pep508).has_conflict_marker());
1154 assert!(UniversalMarker::new(pep508, create_extra_marker("foo")).has_conflict_marker());
1155 }
1156
1157 #[test]
1158 fn resolve() {
1159 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1160 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();
1161 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1162 assert_eq!(
1163 cm.try_to_string().as_deref(),
1164 Some(
1165 "(python_full_version < '3.10' and sys_platform != 'darwin') or (python_full_version >= '3.10' and sys_platform == 'darwin')"
1166 )
1167 );
1168
1169 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-foo'")
1170 .unwrap();
1171 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1172 assert_eq!(
1173 cm.try_to_string().as_deref(),
1174 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1175 );
1176
1177 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'extra-3-pkg-bar'")
1178 .unwrap();
1179 let cm = resolve_activated_extras(cm, None, &known_conflicts);
1180 assert!(cm.is_false());
1181 }
1182
1183 #[test]
1184 fn resolve_unencoded_package_extras() {
1185 let known_conflicts = create_known_conflicts([("foo", "sys_platform == 'darwin'")]);
1186 let package = create_package("pkg");
1187
1188 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'foo'").unwrap();
1189 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1190 assert_eq!(
1191 cm.try_to_string().as_deref(),
1192 Some("python_full_version >= '3.10' and sys_platform == 'darwin'")
1193 );
1194
1195 let cm = MarkerTree::from_str("python_version >= '3.10' and extra != 'foo'").unwrap();
1196 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1197 assert_eq!(
1198 cm.try_to_string().as_deref(),
1199 Some("python_full_version >= '3.10' and sys_platform != 'darwin'")
1200 );
1201
1202 let cm = MarkerTree::from_str("python_version >= '3.10' and extra == 'bar'").unwrap();
1203 let cm = resolve_activated_extras(cm, Some(&package), &known_conflicts);
1204 assert!(cm.is_false());
1205 }
1206}