pep508_rs/marker/
tree.rs

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/// Ways in which marker evaluation can fail
23#[derive(Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Clone, Copy)]
24pub enum MarkerWarningKind {
25    /// Using an old name from PEP 345 instead of the modern equivalent
26    /// <https://peps.python.org/pep-0345/#environment-markers>
27    DeprecatedMarkerName,
28    /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as
29    /// `extra > "perf"` or `extra == os_name`
30    ExtraInvalidComparison,
31    /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"`
32    LexicographicComparison,
33    /// Comparing two markers, such as `os_name != sys_implementation`
34    MarkerMarkerComparison,
35    /// Failed to parse a PEP 440 version or version specifier, e.g. `>=1<2`
36    Pep440Error,
37    /// Comparing two strings, such as `"3.9" > "3.10"`
38    StringStringComparison,
39}
40
41/// Those environment markers with a PEP 440 version as value such as `python_version`
42#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
43#[allow(clippy::enum_variant_names)]
44pub enum MarkerValueVersion {
45    /// `implementation_version`
46    ImplementationVersion,
47    /// `python_full_version`
48    PythonFullVersion,
49    /// `python_version`
50    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/// Those environment markers with an arbitrary string as value such as `sys_platform`
64#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
65pub enum MarkerValueString {
66    /// `implementation_name`
67    ImplementationName,
68    /// `os_name`
69    OsName,
70    /// Deprecated `os.name` from <https://peps.python.org/pep-0345/#environment-markers>
71    OsNameDeprecated,
72    /// `platform_machine`
73    PlatformMachine,
74    /// Deprecated `platform.machine` from <https://peps.python.org/pep-0345/#environment-markers>
75    PlatformMachineDeprecated,
76    /// `platform_python_implementation`
77    PlatformPythonImplementation,
78    /// Deprecated `platform.python_implementation` from <https://peps.python.org/pep-0345/#environment-markers>
79    PlatformPythonImplementationDeprecated,
80    /// Deprecated `python_implementation` from <https://github.com/pypa/packaging/issues/72>
81    PythonImplementationDeprecated,
82    /// `platform_release`
83    PlatformRelease,
84    /// `platform_system`
85    PlatformSystem,
86    /// `platform_version`
87    PlatformVersion,
88    /// Deprecated `platform.version` from <https://peps.python.org/pep-0345/#environment-markers>
89    PlatformVersionDeprecated,
90    /// `sys_platform`
91    SysPlatform,
92    /// Deprecated `sys.platform` from <https://peps.python.org/pep-0345/#environment-markers>
93    SysPlatformDeprecated,
94}
95
96impl Display for MarkerValueString {
97    /// Normalizes deprecated names to the proper ones
98    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/// One of the predefined environment values
119///
120/// <https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers>
121#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
122pub enum MarkerValue {
123    /// Those environment markers with a PEP 440 version as value such as `python_version`
124    MarkerEnvVersion(MarkerValueVersion),
125    /// Those environment markers with an arbitrary string as value such as `sys_platform`
126    MarkerEnvString(MarkerValueString),
127    /// `extra`. This one is special because it's a list and not env but user given
128    Extra,
129    /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows"
130    QuotedString(String),
131}
132
133impl FromStr for MarkerValue {
134    type Err = String;
135
136    /// This is specifically for the reserved values
137    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/// How to compare key and value, such as by `==`, `>` or `not in`
187#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
188pub enum MarkerOperator {
189    /// `==`
190    Equal,
191    /// `!=`
192    NotEqual,
193    /// `>`
194    GreaterThan,
195    /// `>=`
196    GreaterEqual,
197    /// `<`
198    LessThan,
199    /// `<=`
200    LessEqual,
201    /// `~=`
202    TildeEqual,
203    /// `in`
204    In,
205    /// `not in`
206    NotIn,
207    /// The inverse of the `in` operator.
208    ///
209    /// This is not a valid operator when parsing but is used for normalizing
210    /// marker trees.
211    Contains,
212    /// The inverse of the `not in` operator.
213    ///
214    /// This is not a valid operator when parsing but is used for normalizing
215    /// marker trees.
216    NotContains,
217}
218
219impl MarkerOperator {
220    /// Compare two versions, returning `None` for `in` and `not in`.
221    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    /// Inverts this marker operator.
235    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    /// Negates this marker operator.
252    ///
253    /// If a negation doesn't exist, which is only the case for ~=, then this
254    /// returns `None`.
255    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    /// Returns the marker operator and value whose union represents the given range.
272    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    /// Returns a value specifier representing the given lower bound.
292    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    /// Returns a value specifier representing the given upper bound.
301    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    /// PEP 508 allows arbitrary whitespace between "not" and "in", and so do we
314    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                    // start with not
327                    .strip_prefix("not")
328                    // ends with in
329                    .and_then(|space_in| space_in.strip_suffix("in"))
330                    // and has only whitespace in between
331                    .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/// Helper type with a [Version] and its original text
358#[derive(Clone, Debug, Eq, Hash, PartialEq)]
359pub struct StringVersion {
360    /// Original unchanged string
361    pub string: String,
362    /// Parsed version
363    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/// The [`ExtraName`] value used in `extra` markers.
420#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
421pub enum MarkerValueExtra {
422    /// A valid [`ExtraName`].
423    Extra(ExtraName),
424    /// An invalid name, preserved as an arbitrary string.
425    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/// Represents one clause such as `python_version > "3.8"`.
447#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
448#[allow(missing_docs)]
449pub enum MarkerExpression {
450    /// A version expression, e.g. `<version key> <version op> <quoted PEP 440 version>`.
451    ///
452    /// Inverted version expressions, such as `<version> <version op> <version key>`, are also
453    /// normalized to this form.
454    Version {
455        key: MarkerValueVersion,
456        specifier: VersionSpecifier,
457    },
458    /// A version in list expression, e.g. `<version key> in <quoted list of PEP 440 versions>`.
459    ///
460    /// A special case of [`MarkerExpression::String`] with the [`MarkerOperator::In`] operator for
461    /// [`MarkerValueVersion`] values.
462    ///
463    /// See [`parse::parse_version_in_expr`] for details on the supported syntax.
464    ///
465    /// Negated expressions, using "not in" are represented using `negated = true`.
466    VersionIn {
467        key: MarkerValueVersion,
468        versions: Vec<Version>,
469        negated: bool,
470    },
471    /// An string marker comparison, e.g. `sys_platform == '...'`.
472    ///
473    /// Inverted string expressions, e.g `'...' == sys_platform`, are also normalized to this form.
474    String {
475        key: MarkerValueString,
476        operator: MarkerOperator,
477        value: String,
478    },
479    /// `extra <extra op> '...'` or `'...' <extra op> extra`.
480    Extra {
481        operator: ExtraOperator,
482        name: MarkerValueExtra,
483    },
484}
485
486/// The operator for an extra expression, either '==' or '!='.
487#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
488pub enum ExtraOperator {
489    /// `==`
490    Equal,
491    /// `!=`
492    NotEqual,
493}
494
495impl ExtraOperator {
496    /// Creates a [`ExtraOperator`] from an equivalent [`MarkerOperator`].
497    ///
498    /// Returns `None` if the operator is not supported for extras.
499    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    /// Negates this operator.
508    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    /// Parse a [`MarkerExpression`] from a string with the given reporter.
527    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    /// Parse a [`MarkerExpression`] from a string.
549    ///
550    /// Returns `None` if the expression consists entirely of meaningless expressions
551    /// that are ignored, such as `os_name ~= 'foo'`.
552    #[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/// Represents one or more nested marker expressions with and/or/parentheses.
600///
601/// Marker trees are canonical, meaning any two functionally equivalent markers
602/// will compare equally. Markers also support efficient polynomial-time operations,
603/// such as conjunction and disjunction.
604// TODO(ibraheem): decide whether we want to implement `Copy` for marker trees
605#[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    /// Like [`FromStr::from_str`], but the caller chooses the return type generic.
634    pub fn parse_str<T: Pep508Url>(markers: &str) -> Result<Self, Pep508Error<T>> {
635        parse::parse_markers(markers, &mut TracingReporter)
636    }
637
638    /// Parse a [`MarkerTree`] from a string with the given reporter.
639    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    /// An empty marker that always evaluates to `true`.
647    pub const TRUE: MarkerTree = MarkerTree(NodeId::TRUE);
648
649    /// An unsatisfiable marker that always evaluates to `false`.
650    pub const FALSE: MarkerTree = MarkerTree(NodeId::FALSE);
651
652    /// Returns a marker tree for a single expression.
653    pub fn expression(expr: MarkerExpression) -> MarkerTree {
654        MarkerTree(INTERNER.lock().expression(expr))
655    }
656
657    /// Whether the marker always evaluates to `true`.
658    ///
659    /// If this method returns `true`, it is definitively known that the marker will
660    /// evaluate to `true` in any environment. However, this method may return false
661    /// negatives, i.e. it may not be able to detect that a marker is always true for
662    /// complex expressions.
663    pub fn is_true(&self) -> bool {
664        self.0.is_true()
665    }
666
667    /// Whether the marker always evaluates to `false`, i.e. the expression is not
668    /// satisfiable in any environment.
669    ///
670    /// If this method returns `true`, it is definitively known that the marker will
671    /// evaluate to `false` in any environment. However, this method may return false
672    /// negatives, i.e. it may not be able to detect that a marker is unsatisfiable
673    /// for complex expressions.
674    pub fn is_false(&self) -> bool {
675        self.0.is_false()
676    }
677
678    /// Returns a new marker tree that is the negation of this one.
679    #[must_use]
680    pub fn negate(&self) -> MarkerTree {
681        MarkerTree(self.0.not())
682    }
683
684    /// Combine this marker tree with the one given via a conjunction.
685    #[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    /// Combine this marker tree with the one given via a disjunction.
691    #[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    /// Returns `true` if there is no environment in which both marker trees can apply,
697    /// i.e. their conjunction is always `false`.
698    ///
699    /// If this method returns `true`, it is definitively known that the two markers can
700    /// never both evaluate to `true` in a given environment. However, this method may return
701    /// false negatives, i.e. it may not be able to detect that two markers are disjoint for
702    /// complex expressions.
703    pub fn is_disjoint(&self, other: &MarkerTree) -> bool {
704        INTERNER.lock().is_disjoint(self.0, other.0)
705    }
706
707    /// Returns the contents of this marker tree, if it contains at least one expression.
708    ///
709    /// If the marker is `true`, this method will return `None`.
710    /// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
711    ///
712    /// The returned type implements [`Display`] and [`serde::Serialize`].
713    pub fn contents(&self) -> Option<MarkerTreeContents> {
714        if self.is_true() {
715            return None;
716        }
717
718        Some(MarkerTreeContents(self.clone()))
719    }
720
721    /// Returns a simplified string representation of this marker, if it contains at least one
722    /// expression.
723    ///
724    /// If the marker is `true`, this method will return `None`.
725    /// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
726    pub fn try_to_string(&self) -> Option<String> {
727        self.contents().map(|contents| contents.to_string())
728    }
729
730    /// Returns the underlying [`MarkerTreeKind`] of the root node.
731    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    /// Returns a simplified DNF expression for this marker tree.
798    pub fn to_dnf(&self) -> Vec<Vec<MarkerExpression>> {
799        simplify::to_dnf(self)
800    }
801
802    /// Does this marker apply in the given environment?
803    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    /// Evaluates this marker tree against an optional environment and a
809    /// possibly empty sequence of extras.
810    ///
811    /// When an environment is not provided, all marker expressions based on
812    /// the environment evaluate to `true`. That is, this provides environment
813    /// independent marker evaluation. In practice, this means only the extras
814    /// are evaluated when an environment is not provided.
815    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    /// Same as [`Self::evaluate`], but instead of using logging to warn, you can pass your own
828    /// handler for warnings
829    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                    // todo(ibraheem): avoid cloning here, `contains` should accept `&impl Borrow<V>`
878                    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    /// Checks if the requirement should be activated with the given set of active extras and a set
910    /// of possible python versions (from `requires-python`) without evaluating the remaining
911    /// environment markers, i.e. if there is potentially an environment that could activate this
912    /// requirement.
913    ///
914    /// Note that unlike [`Self::evaluate`] this does not perform any checks for bogus expressions but
915    /// will simply return true. As caller you should separately perform a check with an environment
916    /// and forward all warnings.
917    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    /// Checks if the requirement should be activated with the given set of active extras without evaluating
958    /// the remaining environment markers, i.e. if there is potentially an environment that could activate this
959    /// requirement.
960    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    /// Same as [`Self::evaluate`], but instead of using logging to warn, you get a Vec with all
988    /// warnings collected
989    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    /// Report the deprecated marker from <https://peps.python.org/pep-0345/#environment-markers>
1004    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    /// Find a top level `extra == "..."` expression.
1084    ///
1085    /// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
1086    /// main conjunction.
1087    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            // Because the marker tree is in DNF form, we must verify that the extra expression is part
1101            // of all solutions to this marker.
1102            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    /// Simplify this marker by *assuming* that the Python version range
1117    /// provided is true and that the complement of it is false.
1118    ///
1119    /// For example, with `requires-python = '>=3.8'` and a marker tree of
1120    /// `python_full_version >= '3.8' and python_full_version <= '3.10'`, this
1121    /// would result in a marker of `python_full_version <= '3.10'`.
1122    ///
1123    /// This is useful when one wants to write "simpler" markers in a
1124    /// particular context with a bound on the supported Python versions.
1125    /// In general, the simplified markers returned shouldn't be used for
1126    /// evaluation. Instead, they should be turned back into their more
1127    /// "complex" form first.
1128    ///
1129    /// Note that simplifying a marker and then complexifying it, even
1130    /// with the same Python version bounds, is a lossy operation. For
1131    /// example, simplifying `python_version < '3.7'` with `requires-python
1132    /// = ">=3.8"` will result in a marker that always returns false (e.g.,
1133    /// `python_version < '0'`). Therefore, complexifying an always-false
1134    /// marker will result in a marker that is still always false, despite
1135    /// the fact that the original marker was true for `<3.7`. Therefore,
1136    /// simplifying should only be done as a one-way transformation when it is
1137    /// known that `requires-python` reflects an eternal lower bound on the
1138    /// results of that simplification. (If `requires-python` changes, then one
1139    /// should reconstitute all relevant markers from the source data.)
1140    #[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    /// Complexify marker tree by requiring the given Python version range
1155    /// to be true in order for this marker tree to evaluate to true in all
1156    /// circumstances.
1157    ///
1158    /// For example, with `requires-python = '>=3.8'` and a marker tree of
1159    /// `python_full_version <= '3.10'`, this would result in a marker of
1160    /// `python_full_version >= '3.8' and python_full_version <= '3.10'`.
1161    #[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    /// Remove the extras from a marker, returning `None` if the marker tree evaluates to `true`.
1176    ///
1177    /// Any `extra` markers that are always `true` given the provided extras will be removed.
1178    /// Any `extra` markers that are always `false` given the provided extras will be left
1179    /// unchanged.
1180    ///
1181    /// For example, if `dev` is a provided extra, given `sys_platform == 'linux' and extra == 'dev'`,
1182    /// the marker will be simplified to `sys_platform == 'linux'`.
1183    #[must_use]
1184    pub fn simplify_extras(self, extras: &[ExtraName]) -> MarkerTree {
1185        self.simplify_extras_with(|name| extras.contains(name))
1186    }
1187
1188    /// Remove the extras from a marker, returning `None` if the marker tree evaluates to `true`.
1189    ///
1190    /// Any `extra` markers that are always `true` given the provided predicate will be removed.
1191    /// Any `extra` markers that are always `false` given the provided predicate will be left
1192    /// unchanged.
1193    ///
1194    /// For example, if `is_extra('dev')` is true, given
1195    /// `sys_platform == 'linux' and extra == 'dev'`, the marker will be simplified to
1196    /// `sys_platform == 'linux'`.
1197    #[must_use]
1198    pub fn simplify_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> MarkerTree {
1199        // Because `simplify_extras_with_impl` is recursive, and we need to use
1200        // our predicate in recursive calls, we need the predicate itself to
1201        // have some indirection (or else we'd have to clone it). To avoid a
1202        // recursive type at codegen time, we just introduce the indirection
1203        // here, but keep the calling API ergonomic.
1204        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    /// Formats a [`MarkerTree`] as a graph.
1233    ///
1234    /// This is useful for debugging when one wants to look at a
1235    /// representation of a `MarkerTree` that is more faithful to its
1236    /// internal representation.
1237    pub fn debug_graph(&self) -> MarkerTreeDebugGraph<'_> {
1238        MarkerTreeDebugGraph { marker: self }
1239    }
1240
1241    /// Formats a [`MarkerTree`] in its "raw" representation.
1242    ///
1243    /// This is useful for debugging when one wants to look at a
1244    /// representation of a `MarkerTree` that is precisely identical
1245    /// to its internal representation.
1246    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/// Formats a [`MarkerTree`] as a graph.
1340///
1341/// This type is created by the [`MarkerTree::debug_graph`] routine.
1342#[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/// Formats a [`MarkerTree`] using its raw internals.
1354///
1355/// This is very verbose and likely only useful if you're working
1356/// on the internals of this crate.
1357///
1358/// This type is created by the [`MarkerTree::debug_raw`] routine.
1359#[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/// The underlying kind of an arbitrary node in a [`MarkerTree`].
1372///
1373/// A marker tree is represented as an algebraic decision tree with two terminal nodes
1374/// `True` or `False`. The edges of a given node correspond to a particular assignment of
1375/// a value to that variable.
1376#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
1377pub enum MarkerTreeKind<'a> {
1378    /// An empty marker that always evaluates to `true`.
1379    True,
1380    /// An unsatisfiable marker that always evaluates to `false`.
1381    False,
1382    /// A version expression.
1383    Version(VersionMarkerTree<'a>),
1384    /// A string expression.
1385    String(StringMarkerTree<'a>),
1386    /// A string expression with the `in` operator.
1387    In(InMarkerTree<'a>),
1388    /// A string expression with the `contains` operator.
1389    Contains(ContainsMarkerTree<'a>),
1390    /// A string expression.
1391    Extra(ExtraMarkerTree<'a>),
1392}
1393
1394/// A version marker node, such as `python_version < '3.7'`.
1395#[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    /// The key for this node.
1404    pub fn key(&self) -> &MarkerValueVersion {
1405        &self.key
1406    }
1407
1408    /// The edges of this node, corresponding to possible output ranges of the given variable.
1409    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/// A string marker node, such as `os_name == 'Linux'`.
1431#[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    /// The key for this node.
1440    pub fn key(&self) -> &MarkerValueString {
1441        &self.key
1442    }
1443
1444    /// The edges of this node, corresponding to possible output ranges of the given variable.
1445    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/// A string marker node with the `in` operator, such as `os_name in 'WindowsLinux'`.
1467#[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    /// The key (LHS) for this expression.
1477    pub fn key(&self) -> &MarkerValueString {
1478        &self.key
1479    }
1480
1481    /// The value (RHS) for this expression.
1482    pub fn value(&self) -> &str {
1483        self.value
1484    }
1485
1486    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1487    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1488        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1489    }
1490
1491    /// Returns the subtree associated with the given edge value.
1492    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/// A string marker node with inverse of the `in` operator, such as `'nux' in os_name`.
1517#[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    /// The key (LHS) for this expression.
1527    pub fn key(&self) -> &MarkerValueString {
1528        &self.key
1529    }
1530
1531    /// The value (RHS) for this expression.
1532    pub fn value(&self) -> &str {
1533        self.value
1534    }
1535
1536    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1537    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1538        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1539    }
1540
1541    /// Returns the subtree associated with the given edge value.
1542    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/// A node representing the existence or absence of a given extra, such as `extra == 'bar'`.
1567#[derive(PartialEq, Eq, Clone, Debug)]
1568pub struct ExtraMarkerTree<'a> {
1569    name: &'a MarkerValueExtra,
1570    high: NodeId,
1571    low: NodeId,
1572}
1573
1574impl ExtraMarkerTree<'_> {
1575    /// Returns the name of the extra in this expression.
1576    pub fn name(&self) -> &MarkerValueExtra {
1577        self.name
1578    }
1579
1580    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1581    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1582        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1583    }
1584
1585    /// Returns the subtree associated with the given edge value.
1586    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/// A marker tree that contains at least one expression.
1610///
1611/// See [`MarkerTree::contents`] for details.
1612#[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        // Normalize all `false` expressions to the same trivially false expression.
1645        if self.0.is_false() {
1646            return write!(f, "python_version < '0'");
1647        }
1648
1649        // Write the output in DNF form.
1650        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    /// Copied from <https://github.com/pypa/packaging/blob/85ff971a250dc01db188ef9775499c15553a8c95/tests/test_markers.py#L175-L221>
1737    #[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        // Note that `3.12.1` will still match.
1799        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        // Meaningless expressions are ignored, so this is always true.
2023        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        // Given `os_name == "nt" and extra == "dev"`, simplify to `os_name == "nt"`.
2154        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        // Given `os_name == "nt" or extra == "dev"`, remove the marker entirely.
2160        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        // Given `extra == "dev"`, remove the marker entirely.
2165        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        // Given `extra == "dev" and extra == "test"`, simplify to `extra == "test"`.
2170        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        // Given `os_name == "nt" and extra == "test"`, don't simplify.
2176        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        // Given `os_name == "nt" and (python_version == "3.7" or extra == "dev")`, simplify to
2183        // `os_name == "nt".
2184        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        // Given `os_name == "nt" or (python_version == "3.7" and extra == "dev")`, simplify to
2193        // `os_name == "nt" or python_version == "3.7"`.
2194        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        // Technically these is are valid substring comparison, but we do not allow them.
2211        // e.g., using a version with patch components with `python_version` is considered
2212        // impossible to satisfy since the value it is truncated at the minor version
2213        assert_false("python_version in '3.9.0'");
2214        // e.g., using a version that is not PEP 440 compliant is considered arbitrary
2215        assert_true("python_version in 'foo'");
2216        // e.g., including `*` versions, which would require tracking a version specifier
2217        assert_true("python_version in '3.9.*'");
2218        // e.g., when non-whitespace separators are present
2219        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        // e.g, when one of the values cannot be true
2224        // TODO(zanieb): This seems like a quirk of the `python_full_version` normalization, this
2225        // should just act as though the patch version isn't present
2226        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        // `<version> in`
2235        // e.g., when the range is not contiguous
2236        assert_simplifies(
2237            "python_version in '3.9 3.11'",
2238            "python_full_version == '3.9.*' or python_full_version == '3.11.*'",
2239        );
2240        // e.g., when the range is contiguous
2241        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        // e.g., with `implementation_version` instead of `python_version`
2246        assert_simplifies(
2247            "implementation_version in '3.9 3.11'",
2248            "implementation_version == '3.9' or implementation_version == '3.11'",
2249        );
2250
2251        // '<version> not in'
2252        // e.g., when the range is not contiguous
2253        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        // e.g, when the range is contiguous
2258        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        // e.g., with `implementation_version` instead of `python_version`
2263        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        // a quirk of how pubgrub works, but this is considered part of normalization
2293        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        // flatten nested expressions
2342        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        // complex expressions
2353        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        // post-normalization filtering
2384        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        // normalize out redundant ranges
2390        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        // normalize `!=` operators
2402        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        // normalize out redundant expressions
2420        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        // ~= is nonsense on string markers, so the markers is ignored and always
2490        // evaluates to true. Thus the negation always returns false.
2491        assert_eq!(m("sys_platform ~= 'linux'").negate(), MarkerTree::FALSE);
2492
2493        // As above, arbitrary exprs remain arbitrary.
2494        assert_eq!(m("'foo' == 'bar'").negate(), MarkerTree::FALSE);
2495
2496        // Conjunctions
2497        assert_eq!(
2498            m("os_name == 'bar' and os_name == 'foo'").negate(),
2499            m("os_name != 'bar' or os_name != 'foo'")
2500        );
2501        // Disjunctions
2502        assert_eq!(
2503            m("os_name == 'bar' or os_name == 'foo'").negate(),
2504            m("os_name != 'bar' and os_name != 'foo'")
2505        );
2506
2507        // Always true negates to always false!
2508        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        // This expression should simplify to:
2517        // `(implementation_name == 'pypy' and sys_platform != 'win32')
2518        //   or (sys_platform == 'win32' or os_name != 'nt')
2519        //   or (implementation != 'pypy' or os_name == 'nt')`
2520        //
2521        // However, simplifying this expression is NP-complete and requires an exponential
2522        // algorithm such as Quine-McCluskey, which is not currently implemented.
2523        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        // This is another case we cannot simplify fully, depending on the variable order.
2535        // The expression is equivalent to `sys_platform == 'x' or (os_name == 'Linux' and platform_system == 'win32')`.
2536        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        // Again, the extra `<3.7` and `>=3.9` expressions cannot be seen as redundant due to them being interdependent.
2554        // TODO(ibraheem): We might be able to simplify these by checking for the negation of the combined ranges before we split them.
2555        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        // `python_version == 'Linux'` is nonsense and ignored, thus the first marker
2645        // is always `true` and not disjoint.
2646        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        // basic version bounds checking should still work with lexicographical comparisons
2699        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        // complicated `in` intersections are not supported
2711        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        // Takes optional lower (inclusive) and upper (exclusive)
2901        // bounds representing `requires-python` and a "simplified"
2902        // marker, and returns the "complexified" marker. That is, a
2903        // marker that embeds the `requires-python` constraint into it.
2904        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            // Kinda weird, but this normalizes to `false`, just like the above.
2930            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            // Kinda weird, but this normalizes to `false`, just like the above.
2936            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            // Kinda weird, but this normalizes to `false`, just like the above.
2950            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            // Kinda weird, but this normalizes to `false`, just like the above.
2964            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        // Tests a tricky case where if a marker is always true, then
2986        // complexifying it will proceed correctly by adding the
2987        // requires-python constraint. This is a regression test for
2988        // an early implementation that special cased the "always
2989        // true" case to return "always true" regardless of the
2990        // requires-python bounds.
2991        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        // Takes optional lower (inclusive) and upper (exclusive)
3004        // bounds representing `requires-python` and a "complexified"
3005        // marker, and returns the "simplified" marker. That is, a
3006        // marker that assumes `requires-python` is true.
3007        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            // Kinda weird, but this normalizes to `false`, just like the above.
3029            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            // Given `requires-python = '>=3.8,<3.11'`, only `3.8.*`
3042            // and `3.10.*` can possibly be true. So this simplifies
3043            // to `!= 3.9.*`.
3044            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}