1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::{self, Display, Formatter};
4use std::ops::{Bound, Deref};
5use std::str::FromStr;
6
7use arcstr::ArcStr;
8use itertools::Itertools;
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
10use version_ranges::Ranges;
11
12use uv_normalize::{ExtraName, GroupName};
13use uv_pep440::{Version, VersionParseError, VersionSpecifier};
14
15use super::algebra::{Edges, INTERNER, NodeId, Variable};
16use super::simplify;
17use crate::cursor::Cursor;
18use crate::marker::lowering::{
19 CanonicalMarkerListPair, CanonicalMarkerValueString, CanonicalMarkerValueVersion,
20};
21use crate::marker::parse;
22use crate::{
23 CanonicalMarkerValueExtra, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url,
24 Reporter, TracingReporter,
25};
26
27#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
29pub enum MarkerWarningKind {
30 DeprecatedMarkerName,
33 ExtraInvalidComparison,
36 ExtrasInvalidComparison,
39 DependencyGroupsInvalidComparison,
42 LexicographicComparison,
44 MarkerMarkerComparison,
46 Pep440Error,
48 StringStringComparison,
50}
51
52#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
54pub enum MarkerValueVersion {
55 ImplementationVersion,
57 PythonFullVersion,
59 PythonVersion,
61}
62
63impl Display for MarkerValueVersion {
64 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::ImplementationVersion => f.write_str("implementation_version"),
67 Self::PythonFullVersion => f.write_str("python_full_version"),
68 Self::PythonVersion => f.write_str("python_version"),
69 }
70 }
71}
72
73#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
75pub enum MarkerValueString {
76 ImplementationName,
78 OsName,
80 OsNameDeprecated,
82 PlatformMachine,
84 PlatformMachineDeprecated,
86 PlatformPythonImplementation,
88 PlatformPythonImplementationDeprecated,
90 PythonImplementationDeprecated,
92 PlatformRelease,
94 PlatformSystem,
96 PlatformVersion,
98 PlatformVersionDeprecated,
100 SysPlatform,
102 SysPlatformDeprecated,
104}
105
106impl Display for MarkerValueString {
107 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109 match self {
110 Self::ImplementationName => f.write_str("implementation_name"),
111 Self::OsName | Self::OsNameDeprecated => f.write_str("os_name"),
112 Self::PlatformMachine | Self::PlatformMachineDeprecated => {
113 f.write_str("platform_machine")
114 }
115 Self::PlatformPythonImplementation
116 | Self::PlatformPythonImplementationDeprecated
117 | Self::PythonImplementationDeprecated => f.write_str("platform_python_implementation"),
118 Self::PlatformRelease => f.write_str("platform_release"),
119 Self::PlatformSystem => f.write_str("platform_system"),
120 Self::PlatformVersion | Self::PlatformVersionDeprecated => {
121 f.write_str("platform_version")
122 }
123 Self::SysPlatform | Self::SysPlatformDeprecated => f.write_str("sys_platform"),
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
132pub enum MarkerValueList {
133 Extras,
135 DependencyGroups,
137}
138
139impl Display for MarkerValueList {
140 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141 match self {
142 Self::Extras => f.write_str("extras"),
143 Self::DependencyGroups => f.write_str("dependency_groups"),
144 }
145 }
146}
147
148#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
152pub enum MarkerValue {
153 MarkerEnvVersion(MarkerValueVersion),
155 MarkerEnvString(MarkerValueString),
157 MarkerEnvList(MarkerValueList),
159 Extra,
161 QuotedString(ArcStr),
163}
164
165impl FromStr for MarkerValue {
166 type Err = String;
167
168 fn from_str(s: &str) -> Result<Self, Self::Err> {
170 let value = match s {
171 "implementation_name" => Self::MarkerEnvString(MarkerValueString::ImplementationName),
172 "implementation_version" => {
173 Self::MarkerEnvVersion(MarkerValueVersion::ImplementationVersion)
174 }
175 "os_name" => Self::MarkerEnvString(MarkerValueString::OsName),
176 "os.name" => Self::MarkerEnvString(MarkerValueString::OsNameDeprecated),
177 "platform_machine" => Self::MarkerEnvString(MarkerValueString::PlatformMachine),
178 "platform.machine" => {
179 Self::MarkerEnvString(MarkerValueString::PlatformMachineDeprecated)
180 }
181 "platform_python_implementation" => {
182 Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementation)
183 }
184 "platform.python_implementation" => {
185 Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementationDeprecated)
186 }
187 "python_implementation" => {
188 Self::MarkerEnvString(MarkerValueString::PythonImplementationDeprecated)
189 }
190 "platform_release" => Self::MarkerEnvString(MarkerValueString::PlatformRelease),
191 "platform_system" => Self::MarkerEnvString(MarkerValueString::PlatformSystem),
192 "platform_version" => Self::MarkerEnvString(MarkerValueString::PlatformVersion),
193 "platform.version" => {
194 Self::MarkerEnvString(MarkerValueString::PlatformVersionDeprecated)
195 }
196 "python_full_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonFullVersion),
197 "python_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonVersion),
198 "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform),
199 "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated),
200 "extras" => Self::MarkerEnvList(MarkerValueList::Extras),
201 "dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups),
202 "extra" => Self::Extra,
203 _ => return Err(format!("Invalid key: {s}")),
204 };
205 Ok(value)
206 }
207}
208
209impl Display for MarkerValue {
210 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211 match self {
212 Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f),
213 Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f),
214 Self::MarkerEnvList(marker_value_contains) => marker_value_contains.fmt(f),
215 Self::Extra => f.write_str("extra"),
216 Self::QuotedString(value) => write!(f, "'{value}'"),
217 }
218 }
219}
220
221#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
223pub enum MarkerOperator {
224 Equal,
226 NotEqual,
228 GreaterThan,
230 GreaterEqual,
232 LessThan,
234 LessEqual,
236 TildeEqual,
238 In,
240 NotIn,
242 Contains,
247 NotContains,
252}
253
254impl MarkerOperator {
255 pub(crate) fn to_pep440_operator(self) -> Option<uv_pep440::Operator> {
257 match self {
258 Self::Equal => Some(uv_pep440::Operator::Equal),
259 Self::NotEqual => Some(uv_pep440::Operator::NotEqual),
260 Self::GreaterThan => Some(uv_pep440::Operator::GreaterThan),
261 Self::GreaterEqual => Some(uv_pep440::Operator::GreaterThanEqual),
262 Self::LessThan => Some(uv_pep440::Operator::LessThan),
263 Self::LessEqual => Some(uv_pep440::Operator::LessThanEqual),
264 Self::TildeEqual => Some(uv_pep440::Operator::TildeEqual),
265 _ => None,
266 }
267 }
268
269 pub(crate) fn invert(self) -> Self {
271 match self {
272 Self::LessThan => Self::GreaterThan,
273 Self::LessEqual => Self::GreaterEqual,
274 Self::GreaterThan => Self::LessThan,
275 Self::GreaterEqual => Self::LessEqual,
276 Self::Equal => Self::Equal,
277 Self::NotEqual => Self::NotEqual,
278 Self::TildeEqual => Self::TildeEqual,
279 Self::In => Self::Contains,
280 Self::NotIn => Self::NotContains,
281 Self::Contains => Self::In,
282 Self::NotContains => Self::NotIn,
283 }
284 }
285
286 pub(crate) fn negate(self) -> Option<Self> {
291 Some(match self {
292 Self::Equal => Self::NotEqual,
293 Self::NotEqual => Self::Equal,
294 Self::TildeEqual => return None,
295 Self::LessThan => Self::GreaterEqual,
296 Self::LessEqual => Self::GreaterThan,
297 Self::GreaterThan => Self::LessEqual,
298 Self::GreaterEqual => Self::LessThan,
299 Self::In => Self::NotIn,
300 Self::NotIn => Self::In,
301 Self::Contains => Self::NotContains,
302 Self::NotContains => Self::Contains,
303 })
304 }
305
306 pub fn from_bounds(
308 bounds: (&Bound<ArcStr>, &Bound<ArcStr>),
309 ) -> impl Iterator<Item = (Self, ArcStr)> {
310 let (b1, b2) = match bounds {
311 (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
312 (Some((Self::Equal, v1.clone())), None)
313 }
314 (Bound::Excluded(v1), Bound::Excluded(v2)) if v1 == v2 => {
315 (Some((Self::NotEqual, v1.clone())), None)
316 }
317 (lower, upper) => (Self::from_lower_bound(lower), Self::from_upper_bound(upper)),
318 };
319
320 b1.into_iter().chain(b2)
321 }
322
323 pub fn from_lower_bound(bound: &Bound<ArcStr>) -> Option<(Self, ArcStr)> {
325 match bound {
326 Bound::Included(value) => Some((Self::GreaterEqual, value.clone())),
327 Bound::Excluded(value) => Some((Self::GreaterThan, value.clone())),
328 Bound::Unbounded => None,
329 }
330 }
331
332 pub fn from_upper_bound(bound: &Bound<ArcStr>) -> Option<(Self, ArcStr)> {
334 match bound {
335 Bound::Included(value) => Some((Self::LessEqual, value.clone())),
336 Bound::Excluded(value) => Some((Self::LessThan, value.clone())),
337 Bound::Unbounded => None,
338 }
339 }
340}
341
342impl FromStr for MarkerOperator {
343 type Err = String;
344
345 fn from_str(s: &str) -> Result<Self, Self::Err> {
347 let value = match s {
348 "==" => Self::Equal,
349 "!=" => Self::NotEqual,
350 ">" => Self::GreaterThan,
351 ">=" => Self::GreaterEqual,
352 "<" => Self::LessThan,
353 "<=" => Self::LessEqual,
354 "~=" => Self::TildeEqual,
355 "in" => Self::In,
356 not_space_in
357 if not_space_in
358 .strip_prefix("not")
360 .and_then(|space_in| space_in.strip_suffix("in"))
362 .is_some_and(|space| !space.is_empty() && space.trim().is_empty()) =>
364 {
365 Self::NotIn
366 }
367 other => return Err(format!("Invalid comparator: {other}")),
368 };
369 Ok(value)
370 }
371}
372
373impl Display for MarkerOperator {
374 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
375 f.write_str(match self {
376 Self::Equal => "==",
377 Self::NotEqual => "!=",
378 Self::GreaterThan => ">",
379 Self::GreaterEqual => ">=",
380 Self::LessThan => "<",
381 Self::LessEqual => "<=",
382 Self::TildeEqual => "~=",
383 Self::In | Self::Contains => "in",
384 Self::NotIn | Self::NotContains => "not in",
385 })
386 }
387}
388
389#[derive(Clone, Debug, Eq, Hash, PartialEq)]
391pub struct StringVersion {
392 pub string: String,
394 pub version: Version,
396}
397
398impl From<Version> for StringVersion {
399 fn from(version: Version) -> Self {
400 Self {
401 string: version.to_string(),
402 version,
403 }
404 }
405}
406
407impl FromStr for StringVersion {
408 type Err = VersionParseError;
409
410 fn from_str(s: &str) -> Result<Self, Self::Err> {
411 Ok(Self {
412 string: s.to_string(),
413 version: Version::from_str(s)?,
414 })
415 }
416}
417
418impl Display for StringVersion {
419 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
420 self.string.fmt(f)
421 }
422}
423
424impl Serialize for StringVersion {
425 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
426 where
427 S: Serializer,
428 {
429 serializer.serialize_str(&self.string)
430 }
431}
432
433impl<'de> Deserialize<'de> for StringVersion {
434 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
435 where
436 D: Deserializer<'de>,
437 {
438 struct Visitor;
439
440 impl de::Visitor<'_> for Visitor {
441 type Value = StringVersion;
442
443 fn expecting(&self, f: &mut Formatter) -> fmt::Result {
444 f.write_str("a string")
445 }
446
447 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
448 StringVersion::from_str(v).map_err(de::Error::custom)
449 }
450 }
451
452 deserializer.deserialize_str(Visitor)
453 }
454}
455
456impl Deref for StringVersion {
457 type Target = Version;
458
459 fn deref(&self) -> &Self::Target {
460 &self.version
461 }
462}
463
464#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
466pub enum MarkerValueExtra {
467 Extra(ExtraName),
469 Arbitrary(String),
471}
472
473impl MarkerValueExtra {
474 pub fn as_extra(&self) -> Option<&ExtraName> {
476 match self {
477 Self::Extra(extra) => Some(extra),
478 Self::Arbitrary(_) => None,
479 }
480 }
481
482 fn into_extra(self) -> Option<ExtraName> {
484 match self {
485 Self::Extra(extra) => Some(extra),
486 Self::Arbitrary(_) => None,
487 }
488 }
489}
490
491impl Display for MarkerValueExtra {
492 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
493 match self {
494 Self::Extra(extra) => extra.fmt(f),
495 Self::Arbitrary(string) => string.fmt(f),
496 }
497 }
498}
499
500#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
502#[allow(missing_docs)]
503pub enum MarkerExpression {
504 Version {
509 key: MarkerValueVersion,
510 specifier: VersionSpecifier,
511 },
512 VersionIn {
521 key: MarkerValueVersion,
522 versions: Vec<Version>,
523 operator: ContainerOperator,
524 },
525 String {
529 key: MarkerValueString,
530 operator: MarkerOperator,
531 value: ArcStr,
532 },
533 List {
535 pair: CanonicalMarkerListPair,
536 operator: ContainerOperator,
537 },
538 Extra {
540 name: MarkerValueExtra,
541 operator: ExtraOperator,
542 },
543}
544
545#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
547pub(crate) enum MarkerExpressionKind {
548 Version(MarkerValueVersion),
550 VersionIn(MarkerValueVersion),
552 String(MarkerValueString),
554 List(MarkerValueList),
556 Extra,
558}
559
560#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
562pub enum ExtraOperator {
563 Equal,
565 NotEqual,
567}
568
569impl ExtraOperator {
570 pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option<Self> {
574 match operator {
575 MarkerOperator::Equal => Some(Self::Equal),
576 MarkerOperator::NotEqual => Some(Self::NotEqual),
577 _ => None,
578 }
579 }
580
581 pub(crate) fn negate(&self) -> Self {
583 match self {
584 Self::Equal => Self::NotEqual,
585 Self::NotEqual => Self::Equal,
586 }
587 }
588}
589
590impl Display for ExtraOperator {
591 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
592 f.write_str(match self {
593 Self::Equal => "==",
594 Self::NotEqual => "!=",
595 })
596 }
597}
598
599#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
601pub enum ContainerOperator {
602 In,
604 NotIn,
606}
607
608impl ContainerOperator {
609 pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option<Self> {
613 match operator {
614 MarkerOperator::In => Some(Self::In),
615 MarkerOperator::NotIn => Some(Self::NotIn),
616 _ => None,
617 }
618 }
619}
620
621impl Display for ContainerOperator {
622 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
623 f.write_str(match self {
624 Self::In => "in",
625 Self::NotIn => "not in",
626 })
627 }
628}
629
630impl MarkerExpression {
631 pub fn parse_reporter(
633 s: &str,
634 reporter: &mut impl Reporter,
635 ) -> Result<Option<Self>, Pep508Error> {
636 let mut chars = Cursor::new(s);
637 let expression = parse::parse_marker_key_op_value(&mut chars, reporter)?;
638 chars.eat_whitespace();
639 if let Some((pos, unexpected)) = chars.next() {
640 return Err(Pep508Error {
641 message: Pep508ErrorSource::String(format!(
642 "Unexpected character '{unexpected}', expected end of input"
643 )),
644 start: pos,
645 len: chars.remaining(),
646 input: chars.to_string(),
647 });
648 }
649
650 Ok(expression)
651 }
652
653 #[expect(clippy::should_implement_trait)]
658 pub fn from_str(s: &str) -> Result<Option<Self>, Pep508Error> {
659 Self::parse_reporter(s, &mut TracingReporter)
660 }
661
662 pub(crate) fn kind(&self) -> MarkerExpressionKind {
664 match self {
665 Self::Version { key, .. } => MarkerExpressionKind::Version(*key),
666 Self::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key),
667 Self::String { key, .. } => MarkerExpressionKind::String(*key),
668 Self::List { pair, .. } => MarkerExpressionKind::List(pair.key()),
669 Self::Extra { .. } => MarkerExpressionKind::Extra,
670 }
671 }
672}
673
674impl Display for MarkerExpression {
675 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
676 match self {
677 Self::Version { key, specifier } => {
678 let (op, version) = (specifier.operator(), specifier.version());
679 if op == &uv_pep440::Operator::EqualStar || op == &uv_pep440::Operator::NotEqualStar
680 {
681 return write!(f, "{key} {op} '{version}.*'");
682 }
683 write!(f, "{key} {op} '{version}'")
684 }
685 Self::VersionIn {
686 key,
687 versions,
688 operator,
689 } => {
690 let versions = versions.iter().map(ToString::to_string).join(" ");
691 write!(f, "{key} {operator} '{versions}'")
692 }
693 Self::String {
694 key,
695 operator,
696 value,
697 } => {
698 if matches!(
699 operator,
700 MarkerOperator::Contains | MarkerOperator::NotContains
701 ) {
702 return write!(f, "'{value}' {} {key}", operator.invert());
703 }
704
705 write!(f, "{key} {operator} '{value}'")
706 }
707 Self::List { pair, operator } => {
708 write!(f, "'{}' {} {}", pair.value(), operator, pair.key())
709 }
710 Self::Extra { operator, name } => {
711 write!(f, "extra {operator} '{name}'")
712 }
713 }
714 }
715}
716
717#[derive(Debug, Copy, Clone)]
719enum ExtrasEnvironment<'a> {
720 Extras(&'a [ExtraName]),
722 Pep751(&'a [ExtraName], &'a [GroupName]),
724}
725
726impl<'a> ExtrasEnvironment<'a> {
727 fn from_extras(extras: &'a [ExtraName]) -> Self {
729 Self::Extras(extras)
730 }
731
732 fn from_pep751(extras: &'a [ExtraName], dependency_groups: &'a [GroupName]) -> Self {
734 Self::Pep751(extras, dependency_groups)
735 }
736
737 fn extra(&self) -> &[ExtraName] {
739 match self {
740 Self::Extras(extra) => extra,
741 Self::Pep751(..) => &[],
742 }
743 }
744
745 fn extras(&self) -> &[ExtraName] {
747 match self {
748 Self::Extras(..) => &[],
749 Self::Pep751(extras, ..) => extras,
750 }
751 }
752
753 fn dependency_groups(&self) -> &[GroupName] {
755 match self {
756 Self::Extras(..) => &[],
757 Self::Pep751(.., groups) => groups,
758 }
759 }
760}
761
762#[derive(Clone, Copy, Eq, Hash, PartialEq)]
768pub struct MarkerTree(NodeId);
769
770impl Default for MarkerTree {
771 fn default() -> Self {
772 Self::TRUE
773 }
774}
775
776impl<'de> Deserialize<'de> for MarkerTree {
777 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
778 where
779 D: Deserializer<'de>,
780 {
781 struct Visitor;
782
783 impl de::Visitor<'_> for Visitor {
784 type Value = MarkerTree;
785
786 fn expecting(&self, f: &mut Formatter) -> fmt::Result {
787 f.write_str("a string")
788 }
789
790 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
791 MarkerTree::from_str(v).map_err(de::Error::custom)
792 }
793 }
794
795 deserializer.deserialize_str(Visitor)
796 }
797}
798
799impl FromStr for MarkerTree {
800 type Err = Pep508Error;
801
802 fn from_str(markers: &str) -> Result<Self, Self::Err> {
803 parse::parse_markers(markers, &mut TracingReporter)
804 }
805}
806
807impl MarkerTree {
808 pub fn parse_str<T: Pep508Url>(markers: &str) -> Result<Self, Pep508Error<T>> {
810 parse::parse_markers(markers, &mut TracingReporter)
811 }
812
813 pub fn parse_reporter(
815 markers: &str,
816 reporter: &mut impl Reporter,
817 ) -> Result<Self, Pep508Error> {
818 parse::parse_markers(markers, reporter)
819 }
820
821 pub const TRUE: Self = Self(NodeId::TRUE);
823
824 pub const FALSE: Self = Self(NodeId::FALSE);
826
827 pub fn expression(expr: MarkerExpression) -> Self {
829 Self(INTERNER.lock().expression(expr))
830 }
831
832 pub fn is_true(self) -> bool {
839 self.0.is_true()
840 }
841
842 pub fn is_false(self) -> bool {
850 self.0.is_false()
851 }
852
853 #[must_use]
855 pub fn negate(self) -> Self {
856 Self(self.0.not())
857 }
858
859 pub fn and(&mut self, tree: Self) {
861 self.0 = INTERNER.lock().and(self.0, tree.0);
862 }
863
864 pub fn or(&mut self, tree: Self) {
866 self.0 = INTERNER.lock().or(self.0, tree.0);
867 }
868
869 pub fn implies(&mut self, consequent: Self) {
875 *self = self.negate();
878 self.or(consequent);
879 }
880
881 pub fn is_disjoint(self, other: Self) -> bool {
889 INTERNER.lock().is_disjoint(self.0, other.0)
890 }
891
892 pub fn contents(self) -> Option<MarkerTreeContents> {
899 if self.is_true() {
900 return None;
901 }
902
903 Some(MarkerTreeContents(self))
904 }
905
906 pub fn try_to_string(self) -> Option<String> {
912 self.contents().map(|contents| contents.to_string())
913 }
914
915 pub fn kind(self) -> MarkerTreeKind<'static> {
917 if self.is_true() {
918 return MarkerTreeKind::True;
919 }
920
921 if self.is_false() {
922 return MarkerTreeKind::False;
923 }
924
925 let node = INTERNER.shared.node(self.0);
926 match &node.var {
927 Variable::Version(key) => {
928 let Edges::Version { edges: ref map } = node.children else {
929 unreachable!()
930 };
931 MarkerTreeKind::Version(VersionMarkerTree {
932 id: self.0,
933 key: *key,
934 map,
935 })
936 }
937 Variable::String(key) => {
938 let Edges::String { edges: ref map } = node.children else {
939 unreachable!()
940 };
941 MarkerTreeKind::String(StringMarkerTree {
942 id: self.0,
943 key: *key,
944 map,
945 })
946 }
947 Variable::In { key, value } => {
948 let Edges::Boolean { low, high } = node.children else {
949 unreachable!()
950 };
951 MarkerTreeKind::In(InMarkerTree {
952 key: *key,
953 value,
954 high: high.negate(self.0),
955 low: low.negate(self.0),
956 })
957 }
958 Variable::Contains { key, value } => {
959 let Edges::Boolean { low, high } = node.children else {
960 unreachable!()
961 };
962 MarkerTreeKind::Contains(ContainsMarkerTree {
963 key: *key,
964 value,
965 high: high.negate(self.0),
966 low: low.negate(self.0),
967 })
968 }
969 Variable::List(key) => {
970 let Edges::Boolean { low, high } = node.children else {
971 unreachable!()
972 };
973 MarkerTreeKind::List(ListMarkerTree {
974 pair: key,
975 high: high.negate(self.0),
976 low: low.negate(self.0),
977 })
978 }
979 Variable::Extra(name) => {
980 let Edges::Boolean { low, high } = node.children else {
981 unreachable!()
982 };
983 MarkerTreeKind::Extra(ExtraMarkerTree {
984 name,
985 high: high.negate(self.0),
986 low: low.negate(self.0),
987 })
988 }
989 }
990 }
991
992 pub fn to_dnf(self) -> Vec<Vec<MarkerExpression>> {
994 simplify::to_dnf(self)
995 }
996
997 pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
999 self.evaluate_reporter_impl(
1000 env,
1001 ExtrasEnvironment::from_extras(extras),
1002 &mut TracingReporter,
1003 )
1004 }
1005
1006 pub fn evaluate_pep751(
1010 self,
1011 env: &MarkerEnvironment,
1012 extras: &[ExtraName],
1013 groups: &[GroupName],
1014 ) -> bool {
1015 self.evaluate_reporter_impl(
1016 env,
1017 ExtrasEnvironment::from_pep751(extras, groups),
1018 &mut TracingReporter,
1019 )
1020 }
1021
1022 pub fn evaluate_optional_environment(
1030 self,
1031 env: Option<&MarkerEnvironment>,
1032 extras: &[ExtraName],
1033 ) -> bool {
1034 match env {
1035 None => self.evaluate_extras(extras),
1036 Some(env) => self.evaluate_reporter_impl(
1037 env,
1038 ExtrasEnvironment::from_extras(extras),
1039 &mut TracingReporter,
1040 ),
1041 }
1042 }
1043
1044 pub fn evaluate_reporter(
1047 self,
1048 env: &MarkerEnvironment,
1049 extras: &[ExtraName],
1050 reporter: &mut impl Reporter,
1051 ) -> bool {
1052 self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter)
1053 }
1054
1055 fn evaluate_reporter_impl(
1056 self,
1057 env: &MarkerEnvironment,
1058 extras: ExtrasEnvironment,
1059 reporter: &mut impl Reporter,
1060 ) -> bool {
1061 match self.kind() {
1062 MarkerTreeKind::True => return true,
1063 MarkerTreeKind::False => return false,
1064 MarkerTreeKind::Version(marker) => {
1065 for (range, tree) in marker.edges() {
1066 if range.contains(env.get_version(marker.key())) {
1067 return tree.evaluate_reporter_impl(env, extras, reporter);
1068 }
1069 }
1070 }
1071 MarkerTreeKind::String(marker) => {
1072 for (range, tree) in marker.children() {
1073 let l_string = env.get_string(marker.key());
1074
1075 if range.as_singleton().is_none() {
1076 if let Some((start, end)) = range.bounding_range() {
1077 if let Bound::Included(value) | Bound::Excluded(value) = start {
1078 reporter.report(
1079 MarkerWarningKind::LexicographicComparison,
1080 format!("Comparing {l_string} and {value} lexicographically"),
1081 );
1082 }
1083
1084 if let Bound::Included(value) | Bound::Excluded(value) = end {
1085 reporter.report(
1086 MarkerWarningKind::LexicographicComparison,
1087 format!("Comparing {l_string} and {value} lexicographically"),
1088 );
1089 }
1090 }
1091 }
1092
1093 if range.contains(l_string) {
1094 return tree.evaluate_reporter_impl(env, extras, reporter);
1095 }
1096 }
1097 }
1098 MarkerTreeKind::In(marker) => {
1099 return marker
1100 .edge(marker.value().contains(env.get_string(marker.key())))
1101 .evaluate_reporter_impl(env, extras, reporter);
1102 }
1103 MarkerTreeKind::Contains(marker) => {
1104 return marker
1105 .edge(env.get_string(marker.key()).contains(marker.value()))
1106 .evaluate_reporter_impl(env, extras, reporter);
1107 }
1108 MarkerTreeKind::Extra(marker) => {
1109 return marker
1110 .edge(extras.extra().contains(marker.name().extra()))
1111 .evaluate_reporter_impl(env, extras, reporter);
1112 }
1113 MarkerTreeKind::List(marker) => {
1114 let edge = match marker.pair() {
1115 CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra),
1116 CanonicalMarkerListPair::DependencyGroup(dependency_group) => {
1117 extras.dependency_groups().contains(dependency_group)
1118 }
1119 CanonicalMarkerListPair::Arbitrary { .. } => return false,
1121 };
1122
1123 return marker
1124 .edge(edge)
1125 .evaluate_reporter_impl(env, extras, reporter);
1126 }
1127 }
1128
1129 false
1130 }
1131
1132 pub fn evaluate_extras(self, extras: &[ExtraName]) -> bool {
1136 match self.kind() {
1137 MarkerTreeKind::True => true,
1138 MarkerTreeKind::False => false,
1139 MarkerTreeKind::Version(marker) => {
1140 marker.edges().any(|(_, tree)| tree.evaluate_extras(extras))
1141 }
1142 MarkerTreeKind::String(marker) => marker
1143 .children()
1144 .any(|(_, tree)| tree.evaluate_extras(extras)),
1145 MarkerTreeKind::In(marker) => marker
1146 .children()
1147 .any(|(_, tree)| tree.evaluate_extras(extras)),
1148 MarkerTreeKind::Contains(marker) => marker
1149 .children()
1150 .any(|(_, tree)| tree.evaluate_extras(extras)),
1151 MarkerTreeKind::List(marker) => marker
1152 .children()
1153 .any(|(_, tree)| tree.evaluate_extras(extras)),
1154 MarkerTreeKind::Extra(marker) => marker
1155 .edge(extras.contains(marker.name().extra()))
1156 .evaluate_extras(extras),
1157 }
1158 }
1159
1160 pub fn evaluate_only_extras(self, extras: &[ExtraName]) -> bool {
1162 match self.kind() {
1163 MarkerTreeKind::True => true,
1164 MarkerTreeKind::False => false,
1165 MarkerTreeKind::Version(marker) => marker
1166 .edges()
1167 .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1168 MarkerTreeKind::String(marker) => marker
1169 .children()
1170 .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1171 MarkerTreeKind::In(marker) => marker
1172 .children()
1173 .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1174 MarkerTreeKind::Contains(marker) => marker
1175 .children()
1176 .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1177 MarkerTreeKind::List(marker) => marker
1178 .children()
1179 .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1180 MarkerTreeKind::Extra(marker) => marker
1181 .edge(extras.contains(marker.name().extra()))
1182 .evaluate_only_extras(extras),
1183 }
1184 }
1185
1186 pub fn top_level_extra(self) -> Option<MarkerExpression> {
1191 let mut extra_expression = None;
1192 for conjunction in self.to_dnf() {
1193 let found = conjunction.iter().find(|expression| {
1194 matches!(
1195 expression,
1196 MarkerExpression::Extra {
1197 operator: ExtraOperator::Equal,
1198 ..
1199 }
1200 )
1201 })?;
1202
1203 if let Some(ref extra_expression) = extra_expression {
1206 if *extra_expression != *found {
1207 return None;
1208 }
1209
1210 continue;
1211 }
1212
1213 extra_expression = Some(found.clone());
1214 }
1215
1216 extra_expression
1217 }
1218
1219 pub fn top_level_extra_name(self) -> Option<Cow<'static, ExtraName>> {
1224 if let MarkerTreeKind::Extra(marker) = self.kind() {
1226 if marker.edge(true).is_true() {
1227 let CanonicalMarkerValueExtra::Extra(extra) = marker.name;
1228 return Some(Cow::Borrowed(extra));
1229 }
1230 }
1231
1232 let extra_expression = self.top_level_extra()?;
1233
1234 match extra_expression {
1235 MarkerExpression::Extra { name, .. } => name.into_extra().map(Cow::Owned),
1236 _ => unreachable!(),
1237 }
1238 }
1239
1240 #[must_use]
1265 pub fn simplify_python_versions(self, lower: Bound<&Version>, upper: Bound<&Version>) -> Self {
1266 Self(
1267 INTERNER
1268 .lock()
1269 .simplify_python_versions(self.0, lower, upper),
1270 )
1271 }
1272
1273 #[must_use]
1281 pub fn complexify_python_versions(
1282 self,
1283 lower: Bound<&Version>,
1284 upper: Bound<&Version>,
1285 ) -> Self {
1286 Self(
1287 INTERNER
1288 .lock()
1289 .complexify_python_versions(self.0, lower, upper),
1290 )
1291 }
1292
1293 #[must_use]
1302 pub fn simplify_extras(self, extras: &[ExtraName]) -> Self {
1303 self.simplify_extras_with(|name| extras.contains(name))
1304 }
1305
1306 #[must_use]
1317 pub fn simplify_not_extras(self, extras: &[ExtraName]) -> Self {
1318 self.simplify_not_extras_with(|name| extras.contains(name))
1319 }
1320
1321 #[must_use]
1331 pub fn simplify_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> Self {
1332 self.simplify_extras_with_impl(&is_extra)
1338 }
1339
1340 #[must_use]
1351 pub fn simplify_not_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> Self {
1352 self.simplify_not_extras_with_impl(&is_extra)
1358 }
1359
1360 #[must_use]
1365 pub fn without_extras(self) -> Self {
1366 Self(INTERNER.lock().without_extras(self.0))
1367 }
1368
1369 #[must_use]
1374 pub fn only_extras(self) -> Self {
1375 Self(INTERNER.lock().only_extras(self.0))
1376 }
1377
1378 pub fn visit_extras(self, mut f: impl FnMut(MarkerOperator, &ExtraName)) {
1383 fn imp(tree: MarkerTree, f: &mut impl FnMut(MarkerOperator, &ExtraName)) {
1384 match tree.kind() {
1385 MarkerTreeKind::True | MarkerTreeKind::False => {}
1386 MarkerTreeKind::Version(kind) => {
1387 for (tree, _) in simplify::collect_edges(kind.edges()) {
1388 imp(tree, f);
1389 }
1390 }
1391 MarkerTreeKind::String(kind) => {
1392 for (tree, _) in simplify::collect_edges(kind.children()) {
1393 imp(tree, f);
1394 }
1395 }
1396 MarkerTreeKind::In(kind) => {
1397 for (_, tree) in kind.children() {
1398 imp(tree, f);
1399 }
1400 }
1401 MarkerTreeKind::Contains(kind) => {
1402 for (_, tree) in kind.children() {
1403 imp(tree, f);
1404 }
1405 }
1406 MarkerTreeKind::List(kind) => {
1407 for (_, tree) in kind.children() {
1408 imp(tree, f);
1409 }
1410 }
1411 MarkerTreeKind::Extra(kind) => {
1412 if kind.low.is_false() {
1413 f(MarkerOperator::Equal, kind.name().extra());
1414 } else {
1415 f(MarkerOperator::NotEqual, kind.name().extra());
1416 }
1417 for (_, tree) in kind.children() {
1418 imp(tree, f);
1419 }
1420 }
1421 }
1422 }
1423 imp(self, &mut f);
1424 }
1425
1426 fn simplify_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> Self {
1427 Self(INTERNER.lock().restrict(self.0, &|var| match var {
1428 Variable::Extra(name) => is_extra(name.extra()).then_some(true),
1429 _ => None,
1430 }))
1431 }
1432
1433 fn simplify_not_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> Self {
1434 Self(INTERNER.lock().restrict(self.0, &|var| match var {
1435 Variable::Extra(name) => is_extra(name.extra()).then_some(false),
1436 _ => None,
1437 }))
1438 }
1439}
1440
1441impl fmt::Debug for MarkerTree {
1442 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1443 if self.is_true() {
1444 return write!(f, "true");
1445 }
1446 if self.is_false() {
1447 return write!(f, "false");
1448 }
1449 write!(f, "{}", self.contents().unwrap())
1450 }
1451}
1452
1453impl MarkerTree {
1454 pub fn debug_graph(&self) -> MarkerTreeDebugGraph<'_> {
1460 MarkerTreeDebugGraph { marker: self }
1461 }
1462
1463 pub fn debug_raw(&self) -> MarkerTreeDebugRaw<'_> {
1469 MarkerTreeDebugRaw { marker: self }
1470 }
1471
1472 fn fmt_graph(self, f: &mut Formatter<'_>, level: usize) -> fmt::Result {
1473 match self.kind() {
1474 MarkerTreeKind::True => return write!(f, "true"),
1475 MarkerTreeKind::False => return write!(f, "false"),
1476 MarkerTreeKind::Version(kind) => {
1477 for (tree, range) in simplify::collect_edges(kind.edges()) {
1478 writeln!(f)?;
1479 for _ in 0..level {
1480 write!(f, " ")?;
1481 }
1482
1483 write!(f, "{key}{range} -> ", key = kind.key())?;
1484 tree.fmt_graph(f, level + 1)?;
1485 }
1486 }
1487 MarkerTreeKind::String(kind) => {
1488 for (tree, range) in simplify::collect_edges(kind.children()) {
1489 writeln!(f)?;
1490 for _ in 0..level {
1491 write!(f, " ")?;
1492 }
1493
1494 write!(f, "{key}{range} -> ", key = kind.key())?;
1495 tree.fmt_graph(f, level + 1)?;
1496 }
1497 }
1498 MarkerTreeKind::In(kind) => {
1499 writeln!(f)?;
1500 for _ in 0..level {
1501 write!(f, " ")?;
1502 }
1503 write!(f, "{} in {} -> ", kind.key(), kind.value())?;
1504 kind.edge(true).fmt_graph(f, level + 1)?;
1505
1506 writeln!(f)?;
1507 for _ in 0..level {
1508 write!(f, " ")?;
1509 }
1510 write!(f, "{} not in {} -> ", kind.key(), kind.value())?;
1511 kind.edge(false).fmt_graph(f, level + 1)?;
1512 }
1513 MarkerTreeKind::Contains(kind) => {
1514 writeln!(f)?;
1515 for _ in 0..level {
1516 write!(f, " ")?;
1517 }
1518 write!(f, "{} in {} -> ", kind.value(), kind.key())?;
1519 kind.edge(true).fmt_graph(f, level + 1)?;
1520
1521 writeln!(f)?;
1522 for _ in 0..level {
1523 write!(f, " ")?;
1524 }
1525 write!(f, "{} not in {} -> ", kind.value(), kind.key())?;
1526 kind.edge(false).fmt_graph(f, level + 1)?;
1527 }
1528 MarkerTreeKind::List(kind) => {
1529 writeln!(f)?;
1530 for _ in 0..level {
1531 write!(f, " ")?;
1532 }
1533 write!(f, "{} in {} -> ", kind.value(), kind.key())?;
1534 kind.edge(true).fmt_graph(f, level + 1)?;
1535
1536 writeln!(f)?;
1537 for _ in 0..level {
1538 write!(f, " ")?;
1539 }
1540 write!(f, "{} not in {} -> ", kind.value(), kind.key())?;
1541 kind.edge(false).fmt_graph(f, level + 1)?;
1542 }
1543 MarkerTreeKind::Extra(kind) => {
1544 writeln!(f)?;
1545 for _ in 0..level {
1546 write!(f, " ")?;
1547 }
1548 write!(f, "extra == {} -> ", kind.name())?;
1549 kind.edge(true).fmt_graph(f, level + 1)?;
1550
1551 writeln!(f)?;
1552 for _ in 0..level {
1553 write!(f, " ")?;
1554 }
1555 write!(f, "extra != {} -> ", kind.name())?;
1556 kind.edge(false).fmt_graph(f, level + 1)?;
1557 }
1558 }
1559
1560 Ok(())
1561 }
1562}
1563
1564impl PartialOrd for MarkerTree {
1565 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1566 Some(self.cmp(other))
1567 }
1568}
1569
1570impl Ord for MarkerTree {
1571 fn cmp(&self, other: &Self) -> Ordering {
1572 self.kind().cmp(&other.kind())
1573 }
1574}
1575
1576#[derive(Clone)]
1580pub struct MarkerTreeDebugGraph<'a> {
1581 marker: &'a MarkerTree,
1582}
1583
1584impl fmt::Debug for MarkerTreeDebugGraph<'_> {
1585 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1586 self.marker.fmt_graph(f, 0)
1587 }
1588}
1589
1590#[derive(Clone)]
1597pub struct MarkerTreeDebugRaw<'a> {
1598 marker: &'a MarkerTree,
1599}
1600
1601impl fmt::Debug for MarkerTreeDebugRaw<'_> {
1602 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1603 let node = INTERNER.shared.node(self.marker.0);
1604 f.debug_tuple("MarkerTreeDebugRaw").field(node).finish()
1605 }
1606}
1607
1608#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
1614pub enum MarkerTreeKind<'a> {
1615 True,
1617 False,
1619 Version(VersionMarkerTree<'a>),
1621 String(StringMarkerTree<'a>),
1623 In(InMarkerTree<'a>),
1625 Contains(ContainsMarkerTree<'a>),
1627 List(ListMarkerTree<'a>),
1629 Extra(ExtraMarkerTree<'a>),
1631}
1632
1633#[derive(PartialEq, Eq, Clone, Debug)]
1635pub struct VersionMarkerTree<'a> {
1636 id: NodeId,
1637 key: CanonicalMarkerValueVersion,
1638 map: &'a [(Ranges<Version>, NodeId)],
1639}
1640
1641impl VersionMarkerTree<'_> {
1642 pub fn key(&self) -> CanonicalMarkerValueVersion {
1644 self.key
1645 }
1646
1647 pub fn edges(&self) -> impl ExactSizeIterator<Item = (&Ranges<Version>, MarkerTree)> + '_ {
1649 self.map
1650 .iter()
1651 .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1652 }
1653}
1654
1655impl PartialOrd for VersionMarkerTree<'_> {
1656 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1657 Some(self.cmp(other))
1658 }
1659}
1660
1661impl Ord for VersionMarkerTree<'_> {
1662 fn cmp(&self, other: &Self) -> Ordering {
1663 self.key()
1664 .cmp(&other.key())
1665 .then_with(|| self.edges().cmp(other.edges()))
1666 }
1667}
1668
1669#[derive(PartialEq, Eq, Clone, Debug)]
1671pub struct StringMarkerTree<'a> {
1672 id: NodeId,
1673 key: CanonicalMarkerValueString,
1674 map: &'a [(Ranges<ArcStr>, NodeId)],
1675}
1676
1677impl StringMarkerTree<'_> {
1678 pub fn key(&self) -> CanonicalMarkerValueString {
1680 self.key
1681 }
1682
1683 pub fn children(&self) -> impl ExactSizeIterator<Item = (&Ranges<ArcStr>, MarkerTree)> {
1685 self.map
1686 .iter()
1687 .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1688 }
1689}
1690
1691impl PartialOrd for StringMarkerTree<'_> {
1692 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1693 Some(self.cmp(other))
1694 }
1695}
1696
1697impl Ord for StringMarkerTree<'_> {
1698 fn cmp(&self, other: &Self) -> Ordering {
1699 self.key()
1700 .cmp(&other.key())
1701 .then_with(|| self.children().cmp(other.children()))
1702 }
1703}
1704
1705#[derive(PartialEq, Eq, Clone, Debug)]
1707pub struct InMarkerTree<'a> {
1708 key: CanonicalMarkerValueString,
1709 value: &'a ArcStr,
1710 high: NodeId,
1711 low: NodeId,
1712}
1713
1714impl InMarkerTree<'_> {
1715 pub fn key(&self) -> CanonicalMarkerValueString {
1717 self.key
1718 }
1719
1720 pub fn value(&self) -> &ArcStr {
1722 self.value
1723 }
1724
1725 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1727 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1728 }
1729
1730 pub fn edge(&self, value: bool) -> MarkerTree {
1732 if value {
1733 MarkerTree(self.high)
1734 } else {
1735 MarkerTree(self.low)
1736 }
1737 }
1738}
1739
1740impl PartialOrd for InMarkerTree<'_> {
1741 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1742 Some(self.cmp(other))
1743 }
1744}
1745
1746impl Ord for InMarkerTree<'_> {
1747 fn cmp(&self, other: &Self) -> Ordering {
1748 self.key()
1749 .cmp(&other.key())
1750 .then_with(|| self.value().cmp(other.value()))
1751 .then_with(|| self.children().cmp(other.children()))
1752 }
1753}
1754
1755#[derive(PartialEq, Eq, Clone, Debug)]
1757pub struct ContainsMarkerTree<'a> {
1758 key: CanonicalMarkerValueString,
1759 value: &'a str,
1760 high: NodeId,
1761 low: NodeId,
1762}
1763
1764impl ContainsMarkerTree<'_> {
1765 pub fn key(&self) -> CanonicalMarkerValueString {
1767 self.key
1768 }
1769
1770 pub fn value(&self) -> &str {
1772 self.value
1773 }
1774
1775 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1777 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1778 }
1779
1780 pub fn edge(&self, value: bool) -> MarkerTree {
1782 if value {
1783 MarkerTree(self.high)
1784 } else {
1785 MarkerTree(self.low)
1786 }
1787 }
1788}
1789
1790impl PartialOrd for ContainsMarkerTree<'_> {
1791 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1792 Some(self.cmp(other))
1793 }
1794}
1795
1796impl Ord for ContainsMarkerTree<'_> {
1797 fn cmp(&self, other: &Self) -> Ordering {
1798 self.key()
1799 .cmp(&other.key())
1800 .then_with(|| self.value().cmp(other.value()))
1801 .then_with(|| self.children().cmp(other.children()))
1802 }
1803}
1804
1805#[derive(PartialEq, Eq, Clone, Debug)]
1806pub struct ListMarkerTree<'a> {
1807 pair: &'a CanonicalMarkerListPair,
1809 high: NodeId,
1810 low: NodeId,
1811}
1812
1813impl ListMarkerTree<'_> {
1814 pub fn pair(&self) -> &CanonicalMarkerListPair {
1816 self.pair
1817 }
1818
1819 pub fn key(&self) -> MarkerValueList {
1821 self.pair.key()
1822 }
1823
1824 pub fn value(&self) -> String {
1826 self.pair.value()
1827 }
1828
1829 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1831 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1832 }
1833
1834 pub fn edge(&self, value: bool) -> MarkerTree {
1836 if value {
1837 MarkerTree(self.high)
1838 } else {
1839 MarkerTree(self.low)
1840 }
1841 }
1842}
1843
1844impl PartialOrd for ListMarkerTree<'_> {
1845 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1846 Some(self.cmp(other))
1847 }
1848}
1849
1850impl Ord for ListMarkerTree<'_> {
1851 fn cmp(&self, other: &Self) -> Ordering {
1852 self.pair()
1853 .cmp(other.pair())
1854 .then_with(|| self.children().cmp(other.children()))
1855 }
1856}
1857
1858#[derive(PartialEq, Eq, Clone, Debug)]
1860pub struct ExtraMarkerTree<'a> {
1861 name: &'a CanonicalMarkerValueExtra,
1862 high: NodeId,
1863 low: NodeId,
1864}
1865
1866impl ExtraMarkerTree<'_> {
1867 pub fn name(&self) -> &CanonicalMarkerValueExtra {
1869 self.name
1870 }
1871
1872 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1874 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1875 }
1876
1877 pub fn edge(&self, value: bool) -> MarkerTree {
1879 if value {
1880 MarkerTree(self.high)
1881 } else {
1882 MarkerTree(self.low)
1883 }
1884 }
1885}
1886
1887impl PartialOrd for ExtraMarkerTree<'_> {
1888 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1889 Some(self.cmp(other))
1890 }
1891}
1892
1893impl Ord for ExtraMarkerTree<'_> {
1894 fn cmp(&self, other: &Self) -> Ordering {
1895 self.name()
1896 .cmp(other.name())
1897 .then_with(|| self.children().cmp(other.children()))
1898 }
1899}
1900
1901#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Debug)]
1905pub struct MarkerTreeContents(MarkerTree);
1906
1907impl From<MarkerTreeContents> for MarkerTree {
1908 fn from(contents: MarkerTreeContents) -> Self {
1909 contents.0
1910 }
1911}
1912
1913impl From<Option<MarkerTreeContents>> for MarkerTree {
1914 fn from(marker: Option<MarkerTreeContents>) -> Self {
1915 marker.map(|contents| contents.0).unwrap_or_default()
1916 }
1917}
1918
1919impl AsRef<MarkerTree> for MarkerTreeContents {
1920 fn as_ref(&self) -> &MarkerTree {
1921 &self.0
1922 }
1923}
1924
1925impl Serialize for MarkerTreeContents {
1926 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1927 where
1928 S: Serializer,
1929 {
1930 serializer.serialize_str(&self.to_string())
1931 }
1932}
1933
1934impl Display for MarkerTreeContents {
1935 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1936 if self.0.is_false() {
1938 return write!(f, "python_version < '0'");
1939 }
1940
1941 let dnf = self.0.to_dnf();
1943 let format_conjunction = |conjunction: &Vec<MarkerExpression>| {
1944 conjunction
1945 .iter()
1946 .map(MarkerExpression::to_string)
1947 .collect::<Vec<String>>()
1948 .join(" and ")
1949 };
1950
1951 let expr = match &dnf[..] {
1952 [conjunction] => format_conjunction(conjunction),
1953 _ => dnf
1954 .iter()
1955 .map(|conjunction| {
1956 if conjunction.len() == 1 {
1957 format_conjunction(conjunction)
1958 } else {
1959 format!("({})", format_conjunction(conjunction))
1960 }
1961 })
1962 .collect::<Vec<String>>()
1963 .join(" or "),
1964 };
1965
1966 f.write_str(&expr)
1967 }
1968}
1969
1970#[cfg(feature = "schemars")]
1971impl schemars::JsonSchema for MarkerTree {
1972 fn schema_name() -> Cow<'static, str> {
1973 Cow::Borrowed("MarkerTree")
1974 }
1975
1976 fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
1977 schemars::json_schema!({
1978 "type": "string",
1979 "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`"
1980 })
1981 }
1982}
1983
1984#[cfg(test)]
1985mod test {
1986 use std::ops::Bound;
1987 use std::str::FromStr;
1988
1989 use insta::assert_snapshot;
1990
1991 use uv_normalize::ExtraName;
1992 use uv_pep440::Version;
1993
1994 use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder};
1995 use crate::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
1996
1997 fn parse_err(input: &str) -> String {
1998 MarkerTree::from_str(input).unwrap_err().to_string()
1999 }
2000
2001 fn m(s: &str) -> MarkerTree {
2002 s.parse().unwrap()
2003 }
2004
2005 fn env37() -> MarkerEnvironment {
2006 MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2007 implementation_name: "",
2008 implementation_version: "3.7",
2009 os_name: "linux",
2010 platform_machine: "x86_64",
2011 platform_python_implementation: "",
2012 platform_release: "",
2013 platform_system: "",
2014 platform_version: "",
2015 python_full_version: "3.7",
2016 python_version: "3.7",
2017 sys_platform: "linux",
2018 })
2019 .unwrap()
2020 }
2021
2022 #[test]
2024 fn test_marker_equivalence() {
2025 let values = [
2026 (r"python_version == '2.7'", r#"python_version == "2.7""#),
2027 (r#"python_version == "2.7""#, r#"python_version == "2.7""#),
2028 (
2029 r#"python_version == "2.7" and os_name == "posix""#,
2030 r#"python_version == "2.7" and os_name == "posix""#,
2031 ),
2032 (
2033 r#"python_version == "2.7" or os_name == "posix""#,
2034 r#"python_version == "2.7" or os_name == "posix""#,
2035 ),
2036 (
2037 r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
2038 r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
2039 ),
2040 (r#"(python_version == "2.7")"#, r#"python_version == "2.7""#),
2041 (
2042 r#"(python_version == "2.7" and sys_platform == "win32")"#,
2043 r#"python_version == "2.7" and sys_platform == "win32""#,
2044 ),
2045 (
2046 r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
2047 r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
2048 ),
2049 ];
2050
2051 for (a, b) in values {
2052 assert_eq!(m(a), m(b), "{a} {b}");
2053 }
2054 }
2055
2056 #[test]
2057 fn simplify_python_versions() {
2058 assert_eq!(
2059 m("(extra == 'foo' and sys_platform == 'win32') or extra == 'foo'")
2060 .simplify_extras(&["foo".parse().unwrap()]),
2061 MarkerTree::TRUE
2062 );
2063
2064 assert_eq!(
2065 m("(python_version <= '3.11' and sys_platform == 'win32') or python_version > '3.11'")
2066 .simplify_python_versions(
2067 Bound::Excluded(Version::new([3, 12])).as_ref(),
2068 Bound::Unbounded.as_ref(),
2069 ),
2070 MarkerTree::TRUE
2071 );
2072
2073 assert_eq!(
2074 m("python_version < '3.10'")
2075 .simplify_python_versions(
2076 Bound::Excluded(Version::new([3, 7])).as_ref(),
2077 Bound::Unbounded.as_ref(),
2078 )
2079 .try_to_string()
2080 .unwrap(),
2081 "python_full_version < '3.10'"
2082 );
2083
2084 assert_eq!(
2086 m("python_version <= '3.12'")
2087 .simplify_python_versions(
2088 Bound::Excluded(Version::new([3, 12])).as_ref(),
2089 Bound::Unbounded.as_ref(),
2090 )
2091 .try_to_string()
2092 .unwrap(),
2093 "python_full_version < '3.13'"
2094 );
2095
2096 assert_eq!(
2097 m("python_full_version <= '3.12'").simplify_python_versions(
2098 Bound::Excluded(Version::new([3, 12])).as_ref(),
2099 Bound::Unbounded.as_ref(),
2100 ),
2101 MarkerTree::FALSE
2102 );
2103
2104 assert_eq!(
2105 m("python_full_version <= '3.12.1'")
2106 .simplify_python_versions(
2107 Bound::Excluded(Version::new([3, 12])).as_ref(),
2108 Bound::Unbounded.as_ref(),
2109 )
2110 .try_to_string()
2111 .unwrap(),
2112 "python_full_version <= '3.12.1'"
2113 );
2114 }
2115
2116 #[test]
2117 fn release_only() {
2118 assert!(m("python_full_version > '3.10' or python_full_version <= '3.10'").is_true());
2119 assert!(
2120 m("python_full_version > '3.10' or python_full_version <= '3.10'")
2121 .negate()
2122 .is_false()
2123 );
2124 assert!(m("python_full_version > '3.10' and python_full_version <= '3.10'").is_false());
2125 }
2126
2127 #[test]
2128 fn test_marker_evaluation() {
2129 let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2130 implementation_name: "",
2131 implementation_version: "2.7",
2132 os_name: "linux",
2133 platform_machine: "",
2134 platform_python_implementation: "",
2135 platform_release: "",
2136 platform_system: "",
2137 platform_version: "",
2138 python_full_version: "2.7",
2139 python_version: "2.7",
2140 sys_platform: "linux",
2141 })
2142 .unwrap();
2143 let env37 = env37();
2144 let marker1 = MarkerTree::from_str("python_version == '2.7'").unwrap();
2145 let marker2 = MarkerTree::from_str(
2146 "os_name == \"linux\" or python_version == \"3.7\" and sys_platform == \"win32\"",
2147 )
2148 .unwrap();
2149 let marker3 = MarkerTree::from_str(
2150 "python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")",
2151 ).unwrap();
2152 assert!(marker1.evaluate(&env27, &[]));
2153 assert!(!marker1.evaluate(&env37, &[]));
2154 assert!(marker2.evaluate(&env27, &[]));
2155 assert!(marker2.evaluate(&env37, &[]));
2156 assert!(marker3.evaluate(&env27, &[]));
2157 assert!(!marker3.evaluate(&env37, &[]));
2158 }
2159
2160 #[test]
2161 fn test_version_in_evaluation() {
2162 let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2163 implementation_name: "",
2164 implementation_version: "2.7",
2165 os_name: "linux",
2166 platform_machine: "",
2167 platform_python_implementation: "",
2168 platform_release: "",
2169 platform_system: "",
2170 platform_version: "",
2171 python_full_version: "2.7",
2172 python_version: "2.7",
2173 sys_platform: "linux",
2174 })
2175 .unwrap();
2176 let env37 = env37();
2177
2178 let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap();
2179 assert!(marker.evaluate(&env27, &[]));
2180 assert!(!marker.evaluate(&env37, &[]));
2181
2182 let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap();
2183 assert!(marker.evaluate(&env27, &[]));
2184 assert!(marker.evaluate(&env37, &[]));
2185
2186 let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap();
2187 assert!(!marker.evaluate(&env27, &[]));
2188 assert!(!marker.evaluate(&env37, &[]));
2189
2190 let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap();
2191 assert!(!marker.evaluate(&env27, &[]));
2192 assert!(marker.evaluate(&env37, &[]));
2193
2194 let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap();
2195 assert!(!marker.evaluate(&env27, &[]));
2196 assert!(!marker.evaluate(&env37, &[]));
2197
2198 let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap();
2199 assert!(marker.evaluate(&env27, &[]));
2200 assert!(marker.evaluate(&env37, &[]));
2201
2202 let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap();
2203 assert!(marker.evaluate(&env27, &[]));
2204 assert!(!marker.evaluate(&env37, &[]));
2205
2206 let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap();
2207 assert!(marker.evaluate(&env27, &[]));
2208 assert!(!marker.evaluate(&env37, &[]));
2209
2210 let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap();
2211 assert!(marker.evaluate(&env27, &[]));
2212 assert!(marker.evaluate(&env37, &[]));
2213
2214 let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap();
2215 assert!(!marker.evaluate(&env27, &[]));
2216 assert!(!marker.evaluate(&env37, &[]));
2217
2218 let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap();
2219 assert!(marker.evaluate(&env27, &[]));
2220 assert!(marker.evaluate(&env37, &[]));
2221 }
2222
2223 #[test]
2224 #[cfg(feature = "tracing")]
2225 #[tracing_test::traced_test]
2226 fn warnings1() {
2227 let env37 = env37();
2228 let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap();
2229 compare_keys.evaluate(&env37, &[]);
2230 logs_contain(
2231 "Comparing two markers with each other doesn't make any sense, will evaluate to false",
2232 );
2233 }
2234
2235 #[test]
2236 #[cfg(feature = "tracing")]
2237 #[tracing_test::traced_test]
2238 fn warnings2() {
2239 let env37 = env37();
2240 let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap();
2241 non_pep440.evaluate(&env37, &[]);
2242 logs_contain(
2243 "Expected PEP 440 version to compare with python_version, found `3.9.`, \
2244 will evaluate to false: after parsing `3.9`, found `.`, which is \
2245 not part of a valid version",
2246 );
2247 }
2248
2249 #[test]
2250 #[cfg(feature = "tracing")]
2251 #[tracing_test::traced_test]
2252 fn warnings3() {
2253 let env37 = env37();
2254 let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap();
2255 string_string.evaluate(&env37, &[]);
2256 logs_contain(
2257 "Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false",
2258 );
2259 }
2260
2261 #[test]
2262 #[cfg(feature = "tracing")]
2263 #[tracing_test::traced_test]
2264 fn warnings4() {
2265 let env37 = env37();
2266 let string_string = MarkerTree::from_str(r"os.name == 'posix' and platform.machine == 'x86_64' and platform.python_implementation == 'CPython' and 'Ubuntu' in platform.version and sys.platform == 'linux'").unwrap();
2267 string_string.evaluate(&env37, &[]);
2268 logs_assert(|lines: &[&str]| {
2269 let lines: Vec<_> = lines
2270 .iter()
2271 .map(|s| s.split_once(" ").unwrap().1)
2272 .collect();
2273 let expected = [
2274 "WARN warnings4: uv_pep508: os.name is deprecated in favor of os_name",
2275 "WARN warnings4: uv_pep508: platform.machine is deprecated in favor of platform_machine",
2276 "WARN warnings4: uv_pep508: platform.python_implementation is deprecated in favor of platform_python_implementation",
2277 "WARN warnings4: uv_pep508: platform.version is deprecated in favor of platform_version",
2278 "WARN warnings4: uv_pep508: sys.platform is deprecated in favor of sys_platform",
2279 "WARN warnings4: uv_pep508: Comparing linux and posix lexicographically",
2280 ];
2281 if lines == expected {
2282 Ok(())
2283 } else {
2284 Err(format!("{lines:#?}"))
2285 }
2286 });
2287 }
2288
2289 #[test]
2290 fn test_not_in() {
2291 MarkerTree::from_str("'posix' not in os_name").unwrap();
2292 }
2293
2294 #[test]
2295 fn test_marker_version_inverted() {
2296 let env37 = env37();
2297 let result = MarkerTree::from_str("python_version > '3.6'")
2298 .unwrap()
2299 .evaluate(&env37, &[]);
2300 assert!(result);
2301
2302 let result = MarkerTree::from_str("'3.6' > python_version")
2303 .unwrap()
2304 .evaluate(&env37, &[]);
2305 assert!(!result);
2306
2307 let result = MarkerTree::from_str("'3.*' == python_version")
2309 .unwrap()
2310 .evaluate(&env37, &[]);
2311 assert!(result);
2312 }
2313
2314 #[test]
2315 fn test_marker_string_inverted() {
2316 let env37 = env37();
2317 let result = MarkerTree::from_str("'nux' in sys_platform")
2318 .unwrap()
2319 .evaluate(&env37, &[]);
2320 assert!(result);
2321
2322 let result = MarkerTree::from_str("sys_platform in 'nux'")
2323 .unwrap()
2324 .evaluate(&env37, &[]);
2325 assert!(!result);
2326 }
2327
2328 #[test]
2329 fn test_marker_version_star() {
2330 let env37 = env37();
2331 let result = MarkerTree::from_str("python_version == '3.7.*'")
2332 .unwrap()
2333 .evaluate(&env37, &[]);
2334 assert!(result);
2335 }
2336
2337 #[test]
2338 fn test_tilde_equal() {
2339 let env37 = env37();
2340 let result = MarkerTree::from_str("python_version ~= '3.7'")
2341 .unwrap()
2342 .evaluate(&env37, &[]);
2343 assert!(result);
2344 }
2345
2346 #[test]
2347 fn test_closing_parentheses() {
2348 MarkerTree::from_str(r#"( "linux" in sys_platform) and extra == 'all'"#).unwrap();
2349 }
2350
2351 #[test]
2352 fn wrong_quotes_dot_star() {
2353 assert_snapshot!(
2354 parse_err(r#"python_version == "3.8".* and python_version >= "3.8""#),
2355 @r#"
2356 Unexpected character '.', expected 'and', 'or' or end of input
2357 python_version == "3.8".* and python_version >= "3.8"
2358 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2359 "#
2360 );
2361 assert_snapshot!(
2362 parse_err(r#"python_version == "3.8".*"#),
2363 @r#"
2364 Unexpected character '.', expected 'and', 'or' or end of input
2365 python_version == "3.8".*
2366 ^
2367 "#
2368 );
2369 }
2370
2371 #[test]
2372 fn test_marker_expression() {
2373 assert_eq!(
2374 MarkerExpression::from_str(r#"os_name == "nt""#)
2375 .unwrap()
2376 .unwrap(),
2377 MarkerExpression::String {
2378 key: MarkerValueString::OsName,
2379 operator: MarkerOperator::Equal,
2380 value: arcstr::literal!("nt")
2381 }
2382 );
2383 }
2384
2385 #[test]
2386 fn test_marker_expression_inverted() {
2387 assert_eq!(
2388 MarkerTree::from_str(
2389 r#""nt" in os_name and '3.7' >= python_version and python_full_version >= '3.7'"#
2390 )
2391 .unwrap()
2392 .contents()
2393 .unwrap()
2394 .to_string(),
2395 "python_full_version == '3.7.*' and 'nt' in os_name",
2396 );
2397 }
2398
2399 #[test]
2400 fn test_marker_expression_to_long() {
2401 let err = MarkerExpression::from_str(r#"os_name == "nt" and python_version >= "3.8""#)
2402 .unwrap_err()
2403 .to_string();
2404 assert_snapshot!(
2405 err,
2406 @r#"
2407 Unexpected character 'a', expected end of input
2408 os_name == "nt" and python_version >= "3.8"
2409 ^^^^^^^^^^^^^^^^^^^^^^^^^^
2410 "#
2411 );
2412 }
2413
2414 #[test]
2415 fn test_marker_environment_from_json() {
2416 let _env: MarkerEnvironment = serde_json::from_str(
2417 r##"{
2418 "implementation_name": "cpython",
2419 "implementation_version": "3.7.13",
2420 "os_name": "posix",
2421 "platform_machine": "x86_64",
2422 "platform_python_implementation": "CPython",
2423 "platform_release": "5.4.188+",
2424 "platform_system": "Linux",
2425 "platform_version": "#1 SMP Sun Apr 24 10:03:06 PDT 2022",
2426 "python_full_version": "3.7.13",
2427 "python_version": "3.7",
2428 "sys_platform": "linux"
2429 }"##,
2430 )
2431 .unwrap();
2432 }
2433
2434 #[test]
2435 fn test_simplify_extras() {
2436 let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "dev""#).unwrap();
2438 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2439 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2440 assert_eq!(simplified, expected);
2441
2442 let markers = MarkerTree::from_str(r#"os_name == "nt" or extra == "dev""#).unwrap();
2444 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2445 assert_eq!(simplified, MarkerTree::TRUE);
2446
2447 let markers = MarkerTree::from_str(r#"extra == "dev""#).unwrap();
2449 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2450 assert_eq!(simplified, MarkerTree::TRUE);
2451
2452 let markers = MarkerTree::from_str(r#"extra == "dev" and extra == "test""#).unwrap();
2454 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2455 let expected = MarkerTree::from_str(r#"extra == "test""#).unwrap();
2456 assert_eq!(simplified, expected);
2457
2458 let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "test""#).unwrap();
2460 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2461 assert_eq!(simplified, markers);
2462
2463 let markers = MarkerTree::from_str(
2466 r#"os_name == "nt" and (python_version == "3.7" or extra == "dev")"#,
2467 )
2468 .unwrap();
2469 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2470 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2471 assert_eq!(simplified, expected);
2472
2473 let markers = MarkerTree::from_str(
2476 r#"os_name == "nt" or (python_version == "3.7" and extra == "dev")"#,
2477 )
2478 .unwrap();
2479 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2480 let expected =
2481 MarkerTree::from_str(r#"os_name == "nt" or python_version == "3.7""#).unwrap();
2482 assert_eq!(simplified, expected);
2483 }
2484
2485 #[test]
2486 fn test_simplify_not_extras() {
2487 let markers = MarkerTree::from_str(r#"os_name == "nt" and extra != "dev""#).unwrap();
2489 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2490 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2491 assert_eq!(simplified, expected);
2492
2493 let markers = MarkerTree::from_str(r#"os_name == "nt" or extra != "dev""#).unwrap();
2495 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2496 assert_eq!(simplified, MarkerTree::TRUE);
2497
2498 let markers = MarkerTree::from_str(r#"extra != "dev""#).unwrap();
2500 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2501 assert_eq!(simplified, MarkerTree::TRUE);
2502
2503 let markers = MarkerTree::from_str(r#"extra != "dev" and extra != "test""#).unwrap();
2505 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2506 let expected = MarkerTree::from_str(r#"extra != "test""#).unwrap();
2507 assert_eq!(simplified, expected);
2508
2509 let markers = MarkerTree::from_str(r#"os_name != "nt" and extra != "test""#).unwrap();
2511 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2512 assert_eq!(simplified, markers);
2513
2514 let markers = MarkerTree::from_str(
2517 r#"os_name == "nt" and (python_version == "3.7" or extra != "dev")"#,
2518 )
2519 .unwrap();
2520 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2521 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2522 assert_eq!(simplified, expected);
2523
2524 let markers = MarkerTree::from_str(
2527 r#"os_name == "nt" or (python_version == "3.7" and extra != "dev")"#,
2528 )
2529 .unwrap();
2530 let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2531 let expected =
2532 MarkerTree::from_str(r#"os_name == "nt" or python_version == "3.7""#).unwrap();
2533 assert_eq!(simplified, expected);
2534 }
2535
2536 #[test]
2537 fn test_marker_simplification() {
2538 assert_false("python_version == '3.9.1'");
2539 assert_true("python_version != '3.9.1'");
2540
2541 assert_simplifies(
2543 "python_version in '3.9.0'",
2544 "python_full_version == '3.9.*'",
2545 );
2546 assert_true("python_version in 'foo'");
2548 assert_true("python_version in '3.9.*'");
2550 assert_true("python_version in '3.9, 3.10'");
2552 assert_true("python_version in '3.9,3.10'");
2553 assert_true("python_version in '3.9 or 3.10'");
2554
2555 assert_simplifies(
2557 "python_version in '3.9 3.10.0 3.11'",
2558 "python_full_version >= '3.9' and python_full_version < '3.12'",
2559 );
2560
2561 assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'");
2562 assert_simplifies(
2563 "python_version == '3.9.0'",
2564 "python_full_version == '3.9.*'",
2565 );
2566 assert_simplifies(
2567 "python_version == '3.9.0.*'",
2568 "python_full_version == '3.9.*'",
2569 );
2570 assert_simplifies(
2571 "python_version == '3.*'",
2572 "python_full_version >= '3' and python_full_version < '4'",
2573 );
2574
2575 assert_simplifies(
2578 "python_version in '3.9 3.11'",
2579 "python_full_version == '3.9.*' or python_full_version == '3.11.*'",
2580 );
2581 assert_simplifies(
2583 "python_version in '3.9 3.10 3.11'",
2584 "python_full_version >= '3.9' and python_full_version < '3.12'",
2585 );
2586 assert_simplifies(
2588 "implementation_version in '3.9 3.11'",
2589 "implementation_version == '3.9' or implementation_version == '3.11'",
2590 );
2591
2592 assert_simplifies(
2595 "python_version not in '3.9 3.11'",
2596 "python_full_version < '3.9' or python_full_version == '3.10.*' or python_full_version >= '3.12'",
2597 );
2598 assert_simplifies(
2600 "python_version not in '3.9 3.10 3.11'",
2601 "python_full_version < '3.9' or python_full_version >= '3.12'",
2602 );
2603 assert_simplifies(
2605 "implementation_version not in '3.9 3.11'",
2606 "implementation_version != '3.9' and implementation_version != '3.11'",
2607 );
2608
2609 assert_simplifies("python_version != '3.9'", "python_full_version != '3.9.*'");
2610
2611 assert_simplifies("python_version >= '3.9.0'", "python_full_version >= '3.9'");
2612 assert_simplifies("python_version <= '3.9.0'", "python_full_version < '3.10'");
2613
2614 assert_simplifies(
2615 "python_version == '3.*'",
2616 "python_full_version >= '3' and python_full_version < '4'",
2617 );
2618 assert_simplifies(
2619 "python_version == '3.0.*'",
2620 "python_full_version == '3.0.*'",
2621 );
2622
2623 assert_simplifies(
2624 "python_version < '3.17' or python_version < '3.18'",
2625 "python_full_version < '3.18'",
2626 );
2627
2628 assert_simplifies(
2629 "python_version > '3.17' or python_version > '3.18' or python_version > '3.12'",
2630 "python_full_version >= '3.13'",
2631 );
2632
2633 assert_simplifies(
2635 "python_version > '3.17.post4' or python_version > '3.18.post4'",
2636 "python_full_version >= '3.18'",
2637 );
2638
2639 assert_simplifies(
2640 "python_version < '3.17' and python_version < '3.18'",
2641 "python_full_version < '3.17'",
2642 );
2643
2644 assert_simplifies(
2645 "python_version <= '3.18' and python_version == '3.18'",
2646 "python_full_version == '3.18.*'",
2647 );
2648
2649 assert_simplifies(
2650 "python_version <= '3.18' or python_version == '3.18'",
2651 "python_full_version < '3.19'",
2652 );
2653
2654 assert_simplifies(
2655 "python_version <= '3.15' or (python_version <= '3.17' and python_version < '3.16')",
2656 "python_full_version < '3.16'",
2657 );
2658
2659 assert_simplifies(
2660 "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15'",
2661 "python_full_version >= '3.17'",
2662 );
2663
2664 assert_simplifies(
2665 "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15' and implementation_version == '1'",
2666 "implementation_version == '1' and python_full_version >= '3.17'",
2667 );
2668
2669 assert_simplifies(
2670 "('3.17' < python_version or '3.16' < python_version) and '3.15' < python_version and implementation_version == '1'",
2671 "implementation_version == '1' and python_full_version >= '3.17'",
2672 );
2673
2674 assert_simplifies("extra == 'a' or extra == 'a'", "extra == 'a'");
2675 assert_simplifies(
2676 "extra == 'a' and extra == 'a' or extra == 'b'",
2677 "extra == 'a' or extra == 'b'",
2678 );
2679
2680 assert!(m("python_version < '3.17' and '3.18' == python_version").is_false());
2681
2682 assert_simplifies(
2684 "((extra == 'a' and extra == 'b') and extra == 'c') and extra == 'b'",
2685 "extra == 'a' and extra == 'b' and extra == 'c'",
2686 );
2687
2688 assert_simplifies(
2689 "((extra == 'a' or extra == 'b') or extra == 'c') or extra == 'b'",
2690 "extra == 'a' or extra == 'b' or extra == 'c'",
2691 );
2692
2693 assert_simplifies(
2695 "extra == 'a' or (extra == 'a' and extra == 'b')",
2696 "extra == 'a'",
2697 );
2698
2699 assert_simplifies(
2700 "extra == 'a' and (extra == 'a' or extra == 'b')",
2701 "extra == 'a'",
2702 );
2703
2704 assert_simplifies(
2705 "(extra == 'a' and (extra == 'a' or extra == 'b')) or extra == 'd'",
2706 "extra == 'a' or extra == 'd'",
2707 );
2708
2709 assert_simplifies(
2710 "((extra == 'a' and extra == 'b') or extra == 'c') or extra == 'b'",
2711 "extra == 'b' or extra == 'c'",
2712 );
2713
2714 assert_simplifies(
2715 "((extra == 'a' or extra == 'b') and extra == 'c') and extra == 'b'",
2716 "extra == 'b' and extra == 'c'",
2717 );
2718
2719 assert_simplifies(
2720 "((extra == 'a' or extra == 'b') and extra == 'c') or extra == 'b'",
2721 "extra == 'b' or (extra == 'a' and extra == 'c')",
2722 );
2723
2724 assert_simplifies(
2726 "(python_version < '3.1' or python_version < '3.2') and (python_version < '3.2' or python_version == '3.3')",
2727 "python_full_version < '3.2'",
2728 );
2729
2730 assert_true("python_version < '3.12.0rc1' or python_version >= '3.12.0rc1'");
2732
2733 assert_true(
2734 "extra == 'a' or (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2735 );
2736
2737 assert_simplifies(
2738 "extra == 'a' and (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2739 "extra == 'a'",
2740 );
2741
2742 assert_true("python_version != '3.10' or python_version < '3.12'");
2744
2745 assert_simplifies(
2746 "python_version != '3.10' or python_version > '3.12'",
2747 "python_full_version != '3.10.*'",
2748 );
2749
2750 assert_simplifies(
2751 "python_version != '3.8' and python_version < '3.10'",
2752 "python_full_version < '3.8' or python_full_version == '3.9.*'",
2753 );
2754
2755 assert_simplifies(
2756 "python_version != '3.8' and python_version != '3.9'",
2757 "python_full_version < '3.8' or python_full_version >= '3.10'",
2758 );
2759
2760 assert_true("sys_platform == 'win32' or sys_platform != 'win32'");
2762
2763 assert_true("'win32' == sys_platform or sys_platform != 'win32'");
2764
2765 assert_true(
2766 "sys_platform == 'win32' or sys_platform == 'win32' or sys_platform != 'win32'",
2767 );
2768
2769 assert!(m("sys_platform == 'win32' and sys_platform != 'win32'").is_false());
2770 }
2771
2772 #[test]
2782 fn test_simplification_extra_versus_other() {
2783 assert_simplifies(
2786 r#"extra != "foo" and (extra == "bar" or extra == "baz")"#,
2787 "(extra == 'bar' and extra != 'foo') or (extra == 'baz' and extra != 'foo')",
2788 );
2789 assert_simplifies(
2793 r#"sys_platform != "foo" and (sys_platform == "bar" or sys_platform == "baz")"#,
2794 "sys_platform == 'bar' or sys_platform == 'baz'",
2795 );
2796
2797 assert_simplifies(
2799 r#"(extra != "bar" and (extra == "foo" or extra == "baz"))
2800 or ((extra != "foo" and extra != "bar") and extra == "baz")"#,
2801 "(extra != 'bar' and extra == 'baz') or (extra != 'bar' and extra == 'foo')",
2802 );
2803 }
2804
2805 #[test]
2806 fn test_python_version_equal_star() {
2807 let cases = [
2809 ("3.*", "3.*", "3.*"),
2810 ("3.0.*", "3.0", "3.0.*"),
2811 ("3.0.0.*", "3.0", "3.0.*"),
2812 ("3.9.*", "3.9", "3.9.*"),
2813 ("3.9.0.*", "3.9", "3.9.*"),
2814 ("3.9.0.0.*", "3.9", "3.9.*"),
2815 ];
2816 for (input, equal_python_version, equal_python_full_version) in cases {
2817 assert_eq!(
2818 m(&format!("python_version == '{input}'")),
2819 m(&format!("python_version == '{equal_python_version}'")),
2820 "{input} {equal_python_version}"
2821 );
2822 assert_eq!(
2823 m(&format!("python_version == '{input}'")),
2824 m(&format!(
2825 "python_full_version == '{equal_python_full_version}'"
2826 )),
2827 "{input} {equal_python_full_version}"
2828 );
2829 }
2830
2831 let cases_false = ["3.9.1.*", "3.9.1.0.*", "3.9.1.0.0.*"];
2832 for input in cases_false {
2833 assert!(
2834 m(&format!("python_version == '{input}'")).is_false(),
2835 "{input}"
2836 );
2837 }
2838 }
2839
2840 #[test]
2841 fn test_tilde_equal_normalization() {
2842 assert_eq!(
2843 m("python_version ~= '3.10.0'"),
2844 m("python_version >= '3.10.0' and python_version < '3.11.0'")
2845 );
2846
2847 assert_eq!(m("python_version ~= '3.10.1'"), MarkerTree::FALSE);
2850
2851 assert_eq!(
2852 m("python_version ~= '3.10'"),
2853 m("python_version >= '3.10' and python_version < '4.0'")
2854 );
2855
2856 assert_eq!(
2857 m("python_full_version ~= '3.10.0'"),
2858 m("python_full_version >= '3.10.0' and python_full_version < '3.11.0'")
2859 );
2860
2861 assert_eq!(
2862 m("python_full_version ~= '3.10'"),
2863 m("python_full_version >= '3.10' and python_full_version < '4.0'")
2864 );
2865 }
2866
2867 #[test]
2878 fn test_extra_implication() {
2879 assert!(implies(
2880 "extra != 'foo' or extra != 'bar'",
2881 "extra != 'foo' or extra != 'bar'",
2882 ));
2883 assert!(!implies(
2884 "extra != 'foo' or extra != 'bar'",
2885 "extra != 'foo'",
2886 ));
2887 assert!(!implies(
2888 "extra != 'foo' or extra != 'bar'",
2889 "extra != 'bar' and extra == 'foo'",
2890 ));
2891
2892 assert!(implies(
2895 "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2896 "extra != 'foo' or extra != 'bar'",
2897 ));
2898 assert!(!implies(
2899 "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2900 "extra != 'foo'",
2901 ));
2902 assert!(!implies(
2903 "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2904 "extra != 'bar' and extra == 'foo'",
2905 ));
2906 }
2907
2908 #[test]
2909 fn test_marker_negation() {
2910 assert_eq!(
2911 m("python_version > '3.6'").negate(),
2912 m("python_version <= '3.6'")
2913 );
2914
2915 assert_eq!(
2916 m("'3.6' < python_version").negate(),
2917 m("python_version <= '3.6'")
2918 );
2919
2920 assert_eq!(
2921 m("python_version != '3.6' and os_name == 'Linux'").negate(),
2922 m("python_version == '3.6' or os_name != 'Linux'")
2923 );
2924
2925 assert_eq!(
2926 m("python_version == '3.6' and os_name != 'Linux'").negate(),
2927 m("python_version != '3.6' or os_name == 'Linux'")
2928 );
2929
2930 assert_eq!(
2931 m("python_version != '3.6.*' and os_name == 'Linux'").negate(),
2932 m("python_version == '3.6.*' or os_name != 'Linux'")
2933 );
2934
2935 assert_eq!(
2936 m("python_version == '3.6.*'").negate(),
2937 m("python_version != '3.6.*'")
2938 );
2939 assert_eq!(
2940 m("python_version != '3.6.*'").negate(),
2941 m("python_version == '3.6.*'")
2942 );
2943
2944 assert_eq!(
2945 m("python_version ~= '3.6'").negate(),
2946 m("python_version < '3.6' or python_version != '3.*'")
2947 );
2948 assert_eq!(
2949 m("'3.6' ~= python_version").negate(),
2950 m("python_version < '3.6' or python_version != '3.*'")
2951 );
2952 assert_eq!(
2953 m("python_version ~= '3.6.2'").negate(),
2954 m("python_version < '3.6.2' or python_version != '3.6.*'")
2955 );
2956
2957 assert_eq!(
2958 m("sys_platform == 'linux'").negate(),
2959 m("sys_platform != 'linux'")
2960 );
2961 assert_eq!(
2962 m("'linux' == sys_platform").negate(),
2963 m("sys_platform != 'linux'")
2964 );
2965
2966 assert_eq!(m("sys_platform ~= 'linux'").negate(), MarkerTree::FALSE);
2969
2970 assert_eq!(m("'foo' == 'bar'").negate(), MarkerTree::FALSE);
2972
2973 assert_eq!(
2975 m("os_name == 'bar' and os_name == 'foo'").negate(),
2976 m("os_name != 'bar' or os_name != 'foo'")
2977 );
2978 assert_eq!(
2980 m("os_name == 'bar' or os_name == 'foo'").negate(),
2981 m("os_name != 'bar' and os_name != 'foo'")
2982 );
2983
2984 assert_eq!(
2986 m("python_version >= '3.6' or python_version < '3.6'").negate(),
2987 m("python_version < '3.6' and python_version >= '3.6'")
2988 );
2989 }
2990
2991 #[test]
2992 fn test_complex_marker_simplification() {
2993 assert_simplifies(
3001 "(implementation_name == 'pypy' and sys_platform != 'win32')
3002 or (implementation_name != 'pypy' and sys_platform == 'win32')
3003 or (sys_platform == 'win32' and os_name != 'nt')
3004 or (sys_platform != 'win32' and os_name == 'nt')",
3005 "(implementation_name == 'pypy' and sys_platform != 'win32') \
3006 or (implementation_name != 'pypy' and sys_platform == 'win32') \
3007 or (os_name != 'nt' and sys_platform == 'win32') \
3008 or (os_name == 'nt' and sys_platform != 'win32')",
3009 );
3010
3011 assert_simplifies(
3014 "(os_name == 'Linux' and platform_system == 'win32')
3015 or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'a')
3016 or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'x')
3017 or (os_name != 'Linux' and platform_system == 'win32' and sys_platform == 'x')
3018 or (os_name == 'Linux' and platform_system != 'win32' and sys_platform == 'x')
3019 or (os_name != 'Linux' and platform_system != 'win32' and sys_platform == 'x')",
3020 "(os_name == 'Linux' and platform_system == 'win32') or sys_platform == 'x'",
3021 );
3022
3023 assert_simplifies("python_version > '3.7'", "python_full_version >= '3.8'");
3024
3025 assert_simplifies(
3026 "(python_version <= '3.7' and os_name == 'Linux') or python_version > '3.7'",
3027 "python_full_version >= '3.8' or os_name == 'Linux'",
3028 );
3029
3030 assert_simplifies(
3033 "(os_name == 'Linux' and sys_platform == 'win32') \
3034 or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.7') \
3035 or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.8')",
3036 "(python_full_version >= '3.7' and python_full_version < '3.9' and sys_platform == 'win32') or (os_name == 'Linux' and sys_platform == 'win32')",
3037 );
3038
3039 assert_simplifies(
3040 "(implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
3041 "os_name == 'nt' and sys_platform == 'win32'",
3042 );
3043
3044 assert_simplifies(
3045 "(sys_platform == 'darwin' or sys_platform == 'win32') and ((implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
3046 "os_name == 'nt' and sys_platform == 'win32'",
3047 );
3048
3049 assert_simplifies(
3050 "(sys_platform == 'darwin' or sys_platform == 'win32')
3051 and ((platform_version != '1' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
3052 "os_name == 'nt' and sys_platform == 'win32'",
3053 );
3054
3055 assert_simplifies(
3056 "(os_name == 'nt' and sys_platform == 'win32') \
3057 or (os_name != 'nt' and platform_version == '1' and (sys_platform == 'win32' or sys_platform == 'win64'))",
3058 "(os_name != 'nt' and platform_version == '1' and sys_platform == 'win64') \
3059 or (os_name == 'nt' and sys_platform == 'win32') \
3060 or (platform_version == '1' and sys_platform == 'win32')",
3061 );
3062
3063 assert_simplifies(
3064 "(os_name == 'nt' and sys_platform == 'win32') or (os_name != 'nt' and (sys_platform == 'win32' or sys_platform == 'win64'))",
3065 "(os_name != 'nt' and sys_platform == 'win64') or sys_platform == 'win32'",
3066 );
3067 }
3068
3069 #[test]
3070 fn test_requires_python() {
3071 fn simplified(marker: &str) -> MarkerTree {
3072 let lower = Bound::Included(Version::new([3, 8]));
3073 let upper = Bound::Unbounded;
3074 m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
3075 }
3076
3077 assert_eq!(simplified("python_version >= '3.8'"), MarkerTree::TRUE);
3078 assert_eq!(
3079 simplified("python_version >= '3.8' or sys_platform == 'win32'"),
3080 MarkerTree::TRUE
3081 );
3082
3083 assert_eq!(
3084 simplified("python_version >= '3.8' and sys_platform == 'win32'"),
3085 m("sys_platform == 'win32'"),
3086 );
3087
3088 assert_eq!(
3089 simplified("python_version == '3.8'")
3090 .try_to_string()
3091 .unwrap(),
3092 "python_full_version < '3.9'"
3093 );
3094
3095 assert_eq!(
3096 simplified("python_version <= '3.10'")
3097 .try_to_string()
3098 .unwrap(),
3099 "python_full_version < '3.11'"
3100 );
3101 }
3102
3103 #[test]
3104 fn test_extra_disjointness() {
3105 assert!(!is_disjoint("extra == 'a'", "python_version == '1'"));
3106
3107 assert!(!is_disjoint("extra == 'a'", "extra == 'a'"));
3108 assert!(!is_disjoint("extra == 'a'", "extra == 'b'"));
3109 assert!(!is_disjoint("extra == 'b'", "extra == 'a'"));
3110 assert!(!is_disjoint("extra == 'b'", "extra != 'a'"));
3111 assert!(!is_disjoint("extra != 'b'", "extra == 'a'"));
3112 assert!(is_disjoint("extra != 'b'", "extra == 'b'"));
3113 assert!(is_disjoint("extra == 'b'", "extra != 'b'"));
3114 }
3115
3116 #[test]
3117 fn test_arbitrary_disjointness() {
3118 assert!(!is_disjoint(
3121 "python_version == 'Linux'",
3122 "python_full_version == '3.7.1'"
3123 ));
3124 }
3125
3126 #[test]
3127 fn test_version_disjointness() {
3128 assert!(!is_disjoint(
3129 "os_name == 'Linux'",
3130 "python_full_version == '3.7.1'"
3131 ));
3132
3133 test_version_bounds_disjointness("python_full_version");
3134
3135 assert!(!is_disjoint(
3136 "python_full_version == '3.7.*'",
3137 "python_full_version == '3.7.1'"
3138 ));
3139
3140 assert!(is_disjoint(
3141 "python_version == '3.7'",
3142 "python_full_version == '3.8'"
3143 ));
3144
3145 assert!(!is_disjoint(
3146 "python_version == '3.7'",
3147 "python_full_version == '3.7.2'"
3148 ));
3149
3150 assert!(is_disjoint(
3151 "python_version > '3.7'",
3152 "python_full_version == '3.7.1'"
3153 ));
3154
3155 assert!(!is_disjoint(
3156 "python_version <= '3.7'",
3157 "python_full_version == '3.7.1'"
3158 ));
3159 }
3160
3161 #[test]
3162 fn test_string_disjointness() {
3163 assert!(!is_disjoint(
3164 "os_name == 'Linux'",
3165 "platform_version == '3.7.1'"
3166 ));
3167 assert!(!is_disjoint(
3168 "implementation_version == '3.7.0'",
3169 "python_full_version == '3.7.1'"
3170 ));
3171
3172 test_version_bounds_disjointness("platform_version");
3174
3175 assert!(is_disjoint("os_name == 'Linux'", "os_name == 'OSX'"));
3176 assert!(is_disjoint("os_name <= 'Linux'", "os_name == 'OSX'"));
3177
3178 assert!(!is_disjoint(
3179 "os_name in 'OSXLinuxWindows'",
3180 "os_name == 'OSX'"
3181 ));
3182 assert!(!is_disjoint("'OSX' in os_name", "'Linux' in os_name"));
3183
3184 assert!(!is_disjoint("os_name in 'OSX'", "os_name in 'Linux'"));
3186 assert!(!is_disjoint(
3187 "os_name in 'OSXLinux'",
3188 "os_name == 'Windows'"
3189 ));
3190
3191 assert!(is_disjoint(
3192 "os_name in 'Windows'",
3193 "os_name not in 'Windows'"
3194 ));
3195 assert!(is_disjoint(
3196 "'Windows' in os_name",
3197 "'Windows' not in os_name"
3198 ));
3199
3200 assert!(!is_disjoint("'Windows' in os_name", "'Windows' in os_name"));
3201 assert!(!is_disjoint("'Linux' in os_name", "os_name not in 'Linux'"));
3202 assert!(!is_disjoint("'Linux' not in os_name", "os_name in 'Linux'"));
3203
3204 assert!(!is_disjoint(
3205 "os_name == 'Linux' and os_name != 'OSX'",
3206 "os_name == 'Linux'"
3207 ));
3208 assert!(is_disjoint(
3209 "os_name == 'Linux' and os_name != 'OSX'",
3210 "os_name == 'OSX'"
3211 ));
3212
3213 assert!(!is_disjoint(
3214 "extra == 'Linux' and extra != 'OSX'",
3215 "extra == 'Linux'"
3216 ));
3217 assert!(is_disjoint(
3218 "extra == 'Linux' and extra != 'OSX'",
3219 "extra == 'OSX'"
3220 ));
3221
3222 assert!(!is_disjoint(
3223 "extra == 'x1' and extra != 'x2'",
3224 "extra == 'x1'"
3225 ));
3226 assert!(is_disjoint(
3227 "extra == 'x1' and extra != 'x2'",
3228 "extra == 'x2'"
3229 ));
3230 }
3231
3232 #[test]
3233 fn is_disjoint_commutative() {
3234 let m1 = m("extra == 'Linux' and extra != 'OSX'");
3235 let m2 = m("extra == 'Linux'");
3236 assert!(!m2.is_disjoint(m1));
3237 assert!(!m1.is_disjoint(m2));
3238 }
3239
3240 #[test]
3241 fn test_combined_disjointness() {
3242 assert!(!is_disjoint(
3243 "os_name == 'a' and platform_version == '1'",
3244 "os_name == 'a'"
3245 ));
3246 assert!(!is_disjoint(
3247 "os_name == 'a' or platform_version == '1'",
3248 "os_name == 'a'"
3249 ));
3250
3251 assert!(is_disjoint(
3252 "os_name == 'a' and platform_version == '1'",
3253 "os_name == 'a' and platform_version == '2'"
3254 ));
3255 assert!(is_disjoint(
3256 "os_name == 'a' and platform_version == '1'",
3257 "'2' == platform_version and os_name == 'a'"
3258 ));
3259 assert!(!is_disjoint(
3260 "os_name == 'a' or platform_version == '1'",
3261 "os_name == 'a' or platform_version == '2'"
3262 ));
3263
3264 assert!(is_disjoint(
3265 "sys_platform == 'darwin' and implementation_name == 'pypy'",
3266 "sys_platform == 'bar' or implementation_name == 'foo'",
3267 ));
3268 assert!(is_disjoint(
3269 "sys_platform == 'bar' or implementation_name == 'foo'",
3270 "sys_platform == 'darwin' and implementation_name == 'pypy'",
3271 ));
3272
3273 assert!(is_disjoint(
3274 "python_version >= '3.7' and implementation_name == 'pypy'",
3275 "python_version < '3.7'"
3276 ));
3277 assert!(is_disjoint(
3278 "implementation_name == 'pypy' and python_version >= '3.7'",
3279 "implementation_name != 'pypy'"
3280 ));
3281 assert!(is_disjoint(
3282 "implementation_name != 'pypy' and python_version >= '3.7'",
3283 "implementation_name == 'pypy'"
3284 ));
3285 }
3286
3287 #[test]
3288 fn test_arbitrary() {
3289 assert!(m("'wat' == 'wat'").is_true());
3290 assert!(m("os_name ~= 'wat'").is_true());
3291 assert!(m("python_version == 'Linux'").is_true());
3292 assert!(m("os_name ~= 'wat' or 'wat' == 'wat' and python_version == 'Linux'").is_true());
3293 }
3294
3295 #[test]
3296 fn test_is_false() {
3297 assert!(m("python_version < '3.10' and python_version >= '3.10'").is_false());
3298 assert!(
3299 m("(python_version < '3.10' and python_version >= '3.10') \
3300 or (python_version < '3.9' and python_version >= '3.9')")
3301 .is_false()
3302 );
3303
3304 assert!(!m("python_version < '3.10'").is_false());
3305 assert!(!m("python_version < '0'").is_false());
3306 assert!(!m("python_version < '3.10' and python_version >= '3.9'").is_false());
3307 assert!(!m("python_version < '3.10' or python_version >= '3.11'").is_false());
3308 }
3309
3310 fn test_version_bounds_disjointness(version: &str) {
3311 assert!(!is_disjoint(
3312 format!("{version} > '2.7.0'"),
3313 format!("{version} == '3.6.0'")
3314 ));
3315 assert!(!is_disjoint(
3316 format!("{version} >= '3.7.0'"),
3317 format!("{version} == '3.7.1'")
3318 ));
3319 assert!(!is_disjoint(
3320 format!("{version} >= '3.7.0'"),
3321 format!("'3.7.1' == {version}")
3322 ));
3323
3324 assert!(is_disjoint(
3325 format!("{version} >= '3.7.1'"),
3326 format!("{version} == '3.7.0'")
3327 ));
3328 assert!(is_disjoint(
3329 format!("'3.7.1' <= {version}"),
3330 format!("{version} == '3.7.0'")
3331 ));
3332
3333 assert!(is_disjoint(
3334 format!("{version} < '3.7.0'"),
3335 format!("{version} == '3.7.0'")
3336 ));
3337 assert!(is_disjoint(
3338 format!("'3.7.0' > {version}"),
3339 format!("{version} == '3.7.0'")
3340 ));
3341 assert!(is_disjoint(
3342 format!("{version} < '3.7.0'"),
3343 format!("{version} == '3.7.1'")
3344 ));
3345
3346 assert!(is_disjoint(
3347 format!("{version} == '3.7.0'"),
3348 format!("{version} == '3.7.1'")
3349 ));
3350 assert!(is_disjoint(
3351 format!("{version} == '3.7.0'"),
3352 format!("{version} != '3.7.0'")
3353 ));
3354 }
3355
3356 fn assert_simplifies(left: &str, right: &str) {
3357 assert_eq!(m(left), m(right), "{left} != {right}");
3358 assert_eq!(m(left).try_to_string().unwrap(), right, "{left} != {right}");
3359 }
3360
3361 fn assert_true(marker: &str) {
3362 assert!(m(marker).is_true(), "{marker} != true");
3363 }
3364
3365 fn assert_false(marker: &str) {
3366 assert!(m(marker).is_false(), "{marker} != false");
3367 }
3368
3369 fn is_disjoint(left: impl AsRef<str>, right: impl AsRef<str>) -> bool {
3370 let (left, right) = (m(left.as_ref()), m(right.as_ref()));
3371 left.is_disjoint(right) && right.is_disjoint(left)
3372 }
3373
3374 fn implies(antecedent: &str, consequent: &str) -> bool {
3375 let mut marker = m(antecedent);
3376 marker.implies(m(consequent));
3377 marker.is_true()
3378 }
3379
3380 #[test]
3381 fn complexified_markers() {
3382 let complexify =
3387 |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
3388 let lower = lower
3389 .map(|release| Bound::Included(Version::new(release)))
3390 .unwrap_or(Bound::Unbounded);
3391 let upper = upper
3392 .map(|release| Bound::Excluded(Version::new(release)))
3393 .unwrap_or(Bound::Unbounded);
3394 m(marker).complexify_python_versions(lower.as_ref(), upper.as_ref())
3395 };
3396
3397 assert_eq!(
3398 complexify(None, None, "python_full_version < '3.10'"),
3399 m("python_full_version < '3.10'"),
3400 );
3401 assert_eq!(
3402 complexify(Some([3, 8]), None, "python_full_version < '3.10'"),
3403 m("python_full_version >= '3.8' and python_full_version < '3.10'"),
3404 );
3405 assert_eq!(
3406 complexify(None, Some([3, 8]), "python_full_version < '3.10'"),
3407 m("python_full_version < '3.8'"),
3408 );
3409 assert_eq!(
3410 complexify(Some([3, 8]), Some([3, 8]), "python_full_version < '3.10'"),
3411 m("python_full_version < '0' and python_full_version > '0'"),
3413 );
3414
3415 assert_eq!(
3416 complexify(Some([3, 11]), None, "python_full_version < '3.10'"),
3417 m("python_full_version < '0' and python_full_version > '0'"),
3419 );
3420 assert_eq!(
3421 complexify(Some([3, 11]), None, "python_full_version >= '3.10'"),
3422 m("python_full_version >= '3.11'"),
3423 );
3424 assert_eq!(
3425 complexify(Some([3, 11]), None, "python_full_version >= '3.12'"),
3426 m("python_full_version >= '3.12'"),
3427 );
3428
3429 assert_eq!(
3430 complexify(None, Some([3, 11]), "python_full_version > '3.12'"),
3431 m("python_full_version < '0' and python_full_version > '0'"),
3433 );
3434 assert_eq!(
3435 complexify(None, Some([3, 11]), "python_full_version <= '3.12'"),
3436 m("python_full_version < '3.11'"),
3437 );
3438 assert_eq!(
3439 complexify(None, Some([3, 11]), "python_full_version <= '3.10'"),
3440 m("python_full_version <= '3.10'"),
3441 );
3442
3443 assert_eq!(
3444 complexify(Some([3, 11]), None, "python_full_version == '3.8'"),
3445 m("python_full_version < '0' and python_full_version > '0'"),
3447 );
3448 assert_eq!(
3449 complexify(
3450 Some([3, 11]),
3451 None,
3452 "python_full_version == '3.8' or python_full_version == '3.12'"
3453 ),
3454 m("python_full_version == '3.12'"),
3455 );
3456 assert_eq!(
3457 complexify(
3458 Some([3, 11]),
3459 None,
3460 "python_full_version == '3.8' \
3461 or python_full_version == '3.11' \
3462 or python_full_version == '3.12'"
3463 ),
3464 m("python_full_version == '3.11' or python_full_version == '3.12'"),
3465 );
3466
3467 assert_eq!(
3474 complexify(
3475 Some([3, 12]),
3476 None,
3477 "python_full_version < '3.10' or python_full_version >= '3.10'"
3478 ),
3479 m("python_full_version >= '3.12'"),
3480 );
3481 }
3482
3483 #[test]
3484 fn simplified_markers() {
3485 let simplify =
3490 |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
3491 let lower = lower
3492 .map(|release| Bound::Included(Version::new(release)))
3493 .unwrap_or(Bound::Unbounded);
3494 let upper = upper
3495 .map(|release| Bound::Excluded(Version::new(release)))
3496 .unwrap_or(Bound::Unbounded);
3497 m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
3498 };
3499
3500 assert_eq!(
3501 simplify(
3502 Some([3, 8]),
3503 None,
3504 "python_full_version >= '3.8' and python_full_version < '3.10'"
3505 ),
3506 m("python_full_version < '3.10'"),
3507 );
3508 assert_eq!(
3509 simplify(Some([3, 8]), None, "python_full_version < '3.7'"),
3510 m("python_full_version < '0' and python_full_version > '0'"),
3512 );
3513 assert_eq!(
3514 simplify(
3515 Some([3, 8]),
3516 Some([3, 11]),
3517 "python_full_version == '3.7.*' \
3518 or python_full_version == '3.8.*' \
3519 or python_full_version == '3.10.*' \
3520 or python_full_version == '3.11.*' \
3521 "
3522 ),
3523 m("python_full_version != '3.9.*'"),
3527 );
3528 assert_eq!(
3529 simplify(
3530 Some([3, 8]),
3531 None,
3532 "python_full_version >= '3.8' and sys_platform == 'win32'"
3533 ),
3534 m("sys_platform == 'win32'"),
3535 );
3536 assert_eq!(
3537 simplify(
3538 Some([3, 8]),
3539 None,
3540 "python_full_version >= '3.9' \
3541 and (sys_platform == 'win32' or python_full_version >= '3.8')",
3542 ),
3543 m("python_full_version >= '3.9'"),
3544 );
3545 }
3546
3547 #[test]
3548 fn without_extras() {
3549 assert_eq!(
3550 m("os_name == 'Linux'").without_extras(),
3551 m("os_name == 'Linux'"),
3552 );
3553 assert!(m("extra == 'foo'").without_extras().is_true());
3554 assert_eq!(
3555 m("os_name == 'Linux' and extra == 'foo'").without_extras(),
3556 m("os_name == 'Linux'"),
3557 );
3558
3559 assert!(
3560 m("
3561 (os_name == 'Linux' and extra == 'foo')
3562 or (os_name != 'Linux' and extra == 'bar')")
3563 .without_extras()
3564 .is_true()
3565 );
3566
3567 assert_eq!(
3568 m("os_name == 'Linux' and extra != 'foo'").without_extras(),
3569 m("os_name == 'Linux'"),
3570 );
3571
3572 assert!(
3573 m("extra != 'extra-project-bar' and extra == 'extra-project-foo'")
3574 .without_extras()
3575 .is_true()
3576 );
3577
3578 assert_eq!(
3579 m("(os_name == 'Darwin' and extra == 'foo') \
3580 or (sys_platform == 'Linux' and extra != 'foo')")
3581 .without_extras(),
3582 m("os_name == 'Darwin' or sys_platform == 'Linux'"),
3583 );
3584 }
3585
3586 #[test]
3587 fn only_extras() {
3588 assert!(m("os_name == 'Linux'").only_extras().is_true());
3589 assert_eq!(m("extra == 'foo'").only_extras(), m("extra == 'foo'"));
3590 assert_eq!(
3591 m("os_name == 'Linux' and extra == 'foo'").only_extras(),
3592 m("extra == 'foo'"),
3593 );
3594 assert!(
3595 m("
3596 (os_name == 'foo' and extra == 'foo')
3597 or (os_name == 'bar' and extra != 'foo')")
3598 .only_extras()
3599 .is_true()
3600 );
3601 assert_eq!(
3602 m("
3603 (os_name == 'Linux' and extra == 'foo')
3604 or (os_name != 'Linux' and extra == 'bar')")
3605 .only_extras(),
3606 m("extra == 'foo' or extra == 'bar'"),
3607 );
3608
3609 assert_eq!(
3610 m("
3611 (implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin')
3612 or (os_name == 'nt' and sys_platform == 'win32')")
3613 .only_extras(),
3614 m("os_name == 'Linux' or os_name != 'Linux'"),
3615 );
3616 }
3617
3618 #[test]
3619 fn visit_extras() {
3620 let collect = |m: MarkerTree| -> Vec<(&'static str, String)> {
3621 let mut collected = vec![];
3622 m.visit_extras(|op, extra| {
3623 let op = match op {
3624 MarkerOperator::Equal => "==",
3625 MarkerOperator::NotEqual => "!=",
3626 _ => unreachable!(),
3627 };
3628 collected.push((op, extra.as_str().to_string()));
3629 });
3630 collected
3631 };
3632 assert_eq!(collect(m("os_name == 'Linux'")), vec![]);
3633 assert_eq!(
3634 collect(m("extra == 'foo'")),
3635 vec![("==", "foo".to_string())]
3636 );
3637 assert_eq!(
3638 collect(m("extra == 'Linux' and extra == 'foo'")),
3639 vec![("==", "foo".to_string()), ("==", "linux".to_string())]
3640 );
3641 assert_eq!(
3642 collect(m("os_name == 'Linux' and extra == 'foo'")),
3643 vec![("==", "foo".to_string())]
3644 );
3645
3646 let marker = "
3647 python_full_version >= '3.12'
3648 and sys_platform == 'darwin'
3649 and extra == 'extra-27-resolution-markers-for-days-cpu'
3650 and extra != 'extra-27-resolution-markers-for-days-cu124'
3651 ";
3652 assert_eq!(
3653 collect(m(marker)),
3654 vec![
3655 ("==", "extra-27-resolution-markers-for-days-cpu".to_string()),
3656 (
3657 "!=",
3658 "extra-27-resolution-markers-for-days-cu124".to_string()
3659 ),
3660 ]
3661 );
3662 }
3663
3664 #[test]
3666 fn marker_normalization_a() {
3667 let left_tree = m("python_version == '3.0.*'");
3668 let left = left_tree.try_to_string().unwrap();
3669 let right = "python_full_version == '3.0.*'";
3670 assert_eq!(left, right, "{left} != {right}");
3671 }
3672
3673 #[test]
3675 fn marker_normalization_b() {
3676 m("python_version >= '3' and python_version <= '3.0'");
3677
3678 let left_tree = m("python_version == '3.0.*'");
3679 let left = left_tree.try_to_string().unwrap();
3680 let right = "python_full_version == '3.0.*'";
3681 assert_eq!(left, right, "{left} != {right}");
3682 }
3683
3684 #[test]
3685 fn marker_normalization_c() {
3686 let left_tree = MarkerTree::from_str("python_version == '3.10.0.*'").unwrap();
3687 let left = left_tree.try_to_string().unwrap();
3688 let right = "python_full_version == '3.10.*'";
3689 assert_eq!(left, right, "{left} != {right}");
3690 }
3691
3692 #[test]
3693 fn evaluate_only_extras() {
3694 let a = ExtraName::from_str("a").unwrap();
3695 let b = ExtraName::from_str("b").unwrap();
3696
3697 let marker = m("extra == 'a' and extra == 'b'");
3698 assert!(!marker.evaluate_only_extras(std::slice::from_ref(&a)));
3699 assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3700 assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3701
3702 let marker = m("(platform_machine == 'inapplicable' and extra == 'b') or extra == 'a'");
3703 assert!(marker.evaluate_only_extras(std::slice::from_ref(&a)));
3704 assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3705 assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3706
3707 let marker = m(
3708 "(platform_machine == 'inapplicable' and extra == 'a') or (platform_machine != 'inapplicable' and extra == 'b')",
3709 );
3710 assert!(!marker.evaluate_only_extras(std::slice::from_ref(&a)));
3711 assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3712 assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3713 }
3714}