1use std::cmp::Ordering;
2use std::collections::HashSet;
3use std::fmt::{self, Display, Formatter};
4use std::ops::{Bound, Deref};
5use std::str::FromStr;
6
7use itertools::Itertools;
8use pep440_rs::{Version, VersionParseError, VersionSpecifier};
9use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
10use version_ranges::Ranges;
11
12use crate::cursor::Cursor;
13use crate::marker::parse;
14use crate::{
15 ExtraName, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter,
16 TracingReporter,
17};
18
19use super::algebra::{Edges, NodeId, Variable, INTERNER};
20use super::simplify;
21
22#[derive(Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Clone, Copy)]
24pub enum MarkerWarningKind {
25 DeprecatedMarkerName,
28 ExtraInvalidComparison,
31 LexicographicComparison,
33 MarkerMarkerComparison,
35 Pep440Error,
37 StringStringComparison,
39}
40
41#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
43#[allow(clippy::enum_variant_names)]
44pub enum MarkerValueVersion {
45 ImplementationVersion,
47 PythonFullVersion,
49 PythonVersion,
51}
52
53impl Display for MarkerValueVersion {
54 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Self::ImplementationVersion => f.write_str("implementation_version"),
57 Self::PythonFullVersion => f.write_str("python_full_version"),
58 Self::PythonVersion => f.write_str("python_version"),
59 }
60 }
61}
62
63#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
65pub enum MarkerValueString {
66 ImplementationName,
68 OsName,
70 OsNameDeprecated,
72 PlatformMachine,
74 PlatformMachineDeprecated,
76 PlatformPythonImplementation,
78 PlatformPythonImplementationDeprecated,
80 PythonImplementationDeprecated,
82 PlatformRelease,
84 PlatformSystem,
86 PlatformVersion,
88 PlatformVersionDeprecated,
90 SysPlatform,
92 SysPlatformDeprecated,
94}
95
96impl Display for MarkerValueString {
97 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99 match self {
100 Self::ImplementationName => f.write_str("implementation_name"),
101 Self::OsName | Self::OsNameDeprecated => f.write_str("os_name"),
102 Self::PlatformMachine | Self::PlatformMachineDeprecated => {
103 f.write_str("platform_machine")
104 }
105 Self::PlatformPythonImplementation
106 | Self::PlatformPythonImplementationDeprecated
107 | Self::PythonImplementationDeprecated => f.write_str("platform_python_implementation"),
108 Self::PlatformRelease => f.write_str("platform_release"),
109 Self::PlatformSystem => f.write_str("platform_system"),
110 Self::PlatformVersion | Self::PlatformVersionDeprecated => {
111 f.write_str("platform_version")
112 }
113 Self::SysPlatform | Self::SysPlatformDeprecated => f.write_str("sys_platform"),
114 }
115 }
116}
117
118#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
122pub enum MarkerValue {
123 MarkerEnvVersion(MarkerValueVersion),
125 MarkerEnvString(MarkerValueString),
127 Extra,
129 QuotedString(String),
131}
132
133impl FromStr for MarkerValue {
134 type Err = String;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
138 let value = match s {
139 "implementation_name" => Self::MarkerEnvString(MarkerValueString::ImplementationName),
140 "implementation_version" => {
141 Self::MarkerEnvVersion(MarkerValueVersion::ImplementationVersion)
142 }
143 "os_name" => Self::MarkerEnvString(MarkerValueString::OsName),
144 "os.name" => Self::MarkerEnvString(MarkerValueString::OsNameDeprecated),
145 "platform_machine" => Self::MarkerEnvString(MarkerValueString::PlatformMachine),
146 "platform.machine" => {
147 Self::MarkerEnvString(MarkerValueString::PlatformMachineDeprecated)
148 }
149 "platform_python_implementation" => {
150 Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementation)
151 }
152 "platform.python_implementation" => {
153 Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementationDeprecated)
154 }
155 "python_implementation" => {
156 Self::MarkerEnvString(MarkerValueString::PythonImplementationDeprecated)
157 }
158 "platform_release" => Self::MarkerEnvString(MarkerValueString::PlatformRelease),
159 "platform_system" => Self::MarkerEnvString(MarkerValueString::PlatformSystem),
160 "platform_version" => Self::MarkerEnvString(MarkerValueString::PlatformVersion),
161 "platform.version" => {
162 Self::MarkerEnvString(MarkerValueString::PlatformVersionDeprecated)
163 }
164 "python_full_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonFullVersion),
165 "python_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonVersion),
166 "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform),
167 "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated),
168 "extra" => Self::Extra,
169 _ => return Err(format!("Invalid key: {s}")),
170 };
171 Ok(value)
172 }
173}
174
175impl Display for MarkerValue {
176 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
177 match self {
178 Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f),
179 Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f),
180 Self::Extra => f.write_str("extra"),
181 Self::QuotedString(value) => write!(f, "'{value}'"),
182 }
183 }
184}
185
186#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
188pub enum MarkerOperator {
189 Equal,
191 NotEqual,
193 GreaterThan,
195 GreaterEqual,
197 LessThan,
199 LessEqual,
201 TildeEqual,
203 In,
205 NotIn,
207 Contains,
212 NotContains,
217}
218
219impl MarkerOperator {
220 pub(crate) fn to_pep440_operator(self) -> Option<pep440_rs::Operator> {
222 match self {
223 Self::Equal => Some(pep440_rs::Operator::Equal),
224 Self::NotEqual => Some(pep440_rs::Operator::NotEqual),
225 Self::GreaterThan => Some(pep440_rs::Operator::GreaterThan),
226 Self::GreaterEqual => Some(pep440_rs::Operator::GreaterThanEqual),
227 Self::LessThan => Some(pep440_rs::Operator::LessThan),
228 Self::LessEqual => Some(pep440_rs::Operator::LessThanEqual),
229 Self::TildeEqual => Some(pep440_rs::Operator::TildeEqual),
230 _ => None,
231 }
232 }
233
234 pub(crate) fn invert(self) -> MarkerOperator {
236 match self {
237 Self::LessThan => Self::GreaterThan,
238 Self::LessEqual => Self::GreaterEqual,
239 Self::GreaterThan => Self::LessThan,
240 Self::GreaterEqual => Self::LessEqual,
241 Self::Equal => Self::Equal,
242 Self::NotEqual => Self::NotEqual,
243 Self::TildeEqual => Self::TildeEqual,
244 Self::In => Self::Contains,
245 Self::NotIn => Self::NotContains,
246 Self::Contains => Self::In,
247 Self::NotContains => Self::NotIn,
248 }
249 }
250
251 pub(crate) fn negate(self) -> Option<MarkerOperator> {
256 Some(match self {
257 Self::Equal => Self::NotEqual,
258 Self::NotEqual => Self::Equal,
259 Self::TildeEqual => return None,
260 Self::LessThan => Self::GreaterEqual,
261 Self::LessEqual => Self::GreaterThan,
262 Self::GreaterThan => Self::LessEqual,
263 Self::GreaterEqual => Self::LessThan,
264 Self::In => Self::NotIn,
265 Self::NotIn => Self::In,
266 Self::Contains => Self::NotContains,
267 Self::NotContains => Self::Contains,
268 })
269 }
270
271 pub fn from_bounds(
273 bounds: (&Bound<String>, &Bound<String>),
274 ) -> impl Iterator<Item = (MarkerOperator, String)> {
275 let (b1, b2) = match bounds {
276 (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
277 (Some((MarkerOperator::Equal, v1.clone())), None)
278 }
279 (Bound::Excluded(v1), Bound::Excluded(v2)) if v1 == v2 => {
280 (Some((MarkerOperator::NotEqual, v1.clone())), None)
281 }
282 (lower, upper) => (
283 MarkerOperator::from_lower_bound(lower),
284 MarkerOperator::from_upper_bound(upper),
285 ),
286 };
287
288 b1.into_iter().chain(b2)
289 }
290
291 pub fn from_lower_bound(bound: &Bound<String>) -> Option<(MarkerOperator, String)> {
293 match bound {
294 Bound::Included(value) => Some((MarkerOperator::GreaterEqual, value.clone())),
295 Bound::Excluded(value) => Some((MarkerOperator::GreaterThan, value.clone())),
296 Bound::Unbounded => None,
297 }
298 }
299
300 pub fn from_upper_bound(bound: &Bound<String>) -> Option<(MarkerOperator, String)> {
302 match bound {
303 Bound::Included(value) => Some((MarkerOperator::LessEqual, value.clone())),
304 Bound::Excluded(value) => Some((MarkerOperator::LessThan, value.clone())),
305 Bound::Unbounded => None,
306 }
307 }
308}
309
310impl FromStr for MarkerOperator {
311 type Err = String;
312
313 fn from_str(s: &str) -> Result<Self, Self::Err> {
315 let value = match s {
316 "==" => Self::Equal,
317 "!=" => Self::NotEqual,
318 ">" => Self::GreaterThan,
319 ">=" => Self::GreaterEqual,
320 "<" => Self::LessThan,
321 "<=" => Self::LessEqual,
322 "~=" => Self::TildeEqual,
323 "in" => Self::In,
324 not_space_in
325 if not_space_in
326 .strip_prefix("not")
328 .and_then(|space_in| space_in.strip_suffix("in"))
330 .is_some_and(|space| !space.is_empty() && space.trim().is_empty()) =>
332 {
333 Self::NotIn
334 }
335 other => return Err(format!("Invalid comparator: {other}")),
336 };
337 Ok(value)
338 }
339}
340
341impl Display for MarkerOperator {
342 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
343 f.write_str(match self {
344 Self::Equal => "==",
345 Self::NotEqual => "!=",
346 Self::GreaterThan => ">",
347 Self::GreaterEqual => ">=",
348 Self::LessThan => "<",
349 Self::LessEqual => "<=",
350 Self::TildeEqual => "~=",
351 Self::In | Self::Contains => "in",
352 Self::NotIn | Self::NotContains => "not in",
353 })
354 }
355}
356
357#[derive(Clone, Debug, Eq, Hash, PartialEq)]
359pub struct StringVersion {
360 pub string: String,
362 pub version: Version,
364}
365
366impl From<Version> for StringVersion {
367 fn from(version: Version) -> Self {
368 Self {
369 string: version.to_string(),
370 version,
371 }
372 }
373}
374
375impl FromStr for StringVersion {
376 type Err = VersionParseError;
377
378 fn from_str(s: &str) -> Result<Self, Self::Err> {
379 Ok(Self {
380 string: s.to_string(),
381 version: Version::from_str(s)?,
382 })
383 }
384}
385
386impl Display for StringVersion {
387 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
388 self.string.fmt(f)
389 }
390}
391
392impl Serialize for StringVersion {
393 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
394 where
395 S: Serializer,
396 {
397 serializer.serialize_str(&self.string)
398 }
399}
400
401impl<'de> Deserialize<'de> for StringVersion {
402 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
403 where
404 D: Deserializer<'de>,
405 {
406 let string = String::deserialize(deserializer)?;
407 Self::from_str(&string).map_err(de::Error::custom)
408 }
409}
410
411impl Deref for StringVersion {
412 type Target = Version;
413
414 fn deref(&self) -> &Self::Target {
415 &self.version
416 }
417}
418
419#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
421pub enum MarkerValueExtra {
422 Extra(ExtraName),
424 Arbitrary(String),
426}
427
428impl MarkerValueExtra {
429 fn as_extra(&self) -> Option<&ExtraName> {
430 match self {
431 Self::Extra(extra) => Some(extra),
432 Self::Arbitrary(_) => None,
433 }
434 }
435}
436
437impl Display for MarkerValueExtra {
438 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
439 match self {
440 Self::Extra(extra) => extra.fmt(f),
441 Self::Arbitrary(string) => string.fmt(f),
442 }
443 }
444}
445
446#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
448#[allow(missing_docs)]
449pub enum MarkerExpression {
450 Version {
455 key: MarkerValueVersion,
456 specifier: VersionSpecifier,
457 },
458 VersionIn {
467 key: MarkerValueVersion,
468 versions: Vec<Version>,
469 negated: bool,
470 },
471 String {
475 key: MarkerValueString,
476 operator: MarkerOperator,
477 value: String,
478 },
479 Extra {
481 operator: ExtraOperator,
482 name: MarkerValueExtra,
483 },
484}
485
486#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
488pub enum ExtraOperator {
489 Equal,
491 NotEqual,
493}
494
495impl ExtraOperator {
496 pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option<ExtraOperator> {
500 match operator {
501 MarkerOperator::Equal => Some(ExtraOperator::Equal),
502 MarkerOperator::NotEqual => Some(ExtraOperator::NotEqual),
503 _ => None,
504 }
505 }
506
507 pub(crate) fn negate(&self) -> ExtraOperator {
509 match *self {
510 ExtraOperator::Equal => ExtraOperator::NotEqual,
511 ExtraOperator::NotEqual => ExtraOperator::Equal,
512 }
513 }
514}
515
516impl Display for ExtraOperator {
517 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
518 f.write_str(match self {
519 Self::Equal => "==",
520 Self::NotEqual => "!=",
521 })
522 }
523}
524
525impl MarkerExpression {
526 pub fn parse_reporter(
528 s: &str,
529 reporter: &mut impl Reporter,
530 ) -> Result<Option<Self>, Pep508Error> {
531 let mut chars = Cursor::new(s);
532 let expression = parse::parse_marker_key_op_value(&mut chars, reporter)?;
533 chars.eat_whitespace();
534 if let Some((pos, unexpected)) = chars.next() {
535 return Err(Pep508Error {
536 message: Pep508ErrorSource::String(format!(
537 "Unexpected character '{unexpected}', expected end of input"
538 )),
539 start: pos,
540 len: chars.remaining(),
541 input: chars.to_string(),
542 });
543 }
544
545 Ok(expression)
546 }
547
548 #[allow(clippy::should_implement_trait)]
553 pub fn from_str(s: &str) -> Result<Option<Self>, Pep508Error> {
554 MarkerExpression::parse_reporter(s, &mut TracingReporter)
555 }
556}
557
558impl Display for MarkerExpression {
559 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
560 match self {
561 MarkerExpression::Version { key, specifier } => {
562 let (op, version) = (specifier.operator(), specifier.version());
563 if op == &pep440_rs::Operator::EqualStar || op == &pep440_rs::Operator::NotEqualStar
564 {
565 return write!(f, "{key} {op} '{version}.*'");
566 }
567 write!(f, "{key} {op} '{version}'")
568 }
569 MarkerExpression::VersionIn {
570 key,
571 versions,
572 negated,
573 } => {
574 let op = if *negated { "not in" } else { "in" };
575 let versions = versions.iter().map(ToString::to_string).join(" ");
576 write!(f, "{key} {op} '{versions}'")
577 }
578 MarkerExpression::String {
579 key,
580 operator,
581 value,
582 } => {
583 if matches!(
584 operator,
585 MarkerOperator::Contains | MarkerOperator::NotContains
586 ) {
587 return write!(f, "'{value}' {} {key}", operator.invert());
588 }
589
590 write!(f, "{key} {operator} '{value}'")
591 }
592 MarkerExpression::Extra { operator, name } => {
593 write!(f, "extra {operator} '{name}'")
594 }
595 }
596 }
597}
598
599#[derive(Clone, Eq, Hash, PartialEq)]
606pub struct MarkerTree(NodeId);
607
608impl Default for MarkerTree {
609 fn default() -> Self {
610 MarkerTree::TRUE
611 }
612}
613
614impl<'de> Deserialize<'de> for MarkerTree {
615 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
616 where
617 D: Deserializer<'de>,
618 {
619 let s = String::deserialize(deserializer)?;
620 FromStr::from_str(&s).map_err(de::Error::custom)
621 }
622}
623
624impl FromStr for MarkerTree {
625 type Err = Pep508Error;
626
627 fn from_str(markers: &str) -> Result<Self, Self::Err> {
628 parse::parse_markers(markers, &mut TracingReporter)
629 }
630}
631
632impl MarkerTree {
633 pub fn parse_str<T: Pep508Url>(markers: &str) -> Result<Self, Pep508Error<T>> {
635 parse::parse_markers(markers, &mut TracingReporter)
636 }
637
638 pub fn parse_reporter(
640 markers: &str,
641 reporter: &mut impl Reporter,
642 ) -> Result<Self, Pep508Error> {
643 parse::parse_markers(markers, reporter)
644 }
645
646 pub const TRUE: MarkerTree = MarkerTree(NodeId::TRUE);
648
649 pub const FALSE: MarkerTree = MarkerTree(NodeId::FALSE);
651
652 pub fn expression(expr: MarkerExpression) -> MarkerTree {
654 MarkerTree(INTERNER.lock().expression(expr))
655 }
656
657 pub fn is_true(&self) -> bool {
664 self.0.is_true()
665 }
666
667 pub fn is_false(&self) -> bool {
675 self.0.is_false()
676 }
677
678 #[must_use]
680 pub fn negate(&self) -> MarkerTree {
681 MarkerTree(self.0.not())
682 }
683
684 #[allow(clippy::needless_pass_by_value)]
686 pub fn and(&mut self, tree: MarkerTree) {
687 self.0 = INTERNER.lock().and(self.0, tree.0);
688 }
689
690 #[allow(clippy::needless_pass_by_value)]
692 pub fn or(&mut self, tree: MarkerTree) {
693 self.0 = INTERNER.lock().or(self.0, tree.0);
694 }
695
696 pub fn is_disjoint(&self, other: &MarkerTree) -> bool {
704 INTERNER.lock().is_disjoint(self.0, other.0)
705 }
706
707 pub fn contents(&self) -> Option<MarkerTreeContents> {
714 if self.is_true() {
715 return None;
716 }
717
718 Some(MarkerTreeContents(self.clone()))
719 }
720
721 pub fn try_to_string(&self) -> Option<String> {
727 self.contents().map(|contents| contents.to_string())
728 }
729
730 pub fn kind(&self) -> MarkerTreeKind<'_> {
732 if self.is_true() {
733 return MarkerTreeKind::True;
734 }
735
736 if self.is_false() {
737 return MarkerTreeKind::False;
738 }
739
740 let node = INTERNER.shared.node(self.0);
741 match &node.var {
742 Variable::Version(key) => {
743 let Edges::Version { edges: ref map } = node.children else {
744 unreachable!()
745 };
746 MarkerTreeKind::Version(VersionMarkerTree {
747 id: self.0,
748 key: key.clone(),
749 map,
750 })
751 }
752 Variable::String(key) => {
753 let Edges::String { edges: ref map } = node.children else {
754 unreachable!()
755 };
756 MarkerTreeKind::String(StringMarkerTree {
757 id: self.0,
758 key: key.clone(),
759 map,
760 })
761 }
762 Variable::In { key, value } => {
763 let Edges::Boolean { low, high } = node.children else {
764 unreachable!()
765 };
766 MarkerTreeKind::In(InMarkerTree {
767 key: key.clone(),
768 value,
769 high: high.negate(self.0),
770 low: low.negate(self.0),
771 })
772 }
773 Variable::Contains { key, value } => {
774 let Edges::Boolean { low, high } = node.children else {
775 unreachable!()
776 };
777 MarkerTreeKind::Contains(ContainsMarkerTree {
778 key: key.clone(),
779 value,
780 high: high.negate(self.0),
781 low: low.negate(self.0),
782 })
783 }
784 Variable::Extra(name) => {
785 let Edges::Boolean { low, high } = node.children else {
786 unreachable!()
787 };
788 MarkerTreeKind::Extra(ExtraMarkerTree {
789 name,
790 high: high.negate(self.0),
791 low: low.negate(self.0),
792 })
793 }
794 }
795 }
796
797 pub fn to_dnf(&self) -> Vec<Vec<MarkerExpression>> {
799 simplify::to_dnf(self)
800 }
801
802 pub fn evaluate(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
804 self.report_deprecated_options(&mut TracingReporter);
805 self.evaluate_reporter_impl(env, extras, &mut TracingReporter)
806 }
807
808 pub fn evaluate_optional_environment(
816 &self,
817 env: Option<&MarkerEnvironment>,
818 extras: &[ExtraName],
819 ) -> bool {
820 self.report_deprecated_options(&mut TracingReporter);
821 match env {
822 None => self.evaluate_extras(extras),
823 Some(env) => self.evaluate_reporter_impl(env, extras, &mut TracingReporter),
824 }
825 }
826
827 pub fn evaluate_reporter(
830 &self,
831 env: &MarkerEnvironment,
832 extras: &[ExtraName],
833 reporter: &mut impl Reporter,
834 ) -> bool {
835 self.report_deprecated_options(reporter);
836 self.evaluate_reporter_impl(env, extras, reporter)
837 }
838
839 fn evaluate_reporter_impl(
840 &self,
841 env: &MarkerEnvironment,
842 extras: &[ExtraName],
843 reporter: &mut impl Reporter,
844 ) -> bool {
845 match self.kind() {
846 MarkerTreeKind::True => return true,
847 MarkerTreeKind::False => return false,
848 MarkerTreeKind::Version(marker) => {
849 for (range, tree) in marker.edges() {
850 if range.contains(env.get_version(marker.key())) {
851 return tree.evaluate_reporter_impl(env, extras, reporter);
852 }
853 }
854 }
855 MarkerTreeKind::String(marker) => {
856 for (range, tree) in marker.children() {
857 let l_string = env.get_string(marker.key());
858
859 if range.as_singleton().is_none() {
860 if let Some((start, end)) = range.bounding_range() {
861 if let Bound::Included(value) | Bound::Excluded(value) = start {
862 reporter.report(
863 MarkerWarningKind::LexicographicComparison,
864 format!("Comparing {l_string} and {value} lexicographically"),
865 );
866 };
867
868 if let Bound::Included(value) | Bound::Excluded(value) = end {
869 reporter.report(
870 MarkerWarningKind::LexicographicComparison,
871 format!("Comparing {l_string} and {value} lexicographically"),
872 );
873 };
874 }
875 }
876
877 let l_string = &l_string.to_string();
879 if range.contains(l_string) {
880 return tree.evaluate_reporter_impl(env, extras, reporter);
881 }
882 }
883 }
884 MarkerTreeKind::In(marker) => {
885 return marker
886 .edge(marker.value().contains(env.get_string(marker.key())))
887 .evaluate_reporter_impl(env, extras, reporter);
888 }
889 MarkerTreeKind::Contains(marker) => {
890 return marker
891 .edge(env.get_string(marker.key()).contains(marker.value()))
892 .evaluate_reporter_impl(env, extras, reporter);
893 }
894 MarkerTreeKind::Extra(marker) => {
895 return marker
896 .edge(
897 marker
898 .name()
899 .as_extra()
900 .is_some_and(|extra| extras.contains(extra)),
901 )
902 .evaluate_reporter_impl(env, extras, reporter);
903 }
904 }
905
906 false
907 }
908
909 pub fn evaluate_extras_and_python_version(
918 &self,
919 extras: &HashSet<ExtraName>,
920 python_versions: &[Version],
921 ) -> bool {
922 match self.kind() {
923 MarkerTreeKind::True => true,
924 MarkerTreeKind::False => false,
925 MarkerTreeKind::Version(marker) => marker.edges().any(|(range, tree)| {
926 if *marker.key() == MarkerValueVersion::PythonVersion {
927 if !python_versions
928 .iter()
929 .any(|version| range.contains(version))
930 {
931 return false;
932 }
933 }
934
935 tree.evaluate_extras_and_python_version(extras, python_versions)
936 }),
937 MarkerTreeKind::String(marker) => marker
938 .children()
939 .any(|(_, tree)| tree.evaluate_extras_and_python_version(extras, python_versions)),
940 MarkerTreeKind::In(marker) => marker
941 .children()
942 .any(|(_, tree)| tree.evaluate_extras_and_python_version(extras, python_versions)),
943 MarkerTreeKind::Contains(marker) => marker
944 .children()
945 .any(|(_, tree)| tree.evaluate_extras_and_python_version(extras, python_versions)),
946 MarkerTreeKind::Extra(marker) => marker
947 .edge(
948 marker
949 .name()
950 .as_extra()
951 .is_some_and(|extra| extras.contains(extra)),
952 )
953 .evaluate_extras_and_python_version(extras, python_versions),
954 }
955 }
956
957 pub fn evaluate_extras(&self, extras: &[ExtraName]) -> bool {
961 match self.kind() {
962 MarkerTreeKind::True => true,
963 MarkerTreeKind::False => false,
964 MarkerTreeKind::Version(marker) => {
965 marker.edges().any(|(_, tree)| tree.evaluate_extras(extras))
966 }
967 MarkerTreeKind::String(marker) => marker
968 .children()
969 .any(|(_, tree)| tree.evaluate_extras(extras)),
970 MarkerTreeKind::In(marker) => marker
971 .children()
972 .any(|(_, tree)| tree.evaluate_extras(extras)),
973 MarkerTreeKind::Contains(marker) => marker
974 .children()
975 .any(|(_, tree)| tree.evaluate_extras(extras)),
976 MarkerTreeKind::Extra(marker) => marker
977 .edge(
978 marker
979 .name()
980 .as_extra()
981 .is_some_and(|extra| extras.contains(extra)),
982 )
983 .evaluate_extras(extras),
984 }
985 }
986
987 pub fn evaluate_collect_warnings(
990 &self,
991 env: &MarkerEnvironment,
992 extras: &[ExtraName],
993 ) -> (bool, Vec<(MarkerWarningKind, String)>) {
994 let mut warnings = Vec::new();
995 let mut reporter = |kind, warning| {
996 warnings.push((kind, warning));
997 };
998 self.report_deprecated_options(&mut reporter);
999 let result = self.evaluate_reporter_impl(env, extras, &mut reporter);
1000 (result, warnings)
1001 }
1002
1003 fn report_deprecated_options(&self, reporter: &mut impl Reporter) {
1005 let string_marker = match self.kind() {
1006 MarkerTreeKind::True | MarkerTreeKind::False => return,
1007 MarkerTreeKind::String(marker) => marker,
1008 MarkerTreeKind::Version(marker) => {
1009 for (_, tree) in marker.edges() {
1010 tree.report_deprecated_options(reporter);
1011 }
1012 return;
1013 }
1014 MarkerTreeKind::In(marker) => {
1015 for (_, tree) in marker.children() {
1016 tree.report_deprecated_options(reporter);
1017 }
1018 return;
1019 }
1020 MarkerTreeKind::Contains(marker) => {
1021 for (_, tree) in marker.children() {
1022 tree.report_deprecated_options(reporter);
1023 }
1024 return;
1025 }
1026 MarkerTreeKind::Extra(marker) => {
1027 for (_, tree) in marker.children() {
1028 tree.report_deprecated_options(reporter);
1029 }
1030 return;
1031 }
1032 };
1033
1034 match string_marker.key() {
1035 MarkerValueString::OsNameDeprecated => {
1036 reporter.report(
1037 MarkerWarningKind::DeprecatedMarkerName,
1038 "os.name is deprecated in favor of os_name".to_string(),
1039 );
1040 }
1041 MarkerValueString::PlatformMachineDeprecated => {
1042 reporter.report(
1043 MarkerWarningKind::DeprecatedMarkerName,
1044 "platform.machine is deprecated in favor of platform_machine".to_string(),
1045 );
1046 }
1047 MarkerValueString::PlatformPythonImplementationDeprecated => {
1048 reporter.report(
1049 MarkerWarningKind::DeprecatedMarkerName,
1050 "platform.python_implementation is deprecated in favor of
1051 platform_python_implementation"
1052 .to_string(),
1053 );
1054 }
1055 MarkerValueString::PythonImplementationDeprecated => {
1056 reporter.report(
1057 MarkerWarningKind::DeprecatedMarkerName,
1058 "python_implementation is deprecated in favor of
1059 platform_python_implementation"
1060 .to_string(),
1061 );
1062 }
1063 MarkerValueString::PlatformVersionDeprecated => {
1064 reporter.report(
1065 MarkerWarningKind::DeprecatedMarkerName,
1066 "platform.version is deprecated in favor of platform_version".to_string(),
1067 );
1068 }
1069 MarkerValueString::SysPlatformDeprecated => {
1070 reporter.report(
1071 MarkerWarningKind::DeprecatedMarkerName,
1072 "sys.platform is deprecated in favor of sys_platform".to_string(),
1073 );
1074 }
1075 _ => {}
1076 }
1077
1078 for (_, tree) in string_marker.children() {
1079 tree.report_deprecated_options(reporter);
1080 }
1081 }
1082
1083 pub fn top_level_extra(&self) -> Option<MarkerExpression> {
1088 let mut extra_expression = None;
1089 for conjunction in self.to_dnf() {
1090 let found = conjunction.iter().find(|expression| {
1091 matches!(
1092 expression,
1093 MarkerExpression::Extra {
1094 operator: ExtraOperator::Equal,
1095 ..
1096 }
1097 )
1098 })?;
1099
1100 if let Some(ref extra_expression) = extra_expression {
1103 if *extra_expression != *found {
1104 return None;
1105 }
1106
1107 continue;
1108 }
1109
1110 extra_expression = Some(found.clone());
1111 }
1112
1113 extra_expression
1114 }
1115
1116 #[must_use]
1141 #[allow(clippy::needless_pass_by_value)]
1142 pub fn simplify_python_versions(
1143 self,
1144 lower: Bound<&Version>,
1145 upper: Bound<&Version>,
1146 ) -> MarkerTree {
1147 MarkerTree(
1148 INTERNER
1149 .lock()
1150 .simplify_python_versions(self.0, lower, upper),
1151 )
1152 }
1153
1154 #[must_use]
1162 #[allow(clippy::needless_pass_by_value)]
1163 pub fn complexify_python_versions(
1164 self,
1165 lower: Bound<&Version>,
1166 upper: Bound<&Version>,
1167 ) -> MarkerTree {
1168 MarkerTree(
1169 INTERNER
1170 .lock()
1171 .complexify_python_versions(self.0, lower, upper),
1172 )
1173 }
1174
1175 #[must_use]
1184 pub fn simplify_extras(self, extras: &[ExtraName]) -> MarkerTree {
1185 self.simplify_extras_with(|name| extras.contains(name))
1186 }
1187
1188 #[must_use]
1198 pub fn simplify_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> MarkerTree {
1199 self.simplify_extras_with_impl(&is_extra)
1205 }
1206
1207 fn simplify_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> MarkerTree {
1208 MarkerTree(INTERNER.lock().restrict(self.0, &|var| {
1209 match var {
1210 Variable::Extra(name) => name
1211 .as_extra()
1212 .and_then(|name| is_extra(name).then_some(true)),
1213 _ => None,
1214 }
1215 }))
1216 }
1217}
1218
1219impl fmt::Debug for MarkerTree {
1220 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1221 if self.is_true() {
1222 return write!(f, "true");
1223 }
1224 if self.is_false() {
1225 return write!(f, "false");
1226 }
1227 write!(f, "{}", self.contents().unwrap())
1228 }
1229}
1230
1231impl MarkerTree {
1232 pub fn debug_graph(&self) -> MarkerTreeDebugGraph<'_> {
1238 MarkerTreeDebugGraph { marker: self }
1239 }
1240
1241 pub fn debug_raw(&self) -> MarkerTreeDebugRaw<'_> {
1247 MarkerTreeDebugRaw { marker: self }
1248 }
1249
1250 fn fmt_graph(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
1251 match self.kind() {
1252 MarkerTreeKind::True => return write!(f, "true"),
1253 MarkerTreeKind::False => return write!(f, "false"),
1254 MarkerTreeKind::Version(kind) => {
1255 for (tree, range) in simplify::collect_edges(kind.edges()) {
1256 writeln!(f)?;
1257 for _ in 0..level {
1258 write!(f, " ")?;
1259 }
1260
1261 write!(f, "{key}{range} -> ", key = kind.key())?;
1262 tree.fmt_graph(f, level + 1)?;
1263 }
1264 }
1265 MarkerTreeKind::String(kind) => {
1266 for (tree, range) in simplify::collect_edges(kind.children()) {
1267 writeln!(f)?;
1268 for _ in 0..level {
1269 write!(f, " ")?;
1270 }
1271
1272 write!(f, "{key}{range} -> ", key = kind.key())?;
1273 tree.fmt_graph(f, level + 1)?;
1274 }
1275 }
1276 MarkerTreeKind::In(kind) => {
1277 writeln!(f)?;
1278 for _ in 0..level {
1279 write!(f, " ")?;
1280 }
1281 write!(f, "{} in {} -> ", kind.key(), kind.value())?;
1282 kind.edge(true).fmt_graph(f, level + 1)?;
1283
1284 writeln!(f)?;
1285 for _ in 0..level {
1286 write!(f, " ")?;
1287 }
1288 write!(f, "{} not in {} -> ", kind.key(), kind.value())?;
1289 kind.edge(false).fmt_graph(f, level + 1)?;
1290 }
1291 MarkerTreeKind::Contains(kind) => {
1292 writeln!(f)?;
1293 for _ in 0..level {
1294 write!(f, " ")?;
1295 }
1296 write!(f, "{} in {} -> ", kind.value(), kind.key())?;
1297 kind.edge(true).fmt_graph(f, level + 1)?;
1298
1299 writeln!(f)?;
1300 for _ in 0..level {
1301 write!(f, " ")?;
1302 }
1303 write!(f, "{} not in {} -> ", kind.value(), kind.key())?;
1304 kind.edge(false).fmt_graph(f, level + 1)?;
1305 }
1306 MarkerTreeKind::Extra(kind) => {
1307 writeln!(f)?;
1308 for _ in 0..level {
1309 write!(f, " ")?;
1310 }
1311 write!(f, "extra == {} -> ", kind.name())?;
1312 kind.edge(true).fmt_graph(f, level + 1)?;
1313
1314 writeln!(f)?;
1315 for _ in 0..level {
1316 write!(f, " ")?;
1317 }
1318 write!(f, "extra != {} -> ", kind.name())?;
1319 kind.edge(false).fmt_graph(f, level + 1)?;
1320 }
1321 }
1322
1323 Ok(())
1324 }
1325}
1326
1327impl PartialOrd for MarkerTree {
1328 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1329 Some(self.cmp(other))
1330 }
1331}
1332
1333impl Ord for MarkerTree {
1334 fn cmp(&self, other: &Self) -> Ordering {
1335 self.kind().cmp(&other.kind())
1336 }
1337}
1338
1339#[derive(Clone)]
1343pub struct MarkerTreeDebugGraph<'a> {
1344 marker: &'a MarkerTree,
1345}
1346
1347impl fmt::Debug for MarkerTreeDebugGraph<'_> {
1348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1349 self.marker.fmt_graph(f, 0)
1350 }
1351}
1352
1353#[derive(Clone)]
1360pub struct MarkerTreeDebugRaw<'a> {
1361 marker: &'a MarkerTree,
1362}
1363
1364impl fmt::Debug for MarkerTreeDebugRaw<'_> {
1365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1366 let node = INTERNER.shared.node(self.marker.0);
1367 f.debug_tuple("MarkerTreeDebugRaw").field(node).finish()
1368 }
1369}
1370
1371#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
1377pub enum MarkerTreeKind<'a> {
1378 True,
1380 False,
1382 Version(VersionMarkerTree<'a>),
1384 String(StringMarkerTree<'a>),
1386 In(InMarkerTree<'a>),
1388 Contains(ContainsMarkerTree<'a>),
1390 Extra(ExtraMarkerTree<'a>),
1392}
1393
1394#[derive(PartialEq, Eq, Clone, Debug)]
1396pub struct VersionMarkerTree<'a> {
1397 id: NodeId,
1398 key: MarkerValueVersion,
1399 map: &'a [(Ranges<Version>, NodeId)],
1400}
1401
1402impl VersionMarkerTree<'_> {
1403 pub fn key(&self) -> &MarkerValueVersion {
1405 &self.key
1406 }
1407
1408 pub fn edges(&self) -> impl ExactSizeIterator<Item = (&Ranges<Version>, MarkerTree)> + '_ {
1410 self.map
1411 .iter()
1412 .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1413 }
1414}
1415
1416impl PartialOrd for VersionMarkerTree<'_> {
1417 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1418 Some(self.cmp(other))
1419 }
1420}
1421
1422impl Ord for VersionMarkerTree<'_> {
1423 fn cmp(&self, other: &Self) -> Ordering {
1424 self.key()
1425 .cmp(other.key())
1426 .then_with(|| self.edges().cmp(other.edges()))
1427 }
1428}
1429
1430#[derive(PartialEq, Eq, Clone, Debug)]
1432pub struct StringMarkerTree<'a> {
1433 id: NodeId,
1434 key: MarkerValueString,
1435 map: &'a [(Ranges<String>, NodeId)],
1436}
1437
1438impl StringMarkerTree<'_> {
1439 pub fn key(&self) -> &MarkerValueString {
1441 &self.key
1442 }
1443
1444 pub fn children(&self) -> impl ExactSizeIterator<Item = (&Ranges<String>, MarkerTree)> {
1446 self.map
1447 .iter()
1448 .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1449 }
1450}
1451
1452impl PartialOrd for StringMarkerTree<'_> {
1453 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1454 Some(self.cmp(other))
1455 }
1456}
1457
1458impl Ord for StringMarkerTree<'_> {
1459 fn cmp(&self, other: &Self) -> Ordering {
1460 self.key()
1461 .cmp(other.key())
1462 .then_with(|| self.children().cmp(other.children()))
1463 }
1464}
1465
1466#[derive(PartialEq, Eq, Clone, Debug)]
1468pub struct InMarkerTree<'a> {
1469 key: MarkerValueString,
1470 value: &'a str,
1471 high: NodeId,
1472 low: NodeId,
1473}
1474
1475impl InMarkerTree<'_> {
1476 pub fn key(&self) -> &MarkerValueString {
1478 &self.key
1479 }
1480
1481 pub fn value(&self) -> &str {
1483 self.value
1484 }
1485
1486 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1488 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1489 }
1490
1491 pub fn edge(&self, value: bool) -> MarkerTree {
1493 if value {
1494 MarkerTree(self.high)
1495 } else {
1496 MarkerTree(self.low)
1497 }
1498 }
1499}
1500
1501impl PartialOrd for InMarkerTree<'_> {
1502 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1503 Some(self.cmp(other))
1504 }
1505}
1506
1507impl Ord for InMarkerTree<'_> {
1508 fn cmp(&self, other: &Self) -> Ordering {
1509 self.key()
1510 .cmp(other.key())
1511 .then_with(|| self.value().cmp(other.value()))
1512 .then_with(|| self.children().cmp(other.children()))
1513 }
1514}
1515
1516#[derive(PartialEq, Eq, Clone, Debug)]
1518pub struct ContainsMarkerTree<'a> {
1519 key: MarkerValueString,
1520 value: &'a str,
1521 high: NodeId,
1522 low: NodeId,
1523}
1524
1525impl ContainsMarkerTree<'_> {
1526 pub fn key(&self) -> &MarkerValueString {
1528 &self.key
1529 }
1530
1531 pub fn value(&self) -> &str {
1533 self.value
1534 }
1535
1536 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1538 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1539 }
1540
1541 pub fn edge(&self, value: bool) -> MarkerTree {
1543 if value {
1544 MarkerTree(self.high)
1545 } else {
1546 MarkerTree(self.low)
1547 }
1548 }
1549}
1550
1551impl PartialOrd for ContainsMarkerTree<'_> {
1552 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1553 Some(self.cmp(other))
1554 }
1555}
1556
1557impl Ord for ContainsMarkerTree<'_> {
1558 fn cmp(&self, other: &Self) -> Ordering {
1559 self.key()
1560 .cmp(other.key())
1561 .then_with(|| self.value().cmp(other.value()))
1562 .then_with(|| self.children().cmp(other.children()))
1563 }
1564}
1565
1566#[derive(PartialEq, Eq, Clone, Debug)]
1568pub struct ExtraMarkerTree<'a> {
1569 name: &'a MarkerValueExtra,
1570 high: NodeId,
1571 low: NodeId,
1572}
1573
1574impl ExtraMarkerTree<'_> {
1575 pub fn name(&self) -> &MarkerValueExtra {
1577 self.name
1578 }
1579
1580 pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1582 [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1583 }
1584
1585 pub fn edge(&self, value: bool) -> MarkerTree {
1587 if value {
1588 MarkerTree(self.high)
1589 } else {
1590 MarkerTree(self.low)
1591 }
1592 }
1593}
1594
1595impl PartialOrd for ExtraMarkerTree<'_> {
1596 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1597 Some(self.cmp(other))
1598 }
1599}
1600
1601impl Ord for ExtraMarkerTree<'_> {
1602 fn cmp(&self, other: &Self) -> Ordering {
1603 self.name()
1604 .cmp(other.name())
1605 .then_with(|| self.children().cmp(other.children()))
1606 }
1607}
1608
1609#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Debug)]
1613pub struct MarkerTreeContents(MarkerTree);
1614
1615impl From<MarkerTreeContents> for MarkerTree {
1616 fn from(contents: MarkerTreeContents) -> Self {
1617 contents.0
1618 }
1619}
1620
1621impl From<Option<MarkerTreeContents>> for MarkerTree {
1622 fn from(marker: Option<MarkerTreeContents>) -> Self {
1623 marker.map(|contents| contents.0).unwrap_or_default()
1624 }
1625}
1626
1627impl AsRef<MarkerTree> for MarkerTreeContents {
1628 fn as_ref(&self) -> &MarkerTree {
1629 &self.0
1630 }
1631}
1632
1633impl Serialize for MarkerTreeContents {
1634 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1635 where
1636 S: Serializer,
1637 {
1638 serializer.serialize_str(&self.to_string())
1639 }
1640}
1641
1642impl Display for MarkerTreeContents {
1643 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1644 if self.0.is_false() {
1646 return write!(f, "python_version < '0'");
1647 }
1648
1649 let dnf = self.0.to_dnf();
1651 let format_conjunction = |conjunction: &Vec<MarkerExpression>| {
1652 conjunction
1653 .iter()
1654 .map(MarkerExpression::to_string)
1655 .collect::<Vec<String>>()
1656 .join(" and ")
1657 };
1658
1659 let expr = match &dnf[..] {
1660 [conjunction] => format_conjunction(conjunction),
1661 _ => dnf
1662 .iter()
1663 .map(|conjunction| {
1664 if conjunction.len() == 1 {
1665 format_conjunction(conjunction)
1666 } else {
1667 format!("({})", format_conjunction(conjunction))
1668 }
1669 })
1670 .collect::<Vec<String>>()
1671 .join(" or "),
1672 };
1673
1674 f.write_str(&expr)
1675 }
1676}
1677
1678#[cfg(feature = "schemars")]
1679impl schemars::JsonSchema for MarkerTree {
1680 fn schema_name() -> String {
1681 "MarkerTree".to_string()
1682 }
1683
1684 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1685 schemars::schema::SchemaObject {
1686 instance_type: Some(schemars::schema::InstanceType::String.into()),
1687 metadata: Some(Box::new(schemars::schema::Metadata {
1688 description: Some(
1689 "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`"
1690 .to_string(),
1691 ),
1692 ..schemars::schema::Metadata::default()
1693 })),
1694 ..schemars::schema::SchemaObject::default()
1695 }
1696 .into()
1697 }
1698}
1699
1700#[cfg(test)]
1701mod test {
1702 use std::ops::Bound;
1703 use std::str::FromStr;
1704
1705 use insta::assert_snapshot;
1706 use pep440_rs::Version;
1707
1708 use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder};
1709 use crate::{ExtraName, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
1710
1711 fn parse_err(input: &str) -> String {
1712 MarkerTree::from_str(input).unwrap_err().to_string()
1713 }
1714
1715 fn m(s: &str) -> MarkerTree {
1716 s.parse().unwrap()
1717 }
1718
1719 fn env37() -> MarkerEnvironment {
1720 MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
1721 implementation_name: "",
1722 implementation_version: "3.7",
1723 os_name: "linux",
1724 platform_machine: "",
1725 platform_python_implementation: "",
1726 platform_release: "",
1727 platform_system: "",
1728 platform_version: "",
1729 python_full_version: "3.7",
1730 python_version: "3.7",
1731 sys_platform: "linux",
1732 })
1733 .unwrap()
1734 }
1735
1736 #[test]
1738 fn test_marker_equivalence() {
1739 let values = [
1740 (r"python_version == '2.7'", r#"python_version == "2.7""#),
1741 (r#"python_version == "2.7""#, r#"python_version == "2.7""#),
1742 (
1743 r#"python_version == "2.7" and os_name == "posix""#,
1744 r#"python_version == "2.7" and os_name == "posix""#,
1745 ),
1746 (
1747 r#"python_version == "2.7" or os_name == "posix""#,
1748 r#"python_version == "2.7" or os_name == "posix""#,
1749 ),
1750 (
1751 r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
1752 r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
1753 ),
1754 (r#"(python_version == "2.7")"#, r#"python_version == "2.7""#),
1755 (
1756 r#"(python_version == "2.7" and sys_platform == "win32")"#,
1757 r#"python_version == "2.7" and sys_platform == "win32""#,
1758 ),
1759 (
1760 r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
1761 r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
1762 ),
1763 ];
1764
1765 for (a, b) in values {
1766 assert_eq!(m(a), m(b), "{a} {b}");
1767 }
1768 }
1769
1770 #[test]
1771 fn simplify_python_versions() {
1772 assert_eq!(
1773 m("(extra == 'foo' and sys_platform == 'win32') or extra == 'foo'")
1774 .simplify_extras(&["foo".parse().unwrap()]),
1775 MarkerTree::TRUE
1776 );
1777
1778 assert_eq!(
1779 m("(python_version <= '3.11' and sys_platform == 'win32') or python_version > '3.11'")
1780 .simplify_python_versions(
1781 Bound::Excluded(Version::new([3, 12])).as_ref(),
1782 Bound::Unbounded.as_ref(),
1783 ),
1784 MarkerTree::TRUE
1785 );
1786
1787 assert_eq!(
1788 m("python_version < '3.10'")
1789 .simplify_python_versions(
1790 Bound::Excluded(Version::new([3, 7])).as_ref(),
1791 Bound::Unbounded.as_ref(),
1792 )
1793 .try_to_string()
1794 .unwrap(),
1795 "python_full_version < '3.10'"
1796 );
1797
1798 assert_eq!(
1800 m("python_version <= '3.12'")
1801 .simplify_python_versions(
1802 Bound::Excluded(Version::new([3, 12])).as_ref(),
1803 Bound::Unbounded.as_ref(),
1804 )
1805 .try_to_string()
1806 .unwrap(),
1807 "python_full_version < '3.13'"
1808 );
1809
1810 assert_eq!(
1811 m("python_full_version <= '3.12'").simplify_python_versions(
1812 Bound::Excluded(Version::new([3, 12])).as_ref(),
1813 Bound::Unbounded.as_ref(),
1814 ),
1815 MarkerTree::FALSE
1816 );
1817
1818 assert_eq!(
1819 m("python_full_version <= '3.12.1'")
1820 .simplify_python_versions(
1821 Bound::Excluded(Version::new([3, 12])).as_ref(),
1822 Bound::Unbounded.as_ref(),
1823 )
1824 .try_to_string()
1825 .unwrap(),
1826 "python_full_version <= '3.12.1'"
1827 );
1828 }
1829
1830 #[test]
1831 fn release_only() {
1832 assert!(m("python_full_version > '3.10' or python_full_version <= '3.10'").is_true());
1833 assert!(
1834 m("python_full_version > '3.10' or python_full_version <= '3.10'")
1835 .negate()
1836 .is_false()
1837 );
1838 assert!(m("python_full_version > '3.10' and python_full_version <= '3.10'").is_false());
1839 }
1840
1841 #[test]
1842 fn test_marker_evaluation() {
1843 let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
1844 implementation_name: "",
1845 implementation_version: "2.7",
1846 os_name: "linux",
1847 platform_machine: "",
1848 platform_python_implementation: "",
1849 platform_release: "",
1850 platform_system: "",
1851 platform_version: "",
1852 python_full_version: "2.7",
1853 python_version: "2.7",
1854 sys_platform: "linux",
1855 })
1856 .unwrap();
1857 let env37 = env37();
1858 let marker1 = MarkerTree::from_str("python_version == '2.7'").unwrap();
1859 let marker2 = MarkerTree::from_str(
1860 "os_name == \"linux\" or python_version == \"3.7\" and sys_platform == \"win32\"",
1861 )
1862 .unwrap();
1863 let marker3 = MarkerTree::from_str(
1864 "python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")",
1865 ).unwrap();
1866 assert!(marker1.evaluate(&env27, &[]));
1867 assert!(!marker1.evaluate(&env37, &[]));
1868 assert!(marker2.evaluate(&env27, &[]));
1869 assert!(marker2.evaluate(&env37, &[]));
1870 assert!(marker3.evaluate(&env27, &[]));
1871 assert!(!marker3.evaluate(&env37, &[]));
1872 }
1873
1874 #[test]
1875 fn test_version_in_evaluation() {
1876 let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
1877 implementation_name: "",
1878 implementation_version: "2.7",
1879 os_name: "linux",
1880 platform_machine: "",
1881 platform_python_implementation: "",
1882 platform_release: "",
1883 platform_system: "",
1884 platform_version: "",
1885 python_full_version: "2.7",
1886 python_version: "2.7",
1887 sys_platform: "linux",
1888 })
1889 .unwrap();
1890 let env37 = env37();
1891
1892 let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap();
1893 assert!(marker.evaluate(&env27, &[]));
1894 assert!(!marker.evaluate(&env37, &[]));
1895
1896 let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap();
1897 assert!(marker.evaluate(&env27, &[]));
1898 assert!(marker.evaluate(&env37, &[]));
1899
1900 let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap();
1901 assert!(!marker.evaluate(&env27, &[]));
1902 assert!(!marker.evaluate(&env37, &[]));
1903
1904 let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap();
1905 assert!(!marker.evaluate(&env27, &[]));
1906 assert!(marker.evaluate(&env37, &[]));
1907
1908 let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap();
1909 assert!(!marker.evaluate(&env27, &[]));
1910 assert!(!marker.evaluate(&env37, &[]));
1911
1912 let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap();
1913 assert!(marker.evaluate(&env27, &[]));
1914 assert!(marker.evaluate(&env37, &[]));
1915
1916 let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap();
1917 assert!(marker.evaluate(&env27, &[]));
1918 assert!(!marker.evaluate(&env37, &[]));
1919
1920 let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap();
1921 assert!(marker.evaluate(&env27, &[]));
1922 assert!(!marker.evaluate(&env37, &[]));
1923
1924 let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap();
1925 assert!(marker.evaluate(&env27, &[]));
1926 assert!(marker.evaluate(&env37, &[]));
1927
1928 let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap();
1929 assert!(!marker.evaluate(&env27, &[]));
1930 assert!(!marker.evaluate(&env37, &[]));
1931
1932 let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap();
1933 assert!(marker.evaluate(&env27, &[]));
1934 assert!(marker.evaluate(&env37, &[]));
1935 }
1936
1937 #[test]
1938 #[cfg(feature = "tracing")]
1939 #[tracing_test::traced_test]
1940 fn warnings1() {
1941 let env37 = env37();
1942 let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap();
1943 compare_keys.evaluate(&env37, &[]);
1944 logs_contain(
1945 "Comparing two markers with each other doesn't make any sense, will evaluate to false",
1946 );
1947 }
1948
1949 #[test]
1950 #[cfg(feature = "tracing")]
1951 #[tracing_test::traced_test]
1952 fn warnings2() {
1953 let env37 = env37();
1954 let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap();
1955 non_pep440.evaluate(&env37, &[]);
1956 logs_contain(
1957 "Expected PEP 440 version to compare with python_version, found `3.9.`, \
1958 will evaluate to false: after parsing `3.9`, found `.`, which is \
1959 not part of a valid version",
1960 );
1961 }
1962
1963 #[test]
1964 #[cfg(feature = "tracing")]
1965 #[tracing_test::traced_test]
1966 fn warnings3() {
1967 let env37 = env37();
1968 let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap();
1969 string_string.evaluate(&env37, &[]);
1970 logs_contain(
1971 "Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false"
1972 );
1973 }
1974
1975 #[test]
1976 #[cfg(feature = "tracing")]
1977 #[tracing_test::traced_test]
1978 fn warnings4() {
1979 let env37 = env37();
1980 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();
1981 string_string.evaluate(&env37, &[]);
1982 logs_assert(|lines: &[&str]| {
1983 let lines: Vec<_> = lines
1984 .iter()
1985 .map(|s| s.split_once(" ").unwrap().1)
1986 .collect();
1987 let expected = [
1988 "WARN warnings4: pep508_rs: os.name is deprecated in favor of os_name",
1989 "WARN warnings4: pep508_rs: platform.machine is deprecated in favor of platform_machine",
1990 "WARN warnings4: pep508_rs: platform.python_implementation is deprecated in favor of",
1991 "WARN warnings4: pep508_rs: sys.platform is deprecated in favor of sys_platform",
1992 "WARN warnings4: pep508_rs: Comparing linux and posix lexicographically"
1993 ];
1994 if lines == expected {
1995 Ok(())
1996 } else {
1997 Err(format!("{lines:?}"))
1998 }
1999 });
2000 }
2001
2002 #[test]
2003 fn test_not_in() {
2004 MarkerTree::from_str("'posix' not in os_name").unwrap();
2005 }
2006
2007 #[test]
2008 fn test_marker_version_inverted() {
2009 let env37 = env37();
2010 let (result, warnings) = MarkerTree::from_str("python_version > '3.6'")
2011 .unwrap()
2012 .evaluate_collect_warnings(&env37, &[]);
2013 assert_eq!(warnings, &[]);
2014 assert!(result);
2015
2016 let (result, warnings) = MarkerTree::from_str("'3.6' > python_version")
2017 .unwrap()
2018 .evaluate_collect_warnings(&env37, &[]);
2019 assert_eq!(warnings, &[]);
2020 assert!(!result);
2021
2022 let (result, warnings) = MarkerTree::from_str("'3.*' == python_version")
2024 .unwrap()
2025 .evaluate_collect_warnings(&env37, &[]);
2026 assert_eq!(warnings, &[]);
2027 assert!(result);
2028 }
2029
2030 #[test]
2031 fn test_marker_string_inverted() {
2032 let env37 = env37();
2033 let (result, warnings) = MarkerTree::from_str("'nux' in sys_platform")
2034 .unwrap()
2035 .evaluate_collect_warnings(&env37, &[]);
2036 assert_eq!(warnings, &[]);
2037 assert!(result);
2038
2039 let (result, warnings) = MarkerTree::from_str("sys_platform in 'nux'")
2040 .unwrap()
2041 .evaluate_collect_warnings(&env37, &[]);
2042 assert_eq!(warnings, &[]);
2043 assert!(!result);
2044 }
2045
2046 #[test]
2047 fn test_marker_version_star() {
2048 let env37 = env37();
2049 let (result, warnings) = MarkerTree::from_str("python_version == '3.7.*'")
2050 .unwrap()
2051 .evaluate_collect_warnings(&env37, &[]);
2052 assert_eq!(warnings, &[]);
2053 assert!(result);
2054 }
2055
2056 #[test]
2057 fn test_tilde_equal() {
2058 let env37 = env37();
2059 let (result, warnings) = MarkerTree::from_str("python_version ~= '3.7'")
2060 .unwrap()
2061 .evaluate_collect_warnings(&env37, &[]);
2062 assert_eq!(warnings, &[]);
2063 assert!(result);
2064 }
2065
2066 #[test]
2067 fn test_closing_parentheses() {
2068 MarkerTree::from_str(r#"( "linux" in sys_platform) and extra == 'all'"#).unwrap();
2069 }
2070
2071 #[test]
2072 fn wrong_quotes_dot_star() {
2073 assert_snapshot!(
2074 parse_err(r#"python_version == "3.8".* and python_version >= "3.8""#),
2075 @r#"
2076 Unexpected character '.', expected 'and', 'or' or end of input
2077 python_version == "3.8".* and python_version >= "3.8"
2078 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"#
2079 );
2080 assert_snapshot!(
2081 parse_err(r#"python_version == "3.8".*"#),
2082 @r#"
2083 Unexpected character '.', expected 'and', 'or' or end of input
2084 python_version == "3.8".*
2085 ^"#
2086 );
2087 }
2088
2089 #[test]
2090 fn test_marker_expression() {
2091 assert_eq!(
2092 MarkerExpression::from_str(r#"os_name == "nt""#)
2093 .unwrap()
2094 .unwrap(),
2095 MarkerExpression::String {
2096 key: MarkerValueString::OsName,
2097 operator: MarkerOperator::Equal,
2098 value: "nt".to_string(),
2099 }
2100 );
2101 }
2102
2103 #[test]
2104 fn test_marker_expression_inverted() {
2105 assert_eq!(
2106 MarkerTree::from_str(
2107 r#""nt" in os_name and '3.7' >= python_version and python_full_version >= '3.7'"#
2108 )
2109 .unwrap()
2110 .contents()
2111 .unwrap()
2112 .to_string(),
2113 "python_full_version == '3.7.*' and 'nt' in os_name",
2114 );
2115 }
2116
2117 #[test]
2118 fn test_marker_expression_to_long() {
2119 let err = MarkerExpression::from_str(r#"os_name == "nt" and python_version >= "3.8""#)
2120 .unwrap_err()
2121 .to_string();
2122 assert_snapshot!(
2123 err,
2124 @r#"
2125 Unexpected character 'a', expected end of input
2126 os_name == "nt" and python_version >= "3.8"
2127 ^^^^^^^^^^^^^^^^^^^^^^^^^^"#
2128 );
2129 }
2130
2131 #[test]
2132 fn test_marker_environment_from_json() {
2133 let _env: MarkerEnvironment = serde_json::from_str(
2134 r##"{
2135 "implementation_name": "cpython",
2136 "implementation_version": "3.7.13",
2137 "os_name": "posix",
2138 "platform_machine": "x86_64",
2139 "platform_python_implementation": "CPython",
2140 "platform_release": "5.4.188+",
2141 "platform_system": "Linux",
2142 "platform_version": "#1 SMP Sun Apr 24 10:03:06 PDT 2022",
2143 "python_full_version": "3.7.13",
2144 "python_version": "3.7",
2145 "sys_platform": "linux"
2146 }"##,
2147 )
2148 .unwrap();
2149 }
2150
2151 #[test]
2152 fn test_simplify_extras() {
2153 let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "dev""#).unwrap();
2155 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2156 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2157 assert_eq!(simplified, expected);
2158
2159 let markers = MarkerTree::from_str(r#"os_name == "nt" or extra == "dev""#).unwrap();
2161 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2162 assert_eq!(simplified, MarkerTree::TRUE);
2163
2164 let markers = MarkerTree::from_str(r#"extra == "dev""#).unwrap();
2166 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2167 assert_eq!(simplified, MarkerTree::TRUE);
2168
2169 let markers = MarkerTree::from_str(r#"extra == "dev" and extra == "test""#).unwrap();
2171 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2172 let expected = MarkerTree::from_str(r#"extra == "test""#).unwrap();
2173 assert_eq!(simplified, expected);
2174
2175 let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "test""#).unwrap();
2177 let simplified = markers
2178 .clone()
2179 .simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2180 assert_eq!(simplified, markers);
2181
2182 let markers = MarkerTree::from_str(
2185 r#"os_name == "nt" and (python_version == "3.7" or extra == "dev")"#,
2186 )
2187 .unwrap();
2188 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2189 let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2190 assert_eq!(simplified, expected);
2191
2192 let markers = MarkerTree::from_str(
2195 r#"os_name == "nt" or (python_version == "3.7" and extra == "dev")"#,
2196 )
2197 .unwrap();
2198 let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2199 let expected =
2200 MarkerTree::from_str(r#"os_name == "nt" or python_version == "3.7""#).unwrap();
2201 assert_eq!(simplified, expected);
2202 }
2203
2204 #[test]
2205 fn test_marker_simplification() {
2206 assert_false("python_version == '3.9.1'");
2207 assert_false("python_version == '3.9.0.*'");
2208 assert_true("python_version != '3.9.1'");
2209
2210 assert_false("python_version in '3.9.0'");
2214 assert_true("python_version in 'foo'");
2216 assert_true("python_version in '3.9.*'");
2218 assert_true("python_version in '3.9, 3.10'");
2220 assert_true("python_version in '3.9,3.10'");
2221 assert_true("python_version in '3.9 or 3.10'");
2222
2223 assert_false("python_version in '3.9 3.10.0 3.11'");
2227
2228 assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'");
2229 assert_simplifies(
2230 "python_version == '3.9.0'",
2231 "python_full_version == '3.9.*'",
2232 );
2233
2234 assert_simplifies(
2237 "python_version in '3.9 3.11'",
2238 "python_full_version == '3.9.*' or python_full_version == '3.11.*'",
2239 );
2240 assert_simplifies(
2242 "python_version in '3.9 3.10 3.11'",
2243 "python_full_version >= '3.9' and python_full_version < '3.12'",
2244 );
2245 assert_simplifies(
2247 "implementation_version in '3.9 3.11'",
2248 "implementation_version == '3.9' or implementation_version == '3.11'",
2249 );
2250
2251 assert_simplifies(
2254 "python_version not in '3.9 3.11'",
2255 "python_full_version < '3.9' or python_full_version == '3.10.*' or python_full_version >= '3.12'",
2256 );
2257 assert_simplifies(
2259 "python_version not in '3.9 3.10 3.11'",
2260 "python_full_version < '3.9' or python_full_version >= '3.12'",
2261 );
2262 assert_simplifies(
2264 "implementation_version not in '3.9 3.11'",
2265 "implementation_version != '3.9' and implementation_version != '3.11'",
2266 );
2267
2268 assert_simplifies("python_version != '3.9'", "python_full_version != '3.9.*'");
2269
2270 assert_simplifies("python_version >= '3.9.0'", "python_full_version >= '3.9'");
2271 assert_simplifies("python_version <= '3.9.0'", "python_full_version < '3.10'");
2272
2273 assert_simplifies(
2274 "python_version == '3.*'",
2275 "python_full_version >= '3' and python_full_version < '4'",
2276 );
2277 assert_simplifies(
2278 "python_version == '3.0.*'",
2279 "python_full_version == '3.0.*'",
2280 );
2281
2282 assert_simplifies(
2283 "python_version < '3.17' or python_version < '3.18'",
2284 "python_full_version < '3.18'",
2285 );
2286
2287 assert_simplifies(
2288 "python_version > '3.17' or python_version > '3.18' or python_version > '3.12'",
2289 "python_full_version >= '3.13'",
2290 );
2291
2292 assert_simplifies(
2294 "python_version > '3.17.post4' or python_version > '3.18.post4'",
2295 "python_full_version >= '3.18'",
2296 );
2297
2298 assert_simplifies(
2299 "python_version < '3.17' and python_version < '3.18'",
2300 "python_full_version < '3.17'",
2301 );
2302
2303 assert_simplifies(
2304 "python_version <= '3.18' and python_version == '3.18'",
2305 "python_full_version == '3.18.*'",
2306 );
2307
2308 assert_simplifies(
2309 "python_version <= '3.18' or python_version == '3.18'",
2310 "python_full_version < '3.19'",
2311 );
2312
2313 assert_simplifies(
2314 "python_version <= '3.15' or (python_version <= '3.17' and python_version < '3.16')",
2315 "python_full_version < '3.16'",
2316 );
2317
2318 assert_simplifies(
2319 "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15'",
2320 "python_full_version >= '3.17'",
2321 );
2322
2323 assert_simplifies(
2324 "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15' and implementation_version == '1'",
2325 "implementation_version == '1' and python_full_version >= '3.17'",
2326 );
2327
2328 assert_simplifies(
2329 "('3.17' < python_version or '3.16' < python_version) and '3.15' < python_version and implementation_version == '1'",
2330 "implementation_version == '1' and python_full_version >= '3.17'",
2331 );
2332
2333 assert_simplifies("extra == 'a' or extra == 'a'", "extra == 'a'");
2334 assert_simplifies(
2335 "extra == 'a' and extra == 'a' or extra == 'b'",
2336 "extra == 'a' or extra == 'b'",
2337 );
2338
2339 assert!(m("python_version < '3.17' and '3.18' == python_version").is_false());
2340
2341 assert_simplifies(
2343 "((extra == 'a' and extra == 'b') and extra == 'c') and extra == 'b'",
2344 "extra == 'a' and extra == 'b' and extra == 'c'",
2345 );
2346
2347 assert_simplifies(
2348 "((extra == 'a' or extra == 'b') or extra == 'c') or extra == 'b'",
2349 "extra == 'a' or extra == 'b' or extra == 'c'",
2350 );
2351
2352 assert_simplifies(
2354 "extra == 'a' or (extra == 'a' and extra == 'b')",
2355 "extra == 'a'",
2356 );
2357
2358 assert_simplifies(
2359 "extra == 'a' and (extra == 'a' or extra == 'b')",
2360 "extra == 'a'",
2361 );
2362
2363 assert_simplifies(
2364 "(extra == 'a' and (extra == 'a' or extra == 'b')) or extra == 'd'",
2365 "extra == 'a' or extra == 'd'",
2366 );
2367
2368 assert_simplifies(
2369 "((extra == 'a' and extra == 'b') or extra == 'c') or extra == 'b'",
2370 "extra == 'b' or extra == 'c'",
2371 );
2372
2373 assert_simplifies(
2374 "((extra == 'a' or extra == 'b') and extra == 'c') and extra == 'b'",
2375 "extra == 'b' and extra == 'c'",
2376 );
2377
2378 assert_simplifies(
2379 "((extra == 'a' or extra == 'b') and extra == 'c') or extra == 'b'",
2380 "(extra == 'a' and extra == 'c') or extra == 'b'",
2381 );
2382
2383 assert_simplifies(
2385 "(python_version < '3.1' or python_version < '3.2') and (python_version < '3.2' or python_version == '3.3')",
2386 "python_full_version < '3.2'",
2387 );
2388
2389 assert_true("python_version < '3.12.0rc1' or python_version >= '3.12.0rc1'");
2391
2392 assert_true(
2393 "extra == 'a' or (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2394 );
2395
2396 assert_simplifies(
2397 "extra == 'a' and (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2398 "extra == 'a'",
2399 );
2400
2401 assert_true("python_version != '3.10' or python_version < '3.12'");
2403
2404 assert_simplifies(
2405 "python_version != '3.10' or python_version > '3.12'",
2406 "python_full_version != '3.10.*'",
2407 );
2408
2409 assert_simplifies(
2410 "python_version != '3.8' and python_version < '3.10'",
2411 "python_full_version < '3.8' or python_full_version == '3.9.*'",
2412 );
2413
2414 assert_simplifies(
2415 "python_version != '3.8' and python_version != '3.9'",
2416 "python_full_version < '3.8' or python_full_version >= '3.10'",
2417 );
2418
2419 assert_true("sys_platform == 'win32' or sys_platform != 'win32'");
2421
2422 assert_true("'win32' == sys_platform or sys_platform != 'win32'");
2423
2424 assert_true(
2425 "sys_platform == 'win32' or sys_platform == 'win32' or sys_platform != 'win32'",
2426 );
2427
2428 assert!(m("sys_platform == 'win32' and sys_platform != 'win32'").is_false());
2429 }
2430
2431 #[test]
2432 fn test_marker_negation() {
2433 assert_eq!(
2434 m("python_version > '3.6'").negate(),
2435 m("python_version <= '3.6'")
2436 );
2437
2438 assert_eq!(
2439 m("'3.6' < python_version").negate(),
2440 m("python_version <= '3.6'")
2441 );
2442
2443 assert_eq!(
2444 m("python_version != '3.6' and os_name == 'Linux'").negate(),
2445 m("python_version == '3.6' or os_name != 'Linux'")
2446 );
2447
2448 assert_eq!(
2449 m("python_version == '3.6' and os_name != 'Linux'").negate(),
2450 m("python_version != '3.6' or os_name == 'Linux'")
2451 );
2452
2453 assert_eq!(
2454 m("python_version != '3.6.*' and os_name == 'Linux'").negate(),
2455 m("python_version == '3.6.*' or os_name != 'Linux'")
2456 );
2457
2458 assert_eq!(
2459 m("python_version == '3.6.*'").negate(),
2460 m("python_version != '3.6.*'")
2461 );
2462 assert_eq!(
2463 m("python_version != '3.6.*'").negate(),
2464 m("python_version == '3.6.*'")
2465 );
2466
2467 assert_eq!(
2468 m("python_version ~= '3.6'").negate(),
2469 m("python_version < '3.6' or python_version != '3.*'")
2470 );
2471 assert_eq!(
2472 m("'3.6' ~= python_version").negate(),
2473 m("python_version < '3.6' or python_version != '3.*'")
2474 );
2475 assert_eq!(
2476 m("python_version ~= '3.6.2'").negate(),
2477 m("python_version < '3.6.2' or python_version != '3.6.*'")
2478 );
2479
2480 assert_eq!(
2481 m("sys_platform == 'linux'").negate(),
2482 m("sys_platform != 'linux'")
2483 );
2484 assert_eq!(
2485 m("'linux' == sys_platform").negate(),
2486 m("sys_platform != 'linux'")
2487 );
2488
2489 assert_eq!(m("sys_platform ~= 'linux'").negate(), MarkerTree::FALSE);
2492
2493 assert_eq!(m("'foo' == 'bar'").negate(), MarkerTree::FALSE);
2495
2496 assert_eq!(
2498 m("os_name == 'bar' and os_name == 'foo'").negate(),
2499 m("os_name != 'bar' or os_name != 'foo'")
2500 );
2501 assert_eq!(
2503 m("os_name == 'bar' or os_name == 'foo'").negate(),
2504 m("os_name != 'bar' and os_name != 'foo'")
2505 );
2506
2507 assert_eq!(
2509 m("python_version >= '3.6' or python_version < '3.6'").negate(),
2510 m("python_version < '3.6' and python_version >= '3.6'")
2511 );
2512 }
2513
2514 #[test]
2515 fn test_complex_marker_simplification() {
2516 assert_simplifies(
2524 "(implementation_name == 'pypy' and sys_platform != 'win32')
2525 or (implementation_name != 'pypy' and sys_platform == 'win32')
2526 or (sys_platform == 'win32' and os_name != 'nt')
2527 or (sys_platform != 'win32' and os_name == 'nt')",
2528 "(os_name != 'nt' and sys_platform == 'win32') \
2529 or (implementation_name != 'pypy' and os_name == 'nt') \
2530 or (implementation_name == 'pypy' and os_name != 'nt') \
2531 or (os_name == 'nt' and sys_platform != 'win32')",
2532 );
2533
2534 assert_simplifies(
2537 "(os_name == 'Linux' and platform_system == 'win32')
2538 or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'a')
2539 or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'x')
2540 or (os_name != 'Linux' and platform_system == 'win32' and sys_platform == 'x')
2541 or (os_name == 'Linux' and platform_system != 'win32' and sys_platform == 'x')
2542 or (os_name != 'Linux' and platform_system != 'win32' and sys_platform == 'x')",
2543 "(os_name != 'Linux' and sys_platform == 'x') or (platform_system != 'win32' and sys_platform == 'x') or (os_name == 'Linux' and platform_system == 'win32')",
2544 );
2545
2546 assert_simplifies("python_version > '3.7'", "python_full_version >= '3.8'");
2547
2548 assert_simplifies(
2549 "(python_version <= '3.7' and os_name == 'Linux') or python_version > '3.7'",
2550 "os_name == 'Linux' or python_full_version >= '3.8'",
2551 );
2552
2553 assert_simplifies(
2556 "(os_name == 'Linux' and sys_platform == 'win32') \
2557 or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.7') \
2558 or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.8')",
2559 "(python_full_version < '3.7' and os_name == 'Linux' and sys_platform == 'win32') \
2560 or (python_full_version >= '3.9' and os_name == 'Linux' and sys_platform == 'win32') \
2561 or (python_full_version >= '3.7' and python_full_version < '3.9' and sys_platform == 'win32')",
2562 );
2563
2564 assert_simplifies(
2565 "(implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
2566 "(implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
2567 );
2568
2569 assert_simplifies(
2570 "(sys_platform == 'darwin' or sys_platform == 'win32')
2571 and ((implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
2572 "(implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
2573 );
2574
2575 assert_simplifies(
2576 "(sys_platform == 'darwin' or sys_platform == 'win32')
2577 and ((platform_version != '1' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
2578 "(os_name == 'nt' and platform_version != '1' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
2579 );
2580
2581 assert_simplifies(
2582 "(os_name == 'nt' and sys_platform == 'win32') \
2583 or (os_name != 'nt' and platform_version == '1' and (sys_platform == 'win32' or sys_platform == 'win64'))",
2584 "(platform_version == '1' and sys_platform == 'win32') \
2585 or (os_name != 'nt' and platform_version == '1' and sys_platform == 'win64') \
2586 or (os_name == 'nt' and sys_platform == 'win32')",
2587 );
2588
2589 assert_simplifies(
2590 "(os_name == 'nt' and sys_platform == 'win32') or (os_name != 'nt' and (sys_platform == 'win32' or sys_platform == 'win64'))",
2591 "(os_name != 'nt' and sys_platform == 'win64') or sys_platform == 'win32'",
2592 );
2593 }
2594
2595 #[test]
2596 fn test_requires_python() {
2597 fn simplified(marker: &str) -> MarkerTree {
2598 let lower = Bound::Included(Version::new([3, 8]));
2599 let upper = Bound::Unbounded;
2600 m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
2601 }
2602
2603 assert_eq!(simplified("python_version >= '3.8'"), MarkerTree::TRUE);
2604 assert_eq!(
2605 simplified("python_version >= '3.8' or sys_platform == 'win32'"),
2606 MarkerTree::TRUE
2607 );
2608
2609 assert_eq!(
2610 simplified("python_version >= '3.8' and sys_platform == 'win32'"),
2611 m("sys_platform == 'win32'"),
2612 );
2613
2614 assert_eq!(
2615 simplified("python_version == '3.8'")
2616 .try_to_string()
2617 .unwrap(),
2618 "python_full_version < '3.9'"
2619 );
2620
2621 assert_eq!(
2622 simplified("python_version <= '3.10'")
2623 .try_to_string()
2624 .unwrap(),
2625 "python_full_version < '3.11'"
2626 );
2627 }
2628
2629 #[test]
2630 fn test_extra_disjointness() {
2631 assert!(!is_disjoint("extra == 'a'", "python_version == '1'"));
2632
2633 assert!(!is_disjoint("extra == 'a'", "extra == 'a'"));
2634 assert!(!is_disjoint("extra == 'a'", "extra == 'b'"));
2635 assert!(!is_disjoint("extra == 'b'", "extra == 'a'"));
2636 assert!(!is_disjoint("extra == 'b'", "extra != 'a'"));
2637 assert!(!is_disjoint("extra != 'b'", "extra == 'a'"));
2638 assert!(is_disjoint("extra != 'b'", "extra == 'b'"));
2639 assert!(is_disjoint("extra == 'b'", "extra != 'b'"));
2640 }
2641
2642 #[test]
2643 fn test_arbitrary_disjointness() {
2644 assert!(!is_disjoint(
2647 "python_version == 'Linux'",
2648 "python_full_version == '3.7.1'"
2649 ));
2650 }
2651
2652 #[test]
2653 fn test_version_disjointness() {
2654 assert!(!is_disjoint(
2655 "os_name == 'Linux'",
2656 "python_full_version == '3.7.1'"
2657 ));
2658
2659 test_version_bounds_disjointness("python_full_version");
2660
2661 assert!(!is_disjoint(
2662 "python_full_version == '3.7.*'",
2663 "python_full_version == '3.7.1'"
2664 ));
2665
2666 assert!(is_disjoint(
2667 "python_version == '3.7'",
2668 "python_full_version == '3.8'"
2669 ));
2670
2671 assert!(!is_disjoint(
2672 "python_version == '3.7'",
2673 "python_full_version == '3.7.2'"
2674 ));
2675
2676 assert!(is_disjoint(
2677 "python_version > '3.7'",
2678 "python_full_version == '3.7.1'"
2679 ));
2680
2681 assert!(!is_disjoint(
2682 "python_version <= '3.7'",
2683 "python_full_version == '3.7.1'"
2684 ));
2685 }
2686
2687 #[test]
2688 fn test_string_disjointness() {
2689 assert!(!is_disjoint(
2690 "os_name == 'Linux'",
2691 "platform_version == '3.7.1'"
2692 ));
2693 assert!(!is_disjoint(
2694 "implementation_version == '3.7.0'",
2695 "python_full_version == '3.7.1'"
2696 ));
2697
2698 test_version_bounds_disjointness("platform_version");
2700
2701 assert!(is_disjoint("os_name == 'Linux'", "os_name == 'OSX'"));
2702 assert!(is_disjoint("os_name <= 'Linux'", "os_name == 'OSX'"));
2703
2704 assert!(!is_disjoint(
2705 "os_name in 'OSXLinuxWindows'",
2706 "os_name == 'OSX'"
2707 ));
2708 assert!(!is_disjoint("'OSX' in os_name", "'Linux' in os_name"));
2709
2710 assert!(!is_disjoint("os_name in 'OSX'", "os_name in 'Linux'"));
2712 assert!(!is_disjoint(
2713 "os_name in 'OSXLinux'",
2714 "os_name == 'Windows'"
2715 ));
2716
2717 assert!(is_disjoint(
2718 "os_name in 'Windows'",
2719 "os_name not in 'Windows'"
2720 ));
2721 assert!(is_disjoint(
2722 "'Windows' in os_name",
2723 "'Windows' not in os_name"
2724 ));
2725
2726 assert!(!is_disjoint("'Windows' in os_name", "'Windows' in os_name"));
2727 assert!(!is_disjoint("'Linux' in os_name", "os_name not in 'Linux'"));
2728 assert!(!is_disjoint("'Linux' not in os_name", "os_name in 'Linux'"));
2729
2730 assert!(!is_disjoint(
2731 "os_name == 'Linux' and os_name != 'OSX'",
2732 "os_name == 'Linux'"
2733 ));
2734 assert!(is_disjoint(
2735 "os_name == 'Linux' and os_name != 'OSX'",
2736 "os_name == 'OSX'"
2737 ));
2738
2739 assert!(!is_disjoint(
2740 "extra == 'Linux' and extra != 'OSX'",
2741 "extra == 'Linux'"
2742 ));
2743 assert!(is_disjoint(
2744 "extra == 'Linux' and extra != 'OSX'",
2745 "extra == 'OSX'"
2746 ));
2747
2748 assert!(!is_disjoint(
2749 "extra == 'x1' and extra != 'x2'",
2750 "extra == 'x1'"
2751 ));
2752 assert!(is_disjoint(
2753 "extra == 'x1' and extra != 'x2'",
2754 "extra == 'x2'"
2755 ));
2756 }
2757
2758 #[test]
2759 fn is_disjoint_commutative() {
2760 let m1 = m("extra == 'Linux' and extra != 'OSX'");
2761 let m2 = m("extra == 'Linux'");
2762 assert!(!m2.is_disjoint(&m1));
2763 assert!(!m1.is_disjoint(&m2));
2764 }
2765
2766 #[test]
2767 fn test_combined_disjointness() {
2768 assert!(!is_disjoint(
2769 "os_name == 'a' and platform_version == '1'",
2770 "os_name == 'a'"
2771 ));
2772 assert!(!is_disjoint(
2773 "os_name == 'a' or platform_version == '1'",
2774 "os_name == 'a'"
2775 ));
2776
2777 assert!(is_disjoint(
2778 "os_name == 'a' and platform_version == '1'",
2779 "os_name == 'a' and platform_version == '2'"
2780 ));
2781 assert!(is_disjoint(
2782 "os_name == 'a' and platform_version == '1'",
2783 "'2' == platform_version and os_name == 'a'"
2784 ));
2785 assert!(!is_disjoint(
2786 "os_name == 'a' or platform_version == '1'",
2787 "os_name == 'a' or platform_version == '2'"
2788 ));
2789
2790 assert!(is_disjoint(
2791 "sys_platform == 'darwin' and implementation_name == 'pypy'",
2792 "sys_platform == 'bar' or implementation_name == 'foo'",
2793 ));
2794 assert!(is_disjoint(
2795 "sys_platform == 'bar' or implementation_name == 'foo'",
2796 "sys_platform == 'darwin' and implementation_name == 'pypy'",
2797 ));
2798
2799 assert!(is_disjoint(
2800 "python_version >= '3.7' and implementation_name == 'pypy'",
2801 "python_version < '3.7'"
2802 ));
2803 assert!(is_disjoint(
2804 "implementation_name == 'pypy' and python_version >= '3.7'",
2805 "implementation_name != 'pypy'"
2806 ));
2807 assert!(is_disjoint(
2808 "implementation_name != 'pypy' and python_version >= '3.7'",
2809 "implementation_name == 'pypy'"
2810 ));
2811 }
2812
2813 #[test]
2814 fn test_arbitrary() {
2815 assert!(m("'wat' == 'wat'").is_true());
2816 assert!(m("os_name ~= 'wat'").is_true());
2817 assert!(m("python_version == 'Linux'").is_true());
2818 assert!(m("os_name ~= 'wat' or 'wat' == 'wat' and python_version == 'Linux'").is_true());
2819 }
2820
2821 #[test]
2822 fn test_is_false() {
2823 assert!(m("python_version < '3.10' and python_version >= '3.10'").is_false());
2824 assert!(m("(python_version < '3.10' and python_version >= '3.10') \
2825 or (python_version < '3.9' and python_version >= '3.9')",)
2826 .is_false());
2827
2828 assert!(!m("python_version < '3.10'").is_false());
2829 assert!(!m("python_version < '0'").is_false());
2830 assert!(!m("python_version < '3.10' and python_version >= '3.9'").is_false());
2831 assert!(!m("python_version < '3.10' or python_version >= '3.11'").is_false());
2832 }
2833
2834 fn test_version_bounds_disjointness(version: &str) {
2835 assert!(!is_disjoint(
2836 format!("{version} > '2.7.0'"),
2837 format!("{version} == '3.6.0'")
2838 ));
2839 assert!(!is_disjoint(
2840 format!("{version} >= '3.7.0'"),
2841 format!("{version} == '3.7.1'")
2842 ));
2843 assert!(!is_disjoint(
2844 format!("{version} >= '3.7.0'"),
2845 format!("'3.7.1' == {version}")
2846 ));
2847
2848 assert!(is_disjoint(
2849 format!("{version} >= '3.7.1'"),
2850 format!("{version} == '3.7.0'")
2851 ));
2852 assert!(is_disjoint(
2853 format!("'3.7.1' <= {version}"),
2854 format!("{version} == '3.7.0'")
2855 ));
2856
2857 assert!(is_disjoint(
2858 format!("{version} < '3.7.0'"),
2859 format!("{version} == '3.7.0'")
2860 ));
2861 assert!(is_disjoint(
2862 format!("'3.7.0' > {version}"),
2863 format!("{version} == '3.7.0'")
2864 ));
2865 assert!(is_disjoint(
2866 format!("{version} < '3.7.0'"),
2867 format!("{version} == '3.7.1'")
2868 ));
2869
2870 assert!(is_disjoint(
2871 format!("{version} == '3.7.0'"),
2872 format!("{version} == '3.7.1'")
2873 ));
2874 assert!(is_disjoint(
2875 format!("{version} == '3.7.0'"),
2876 format!("{version} != '3.7.0'")
2877 ));
2878 }
2879
2880 fn assert_simplifies(left: &str, right: &str) {
2881 assert_eq!(m(left), m(right), "{left} != {right}");
2882 assert_eq!(m(left).try_to_string().unwrap(), right, "{left} != {right}");
2883 }
2884
2885 fn assert_true(marker: &str) {
2886 assert!(m(marker).is_true(), "{marker} != true");
2887 }
2888
2889 fn assert_false(marker: &str) {
2890 assert!(m(marker).is_false(), "{marker} != false");
2891 }
2892
2893 fn is_disjoint(left: impl AsRef<str>, right: impl AsRef<str>) -> bool {
2894 let (left, right) = (m(left.as_ref()), m(right.as_ref()));
2895 left.is_disjoint(&right) && right.is_disjoint(&left)
2896 }
2897
2898 #[test]
2899 fn complexified_markers() {
2900 let complexify =
2905 |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
2906 let lower = lower
2907 .map(|release| Bound::Included(Version::new(release)))
2908 .unwrap_or(Bound::Unbounded);
2909 let upper = upper
2910 .map(|release| Bound::Excluded(Version::new(release)))
2911 .unwrap_or(Bound::Unbounded);
2912 m(marker).complexify_python_versions(lower.as_ref(), upper.as_ref())
2913 };
2914
2915 assert_eq!(
2916 complexify(None, None, "python_full_version < '3.10'"),
2917 m("python_full_version < '3.10'"),
2918 );
2919 assert_eq!(
2920 complexify(Some([3, 8]), None, "python_full_version < '3.10'"),
2921 m("python_full_version >= '3.8' and python_full_version < '3.10'"),
2922 );
2923 assert_eq!(
2924 complexify(None, Some([3, 8]), "python_full_version < '3.10'"),
2925 m("python_full_version < '3.8'"),
2926 );
2927 assert_eq!(
2928 complexify(Some([3, 8]), Some([3, 8]), "python_full_version < '3.10'"),
2929 m("python_full_version < '0' and python_full_version > '0'"),
2931 );
2932
2933 assert_eq!(
2934 complexify(Some([3, 11]), None, "python_full_version < '3.10'"),
2935 m("python_full_version < '0' and python_full_version > '0'"),
2937 );
2938 assert_eq!(
2939 complexify(Some([3, 11]), None, "python_full_version >= '3.10'"),
2940 m("python_full_version >= '3.11'"),
2941 );
2942 assert_eq!(
2943 complexify(Some([3, 11]), None, "python_full_version >= '3.12'"),
2944 m("python_full_version >= '3.12'"),
2945 );
2946
2947 assert_eq!(
2948 complexify(None, Some([3, 11]), "python_full_version > '3.12'"),
2949 m("python_full_version < '0' and python_full_version > '0'"),
2951 );
2952 assert_eq!(
2953 complexify(None, Some([3, 11]), "python_full_version <= '3.12'"),
2954 m("python_full_version < '3.11'"),
2955 );
2956 assert_eq!(
2957 complexify(None, Some([3, 11]), "python_full_version <= '3.10'"),
2958 m("python_full_version <= '3.10'"),
2959 );
2960
2961 assert_eq!(
2962 complexify(Some([3, 11]), None, "python_full_version == '3.8'"),
2963 m("python_full_version < '0' and python_full_version > '0'"),
2965 );
2966 assert_eq!(
2967 complexify(
2968 Some([3, 11]),
2969 None,
2970 "python_full_version == '3.8' or python_full_version == '3.12'"
2971 ),
2972 m("python_full_version == '3.12'"),
2973 );
2974 assert_eq!(
2975 complexify(
2976 Some([3, 11]),
2977 None,
2978 "python_full_version == '3.8' \
2979 or python_full_version == '3.11' \
2980 or python_full_version == '3.12'"
2981 ),
2982 m("python_full_version == '3.11' or python_full_version == '3.12'"),
2983 );
2984
2985 assert_eq!(
2992 complexify(
2993 Some([3, 12]),
2994 None,
2995 "python_full_version < '3.10' or python_full_version >= '3.10'"
2996 ),
2997 m("python_full_version >= '3.12'"),
2998 );
2999 }
3000
3001 #[test]
3002 fn simplified_markers() {
3003 let simplify =
3008 |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
3009 let lower = lower
3010 .map(|release| Bound::Included(Version::new(release)))
3011 .unwrap_or(Bound::Unbounded);
3012 let upper = upper
3013 .map(|release| Bound::Excluded(Version::new(release)))
3014 .unwrap_or(Bound::Unbounded);
3015 m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
3016 };
3017
3018 assert_eq!(
3019 simplify(
3020 Some([3, 8]),
3021 None,
3022 "python_full_version >= '3.8' and python_full_version < '3.10'"
3023 ),
3024 m("python_full_version < '3.10'"),
3025 );
3026 assert_eq!(
3027 simplify(Some([3, 8]), None, "python_full_version < '3.7'"),
3028 m("python_full_version < '0' and python_full_version > '0'"),
3030 );
3031 assert_eq!(
3032 simplify(
3033 Some([3, 8]),
3034 Some([3, 11]),
3035 "python_full_version == '3.7.*' \
3036 or python_full_version == '3.8.*' \
3037 or python_full_version == '3.10.*' \
3038 or python_full_version == '3.11.*' \
3039 "
3040 ),
3041 m("python_full_version != '3.9.*'"),
3045 );
3046 assert_eq!(
3047 simplify(
3048 Some([3, 8]),
3049 None,
3050 "python_full_version >= '3.8' and sys_platform == 'win32'"
3051 ),
3052 m("sys_platform == 'win32'"),
3053 );
3054 assert_eq!(
3055 simplify(
3056 Some([3, 8]),
3057 None,
3058 "python_full_version >= '3.9' \
3059 and (sys_platform == 'win32' or python_full_version >= '3.8')",
3060 ),
3061 m("python_full_version >= '3.9'"),
3062 );
3063 }
3064}