Skip to main content

uv_pep508/marker/
tree.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::{self, Display, Formatter};
4use std::ops::{Bound, Deref};
5use std::str::FromStr;
6
7use arcstr::ArcStr;
8use itertools::Itertools;
9use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
10use version_ranges::Ranges;
11
12use uv_normalize::{ExtraName, GroupName};
13use uv_pep440::{Version, VersionParseError, VersionSpecifier};
14
15use super::algebra::{Edges, INTERNER, NodeId, Variable};
16use super::simplify;
17use crate::cursor::Cursor;
18use crate::marker::lowering::{
19    CanonicalMarkerListPair, CanonicalMarkerValueString, CanonicalMarkerValueVersion,
20};
21use crate::marker::parse;
22use crate::{
23    CanonicalMarkerValueExtra, MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url,
24    Reporter, TracingReporter,
25};
26
27/// Ways in which marker evaluation can fail
28#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
29pub enum MarkerWarningKind {
30    /// Using an old name from PEP 345 instead of the modern equivalent
31    /// <https://peps.python.org/pep-0345/#environment-markers>
32    DeprecatedMarkerName,
33    /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as
34    /// `extra > "perf"` or `extra == os_name`
35    ExtraInvalidComparison,
36    /// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as
37    /// `extras > "perf"` or `extras == os_name`
38    ExtrasInvalidComparison,
39    /// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`,
40    /// such as `dependency_groups > "perf"` or `dependency_groups == os_name`
41    DependencyGroupsInvalidComparison,
42    /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"`
43    LexicographicComparison,
44    /// Comparing two markers, such as `os_name != sys_implementation`
45    MarkerMarkerComparison,
46    /// Failed to parse a PEP 440 version or version specifier, e.g. `>=1<2`
47    Pep440Error,
48    /// Comparing two strings, such as `"3.9" > "3.10"`
49    StringStringComparison,
50}
51
52/// Those environment markers with a PEP 440 version as value such as `python_version`
53#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
54pub enum MarkerValueVersion {
55    /// `implementation_version`
56    ImplementationVersion,
57    /// `python_full_version`
58    PythonFullVersion,
59    /// `python_version`
60    PythonVersion,
61}
62
63impl Display for MarkerValueVersion {
64    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::ImplementationVersion => f.write_str("implementation_version"),
67            Self::PythonFullVersion => f.write_str("python_full_version"),
68            Self::PythonVersion => f.write_str("python_version"),
69        }
70    }
71}
72
73/// Those environment markers with an arbitrary string as value such as `sys_platform`
74#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
75pub enum MarkerValueString {
76    /// `implementation_name`
77    ImplementationName,
78    /// `os_name`
79    OsName,
80    /// Deprecated `os.name` from <https://peps.python.org/pep-0345/#environment-markers>
81    OsNameDeprecated,
82    /// `platform_machine`
83    PlatformMachine,
84    /// Deprecated `platform.machine` from <https://peps.python.org/pep-0345/#environment-markers>
85    PlatformMachineDeprecated,
86    /// `platform_python_implementation`
87    PlatformPythonImplementation,
88    /// Deprecated `platform.python_implementation` from <https://peps.python.org/pep-0345/#environment-markers>
89    PlatformPythonImplementationDeprecated,
90    /// Deprecated `python_implementation` from <https://github.com/pypa/packaging/issues/72>
91    PythonImplementationDeprecated,
92    /// `platform_release`
93    PlatformRelease,
94    /// `platform_system`
95    PlatformSystem,
96    /// `platform_version`
97    PlatformVersion,
98    /// Deprecated `platform.version` from <https://peps.python.org/pep-0345/#environment-markers>
99    PlatformVersionDeprecated,
100    /// `sys_platform`
101    SysPlatform,
102    /// Deprecated `sys.platform` from <https://peps.python.org/pep-0345/#environment-markers>
103    SysPlatformDeprecated,
104}
105
106impl Display for MarkerValueString {
107    /// Normalizes deprecated names to the proper ones
108    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
109        match self {
110            Self::ImplementationName => f.write_str("implementation_name"),
111            Self::OsName | Self::OsNameDeprecated => f.write_str("os_name"),
112            Self::PlatformMachine | Self::PlatformMachineDeprecated => {
113                f.write_str("platform_machine")
114            }
115            Self::PlatformPythonImplementation
116            | Self::PlatformPythonImplementationDeprecated
117            | Self::PythonImplementationDeprecated => f.write_str("platform_python_implementation"),
118            Self::PlatformRelease => f.write_str("platform_release"),
119            Self::PlatformSystem => f.write_str("platform_system"),
120            Self::PlatformVersion | Self::PlatformVersionDeprecated => {
121                f.write_str("platform_version")
122            }
123            Self::SysPlatform | Self::SysPlatformDeprecated => f.write_str("sys_platform"),
124        }
125    }
126}
127
128/// Those markers with exclusively `in` and `not in` operators.
129///
130/// Contains PEP 751 lockfile markers.
131#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
132pub enum MarkerValueList {
133    /// `extras`. This one is special because it's a list, and user-provided
134    Extras,
135    /// `dependency_groups`. This one is special because it's a list, and user-provided
136    DependencyGroups,
137}
138
139impl Display for MarkerValueList {
140    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
141        match self {
142            Self::Extras => f.write_str("extras"),
143            Self::DependencyGroups => f.write_str("dependency_groups"),
144        }
145    }
146}
147
148/// One of the predefined environment values
149///
150/// <https://packaging.python.org/en/latest/specifications/dependency-specifiers/#environment-markers>
151#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
152pub enum MarkerValue {
153    /// Those environment markers with a PEP 440 version as value such as `python_version`
154    MarkerEnvVersion(MarkerValueVersion),
155    /// Those environment markers with an arbitrary string as value such as `sys_platform`
156    MarkerEnvString(MarkerValueString),
157    /// Those markers with exclusively `in` and `not in` operators
158    MarkerEnvList(MarkerValueList),
159    /// `extra`. This one is special because it's a list, and user-provided
160    Extra,
161    /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows"
162    QuotedString(ArcStr),
163}
164
165impl FromStr for MarkerValue {
166    type Err = String;
167
168    /// This is specifically for the reserved values
169    fn from_str(s: &str) -> Result<Self, Self::Err> {
170        let value = match s {
171            "implementation_name" => Self::MarkerEnvString(MarkerValueString::ImplementationName),
172            "implementation_version" => {
173                Self::MarkerEnvVersion(MarkerValueVersion::ImplementationVersion)
174            }
175            "os_name" => Self::MarkerEnvString(MarkerValueString::OsName),
176            "os.name" => Self::MarkerEnvString(MarkerValueString::OsNameDeprecated),
177            "platform_machine" => Self::MarkerEnvString(MarkerValueString::PlatformMachine),
178            "platform.machine" => {
179                Self::MarkerEnvString(MarkerValueString::PlatformMachineDeprecated)
180            }
181            "platform_python_implementation" => {
182                Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementation)
183            }
184            "platform.python_implementation" => {
185                Self::MarkerEnvString(MarkerValueString::PlatformPythonImplementationDeprecated)
186            }
187            "python_implementation" => {
188                Self::MarkerEnvString(MarkerValueString::PythonImplementationDeprecated)
189            }
190            "platform_release" => Self::MarkerEnvString(MarkerValueString::PlatformRelease),
191            "platform_system" => Self::MarkerEnvString(MarkerValueString::PlatformSystem),
192            "platform_version" => Self::MarkerEnvString(MarkerValueString::PlatformVersion),
193            "platform.version" => {
194                Self::MarkerEnvString(MarkerValueString::PlatformVersionDeprecated)
195            }
196            "python_full_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonFullVersion),
197            "python_version" => Self::MarkerEnvVersion(MarkerValueVersion::PythonVersion),
198            "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform),
199            "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated),
200            "extras" => Self::MarkerEnvList(MarkerValueList::Extras),
201            "dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups),
202            "extra" => Self::Extra,
203            _ => return Err(format!("Invalid key: {s}")),
204        };
205        Ok(value)
206    }
207}
208
209impl Display for MarkerValue {
210    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
211        match self {
212            Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f),
213            Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f),
214            Self::MarkerEnvList(marker_value_contains) => marker_value_contains.fmt(f),
215            Self::Extra => f.write_str("extra"),
216            Self::QuotedString(value) => write!(f, "'{value}'"),
217        }
218    }
219}
220
221/// How to compare key and value, such as by `==`, `>` or `not in`
222#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
223pub enum MarkerOperator {
224    /// `==`
225    Equal,
226    /// `!=`
227    NotEqual,
228    /// `>`
229    GreaterThan,
230    /// `>=`
231    GreaterEqual,
232    /// `<`
233    LessThan,
234    /// `<=`
235    LessEqual,
236    /// `~=`
237    TildeEqual,
238    /// `in`
239    In,
240    /// `not in`
241    NotIn,
242    /// The inverse of the `in` operator.
243    ///
244    /// This is not a valid operator when parsing but is used for normalizing
245    /// marker trees.
246    Contains,
247    /// The inverse of the `not in` operator.
248    ///
249    /// This is not a valid operator when parsing but is used for normalizing
250    /// marker trees.
251    NotContains,
252}
253
254impl MarkerOperator {
255    /// Compare two versions, returning `None` for `in` and `not in`.
256    pub(crate) fn to_pep440_operator(self) -> Option<uv_pep440::Operator> {
257        match self {
258            Self::Equal => Some(uv_pep440::Operator::Equal),
259            Self::NotEqual => Some(uv_pep440::Operator::NotEqual),
260            Self::GreaterThan => Some(uv_pep440::Operator::GreaterThan),
261            Self::GreaterEqual => Some(uv_pep440::Operator::GreaterThanEqual),
262            Self::LessThan => Some(uv_pep440::Operator::LessThan),
263            Self::LessEqual => Some(uv_pep440::Operator::LessThanEqual),
264            Self::TildeEqual => Some(uv_pep440::Operator::TildeEqual),
265            _ => None,
266        }
267    }
268
269    /// Inverts this marker operator.
270    pub(crate) fn invert(self) -> Self {
271        match self {
272            Self::LessThan => Self::GreaterThan,
273            Self::LessEqual => Self::GreaterEqual,
274            Self::GreaterThan => Self::LessThan,
275            Self::GreaterEqual => Self::LessEqual,
276            Self::Equal => Self::Equal,
277            Self::NotEqual => Self::NotEqual,
278            Self::TildeEqual => Self::TildeEqual,
279            Self::In => Self::Contains,
280            Self::NotIn => Self::NotContains,
281            Self::Contains => Self::In,
282            Self::NotContains => Self::NotIn,
283        }
284    }
285
286    /// Negates this marker operator.
287    ///
288    /// If a negation doesn't exist, which is only the case for ~=, then this
289    /// returns `None`.
290    pub(crate) fn negate(self) -> Option<Self> {
291        Some(match self {
292            Self::Equal => Self::NotEqual,
293            Self::NotEqual => Self::Equal,
294            Self::TildeEqual => return None,
295            Self::LessThan => Self::GreaterEqual,
296            Self::LessEqual => Self::GreaterThan,
297            Self::GreaterThan => Self::LessEqual,
298            Self::GreaterEqual => Self::LessThan,
299            Self::In => Self::NotIn,
300            Self::NotIn => Self::In,
301            Self::Contains => Self::NotContains,
302            Self::NotContains => Self::Contains,
303        })
304    }
305
306    /// Returns the marker operator and value whose union represents the given range.
307    pub fn from_bounds(
308        bounds: (&Bound<ArcStr>, &Bound<ArcStr>),
309    ) -> impl Iterator<Item = (Self, ArcStr)> {
310        let (b1, b2) = match bounds {
311            (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
312                (Some((Self::Equal, v1.clone())), None)
313            }
314            (Bound::Excluded(v1), Bound::Excluded(v2)) if v1 == v2 => {
315                (Some((Self::NotEqual, v1.clone())), None)
316            }
317            (lower, upper) => (Self::from_lower_bound(lower), Self::from_upper_bound(upper)),
318        };
319
320        b1.into_iter().chain(b2)
321    }
322
323    /// Returns a value specifier representing the given lower bound.
324    pub fn from_lower_bound(bound: &Bound<ArcStr>) -> Option<(Self, ArcStr)> {
325        match bound {
326            Bound::Included(value) => Some((Self::GreaterEqual, value.clone())),
327            Bound::Excluded(value) => Some((Self::GreaterThan, value.clone())),
328            Bound::Unbounded => None,
329        }
330    }
331
332    /// Returns a value specifier representing the given upper bound.
333    pub fn from_upper_bound(bound: &Bound<ArcStr>) -> Option<(Self, ArcStr)> {
334        match bound {
335            Bound::Included(value) => Some((Self::LessEqual, value.clone())),
336            Bound::Excluded(value) => Some((Self::LessThan, value.clone())),
337            Bound::Unbounded => None,
338        }
339    }
340}
341
342impl FromStr for MarkerOperator {
343    type Err = String;
344
345    /// PEP 508 allows arbitrary whitespace between "not" and "in", and so do we
346    fn from_str(s: &str) -> Result<Self, Self::Err> {
347        let value = match s {
348            "==" => Self::Equal,
349            "!=" => Self::NotEqual,
350            ">" => Self::GreaterThan,
351            ">=" => Self::GreaterEqual,
352            "<" => Self::LessThan,
353            "<=" => Self::LessEqual,
354            "~=" => Self::TildeEqual,
355            "in" => Self::In,
356            not_space_in
357                if not_space_in
358                    // start with not
359                    .strip_prefix("not")
360                    // ends with in
361                    .and_then(|space_in| space_in.strip_suffix("in"))
362                    // and has only whitespace in between
363                    .is_some_and(|space| !space.is_empty() && space.trim().is_empty()) =>
364            {
365                Self::NotIn
366            }
367            other => return Err(format!("Invalid comparator: {other}")),
368        };
369        Ok(value)
370    }
371}
372
373impl Display for MarkerOperator {
374    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
375        f.write_str(match self {
376            Self::Equal => "==",
377            Self::NotEqual => "!=",
378            Self::GreaterThan => ">",
379            Self::GreaterEqual => ">=",
380            Self::LessThan => "<",
381            Self::LessEqual => "<=",
382            Self::TildeEqual => "~=",
383            Self::In | Self::Contains => "in",
384            Self::NotIn | Self::NotContains => "not in",
385        })
386    }
387}
388
389/// Helper type with a [Version] and its original text
390#[derive(Clone, Debug, Eq, Hash, PartialEq)]
391pub struct StringVersion {
392    /// Original unchanged string
393    pub string: String,
394    /// Parsed version
395    pub version: Version,
396}
397
398impl From<Version> for StringVersion {
399    fn from(version: Version) -> Self {
400        Self {
401            string: version.to_string(),
402            version,
403        }
404    }
405}
406
407impl FromStr for StringVersion {
408    type Err = VersionParseError;
409
410    fn from_str(s: &str) -> Result<Self, Self::Err> {
411        Ok(Self {
412            string: s.to_string(),
413            version: Version::from_str(s)?,
414        })
415    }
416}
417
418impl Display for StringVersion {
419    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
420        self.string.fmt(f)
421    }
422}
423
424impl Serialize for StringVersion {
425    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
426    where
427        S: Serializer,
428    {
429        serializer.serialize_str(&self.string)
430    }
431}
432
433impl<'de> Deserialize<'de> for StringVersion {
434    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
435    where
436        D: Deserializer<'de>,
437    {
438        struct Visitor;
439
440        impl de::Visitor<'_> for Visitor {
441            type Value = StringVersion;
442
443            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
444                f.write_str("a string")
445            }
446
447            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
448                StringVersion::from_str(v).map_err(de::Error::custom)
449            }
450        }
451
452        deserializer.deserialize_str(Visitor)
453    }
454}
455
456impl Deref for StringVersion {
457    type Target = Version;
458
459    fn deref(&self) -> &Self::Target {
460        &self.version
461    }
462}
463
464/// The [`ExtraName`] value used in `extra` and `extras` markers.
465#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
466pub enum MarkerValueExtra {
467    /// A valid [`ExtraName`].
468    Extra(ExtraName),
469    /// An invalid name, preserved as an arbitrary string.
470    Arbitrary(String),
471}
472
473impl MarkerValueExtra {
474    /// Returns the [`ExtraName`] for this value, if it is a valid extra.
475    pub fn as_extra(&self) -> Option<&ExtraName> {
476        match self {
477            Self::Extra(extra) => Some(extra),
478            Self::Arbitrary(_) => None,
479        }
480    }
481
482    /// Convert the [`MarkerValueExtra`] to an [`ExtraName`], if possible.
483    fn into_extra(self) -> Option<ExtraName> {
484        match self {
485            Self::Extra(extra) => Some(extra),
486            Self::Arbitrary(_) => None,
487        }
488    }
489}
490
491impl Display for MarkerValueExtra {
492    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
493        match self {
494            Self::Extra(extra) => extra.fmt(f),
495            Self::Arbitrary(string) => string.fmt(f),
496        }
497    }
498}
499
500/// Represents one clause such as `python_version > "3.8"`.
501#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
502#[allow(missing_docs)]
503pub enum MarkerExpression {
504    /// A version expression, e.g. `<version key> <version op> <quoted PEP 440 version>`.
505    ///
506    /// Inverted version expressions, such as `<version> <version op> <version key>`, are also
507    /// normalized to this form.
508    Version {
509        key: MarkerValueVersion,
510        specifier: VersionSpecifier,
511    },
512    /// A version in list expression, e.g. `<version key> in <quoted list of PEP 440 versions>`.
513    ///
514    /// A special case of [`MarkerExpression::String`] with the [`MarkerOperator::In`] operator for
515    /// [`MarkerValueVersion`] values.
516    ///
517    /// See [`parse::parse_version_in_expr`] for details on the supported syntax.
518    ///
519    /// Negated expressions, using "not in" are represented using `negated = true`.
520    VersionIn {
521        key: MarkerValueVersion,
522        versions: Vec<Version>,
523        operator: ContainerOperator,
524    },
525    /// An string marker comparison, e.g. `sys_platform == '...'`.
526    ///
527    /// Inverted string expressions, e.g `'...' == sys_platform`, are also normalized to this form.
528    String {
529        key: MarkerValueString,
530        operator: MarkerOperator,
531        value: ArcStr,
532    },
533    /// `'...' in <key>`, a PEP 751 expression.
534    List {
535        pair: CanonicalMarkerListPair,
536        operator: ContainerOperator,
537    },
538    /// `extra <extra op> '...'` or `'...' <extra op> extra`.
539    Extra {
540        name: MarkerValueExtra,
541        operator: ExtraOperator,
542    },
543}
544
545/// The kind of a [`MarkerExpression`].
546#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
547pub(crate) enum MarkerExpressionKind {
548    /// A version expression, e.g. `<version key> <version op> <quoted PEP 440 version>`.
549    Version(MarkerValueVersion),
550    /// A version `in` expression, e.g. `<version key> in <quoted list of PEP 440 versions>`.
551    VersionIn(MarkerValueVersion),
552    /// A string marker comparison, e.g. `sys_platform == '...'`.
553    String(MarkerValueString),
554    /// A list `in` or `not in` expression, e.g. `'...' in dependency_groups`.
555    List(MarkerValueList),
556    /// An extra expression, e.g. `extra == '...'`.
557    Extra,
558}
559
560/// The operator for an extra expression, either '==' or '!='.
561#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
562pub enum ExtraOperator {
563    /// `==`
564    Equal,
565    /// `!=`
566    NotEqual,
567}
568
569impl ExtraOperator {
570    /// Creates a [`ExtraOperator`] from an equivalent [`MarkerOperator`].
571    ///
572    /// Returns `None` if the operator is not supported for extras.
573    pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option<Self> {
574        match operator {
575            MarkerOperator::Equal => Some(Self::Equal),
576            MarkerOperator::NotEqual => Some(Self::NotEqual),
577            _ => None,
578        }
579    }
580
581    /// Negates this operator.
582    pub(crate) fn negate(&self) -> Self {
583        match self {
584            Self::Equal => Self::NotEqual,
585            Self::NotEqual => Self::Equal,
586        }
587    }
588}
589
590impl Display for ExtraOperator {
591    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
592        f.write_str(match self {
593            Self::Equal => "==",
594            Self::NotEqual => "!=",
595        })
596    }
597}
598
599/// The operator for a container expression, either 'in' or 'not in'.
600#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
601pub enum ContainerOperator {
602    /// `in`
603    In,
604    /// `not in`
605    NotIn,
606}
607
608impl ContainerOperator {
609    /// Creates a [`ContainerOperator`] from an equivalent [`MarkerOperator`].
610    ///
611    /// Returns `None` if the operator is not supported for containers.
612    pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option<Self> {
613        match operator {
614            MarkerOperator::In => Some(Self::In),
615            MarkerOperator::NotIn => Some(Self::NotIn),
616            _ => None,
617        }
618    }
619}
620
621impl Display for ContainerOperator {
622    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
623        f.write_str(match self {
624            Self::In => "in",
625            Self::NotIn => "not in",
626        })
627    }
628}
629
630impl MarkerExpression {
631    /// Parse a [`MarkerExpression`] from a string with the given reporter.
632    pub fn parse_reporter(
633        s: &str,
634        reporter: &mut impl Reporter,
635    ) -> Result<Option<Self>, Pep508Error> {
636        let mut chars = Cursor::new(s);
637        let expression = parse::parse_marker_key_op_value(&mut chars, reporter)?;
638        chars.eat_whitespace();
639        if let Some((pos, unexpected)) = chars.next() {
640            return Err(Pep508Error {
641                message: Pep508ErrorSource::String(format!(
642                    "Unexpected character '{unexpected}', expected end of input"
643                )),
644                start: pos,
645                len: chars.remaining(),
646                input: chars.to_string(),
647            });
648        }
649
650        Ok(expression)
651    }
652
653    /// Parse a [`MarkerExpression`] from a string.
654    ///
655    /// Returns `None` if the expression consists entirely of meaningless expressions
656    /// that are ignored, such as `os_name ~= 'foo'`.
657    #[expect(clippy::should_implement_trait)]
658    pub fn from_str(s: &str) -> Result<Option<Self>, Pep508Error> {
659        Self::parse_reporter(s, &mut TracingReporter)
660    }
661
662    /// Return the kind of this marker expression.
663    pub(crate) fn kind(&self) -> MarkerExpressionKind {
664        match self {
665            Self::Version { key, .. } => MarkerExpressionKind::Version(*key),
666            Self::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key),
667            Self::String { key, .. } => MarkerExpressionKind::String(*key),
668            Self::List { pair, .. } => MarkerExpressionKind::List(pair.key()),
669            Self::Extra { .. } => MarkerExpressionKind::Extra,
670        }
671    }
672}
673
674impl Display for MarkerExpression {
675    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
676        match self {
677            Self::Version { key, specifier } => {
678                let (op, version) = (specifier.operator(), specifier.version());
679                if op == &uv_pep440::Operator::EqualStar || op == &uv_pep440::Operator::NotEqualStar
680                {
681                    return write!(f, "{key} {op} '{version}.*'");
682                }
683                write!(f, "{key} {op} '{version}'")
684            }
685            Self::VersionIn {
686                key,
687                versions,
688                operator,
689            } => {
690                let versions = versions.iter().map(ToString::to_string).join(" ");
691                write!(f, "{key} {operator} '{versions}'")
692            }
693            Self::String {
694                key,
695                operator,
696                value,
697            } => {
698                if matches!(
699                    operator,
700                    MarkerOperator::Contains | MarkerOperator::NotContains
701                ) {
702                    return write!(f, "'{value}' {} {key}", operator.invert());
703                }
704
705                write!(f, "{key} {operator} '{value}'")
706            }
707            Self::List { pair, operator } => {
708                write!(f, "'{}' {} {}", pair.value(), operator, pair.key())
709            }
710            Self::Extra { operator, name } => {
711                write!(f, "extra {operator} '{name}'")
712            }
713        }
714    }
715}
716
717/// The extra and dependency group names to use when evaluating a marker tree.
718#[derive(Debug, Copy, Clone)]
719enum ExtrasEnvironment<'a> {
720    /// E.g., `extra == '...'`
721    Extras(&'a [ExtraName]),
722    /// E.g., `'...' in extras` or `'...' in dependency_groups`
723    Pep751(&'a [ExtraName], &'a [GroupName]),
724}
725
726impl<'a> ExtrasEnvironment<'a> {
727    /// Creates a new [`ExtrasEnvironment`] for the given `extra` names.
728    fn from_extras(extras: &'a [ExtraName]) -> Self {
729        Self::Extras(extras)
730    }
731
732    /// Creates a new [`ExtrasEnvironment`] for the given PEP 751 `extras` and `dependency_groups`.
733    fn from_pep751(extras: &'a [ExtraName], dependency_groups: &'a [GroupName]) -> Self {
734        Self::Pep751(extras, dependency_groups)
735    }
736
737    /// Returns the `extra` names in this environment.
738    fn extra(&self) -> &[ExtraName] {
739        match self {
740            Self::Extras(extra) => extra,
741            Self::Pep751(..) => &[],
742        }
743    }
744
745    /// Returns the `extras` names in this environment, as in a PEP 751 lockfile.
746    fn extras(&self) -> &[ExtraName] {
747        match self {
748            Self::Extras(..) => &[],
749            Self::Pep751(extras, ..) => extras,
750        }
751    }
752
753    /// Returns the `dependency_group` group names in this environment, as in a PEP 751 lockfile.
754    fn dependency_groups(&self) -> &[GroupName] {
755        match self {
756            Self::Extras(..) => &[],
757            Self::Pep751(.., groups) => groups,
758        }
759    }
760}
761
762/// Represents one or more nested marker expressions with and/or/parentheses.
763///
764/// Marker trees are canonical, meaning any two functionally equivalent markers
765/// will compare equally. Markers also support efficient polynomial-time operations,
766/// such as conjunction and disjunction.
767#[derive(Clone, Copy, Eq, Hash, PartialEq)]
768pub struct MarkerTree(NodeId);
769
770impl Default for MarkerTree {
771    fn default() -> Self {
772        Self::TRUE
773    }
774}
775
776impl<'de> Deserialize<'de> for MarkerTree {
777    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
778    where
779        D: Deserializer<'de>,
780    {
781        struct Visitor;
782
783        impl de::Visitor<'_> for Visitor {
784            type Value = MarkerTree;
785
786            fn expecting(&self, f: &mut Formatter) -> fmt::Result {
787                f.write_str("a string")
788            }
789
790            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
791                MarkerTree::from_str(v).map_err(de::Error::custom)
792            }
793        }
794
795        deserializer.deserialize_str(Visitor)
796    }
797}
798
799impl FromStr for MarkerTree {
800    type Err = Pep508Error;
801
802    fn from_str(markers: &str) -> Result<Self, Self::Err> {
803        parse::parse_markers(markers, &mut TracingReporter)
804    }
805}
806
807impl MarkerTree {
808    /// Like [`FromStr::from_str`], but the caller chooses the return type generic.
809    pub fn parse_str<T: Pep508Url>(markers: &str) -> Result<Self, Pep508Error<T>> {
810        parse::parse_markers(markers, &mut TracingReporter)
811    }
812
813    /// Parse a [`MarkerTree`] from a string with the given reporter.
814    pub fn parse_reporter(
815        markers: &str,
816        reporter: &mut impl Reporter,
817    ) -> Result<Self, Pep508Error> {
818        parse::parse_markers(markers, reporter)
819    }
820
821    /// An empty marker that always evaluates to `true`.
822    pub const TRUE: Self = Self(NodeId::TRUE);
823
824    /// An unsatisfiable marker that always evaluates to `false`.
825    pub const FALSE: Self = Self(NodeId::FALSE);
826
827    /// Returns a marker tree for a single expression.
828    pub fn expression(expr: MarkerExpression) -> Self {
829        Self(INTERNER.lock().expression(expr))
830    }
831
832    /// Whether the marker always evaluates to `true`.
833    ///
834    /// If this method returns `true`, it is definitively known that the marker will
835    /// evaluate to `true` in any environment. However, this method may return false
836    /// negatives, i.e. it may not be able to detect that a marker is always true for
837    /// complex expressions.
838    pub fn is_true(self) -> bool {
839        self.0.is_true()
840    }
841
842    /// Whether the marker always evaluates to `false`, i.e. the expression is not
843    /// satisfiable in any environment.
844    ///
845    /// If this method returns `true`, it is definitively known that the marker will
846    /// evaluate to `false` in any environment. However, this method may return false
847    /// negatives, i.e. it may not be able to detect that a marker is unsatisfiable
848    /// for complex expressions.
849    pub fn is_false(self) -> bool {
850        self.0.is_false()
851    }
852
853    /// Returns a new marker tree that is the negation of this one.
854    #[must_use]
855    pub fn negate(self) -> Self {
856        Self(self.0.not())
857    }
858
859    /// Combine this marker tree with the one given via a conjunction.
860    pub fn and(&mut self, tree: Self) {
861        self.0 = INTERNER.lock().and(self.0, tree.0);
862    }
863
864    /// Combine this marker tree with the one given via a disjunction.
865    pub fn or(&mut self, tree: Self) {
866        self.0 = INTERNER.lock().or(self.0, tree.0);
867    }
868
869    /// Sets this to a marker equivalent to the implication of this one and the
870    /// given consequent.
871    ///
872    /// If the marker set is always `true`, then it can be said that `self`
873    /// implies `consequent`.
874    pub fn implies(&mut self, consequent: Self) {
875        // This could probably be optimized, but is clearly
876        // correct, since logical implication is `-P or Q`.
877        *self = self.negate();
878        self.or(consequent);
879    }
880
881    /// Returns `true` if there is no environment in which both marker trees can apply,
882    /// i.e. their conjunction is always `false`.
883    ///
884    /// If this method returns `true`, it is definitively known that the two markers can
885    /// never both evaluate to `true` in a given environment. However, this method may return
886    /// false negatives, i.e. it may not be able to detect that two markers are disjoint for
887    /// complex expressions.
888    pub fn is_disjoint(self, other: Self) -> bool {
889        INTERNER.lock().is_disjoint(self.0, other.0)
890    }
891
892    /// Returns the contents of this marker tree, if it contains at least one expression.
893    ///
894    /// If the marker is `true`, this method will return `None`.
895    /// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
896    ///
897    /// The returned type implements [`Display`] and [`serde::Serialize`].
898    pub fn contents(self) -> Option<MarkerTreeContents> {
899        if self.is_true() {
900            return None;
901        }
902
903        Some(MarkerTreeContents(self))
904    }
905
906    /// Returns a simplified string representation of this marker, if it contains at least one
907    /// expression.
908    ///
909    /// If the marker is `true`, this method will return `None`.
910    /// If the marker is `false`, the marker is represented as the normalized expression, `python_version < '0'`.
911    pub fn try_to_string(self) -> Option<String> {
912        self.contents().map(|contents| contents.to_string())
913    }
914
915    /// Returns the underlying [`MarkerTreeKind`] of the root node.
916    pub fn kind(self) -> MarkerTreeKind<'static> {
917        if self.is_true() {
918            return MarkerTreeKind::True;
919        }
920
921        if self.is_false() {
922            return MarkerTreeKind::False;
923        }
924
925        let node = INTERNER.shared.node(self.0);
926        match &node.var {
927            Variable::Version(key) => {
928                let Edges::Version { edges: ref map } = node.children else {
929                    unreachable!()
930                };
931                MarkerTreeKind::Version(VersionMarkerTree {
932                    id: self.0,
933                    key: *key,
934                    map,
935                })
936            }
937            Variable::String(key) => {
938                let Edges::String { edges: ref map } = node.children else {
939                    unreachable!()
940                };
941                MarkerTreeKind::String(StringMarkerTree {
942                    id: self.0,
943                    key: *key,
944                    map,
945                })
946            }
947            Variable::In { key, value } => {
948                let Edges::Boolean { low, high } = node.children else {
949                    unreachable!()
950                };
951                MarkerTreeKind::In(InMarkerTree {
952                    key: *key,
953                    value,
954                    high: high.negate(self.0),
955                    low: low.negate(self.0),
956                })
957            }
958            Variable::Contains { key, value } => {
959                let Edges::Boolean { low, high } = node.children else {
960                    unreachable!()
961                };
962                MarkerTreeKind::Contains(ContainsMarkerTree {
963                    key: *key,
964                    value,
965                    high: high.negate(self.0),
966                    low: low.negate(self.0),
967                })
968            }
969            Variable::List(key) => {
970                let Edges::Boolean { low, high } = node.children else {
971                    unreachable!()
972                };
973                MarkerTreeKind::List(ListMarkerTree {
974                    pair: key,
975                    high: high.negate(self.0),
976                    low: low.negate(self.0),
977                })
978            }
979            Variable::Extra(name) => {
980                let Edges::Boolean { low, high } = node.children else {
981                    unreachable!()
982                };
983                MarkerTreeKind::Extra(ExtraMarkerTree {
984                    name,
985                    high: high.negate(self.0),
986                    low: low.negate(self.0),
987                })
988            }
989        }
990    }
991
992    /// Returns a simplified DNF expression for this marker tree.
993    pub fn to_dnf(self) -> Vec<Vec<MarkerExpression>> {
994        simplify::to_dnf(self)
995    }
996
997    /// Does this marker apply in the given environment?
998    pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
999        self.evaluate_reporter_impl(
1000            env,
1001            ExtrasEnvironment::from_extras(extras),
1002            &mut TracingReporter,
1003        )
1004    }
1005
1006    /// Evaluate a marker in the context of a PEP 751 lockfile, which exposes several additional
1007    /// markers (`extras` and `dependency_groups`) that are not available in any other context,
1008    /// per the spec.
1009    pub fn evaluate_pep751(
1010        self,
1011        env: &MarkerEnvironment,
1012        extras: &[ExtraName],
1013        groups: &[GroupName],
1014    ) -> bool {
1015        self.evaluate_reporter_impl(
1016            env,
1017            ExtrasEnvironment::from_pep751(extras, groups),
1018            &mut TracingReporter,
1019        )
1020    }
1021
1022    /// Evaluates this marker tree against an optional environment and a
1023    /// possibly empty sequence of extras.
1024    ///
1025    /// When an environment is not provided, all marker expressions based on
1026    /// the environment evaluate to `true`. That is, this provides environment
1027    /// independent marker evaluation. In practice, this means only the extras
1028    /// are evaluated when an environment is not provided.
1029    pub fn evaluate_optional_environment(
1030        self,
1031        env: Option<&MarkerEnvironment>,
1032        extras: &[ExtraName],
1033    ) -> bool {
1034        match env {
1035            None => self.evaluate_extras(extras),
1036            Some(env) => self.evaluate_reporter_impl(
1037                env,
1038                ExtrasEnvironment::from_extras(extras),
1039                &mut TracingReporter,
1040            ),
1041        }
1042    }
1043
1044    /// Same as [`Self::evaluate`], but instead of using logging to warn, you can pass your own
1045    /// handler for warnings
1046    pub fn evaluate_reporter(
1047        self,
1048        env: &MarkerEnvironment,
1049        extras: &[ExtraName],
1050        reporter: &mut impl Reporter,
1051    ) -> bool {
1052        self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter)
1053    }
1054
1055    fn evaluate_reporter_impl(
1056        self,
1057        env: &MarkerEnvironment,
1058        extras: ExtrasEnvironment,
1059        reporter: &mut impl Reporter,
1060    ) -> bool {
1061        match self.kind() {
1062            MarkerTreeKind::True => return true,
1063            MarkerTreeKind::False => return false,
1064            MarkerTreeKind::Version(marker) => {
1065                for (range, tree) in marker.edges() {
1066                    if range.contains(env.get_version(marker.key())) {
1067                        return tree.evaluate_reporter_impl(env, extras, reporter);
1068                    }
1069                }
1070            }
1071            MarkerTreeKind::String(marker) => {
1072                for (range, tree) in marker.children() {
1073                    let l_string = env.get_string(marker.key());
1074
1075                    if range.as_singleton().is_none() {
1076                        if let Some((start, end)) = range.bounding_range() {
1077                            if let Bound::Included(value) | Bound::Excluded(value) = start {
1078                                reporter.report(
1079                                    MarkerWarningKind::LexicographicComparison,
1080                                    format!("Comparing {l_string} and {value} lexicographically"),
1081                                );
1082                            }
1083
1084                            if let Bound::Included(value) | Bound::Excluded(value) = end {
1085                                reporter.report(
1086                                    MarkerWarningKind::LexicographicComparison,
1087                                    format!("Comparing {l_string} and {value} lexicographically"),
1088                                );
1089                            }
1090                        }
1091                    }
1092
1093                    if range.contains(l_string) {
1094                        return tree.evaluate_reporter_impl(env, extras, reporter);
1095                    }
1096                }
1097            }
1098            MarkerTreeKind::In(marker) => {
1099                return marker
1100                    .edge(marker.value().contains(env.get_string(marker.key())))
1101                    .evaluate_reporter_impl(env, extras, reporter);
1102            }
1103            MarkerTreeKind::Contains(marker) => {
1104                return marker
1105                    .edge(env.get_string(marker.key()).contains(marker.value()))
1106                    .evaluate_reporter_impl(env, extras, reporter);
1107            }
1108            MarkerTreeKind::Extra(marker) => {
1109                return marker
1110                    .edge(extras.extra().contains(marker.name().extra()))
1111                    .evaluate_reporter_impl(env, extras, reporter);
1112            }
1113            MarkerTreeKind::List(marker) => {
1114                let edge = match marker.pair() {
1115                    CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra),
1116                    CanonicalMarkerListPair::DependencyGroup(dependency_group) => {
1117                        extras.dependency_groups().contains(dependency_group)
1118                    }
1119                    // Invalid marker expression
1120                    CanonicalMarkerListPair::Arbitrary { .. } => return false,
1121                };
1122
1123                return marker
1124                    .edge(edge)
1125                    .evaluate_reporter_impl(env, extras, reporter);
1126            }
1127        }
1128
1129        false
1130    }
1131
1132    /// Checks if the requirement should be activated with the given set of active extras without evaluating
1133    /// the remaining environment markers, i.e. if there is potentially an environment that could activate this
1134    /// requirement.
1135    pub fn evaluate_extras(self, extras: &[ExtraName]) -> bool {
1136        match self.kind() {
1137            MarkerTreeKind::True => true,
1138            MarkerTreeKind::False => false,
1139            MarkerTreeKind::Version(marker) => {
1140                marker.edges().any(|(_, tree)| tree.evaluate_extras(extras))
1141            }
1142            MarkerTreeKind::String(marker) => marker
1143                .children()
1144                .any(|(_, tree)| tree.evaluate_extras(extras)),
1145            MarkerTreeKind::In(marker) => marker
1146                .children()
1147                .any(|(_, tree)| tree.evaluate_extras(extras)),
1148            MarkerTreeKind::Contains(marker) => marker
1149                .children()
1150                .any(|(_, tree)| tree.evaluate_extras(extras)),
1151            MarkerTreeKind::List(marker) => marker
1152                .children()
1153                .any(|(_, tree)| tree.evaluate_extras(extras)),
1154            MarkerTreeKind::Extra(marker) => marker
1155                .edge(extras.contains(marker.name().extra()))
1156                .evaluate_extras(extras),
1157        }
1158    }
1159
1160    /// Returns true if this marker simplifies to true if the given set of extras is activated.
1161    pub fn evaluate_only_extras(self, extras: &[ExtraName]) -> bool {
1162        match self.kind() {
1163            MarkerTreeKind::True => true,
1164            MarkerTreeKind::False => false,
1165            MarkerTreeKind::Version(marker) => marker
1166                .edges()
1167                .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1168            MarkerTreeKind::String(marker) => marker
1169                .children()
1170                .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1171            MarkerTreeKind::In(marker) => marker
1172                .children()
1173                .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1174            MarkerTreeKind::Contains(marker) => marker
1175                .children()
1176                .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1177            MarkerTreeKind::List(marker) => marker
1178                .children()
1179                .all(|(_, tree)| tree.evaluate_only_extras(extras)),
1180            MarkerTreeKind::Extra(marker) => marker
1181                .edge(extras.contains(marker.name().extra()))
1182                .evaluate_only_extras(extras),
1183        }
1184    }
1185
1186    /// Find a top level `extra == "..."` expression.
1187    ///
1188    /// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
1189    /// main conjunction.
1190    pub fn top_level_extra(self) -> Option<MarkerExpression> {
1191        let mut extra_expression = None;
1192        for conjunction in self.to_dnf() {
1193            let found = conjunction.iter().find(|expression| {
1194                matches!(
1195                    expression,
1196                    MarkerExpression::Extra {
1197                        operator: ExtraOperator::Equal,
1198                        ..
1199                    }
1200                )
1201            })?;
1202
1203            // Because the marker tree is in DNF form, we must verify that the extra expression is part
1204            // of all solutions to this marker.
1205            if let Some(ref extra_expression) = extra_expression {
1206                if *extra_expression != *found {
1207                    return None;
1208                }
1209
1210                continue;
1211            }
1212
1213            extra_expression = Some(found.clone());
1214        }
1215
1216        extra_expression
1217    }
1218
1219    /// Find a top level `extra == "..."` name.
1220    ///
1221    /// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part of the
1222    /// main conjunction.
1223    pub fn top_level_extra_name(self) -> Option<Cow<'static, ExtraName>> {
1224        // Fast path: The marker is only a `extra == "..."`.
1225        if let MarkerTreeKind::Extra(marker) = self.kind() {
1226            if marker.edge(true).is_true() {
1227                let CanonicalMarkerValueExtra::Extra(extra) = marker.name;
1228                return Some(Cow::Borrowed(extra));
1229            }
1230        }
1231
1232        let extra_expression = self.top_level_extra()?;
1233
1234        match extra_expression {
1235            MarkerExpression::Extra { name, .. } => name.into_extra().map(Cow::Owned),
1236            _ => unreachable!(),
1237        }
1238    }
1239
1240    /// Simplify this marker by *assuming* that the Python version range
1241    /// provided is true and that the complement of it is false.
1242    ///
1243    /// For example, with `requires-python = '>=3.8'` and a marker tree of
1244    /// `python_full_version >= '3.8' and python_full_version <= '3.10'`, this
1245    /// would result in a marker of `python_full_version <= '3.10'`.
1246    ///
1247    /// This is useful when one wants to write "simpler" markers in a
1248    /// particular context with a bound on the supported Python versions.
1249    /// In general, the simplified markers returned shouldn't be used for
1250    /// evaluation. Instead, they should be turned back into their more
1251    /// "complex" form first.
1252    ///
1253    /// Note that simplifying a marker and then complexifying it, even
1254    /// with the same Python version bounds, is a lossy operation. For
1255    /// example, simplifying `python_version < '3.7'` with `requires-python
1256    /// = ">=3.8"` will result in a marker that always returns false (e.g.,
1257    /// `python_version < '0'`). Therefore, complexifying an always-false
1258    /// marker will result in a marker that is still always false, despite
1259    /// the fact that the original marker was true for `<3.7`. Therefore,
1260    /// simplifying should only be done as a one-way transformation when it is
1261    /// known that `requires-python` reflects an eternal lower bound on the
1262    /// results of that simplification. (If `requires-python` changes, then one
1263    /// should reconstitute all relevant markers from the source data.)
1264    #[must_use]
1265    pub fn simplify_python_versions(self, lower: Bound<&Version>, upper: Bound<&Version>) -> Self {
1266        Self(
1267            INTERNER
1268                .lock()
1269                .simplify_python_versions(self.0, lower, upper),
1270        )
1271    }
1272
1273    /// Complexify marker tree by requiring the given Python version range
1274    /// to be true in order for this marker tree to evaluate to true in all
1275    /// circumstances.
1276    ///
1277    /// For example, with `requires-python = '>=3.8'` and a marker tree of
1278    /// `python_full_version <= '3.10'`, this would result in a marker of
1279    /// `python_full_version >= '3.8' and python_full_version <= '3.10'`.
1280    #[must_use]
1281    pub fn complexify_python_versions(
1282        self,
1283        lower: Bound<&Version>,
1284        upper: Bound<&Version>,
1285    ) -> Self {
1286        Self(
1287            INTERNER
1288                .lock()
1289                .complexify_python_versions(self.0, lower, upper),
1290        )
1291    }
1292
1293    /// Remove the extras from a marker, returning `None` if the marker tree evaluates to `true`.
1294    ///
1295    /// Any `extra` markers that are always `true` given the provided extras will be removed.
1296    /// Any `extra` markers that are always `false` given the provided extras will be left
1297    /// unchanged.
1298    ///
1299    /// For example, if `dev` is a provided extra, given `sys_platform == 'linux' and extra == 'dev'`,
1300    /// the marker will be simplified to `sys_platform == 'linux'`.
1301    #[must_use]
1302    pub fn simplify_extras(self, extras: &[ExtraName]) -> Self {
1303        self.simplify_extras_with(|name| extras.contains(name))
1304    }
1305
1306    /// Remove negated extras from a marker, returning `None` if the marker
1307    /// tree evaluates to `true`.
1308    ///
1309    /// Any negated `extra` markers that are always `true` given the provided
1310    /// extras will be removed. Any `extra` markers that are always `false`
1311    /// given the provided extras will be left unchanged.
1312    ///
1313    /// For example, if `dev` is a provided extra, given `sys_platform
1314    /// == 'linux' and extra != 'dev'`, the marker will be simplified to
1315    /// `sys_platform == 'linux'`.
1316    #[must_use]
1317    pub fn simplify_not_extras(self, extras: &[ExtraName]) -> Self {
1318        self.simplify_not_extras_with(|name| extras.contains(name))
1319    }
1320
1321    /// Remove the extras from a marker, returning `None` if the marker tree evaluates to `true`.
1322    ///
1323    /// Any `extra` markers that are always `true` given the provided predicate will be removed.
1324    /// Any `extra` markers that are always `false` given the provided predicate will be left
1325    /// unchanged.
1326    ///
1327    /// For example, if `is_extra('dev')` is true, given
1328    /// `sys_platform == 'linux' and extra == 'dev'`, the marker will be simplified to
1329    /// `sys_platform == 'linux'`.
1330    #[must_use]
1331    pub fn simplify_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> Self {
1332        // Because `simplify_extras_with_impl` is recursive, and we need to use
1333        // our predicate in recursive calls, we need the predicate itself to
1334        // have some indirection (or else we'd have to clone it). To avoid a
1335        // recursive type at codegen time, we just introduce the indirection
1336        // here, but keep the calling API ergonomic.
1337        self.simplify_extras_with_impl(&is_extra)
1338    }
1339
1340    /// Remove negated extras from a marker, returning `None` if the marker tree evaluates to
1341    /// `true`.
1342    ///
1343    /// Any negated `extra` markers that are always `true` given the provided
1344    /// predicate will be removed. Any `extra` markers that are always `false`
1345    /// given the provided predicate will be left unchanged.
1346    ///
1347    /// For example, if `is_extra('dev')` is true, given
1348    /// `sys_platform == 'linux' and extra != 'dev'`, the marker will be simplified to
1349    /// `sys_platform == 'linux'`.
1350    #[must_use]
1351    pub fn simplify_not_extras_with(self, is_extra: impl Fn(&ExtraName) -> bool) -> Self {
1352        // Because `simplify_extras_with_impl` is recursive, and we need to use
1353        // our predicate in recursive calls, we need the predicate itself to
1354        // have some indirection (or else we'd have to clone it). To avoid a
1355        // recursive type at codegen time, we just introduce the indirection
1356        // here, but keep the calling API ergonomic.
1357        self.simplify_not_extras_with_impl(&is_extra)
1358    }
1359
1360    /// Returns a new `MarkerTree` where all `extra` expressions are removed.
1361    ///
1362    /// If the marker only consisted of `extra` expressions, then a marker that
1363    /// is always true is returned.
1364    #[must_use]
1365    pub fn without_extras(self) -> Self {
1366        Self(INTERNER.lock().without_extras(self.0))
1367    }
1368
1369    /// Returns a new `MarkerTree` where only `extra` expressions are removed.
1370    ///
1371    /// If the marker did not contain any `extra` expressions, then a marker
1372    /// that is always true is returned.
1373    #[must_use]
1374    pub fn only_extras(self) -> Self {
1375        Self(INTERNER.lock().only_extras(self.0))
1376    }
1377
1378    /// Calls the provided function on every `extra` in this tree.
1379    ///
1380    /// The operator provided to the function is guaranteed to be
1381    /// `MarkerOperator::Equal` or `MarkerOperator::NotEqual`.
1382    pub fn visit_extras(self, mut f: impl FnMut(MarkerOperator, &ExtraName)) {
1383        fn imp(tree: MarkerTree, f: &mut impl FnMut(MarkerOperator, &ExtraName)) {
1384            match tree.kind() {
1385                MarkerTreeKind::True | MarkerTreeKind::False => {}
1386                MarkerTreeKind::Version(kind) => {
1387                    for (tree, _) in simplify::collect_edges(kind.edges()) {
1388                        imp(tree, f);
1389                    }
1390                }
1391                MarkerTreeKind::String(kind) => {
1392                    for (tree, _) in simplify::collect_edges(kind.children()) {
1393                        imp(tree, f);
1394                    }
1395                }
1396                MarkerTreeKind::In(kind) => {
1397                    for (_, tree) in kind.children() {
1398                        imp(tree, f);
1399                    }
1400                }
1401                MarkerTreeKind::Contains(kind) => {
1402                    for (_, tree) in kind.children() {
1403                        imp(tree, f);
1404                    }
1405                }
1406                MarkerTreeKind::List(kind) => {
1407                    for (_, tree) in kind.children() {
1408                        imp(tree, f);
1409                    }
1410                }
1411                MarkerTreeKind::Extra(kind) => {
1412                    if kind.low.is_false() {
1413                        f(MarkerOperator::Equal, kind.name().extra());
1414                    } else {
1415                        f(MarkerOperator::NotEqual, kind.name().extra());
1416                    }
1417                    for (_, tree) in kind.children() {
1418                        imp(tree, f);
1419                    }
1420                }
1421            }
1422        }
1423        imp(self, &mut f);
1424    }
1425
1426    fn simplify_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> Self {
1427        Self(INTERNER.lock().restrict(self.0, &|var| match var {
1428            Variable::Extra(name) => is_extra(name.extra()).then_some(true),
1429            _ => None,
1430        }))
1431    }
1432
1433    fn simplify_not_extras_with_impl(self, is_extra: &impl Fn(&ExtraName) -> bool) -> Self {
1434        Self(INTERNER.lock().restrict(self.0, &|var| match var {
1435            Variable::Extra(name) => is_extra(name.extra()).then_some(false),
1436            _ => None,
1437        }))
1438    }
1439}
1440
1441impl fmt::Debug for MarkerTree {
1442    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1443        if self.is_true() {
1444            return write!(f, "true");
1445        }
1446        if self.is_false() {
1447            return write!(f, "false");
1448        }
1449        write!(f, "{}", self.contents().unwrap())
1450    }
1451}
1452
1453impl MarkerTree {
1454    /// Formats a [`MarkerTree`] as a graph.
1455    ///
1456    /// This is useful for debugging when one wants to look at a
1457    /// representation of a `MarkerTree` that is more faithful to its
1458    /// internal representation.
1459    pub fn debug_graph(&self) -> MarkerTreeDebugGraph<'_> {
1460        MarkerTreeDebugGraph { marker: self }
1461    }
1462
1463    /// Formats a [`MarkerTree`] in its "raw" representation.
1464    ///
1465    /// This is useful for debugging when one wants to look at a
1466    /// representation of a `MarkerTree` that is precisely identical
1467    /// to its internal representation.
1468    pub fn debug_raw(&self) -> MarkerTreeDebugRaw<'_> {
1469        MarkerTreeDebugRaw { marker: self }
1470    }
1471
1472    fn fmt_graph(self, f: &mut Formatter<'_>, level: usize) -> fmt::Result {
1473        match self.kind() {
1474            MarkerTreeKind::True => return write!(f, "true"),
1475            MarkerTreeKind::False => return write!(f, "false"),
1476            MarkerTreeKind::Version(kind) => {
1477                for (tree, range) in simplify::collect_edges(kind.edges()) {
1478                    writeln!(f)?;
1479                    for _ in 0..level {
1480                        write!(f, "  ")?;
1481                    }
1482
1483                    write!(f, "{key}{range} -> ", key = kind.key())?;
1484                    tree.fmt_graph(f, level + 1)?;
1485                }
1486            }
1487            MarkerTreeKind::String(kind) => {
1488                for (tree, range) in simplify::collect_edges(kind.children()) {
1489                    writeln!(f)?;
1490                    for _ in 0..level {
1491                        write!(f, "  ")?;
1492                    }
1493
1494                    write!(f, "{key}{range} -> ", key = kind.key())?;
1495                    tree.fmt_graph(f, level + 1)?;
1496                }
1497            }
1498            MarkerTreeKind::In(kind) => {
1499                writeln!(f)?;
1500                for _ in 0..level {
1501                    write!(f, "  ")?;
1502                }
1503                write!(f, "{} in {} -> ", kind.key(), kind.value())?;
1504                kind.edge(true).fmt_graph(f, level + 1)?;
1505
1506                writeln!(f)?;
1507                for _ in 0..level {
1508                    write!(f, "  ")?;
1509                }
1510                write!(f, "{} not in {} -> ", kind.key(), kind.value())?;
1511                kind.edge(false).fmt_graph(f, level + 1)?;
1512            }
1513            MarkerTreeKind::Contains(kind) => {
1514                writeln!(f)?;
1515                for _ in 0..level {
1516                    write!(f, "  ")?;
1517                }
1518                write!(f, "{} in {} -> ", kind.value(), kind.key())?;
1519                kind.edge(true).fmt_graph(f, level + 1)?;
1520
1521                writeln!(f)?;
1522                for _ in 0..level {
1523                    write!(f, "  ")?;
1524                }
1525                write!(f, "{} not in {} -> ", kind.value(), kind.key())?;
1526                kind.edge(false).fmt_graph(f, level + 1)?;
1527            }
1528            MarkerTreeKind::List(kind) => {
1529                writeln!(f)?;
1530                for _ in 0..level {
1531                    write!(f, "  ")?;
1532                }
1533                write!(f, "{} in {} -> ", kind.value(), kind.key())?;
1534                kind.edge(true).fmt_graph(f, level + 1)?;
1535
1536                writeln!(f)?;
1537                for _ in 0..level {
1538                    write!(f, "  ")?;
1539                }
1540                write!(f, "{} not in {} -> ", kind.value(), kind.key())?;
1541                kind.edge(false).fmt_graph(f, level + 1)?;
1542            }
1543            MarkerTreeKind::Extra(kind) => {
1544                writeln!(f)?;
1545                for _ in 0..level {
1546                    write!(f, "  ")?;
1547                }
1548                write!(f, "extra == {} -> ", kind.name())?;
1549                kind.edge(true).fmt_graph(f, level + 1)?;
1550
1551                writeln!(f)?;
1552                for _ in 0..level {
1553                    write!(f, "  ")?;
1554                }
1555                write!(f, "extra != {} -> ", kind.name())?;
1556                kind.edge(false).fmt_graph(f, level + 1)?;
1557            }
1558        }
1559
1560        Ok(())
1561    }
1562}
1563
1564impl PartialOrd for MarkerTree {
1565    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1566        Some(self.cmp(other))
1567    }
1568}
1569
1570impl Ord for MarkerTree {
1571    fn cmp(&self, other: &Self) -> Ordering {
1572        self.kind().cmp(&other.kind())
1573    }
1574}
1575
1576/// Formats a [`MarkerTree`] as a graph.
1577///
1578/// This type is created by the [`MarkerTree::debug_graph`] routine.
1579#[derive(Clone)]
1580pub struct MarkerTreeDebugGraph<'a> {
1581    marker: &'a MarkerTree,
1582}
1583
1584impl fmt::Debug for MarkerTreeDebugGraph<'_> {
1585    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1586        self.marker.fmt_graph(f, 0)
1587    }
1588}
1589
1590/// Formats a [`MarkerTree`] using its raw internals.
1591///
1592/// This is very verbose and likely only useful if you're working
1593/// on the internals of this crate.
1594///
1595/// This type is created by the [`MarkerTree::debug_raw`] routine.
1596#[derive(Clone)]
1597pub struct MarkerTreeDebugRaw<'a> {
1598    marker: &'a MarkerTree,
1599}
1600
1601impl fmt::Debug for MarkerTreeDebugRaw<'_> {
1602    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1603        let node = INTERNER.shared.node(self.marker.0);
1604        f.debug_tuple("MarkerTreeDebugRaw").field(node).finish()
1605    }
1606}
1607
1608/// The underlying kind of an arbitrary node in a [`MarkerTree`].
1609///
1610/// A marker tree is represented as an algebraic decision tree with two terminal nodes
1611/// `True` or `False`. The edges of a given node correspond to a particular assignment of
1612/// a value to that variable.
1613#[derive(PartialEq, Eq, Clone, Debug, PartialOrd, Ord)]
1614pub enum MarkerTreeKind<'a> {
1615    /// An empty marker that always evaluates to `true`.
1616    True,
1617    /// An unsatisfiable marker that always evaluates to `false`.
1618    False,
1619    /// A version expression.
1620    Version(VersionMarkerTree<'a>),
1621    /// A string expression.
1622    String(StringMarkerTree<'a>),
1623    /// A string expression with the `in` operator.
1624    In(InMarkerTree<'a>),
1625    /// A string expression with the `contains` operator.
1626    Contains(ContainsMarkerTree<'a>),
1627    /// A `in` or `not in` expression.
1628    List(ListMarkerTree<'a>),
1629    /// An extra expression (e.g., `extra == 'dev'`).
1630    Extra(ExtraMarkerTree<'a>),
1631}
1632
1633/// A version marker node, such as `python_version < '3.7'`.
1634#[derive(PartialEq, Eq, Clone, Debug)]
1635pub struct VersionMarkerTree<'a> {
1636    id: NodeId,
1637    key: CanonicalMarkerValueVersion,
1638    map: &'a [(Ranges<Version>, NodeId)],
1639}
1640
1641impl VersionMarkerTree<'_> {
1642    /// The key for this node.
1643    pub fn key(&self) -> CanonicalMarkerValueVersion {
1644        self.key
1645    }
1646
1647    /// The edges of this node, corresponding to possible output ranges of the given variable.
1648    pub fn edges(&self) -> impl ExactSizeIterator<Item = (&Ranges<Version>, MarkerTree)> + '_ {
1649        self.map
1650            .iter()
1651            .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1652    }
1653}
1654
1655impl PartialOrd for VersionMarkerTree<'_> {
1656    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1657        Some(self.cmp(other))
1658    }
1659}
1660
1661impl Ord for VersionMarkerTree<'_> {
1662    fn cmp(&self, other: &Self) -> Ordering {
1663        self.key()
1664            .cmp(&other.key())
1665            .then_with(|| self.edges().cmp(other.edges()))
1666    }
1667}
1668
1669/// A string marker node, such as `os_name == 'Linux'`.
1670#[derive(PartialEq, Eq, Clone, Debug)]
1671pub struct StringMarkerTree<'a> {
1672    id: NodeId,
1673    key: CanonicalMarkerValueString,
1674    map: &'a [(Ranges<ArcStr>, NodeId)],
1675}
1676
1677impl StringMarkerTree<'_> {
1678    /// The key for this node.
1679    pub fn key(&self) -> CanonicalMarkerValueString {
1680        self.key
1681    }
1682
1683    /// The edges of this node, corresponding to possible output ranges of the given variable.
1684    pub fn children(&self) -> impl ExactSizeIterator<Item = (&Ranges<ArcStr>, MarkerTree)> {
1685        self.map
1686            .iter()
1687            .map(|(range, node)| (range, MarkerTree(node.negate(self.id))))
1688    }
1689}
1690
1691impl PartialOrd for StringMarkerTree<'_> {
1692    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1693        Some(self.cmp(other))
1694    }
1695}
1696
1697impl Ord for StringMarkerTree<'_> {
1698    fn cmp(&self, other: &Self) -> Ordering {
1699        self.key()
1700            .cmp(&other.key())
1701            .then_with(|| self.children().cmp(other.children()))
1702    }
1703}
1704
1705/// A string marker node with the `in` operator, such as `os_name in 'WindowsLinux'`.
1706#[derive(PartialEq, Eq, Clone, Debug)]
1707pub struct InMarkerTree<'a> {
1708    key: CanonicalMarkerValueString,
1709    value: &'a ArcStr,
1710    high: NodeId,
1711    low: NodeId,
1712}
1713
1714impl InMarkerTree<'_> {
1715    /// The key (LHS) for this expression.
1716    pub fn key(&self) -> CanonicalMarkerValueString {
1717        self.key
1718    }
1719
1720    /// The value (RHS) for this expression.
1721    pub fn value(&self) -> &ArcStr {
1722        self.value
1723    }
1724
1725    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1726    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1727        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1728    }
1729
1730    /// Returns the subtree associated with the given edge value.
1731    pub fn edge(&self, value: bool) -> MarkerTree {
1732        if value {
1733            MarkerTree(self.high)
1734        } else {
1735            MarkerTree(self.low)
1736        }
1737    }
1738}
1739
1740impl PartialOrd for InMarkerTree<'_> {
1741    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1742        Some(self.cmp(other))
1743    }
1744}
1745
1746impl Ord for InMarkerTree<'_> {
1747    fn cmp(&self, other: &Self) -> Ordering {
1748        self.key()
1749            .cmp(&other.key())
1750            .then_with(|| self.value().cmp(other.value()))
1751            .then_with(|| self.children().cmp(other.children()))
1752    }
1753}
1754
1755/// A string marker node with inverse of the `in` operator, such as `'nux' in os_name`.
1756#[derive(PartialEq, Eq, Clone, Debug)]
1757pub struct ContainsMarkerTree<'a> {
1758    key: CanonicalMarkerValueString,
1759    value: &'a str,
1760    high: NodeId,
1761    low: NodeId,
1762}
1763
1764impl ContainsMarkerTree<'_> {
1765    /// The key (LHS) for this expression.
1766    pub fn key(&self) -> CanonicalMarkerValueString {
1767        self.key
1768    }
1769
1770    /// The value (RHS) for this expression.
1771    pub fn value(&self) -> &str {
1772        self.value
1773    }
1774
1775    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1776    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1777        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1778    }
1779
1780    /// Returns the subtree associated with the given edge value.
1781    pub fn edge(&self, value: bool) -> MarkerTree {
1782        if value {
1783            MarkerTree(self.high)
1784        } else {
1785            MarkerTree(self.low)
1786        }
1787    }
1788}
1789
1790impl PartialOrd for ContainsMarkerTree<'_> {
1791    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1792        Some(self.cmp(other))
1793    }
1794}
1795
1796impl Ord for ContainsMarkerTree<'_> {
1797    fn cmp(&self, other: &Self) -> Ordering {
1798        self.key()
1799            .cmp(&other.key())
1800            .then_with(|| self.value().cmp(other.value()))
1801            .then_with(|| self.children().cmp(other.children()))
1802    }
1803}
1804
1805#[derive(PartialEq, Eq, Clone, Debug)]
1806pub struct ListMarkerTree<'a> {
1807    // No separate canonical type, the type is already canonical.
1808    pair: &'a CanonicalMarkerListPair,
1809    high: NodeId,
1810    low: NodeId,
1811}
1812
1813impl ListMarkerTree<'_> {
1814    /// The key-value pair for this expression
1815    pub fn pair(&self) -> &CanonicalMarkerListPair {
1816        self.pair
1817    }
1818
1819    /// The key (RHS) for this expression.
1820    pub fn key(&self) -> MarkerValueList {
1821        self.pair.key()
1822    }
1823
1824    /// The value (LHS) for this expression.
1825    pub fn value(&self) -> String {
1826        self.pair.value()
1827    }
1828
1829    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1830    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1831        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1832    }
1833
1834    /// Returns the subtree associated with the given edge value.
1835    pub fn edge(&self, value: bool) -> MarkerTree {
1836        if value {
1837            MarkerTree(self.high)
1838        } else {
1839            MarkerTree(self.low)
1840        }
1841    }
1842}
1843
1844impl PartialOrd for ListMarkerTree<'_> {
1845    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1846        Some(self.cmp(other))
1847    }
1848}
1849
1850impl Ord for ListMarkerTree<'_> {
1851    fn cmp(&self, other: &Self) -> Ordering {
1852        self.pair()
1853            .cmp(other.pair())
1854            .then_with(|| self.children().cmp(other.children()))
1855    }
1856}
1857
1858/// A node representing the existence or absence of a given extra, such as `extra == 'bar'`.
1859#[derive(PartialEq, Eq, Clone, Debug)]
1860pub struct ExtraMarkerTree<'a> {
1861    name: &'a CanonicalMarkerValueExtra,
1862    high: NodeId,
1863    low: NodeId,
1864}
1865
1866impl ExtraMarkerTree<'_> {
1867    /// Returns the name of the extra in this expression.
1868    pub fn name(&self) -> &CanonicalMarkerValueExtra {
1869        self.name
1870    }
1871
1872    /// The edges of this node, corresponding to the boolean evaluation of the expression.
1873    pub fn children(&self) -> impl Iterator<Item = (bool, MarkerTree)> {
1874        [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter()
1875    }
1876
1877    /// Returns the subtree associated with the given edge value.
1878    pub fn edge(&self, value: bool) -> MarkerTree {
1879        if value {
1880            MarkerTree(self.high)
1881        } else {
1882            MarkerTree(self.low)
1883        }
1884    }
1885}
1886
1887impl PartialOrd for ExtraMarkerTree<'_> {
1888    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1889        Some(self.cmp(other))
1890    }
1891}
1892
1893impl Ord for ExtraMarkerTree<'_> {
1894    fn cmp(&self, other: &Self) -> Ordering {
1895        self.name()
1896            .cmp(other.name())
1897            .then_with(|| self.children().cmp(other.children()))
1898    }
1899}
1900
1901/// A marker tree that contains at least one expression.
1902///
1903/// See [`MarkerTree::contents`] for details.
1904#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord, Debug)]
1905pub struct MarkerTreeContents(MarkerTree);
1906
1907impl From<MarkerTreeContents> for MarkerTree {
1908    fn from(contents: MarkerTreeContents) -> Self {
1909        contents.0
1910    }
1911}
1912
1913impl From<Option<MarkerTreeContents>> for MarkerTree {
1914    fn from(marker: Option<MarkerTreeContents>) -> Self {
1915        marker.map(|contents| contents.0).unwrap_or_default()
1916    }
1917}
1918
1919impl AsRef<MarkerTree> for MarkerTreeContents {
1920    fn as_ref(&self) -> &MarkerTree {
1921        &self.0
1922    }
1923}
1924
1925impl Serialize for MarkerTreeContents {
1926    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1927    where
1928        S: Serializer,
1929    {
1930        serializer.serialize_str(&self.to_string())
1931    }
1932}
1933
1934impl Display for MarkerTreeContents {
1935    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1936        // Normalize all `false` expressions to the same trivially false expression.
1937        if self.0.is_false() {
1938            return write!(f, "python_version < '0'");
1939        }
1940
1941        // Write the output in DNF form.
1942        let dnf = self.0.to_dnf();
1943        let format_conjunction = |conjunction: &Vec<MarkerExpression>| {
1944            conjunction
1945                .iter()
1946                .map(MarkerExpression::to_string)
1947                .collect::<Vec<String>>()
1948                .join(" and ")
1949        };
1950
1951        let expr = match &dnf[..] {
1952            [conjunction] => format_conjunction(conjunction),
1953            _ => dnf
1954                .iter()
1955                .map(|conjunction| {
1956                    if conjunction.len() == 1 {
1957                        format_conjunction(conjunction)
1958                    } else {
1959                        format!("({})", format_conjunction(conjunction))
1960                    }
1961                })
1962                .collect::<Vec<String>>()
1963                .join(" or "),
1964        };
1965
1966        f.write_str(&expr)
1967    }
1968}
1969
1970#[cfg(feature = "schemars")]
1971impl schemars::JsonSchema for MarkerTree {
1972    fn schema_name() -> Cow<'static, str> {
1973        Cow::Borrowed("MarkerTree")
1974    }
1975
1976    fn json_schema(_generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
1977        schemars::json_schema!({
1978            "type": "string",
1979            "description": "A PEP 508-compliant marker expression, e.g., `sys_platform == 'Darwin'`"
1980        })
1981    }
1982}
1983
1984#[cfg(test)]
1985mod test {
1986    use std::ops::Bound;
1987    use std::str::FromStr;
1988
1989    use insta::assert_snapshot;
1990
1991    use uv_normalize::ExtraName;
1992    use uv_pep440::Version;
1993
1994    use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder};
1995    use crate::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString};
1996
1997    fn parse_err(input: &str) -> String {
1998        MarkerTree::from_str(input).unwrap_err().to_string()
1999    }
2000
2001    fn m(s: &str) -> MarkerTree {
2002        s.parse().unwrap()
2003    }
2004
2005    fn env37() -> MarkerEnvironment {
2006        MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2007            implementation_name: "",
2008            implementation_version: "3.7",
2009            os_name: "linux",
2010            platform_machine: "x86_64",
2011            platform_python_implementation: "",
2012            platform_release: "",
2013            platform_system: "",
2014            platform_version: "",
2015            python_full_version: "3.7",
2016            python_version: "3.7",
2017            sys_platform: "linux",
2018        })
2019        .unwrap()
2020    }
2021
2022    /// Copied from <https://github.com/pypa/packaging/blob/85ff971a250dc01db188ef9775499c15553a8c95/tests/test_markers.py#L175-L221>
2023    #[test]
2024    fn test_marker_equivalence() {
2025        let values = [
2026            (r"python_version == '2.7'", r#"python_version == "2.7""#),
2027            (r#"python_version == "2.7""#, r#"python_version == "2.7""#),
2028            (
2029                r#"python_version == "2.7" and os_name == "posix""#,
2030                r#"python_version == "2.7" and os_name == "posix""#,
2031            ),
2032            (
2033                r#"python_version == "2.7" or os_name == "posix""#,
2034                r#"python_version == "2.7" or os_name == "posix""#,
2035            ),
2036            (
2037                r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
2038                r#"python_version == "2.7" and os_name == "posix" or sys_platform == "win32""#,
2039            ),
2040            (r#"(python_version == "2.7")"#, r#"python_version == "2.7""#),
2041            (
2042                r#"(python_version == "2.7" and sys_platform == "win32")"#,
2043                r#"python_version == "2.7" and sys_platform == "win32""#,
2044            ),
2045            (
2046                r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
2047                r#"python_version == "2.7" and (sys_platform == "win32" or sys_platform == "linux")"#,
2048            ),
2049        ];
2050
2051        for (a, b) in values {
2052            assert_eq!(m(a), m(b), "{a} {b}");
2053        }
2054    }
2055
2056    #[test]
2057    fn simplify_python_versions() {
2058        assert_eq!(
2059            m("(extra == 'foo' and sys_platform == 'win32') or extra == 'foo'")
2060                .simplify_extras(&["foo".parse().unwrap()]),
2061            MarkerTree::TRUE
2062        );
2063
2064        assert_eq!(
2065            m("(python_version <= '3.11' and sys_platform == 'win32') or python_version > '3.11'")
2066                .simplify_python_versions(
2067                    Bound::Excluded(Version::new([3, 12])).as_ref(),
2068                    Bound::Unbounded.as_ref(),
2069                ),
2070            MarkerTree::TRUE
2071        );
2072
2073        assert_eq!(
2074            m("python_version < '3.10'")
2075                .simplify_python_versions(
2076                    Bound::Excluded(Version::new([3, 7])).as_ref(),
2077                    Bound::Unbounded.as_ref(),
2078                )
2079                .try_to_string()
2080                .unwrap(),
2081            "python_full_version < '3.10'"
2082        );
2083
2084        // Note that `3.12.1` will still match.
2085        assert_eq!(
2086            m("python_version <= '3.12'")
2087                .simplify_python_versions(
2088                    Bound::Excluded(Version::new([3, 12])).as_ref(),
2089                    Bound::Unbounded.as_ref(),
2090                )
2091                .try_to_string()
2092                .unwrap(),
2093            "python_full_version < '3.13'"
2094        );
2095
2096        assert_eq!(
2097            m("python_full_version <= '3.12'").simplify_python_versions(
2098                Bound::Excluded(Version::new([3, 12])).as_ref(),
2099                Bound::Unbounded.as_ref(),
2100            ),
2101            MarkerTree::FALSE
2102        );
2103
2104        assert_eq!(
2105            m("python_full_version <= '3.12.1'")
2106                .simplify_python_versions(
2107                    Bound::Excluded(Version::new([3, 12])).as_ref(),
2108                    Bound::Unbounded.as_ref(),
2109                )
2110                .try_to_string()
2111                .unwrap(),
2112            "python_full_version <= '3.12.1'"
2113        );
2114    }
2115
2116    #[test]
2117    fn release_only() {
2118        assert!(m("python_full_version > '3.10' or python_full_version <= '3.10'").is_true());
2119        assert!(
2120            m("python_full_version > '3.10' or python_full_version <= '3.10'")
2121                .negate()
2122                .is_false()
2123        );
2124        assert!(m("python_full_version > '3.10' and python_full_version <= '3.10'").is_false());
2125    }
2126
2127    #[test]
2128    fn test_marker_evaluation() {
2129        let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2130            implementation_name: "",
2131            implementation_version: "2.7",
2132            os_name: "linux",
2133            platform_machine: "",
2134            platform_python_implementation: "",
2135            platform_release: "",
2136            platform_system: "",
2137            platform_version: "",
2138            python_full_version: "2.7",
2139            python_version: "2.7",
2140            sys_platform: "linux",
2141        })
2142        .unwrap();
2143        let env37 = env37();
2144        let marker1 = MarkerTree::from_str("python_version == '2.7'").unwrap();
2145        let marker2 = MarkerTree::from_str(
2146            "os_name == \"linux\" or python_version == \"3.7\" and sys_platform == \"win32\"",
2147        )
2148        .unwrap();
2149        let marker3 = MarkerTree::from_str(
2150            "python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")",
2151        ).unwrap();
2152        assert!(marker1.evaluate(&env27, &[]));
2153        assert!(!marker1.evaluate(&env37, &[]));
2154        assert!(marker2.evaluate(&env27, &[]));
2155        assert!(marker2.evaluate(&env37, &[]));
2156        assert!(marker3.evaluate(&env27, &[]));
2157        assert!(!marker3.evaluate(&env37, &[]));
2158    }
2159
2160    #[test]
2161    fn test_version_in_evaluation() {
2162        let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {
2163            implementation_name: "",
2164            implementation_version: "2.7",
2165            os_name: "linux",
2166            platform_machine: "",
2167            platform_python_implementation: "",
2168            platform_release: "",
2169            platform_system: "",
2170            platform_version: "",
2171            python_full_version: "2.7",
2172            python_version: "2.7",
2173            sys_platform: "linux",
2174        })
2175        .unwrap();
2176        let env37 = env37();
2177
2178        let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap();
2179        assert!(marker.evaluate(&env27, &[]));
2180        assert!(!marker.evaluate(&env37, &[]));
2181
2182        let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap();
2183        assert!(marker.evaluate(&env27, &[]));
2184        assert!(marker.evaluate(&env37, &[]));
2185
2186        let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap();
2187        assert!(!marker.evaluate(&env27, &[]));
2188        assert!(!marker.evaluate(&env37, &[]));
2189
2190        let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap();
2191        assert!(!marker.evaluate(&env27, &[]));
2192        assert!(marker.evaluate(&env37, &[]));
2193
2194        let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap();
2195        assert!(!marker.evaluate(&env27, &[]));
2196        assert!(!marker.evaluate(&env37, &[]));
2197
2198        let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap();
2199        assert!(marker.evaluate(&env27, &[]));
2200        assert!(marker.evaluate(&env37, &[]));
2201
2202        let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap();
2203        assert!(marker.evaluate(&env27, &[]));
2204        assert!(!marker.evaluate(&env37, &[]));
2205
2206        let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap();
2207        assert!(marker.evaluate(&env27, &[]));
2208        assert!(!marker.evaluate(&env37, &[]));
2209
2210        let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap();
2211        assert!(marker.evaluate(&env27, &[]));
2212        assert!(marker.evaluate(&env37, &[]));
2213
2214        let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap();
2215        assert!(!marker.evaluate(&env27, &[]));
2216        assert!(!marker.evaluate(&env37, &[]));
2217
2218        let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap();
2219        assert!(marker.evaluate(&env27, &[]));
2220        assert!(marker.evaluate(&env37, &[]));
2221    }
2222
2223    #[test]
2224    #[cfg(feature = "tracing")]
2225    #[tracing_test::traced_test]
2226    fn warnings1() {
2227        let env37 = env37();
2228        let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap();
2229        compare_keys.evaluate(&env37, &[]);
2230        logs_contain(
2231            "Comparing two markers with each other doesn't make any sense, will evaluate to false",
2232        );
2233    }
2234
2235    #[test]
2236    #[cfg(feature = "tracing")]
2237    #[tracing_test::traced_test]
2238    fn warnings2() {
2239        let env37 = env37();
2240        let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap();
2241        non_pep440.evaluate(&env37, &[]);
2242        logs_contain(
2243            "Expected PEP 440 version to compare with python_version, found `3.9.`, \
2244             will evaluate to false: after parsing `3.9`, found `.`, which is \
2245             not part of a valid version",
2246        );
2247    }
2248
2249    #[test]
2250    #[cfg(feature = "tracing")]
2251    #[tracing_test::traced_test]
2252    fn warnings3() {
2253        let env37 = env37();
2254        let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap();
2255        string_string.evaluate(&env37, &[]);
2256        logs_contain(
2257            "Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false",
2258        );
2259    }
2260
2261    #[test]
2262    #[cfg(feature = "tracing")]
2263    #[tracing_test::traced_test]
2264    fn warnings4() {
2265        let env37 = env37();
2266        let string_string = MarkerTree::from_str(r"os.name == 'posix' and platform.machine == 'x86_64' and platform.python_implementation == 'CPython' and 'Ubuntu' in platform.version and sys.platform == 'linux'").unwrap();
2267        string_string.evaluate(&env37, &[]);
2268        logs_assert(|lines: &[&str]| {
2269            let lines: Vec<_> = lines
2270                .iter()
2271                .map(|s| s.split_once("  ").unwrap().1)
2272                .collect();
2273            let expected = [
2274                "WARN warnings4: uv_pep508: os.name is deprecated in favor of os_name",
2275                "WARN warnings4: uv_pep508: platform.machine is deprecated in favor of platform_machine",
2276                "WARN warnings4: uv_pep508: platform.python_implementation is deprecated in favor of platform_python_implementation",
2277                "WARN warnings4: uv_pep508: platform.version is deprecated in favor of platform_version",
2278                "WARN warnings4: uv_pep508: sys.platform is deprecated in favor of sys_platform",
2279                "WARN warnings4: uv_pep508: Comparing linux and posix lexicographically",
2280            ];
2281            if lines == expected {
2282                Ok(())
2283            } else {
2284                Err(format!("{lines:#?}"))
2285            }
2286        });
2287    }
2288
2289    #[test]
2290    fn test_not_in() {
2291        MarkerTree::from_str("'posix' not in os_name").unwrap();
2292    }
2293
2294    #[test]
2295    fn test_marker_version_inverted() {
2296        let env37 = env37();
2297        let result = MarkerTree::from_str("python_version > '3.6'")
2298            .unwrap()
2299            .evaluate(&env37, &[]);
2300        assert!(result);
2301
2302        let result = MarkerTree::from_str("'3.6' > python_version")
2303            .unwrap()
2304            .evaluate(&env37, &[]);
2305        assert!(!result);
2306
2307        // Meaningless expressions are ignored, so this is always true.
2308        let result = MarkerTree::from_str("'3.*' == python_version")
2309            .unwrap()
2310            .evaluate(&env37, &[]);
2311        assert!(result);
2312    }
2313
2314    #[test]
2315    fn test_marker_string_inverted() {
2316        let env37 = env37();
2317        let result = MarkerTree::from_str("'nux' in sys_platform")
2318            .unwrap()
2319            .evaluate(&env37, &[]);
2320        assert!(result);
2321
2322        let result = MarkerTree::from_str("sys_platform in 'nux'")
2323            .unwrap()
2324            .evaluate(&env37, &[]);
2325        assert!(!result);
2326    }
2327
2328    #[test]
2329    fn test_marker_version_star() {
2330        let env37 = env37();
2331        let result = MarkerTree::from_str("python_version == '3.7.*'")
2332            .unwrap()
2333            .evaluate(&env37, &[]);
2334        assert!(result);
2335    }
2336
2337    #[test]
2338    fn test_tilde_equal() {
2339        let env37 = env37();
2340        let result = MarkerTree::from_str("python_version ~= '3.7'")
2341            .unwrap()
2342            .evaluate(&env37, &[]);
2343        assert!(result);
2344    }
2345
2346    #[test]
2347    fn test_closing_parentheses() {
2348        MarkerTree::from_str(r#"( "linux" in sys_platform) and extra == 'all'"#).unwrap();
2349    }
2350
2351    #[test]
2352    fn wrong_quotes_dot_star() {
2353        assert_snapshot!(
2354            parse_err(r#"python_version == "3.8".* and python_version >= "3.8""#),
2355            @r#"
2356        Unexpected character '.', expected 'and', 'or' or end of input
2357        python_version == "3.8".* and python_version >= "3.8"
2358                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2359        "#
2360        );
2361        assert_snapshot!(
2362            parse_err(r#"python_version == "3.8".*"#),
2363            @r#"
2364        Unexpected character '.', expected 'and', 'or' or end of input
2365        python_version == "3.8".*
2366                               ^
2367        "#
2368        );
2369    }
2370
2371    #[test]
2372    fn test_marker_expression() {
2373        assert_eq!(
2374            MarkerExpression::from_str(r#"os_name == "nt""#)
2375                .unwrap()
2376                .unwrap(),
2377            MarkerExpression::String {
2378                key: MarkerValueString::OsName,
2379                operator: MarkerOperator::Equal,
2380                value: arcstr::literal!("nt")
2381            }
2382        );
2383    }
2384
2385    #[test]
2386    fn test_marker_expression_inverted() {
2387        assert_eq!(
2388            MarkerTree::from_str(
2389                r#""nt" in os_name and '3.7' >= python_version and python_full_version >= '3.7'"#
2390            )
2391            .unwrap()
2392            .contents()
2393            .unwrap()
2394            .to_string(),
2395            "python_full_version == '3.7.*' and 'nt' in os_name",
2396        );
2397    }
2398
2399    #[test]
2400    fn test_marker_expression_to_long() {
2401        let err = MarkerExpression::from_str(r#"os_name == "nt" and python_version >= "3.8""#)
2402            .unwrap_err()
2403            .to_string();
2404        assert_snapshot!(
2405            err,
2406            @r#"
2407        Unexpected character 'a', expected end of input
2408        os_name == "nt" and python_version >= "3.8"
2409                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
2410        "#
2411        );
2412    }
2413
2414    #[test]
2415    fn test_marker_environment_from_json() {
2416        let _env: MarkerEnvironment = serde_json::from_str(
2417            r##"{
2418                "implementation_name": "cpython",
2419                "implementation_version": "3.7.13",
2420                "os_name": "posix",
2421                "platform_machine": "x86_64",
2422                "platform_python_implementation": "CPython",
2423                "platform_release": "5.4.188+",
2424                "platform_system": "Linux",
2425                "platform_version": "#1 SMP Sun Apr 24 10:03:06 PDT 2022",
2426                "python_full_version": "3.7.13",
2427                "python_version": "3.7",
2428                "sys_platform": "linux"
2429            }"##,
2430        )
2431        .unwrap();
2432    }
2433
2434    #[test]
2435    fn test_simplify_extras() {
2436        // Given `os_name == "nt" and extra == "dev"`, simplify to `os_name == "nt"`.
2437        let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "dev""#).unwrap();
2438        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2439        let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2440        assert_eq!(simplified, expected);
2441
2442        // Given `os_name == "nt" or extra == "dev"`, remove the marker entirely.
2443        let markers = MarkerTree::from_str(r#"os_name == "nt" or extra == "dev""#).unwrap();
2444        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2445        assert_eq!(simplified, MarkerTree::TRUE);
2446
2447        // Given `extra == "dev"`, remove the marker entirely.
2448        let markers = MarkerTree::from_str(r#"extra == "dev""#).unwrap();
2449        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2450        assert_eq!(simplified, MarkerTree::TRUE);
2451
2452        // Given `extra == "dev" and extra == "test"`, simplify to `extra == "test"`.
2453        let markers = MarkerTree::from_str(r#"extra == "dev" and extra == "test""#).unwrap();
2454        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2455        let expected = MarkerTree::from_str(r#"extra == "test""#).unwrap();
2456        assert_eq!(simplified, expected);
2457
2458        // Given `os_name == "nt" and extra == "test"`, don't simplify.
2459        let markers = MarkerTree::from_str(r#"os_name == "nt" and extra == "test""#).unwrap();
2460        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2461        assert_eq!(simplified, markers);
2462
2463        // Given `os_name == "nt" and (python_version == "3.7" or extra == "dev")`, simplify to
2464        // `os_name == "nt".
2465        let markers = MarkerTree::from_str(
2466            r#"os_name == "nt" and (python_version == "3.7" or extra == "dev")"#,
2467        )
2468        .unwrap();
2469        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2470        let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2471        assert_eq!(simplified, expected);
2472
2473        // Given `os_name == "nt" or (python_version == "3.7" and extra == "dev")`, simplify to
2474        // `os_name == "nt" or python_version == "3.7"`.
2475        let markers = MarkerTree::from_str(
2476            r#"os_name == "nt" or (python_version == "3.7" and extra == "dev")"#,
2477        )
2478        .unwrap();
2479        let simplified = markers.simplify_extras(&[ExtraName::from_str("dev").unwrap()]);
2480        let expected =
2481            MarkerTree::from_str(r#"os_name == "nt" or python_version == "3.7""#).unwrap();
2482        assert_eq!(simplified, expected);
2483    }
2484
2485    #[test]
2486    fn test_simplify_not_extras() {
2487        // Given `os_name == "nt" and extra != "dev"`, simplify to `os_name == "nt"`.
2488        let markers = MarkerTree::from_str(r#"os_name == "nt" and extra != "dev""#).unwrap();
2489        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2490        let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2491        assert_eq!(simplified, expected);
2492
2493        // Given `os_name == "nt" or extra != "dev"`, remove the marker entirely.
2494        let markers = MarkerTree::from_str(r#"os_name == "nt" or extra != "dev""#).unwrap();
2495        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2496        assert_eq!(simplified, MarkerTree::TRUE);
2497
2498        // Given `extra != "dev"`, remove the marker entirely.
2499        let markers = MarkerTree::from_str(r#"extra != "dev""#).unwrap();
2500        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2501        assert_eq!(simplified, MarkerTree::TRUE);
2502
2503        // Given `extra != "dev" and extra != "test"`, simplify to `extra != "test"`.
2504        let markers = MarkerTree::from_str(r#"extra != "dev" and extra != "test""#).unwrap();
2505        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2506        let expected = MarkerTree::from_str(r#"extra != "test""#).unwrap();
2507        assert_eq!(simplified, expected);
2508
2509        // Given `os_name == "nt" and extra != "test"`, don't simplify.
2510        let markers = MarkerTree::from_str(r#"os_name != "nt" and extra != "test""#).unwrap();
2511        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2512        assert_eq!(simplified, markers);
2513
2514        // Given `os_name == "nt" and (python_version == "3.7" or extra != "dev")`, simplify to
2515        // `os_name == "nt".
2516        let markers = MarkerTree::from_str(
2517            r#"os_name == "nt" and (python_version == "3.7" or extra != "dev")"#,
2518        )
2519        .unwrap();
2520        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2521        let expected = MarkerTree::from_str(r#"os_name == "nt""#).unwrap();
2522        assert_eq!(simplified, expected);
2523
2524        // Given `os_name == "nt" or (python_version == "3.7" and extra != "dev")`, simplify to
2525        // `os_name == "nt" or python_version == "3.7"`.
2526        let markers = MarkerTree::from_str(
2527            r#"os_name == "nt" or (python_version == "3.7" and extra != "dev")"#,
2528        )
2529        .unwrap();
2530        let simplified = markers.simplify_not_extras(&[ExtraName::from_str("dev").unwrap()]);
2531        let expected =
2532            MarkerTree::from_str(r#"os_name == "nt" or python_version == "3.7""#).unwrap();
2533        assert_eq!(simplified, expected);
2534    }
2535
2536    #[test]
2537    fn test_marker_simplification() {
2538        assert_false("python_version == '3.9.1'");
2539        assert_true("python_version != '3.9.1'");
2540
2541        // This is an edge case that happens to be supported, but is not critical to support.
2542        assert_simplifies(
2543            "python_version in '3.9.0'",
2544            "python_full_version == '3.9.*'",
2545        );
2546        // e.g., using a version that is not PEP 440 compliant is considered arbitrary
2547        assert_true("python_version in 'foo'");
2548        // e.g., including `*` versions, which would require tracking a version specifier
2549        assert_true("python_version in '3.9.*'");
2550        // e.g., when non-whitespace separators are present
2551        assert_true("python_version in '3.9, 3.10'");
2552        assert_true("python_version in '3.9,3.10'");
2553        assert_true("python_version in '3.9 or 3.10'");
2554
2555        // This is an edge case that happens to be supported, but is not critical to support.
2556        assert_simplifies(
2557            "python_version in '3.9 3.10.0 3.11'",
2558            "python_full_version >= '3.9' and python_full_version < '3.12'",
2559        );
2560
2561        assert_simplifies("python_version == '3.9'", "python_full_version == '3.9.*'");
2562        assert_simplifies(
2563            "python_version == '3.9.0'",
2564            "python_full_version == '3.9.*'",
2565        );
2566        assert_simplifies(
2567            "python_version == '3.9.0.*'",
2568            "python_full_version == '3.9.*'",
2569        );
2570        assert_simplifies(
2571            "python_version == '3.*'",
2572            "python_full_version >= '3' and python_full_version < '4'",
2573        );
2574
2575        // `<version> in`
2576        // e.g., when the range is not contiguous
2577        assert_simplifies(
2578            "python_version in '3.9 3.11'",
2579            "python_full_version == '3.9.*' or python_full_version == '3.11.*'",
2580        );
2581        // e.g., when the range is contiguous
2582        assert_simplifies(
2583            "python_version in '3.9 3.10 3.11'",
2584            "python_full_version >= '3.9' and python_full_version < '3.12'",
2585        );
2586        // e.g., with `implementation_version` instead of `python_version`
2587        assert_simplifies(
2588            "implementation_version in '3.9 3.11'",
2589            "implementation_version == '3.9' or implementation_version == '3.11'",
2590        );
2591
2592        // '<version> not in'
2593        // e.g., when the range is not contiguous
2594        assert_simplifies(
2595            "python_version not in '3.9 3.11'",
2596            "python_full_version < '3.9' or python_full_version == '3.10.*' or python_full_version >= '3.12'",
2597        );
2598        // e.g, when the range is contiguous
2599        assert_simplifies(
2600            "python_version not in '3.9 3.10 3.11'",
2601            "python_full_version < '3.9' or python_full_version >= '3.12'",
2602        );
2603        // e.g., with `implementation_version` instead of `python_version`
2604        assert_simplifies(
2605            "implementation_version not in '3.9 3.11'",
2606            "implementation_version != '3.9' and implementation_version != '3.11'",
2607        );
2608
2609        assert_simplifies("python_version != '3.9'", "python_full_version != '3.9.*'");
2610
2611        assert_simplifies("python_version >= '3.9.0'", "python_full_version >= '3.9'");
2612        assert_simplifies("python_version <= '3.9.0'", "python_full_version < '3.10'");
2613
2614        assert_simplifies(
2615            "python_version == '3.*'",
2616            "python_full_version >= '3' and python_full_version < '4'",
2617        );
2618        assert_simplifies(
2619            "python_version == '3.0.*'",
2620            "python_full_version == '3.0.*'",
2621        );
2622
2623        assert_simplifies(
2624            "python_version < '3.17' or python_version < '3.18'",
2625            "python_full_version < '3.18'",
2626        );
2627
2628        assert_simplifies(
2629            "python_version > '3.17' or python_version > '3.18' or python_version > '3.12'",
2630            "python_full_version >= '3.13'",
2631        );
2632
2633        // a quirk of how pubgrub works, but this is considered part of normalization
2634        assert_simplifies(
2635            "python_version > '3.17.post4' or python_version > '3.18.post4'",
2636            "python_full_version >= '3.18'",
2637        );
2638
2639        assert_simplifies(
2640            "python_version < '3.17' and python_version < '3.18'",
2641            "python_full_version < '3.17'",
2642        );
2643
2644        assert_simplifies(
2645            "python_version <= '3.18' and python_version == '3.18'",
2646            "python_full_version == '3.18.*'",
2647        );
2648
2649        assert_simplifies(
2650            "python_version <= '3.18' or python_version == '3.18'",
2651            "python_full_version < '3.19'",
2652        );
2653
2654        assert_simplifies(
2655            "python_version <= '3.15' or (python_version <= '3.17' and python_version < '3.16')",
2656            "python_full_version < '3.16'",
2657        );
2658
2659        assert_simplifies(
2660            "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15'",
2661            "python_full_version >= '3.17'",
2662        );
2663
2664        assert_simplifies(
2665            "(python_version > '3.17' or python_version > '3.16') and python_version > '3.15' and implementation_version == '1'",
2666            "implementation_version == '1' and python_full_version >= '3.17'",
2667        );
2668
2669        assert_simplifies(
2670            "('3.17' < python_version or '3.16' < python_version) and '3.15' < python_version and implementation_version == '1'",
2671            "implementation_version == '1' and python_full_version >= '3.17'",
2672        );
2673
2674        assert_simplifies("extra == 'a' or extra == 'a'", "extra == 'a'");
2675        assert_simplifies(
2676            "extra == 'a' and extra == 'a' or extra == 'b'",
2677            "extra == 'a' or extra == 'b'",
2678        );
2679
2680        assert!(m("python_version < '3.17' and '3.18' == python_version").is_false());
2681
2682        // flatten nested expressions
2683        assert_simplifies(
2684            "((extra == 'a' and extra == 'b') and extra == 'c') and extra == 'b'",
2685            "extra == 'a' and extra == 'b' and extra == 'c'",
2686        );
2687
2688        assert_simplifies(
2689            "((extra == 'a' or extra == 'b') or extra == 'c') or extra == 'b'",
2690            "extra == 'a' or extra == 'b' or extra == 'c'",
2691        );
2692
2693        // complex expressions
2694        assert_simplifies(
2695            "extra == 'a' or (extra == 'a' and extra == 'b')",
2696            "extra == 'a'",
2697        );
2698
2699        assert_simplifies(
2700            "extra == 'a' and (extra == 'a' or extra == 'b')",
2701            "extra == 'a'",
2702        );
2703
2704        assert_simplifies(
2705            "(extra == 'a' and (extra == 'a' or extra == 'b')) or extra == 'd'",
2706            "extra == 'a' or extra == 'd'",
2707        );
2708
2709        assert_simplifies(
2710            "((extra == 'a' and extra == 'b') or extra == 'c') or extra == 'b'",
2711            "extra == 'b' or extra == 'c'",
2712        );
2713
2714        assert_simplifies(
2715            "((extra == 'a' or extra == 'b') and extra == 'c') and extra == 'b'",
2716            "extra == 'b' and extra == 'c'",
2717        );
2718
2719        assert_simplifies(
2720            "((extra == 'a' or extra == 'b') and extra == 'c') or extra == 'b'",
2721            "extra == 'b' or (extra == 'a' and extra == 'c')",
2722        );
2723
2724        // post-normalization filtering
2725        assert_simplifies(
2726            "(python_version < '3.1' or python_version < '3.2') and (python_version < '3.2' or python_version == '3.3')",
2727            "python_full_version < '3.2'",
2728        );
2729
2730        // normalize out redundant ranges
2731        assert_true("python_version < '3.12.0rc1' or python_version >= '3.12.0rc1'");
2732
2733        assert_true(
2734            "extra == 'a' or (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2735        );
2736
2737        assert_simplifies(
2738            "extra == 'a' and (python_version < '3.12.0rc1' or python_version >= '3.12.0rc1')",
2739            "extra == 'a'",
2740        );
2741
2742        // normalize `!=` operators
2743        assert_true("python_version != '3.10' or python_version < '3.12'");
2744
2745        assert_simplifies(
2746            "python_version != '3.10' or python_version > '3.12'",
2747            "python_full_version != '3.10.*'",
2748        );
2749
2750        assert_simplifies(
2751            "python_version != '3.8' and python_version < '3.10'",
2752            "python_full_version < '3.8' or python_full_version == '3.9.*'",
2753        );
2754
2755        assert_simplifies(
2756            "python_version != '3.8' and python_version != '3.9'",
2757            "python_full_version < '3.8' or python_full_version >= '3.10'",
2758        );
2759
2760        // normalize out redundant expressions
2761        assert_true("sys_platform == 'win32' or sys_platform != 'win32'");
2762
2763        assert_true("'win32' == sys_platform or sys_platform != 'win32'");
2764
2765        assert_true(
2766            "sys_platform == 'win32' or sys_platform == 'win32' or sys_platform != 'win32'",
2767        );
2768
2769        assert!(m("sys_platform == 'win32' and sys_platform != 'win32'").is_false());
2770    }
2771
2772    /// This tests the difference between simplifying extras and simplifying
2773    /// other stringly-valued marker attributes.
2774    ///
2775    /// In particular, this demonstrates that `extra != "foo"` would actually
2776    /// be more clearly written as `"foo" not in extras`. And similarly, `extra
2777    /// == "foo"` would be written as `"foo" in extras`. This is different from
2778    /// other attributes, like `sys_platform`, where the test is just string
2779    /// equality. That is, `extra` is a set where as `sys_platform` is just a
2780    /// single value.
2781    #[test]
2782    fn test_simplification_extra_versus_other() {
2783        // Here, the `extra != 'foo'` cannot be simplified out, because
2784        // `extra == 'foo'` can be true even when `extra == 'bar'`' is true.
2785        assert_simplifies(
2786            r#"extra != "foo" and (extra == "bar" or extra == "baz")"#,
2787            "(extra == 'bar' and extra != 'foo') or (extra == 'baz' and extra != 'foo')",
2788        );
2789        // But here, the `sys_platform != 'foo'` can be simplified out, because
2790        // it is strictly disjoint with
2791        // `sys_platform == "bar" or sys_platform == "baz"`.
2792        assert_simplifies(
2793            r#"sys_platform != "foo" and (sys_platform == "bar" or sys_platform == "baz")"#,
2794            "sys_platform == 'bar' or sys_platform == 'baz'",
2795        );
2796
2797        // Another case I experimented with and wanted to verify.
2798        assert_simplifies(
2799            r#"(extra != "bar" and (extra == "foo" or extra == "baz"))
2800            or ((extra != "foo" and extra != "bar") and extra == "baz")"#,
2801            "(extra != 'bar' and extra == 'baz') or (extra != 'bar' and extra == 'foo')",
2802        );
2803    }
2804
2805    #[test]
2806    fn test_python_version_equal_star() {
2807        // Input, equivalent with python_version, equivalent with python_full_version
2808        let cases = [
2809            ("3.*", "3.*", "3.*"),
2810            ("3.0.*", "3.0", "3.0.*"),
2811            ("3.0.0.*", "3.0", "3.0.*"),
2812            ("3.9.*", "3.9", "3.9.*"),
2813            ("3.9.0.*", "3.9", "3.9.*"),
2814            ("3.9.0.0.*", "3.9", "3.9.*"),
2815        ];
2816        for (input, equal_python_version, equal_python_full_version) in cases {
2817            assert_eq!(
2818                m(&format!("python_version == '{input}'")),
2819                m(&format!("python_version == '{equal_python_version}'")),
2820                "{input} {equal_python_version}"
2821            );
2822            assert_eq!(
2823                m(&format!("python_version == '{input}'")),
2824                m(&format!(
2825                    "python_full_version == '{equal_python_full_version}'"
2826                )),
2827                "{input} {equal_python_full_version}"
2828            );
2829        }
2830
2831        let cases_false = ["3.9.1.*", "3.9.1.0.*", "3.9.1.0.0.*"];
2832        for input in cases_false {
2833            assert!(
2834                m(&format!("python_version == '{input}'")).is_false(),
2835                "{input}"
2836            );
2837        }
2838    }
2839
2840    #[test]
2841    fn test_tilde_equal_normalization() {
2842        assert_eq!(
2843            m("python_version ~= '3.10.0'"),
2844            m("python_version >= '3.10.0' and python_version < '3.11.0'")
2845        );
2846
2847        // Two digit versions such as `python_version` get padded with a zero, so they can never
2848        // match
2849        assert_eq!(m("python_version ~= '3.10.1'"), MarkerTree::FALSE);
2850
2851        assert_eq!(
2852            m("python_version ~= '3.10'"),
2853            m("python_version >= '3.10' and python_version < '4.0'")
2854        );
2855
2856        assert_eq!(
2857            m("python_full_version ~= '3.10.0'"),
2858            m("python_full_version >= '3.10.0' and python_full_version < '3.11.0'")
2859        );
2860
2861        assert_eq!(
2862            m("python_full_version ~= '3.10'"),
2863            m("python_full_version >= '3.10' and python_full_version < '4.0'")
2864        );
2865    }
2866
2867    /// This tests marker implication.
2868    ///
2869    /// Specifically, these test cases come from a [bug] where `foo` and `bar`
2870    /// are conflicting extras, but results in an ambiguous lock file. This
2871    /// test takes `not(extra == 'foo' and extra == 'bar')` as world knowledge.
2872    /// That is, since they are declared as conflicting, they cannot both be
2873    /// true. This is in turn used to determine whether particular markers are
2874    /// implied by this world knowledge and thus can be removed.
2875    ///
2876    /// [bug]: <https://github.com/astral-sh/uv/issues/9289>
2877    #[test]
2878    fn test_extra_implication() {
2879        assert!(implies(
2880            "extra != 'foo' or extra != 'bar'",
2881            "extra != 'foo' or extra != 'bar'",
2882        ));
2883        assert!(!implies(
2884            "extra != 'foo' or extra != 'bar'",
2885            "extra != 'foo'",
2886        ));
2887        assert!(!implies(
2888            "extra != 'foo' or extra != 'bar'",
2889            "extra != 'bar' and extra == 'foo'",
2890        ));
2891
2892        // This simulates the case when multiple groups of conflicts
2893        // are combined into one "world knowledge" marker.
2894        assert!(implies(
2895            "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2896            "extra != 'foo' or extra != 'bar'",
2897        ));
2898        assert!(!implies(
2899            "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2900            "extra != 'foo'",
2901        ));
2902        assert!(!implies(
2903            "(extra != 'foo' or extra != 'bar') and (extra != 'baz' or extra != 'quux')",
2904            "extra != 'bar' and extra == 'foo'",
2905        ));
2906    }
2907
2908    #[test]
2909    fn test_marker_negation() {
2910        assert_eq!(
2911            m("python_version > '3.6'").negate(),
2912            m("python_version <= '3.6'")
2913        );
2914
2915        assert_eq!(
2916            m("'3.6' < python_version").negate(),
2917            m("python_version <= '3.6'")
2918        );
2919
2920        assert_eq!(
2921            m("python_version != '3.6' and os_name == 'Linux'").negate(),
2922            m("python_version == '3.6' or os_name != 'Linux'")
2923        );
2924
2925        assert_eq!(
2926            m("python_version == '3.6' and os_name != 'Linux'").negate(),
2927            m("python_version != '3.6' or os_name == 'Linux'")
2928        );
2929
2930        assert_eq!(
2931            m("python_version != '3.6.*' and os_name == 'Linux'").negate(),
2932            m("python_version == '3.6.*' or os_name != 'Linux'")
2933        );
2934
2935        assert_eq!(
2936            m("python_version == '3.6.*'").negate(),
2937            m("python_version != '3.6.*'")
2938        );
2939        assert_eq!(
2940            m("python_version != '3.6.*'").negate(),
2941            m("python_version == '3.6.*'")
2942        );
2943
2944        assert_eq!(
2945            m("python_version ~= '3.6'").negate(),
2946            m("python_version < '3.6' or python_version != '3.*'")
2947        );
2948        assert_eq!(
2949            m("'3.6' ~= python_version").negate(),
2950            m("python_version < '3.6' or python_version != '3.*'")
2951        );
2952        assert_eq!(
2953            m("python_version ~= '3.6.2'").negate(),
2954            m("python_version < '3.6.2' or python_version != '3.6.*'")
2955        );
2956
2957        assert_eq!(
2958            m("sys_platform == 'linux'").negate(),
2959            m("sys_platform != 'linux'")
2960        );
2961        assert_eq!(
2962            m("'linux' == sys_platform").negate(),
2963            m("sys_platform != 'linux'")
2964        );
2965
2966        // ~= is nonsense on string markers, so the markers is ignored and always
2967        // evaluates to true. Thus the negation always returns false.
2968        assert_eq!(m("sys_platform ~= 'linux'").negate(), MarkerTree::FALSE);
2969
2970        // As above, arbitrary exprs remain arbitrary.
2971        assert_eq!(m("'foo' == 'bar'").negate(), MarkerTree::FALSE);
2972
2973        // Conjunctions
2974        assert_eq!(
2975            m("os_name == 'bar' and os_name == 'foo'").negate(),
2976            m("os_name != 'bar' or os_name != 'foo'")
2977        );
2978        // Disjunctions
2979        assert_eq!(
2980            m("os_name == 'bar' or os_name == 'foo'").negate(),
2981            m("os_name != 'bar' and os_name != 'foo'")
2982        );
2983
2984        // Always true negates to always false!
2985        assert_eq!(
2986            m("python_version >= '3.6' or python_version < '3.6'").negate(),
2987            m("python_version < '3.6' and python_version >= '3.6'")
2988        );
2989    }
2990
2991    #[test]
2992    fn test_complex_marker_simplification() {
2993        // This expression should simplify to:
2994        // `(implementation_name == 'pypy' and sys_platform != 'win32')
2995        //   or (sys_platform == 'win32' or os_name != 'nt')
2996        //   or (implementation != 'pypy' or os_name == 'nt')`
2997        //
2998        // However, simplifying this expression is NP-complete and requires an exponential
2999        // algorithm such as Quine-McCluskey, which is not currently implemented.
3000        assert_simplifies(
3001            "(implementation_name == 'pypy' and sys_platform != 'win32')
3002                or (implementation_name != 'pypy' and sys_platform == 'win32')
3003                or (sys_platform == 'win32' and os_name != 'nt')
3004                or (sys_platform != 'win32' and os_name == 'nt')",
3005            "(implementation_name == 'pypy' and sys_platform != 'win32') \
3006                or (implementation_name != 'pypy' and sys_platform == 'win32') \
3007                or (os_name != 'nt' and sys_platform == 'win32') \
3008                or (os_name == 'nt' and sys_platform != 'win32')",
3009        );
3010
3011        // This is a case we can simplify fully, but it's dependent on the variable order.
3012        // The expression is equivalent to `sys_platform == 'x' or (os_name == 'Linux' and platform_system == 'win32')`.
3013        assert_simplifies(
3014            "(os_name == 'Linux' and platform_system == 'win32')
3015                or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'a')
3016                or (os_name == 'Linux' and platform_system == 'win32' and sys_platform == 'x')
3017                or (os_name != 'Linux' and platform_system == 'win32' and sys_platform == 'x')
3018                or (os_name == 'Linux' and platform_system != 'win32' and sys_platform == 'x')
3019                or (os_name != 'Linux' and platform_system != 'win32' and sys_platform == 'x')",
3020            "(os_name == 'Linux' and platform_system == 'win32') or sys_platform == 'x'",
3021        );
3022
3023        assert_simplifies("python_version > '3.7'", "python_full_version >= '3.8'");
3024
3025        assert_simplifies(
3026            "(python_version <= '3.7' and os_name == 'Linux') or python_version > '3.7'",
3027            "python_full_version >= '3.8' or os_name == 'Linux'",
3028        );
3029
3030        // Again, the extra `<3.7` and `>=3.9` expressions cannot be seen as redundant due to them being interdependent.
3031        // TODO(ibraheem): We might be able to simplify these by checking for the negation of the combined ranges before we split them.
3032        assert_simplifies(
3033            "(os_name == 'Linux' and sys_platform == 'win32') \
3034                or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.7') \
3035                or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.8')",
3036            "(python_full_version >= '3.7' and python_full_version < '3.9' and sys_platform == 'win32') or (os_name == 'Linux' and sys_platform == 'win32')",
3037        );
3038
3039        assert_simplifies(
3040            "(implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32')",
3041            "os_name == 'nt' and sys_platform == 'win32'",
3042        );
3043
3044        assert_simplifies(
3045            "(sys_platform == 'darwin' or sys_platform == 'win32') and ((implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
3046            "os_name == 'nt' and sys_platform == 'win32'",
3047        );
3048
3049        assert_simplifies(
3050            "(sys_platform == 'darwin' or sys_platform == 'win32')
3051                and ((platform_version != '1' and os_name == 'nt' and sys_platform == 'darwin') or (os_name == 'nt' and sys_platform == 'win32'))",
3052            "os_name == 'nt' and sys_platform == 'win32'",
3053        );
3054
3055        assert_simplifies(
3056            "(os_name == 'nt' and sys_platform == 'win32') \
3057                or (os_name != 'nt' and platform_version == '1' and (sys_platform == 'win32' or sys_platform == 'win64'))",
3058            "(os_name != 'nt' and platform_version == '1' and sys_platform == 'win64') \
3059                or (os_name == 'nt' and sys_platform == 'win32') \
3060                or (platform_version == '1' and sys_platform == 'win32')",
3061        );
3062
3063        assert_simplifies(
3064            "(os_name == 'nt' and sys_platform == 'win32') or (os_name != 'nt' and (sys_platform == 'win32' or sys_platform == 'win64'))",
3065            "(os_name != 'nt' and sys_platform == 'win64') or sys_platform == 'win32'",
3066        );
3067    }
3068
3069    #[test]
3070    fn test_requires_python() {
3071        fn simplified(marker: &str) -> MarkerTree {
3072            let lower = Bound::Included(Version::new([3, 8]));
3073            let upper = Bound::Unbounded;
3074            m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
3075        }
3076
3077        assert_eq!(simplified("python_version >= '3.8'"), MarkerTree::TRUE);
3078        assert_eq!(
3079            simplified("python_version >= '3.8' or sys_platform == 'win32'"),
3080            MarkerTree::TRUE
3081        );
3082
3083        assert_eq!(
3084            simplified("python_version >= '3.8' and sys_platform == 'win32'"),
3085            m("sys_platform == 'win32'"),
3086        );
3087
3088        assert_eq!(
3089            simplified("python_version == '3.8'")
3090                .try_to_string()
3091                .unwrap(),
3092            "python_full_version < '3.9'"
3093        );
3094
3095        assert_eq!(
3096            simplified("python_version <= '3.10'")
3097                .try_to_string()
3098                .unwrap(),
3099            "python_full_version < '3.11'"
3100        );
3101    }
3102
3103    #[test]
3104    fn test_extra_disjointness() {
3105        assert!(!is_disjoint("extra == 'a'", "python_version == '1'"));
3106
3107        assert!(!is_disjoint("extra == 'a'", "extra == 'a'"));
3108        assert!(!is_disjoint("extra == 'a'", "extra == 'b'"));
3109        assert!(!is_disjoint("extra == 'b'", "extra == 'a'"));
3110        assert!(!is_disjoint("extra == 'b'", "extra != 'a'"));
3111        assert!(!is_disjoint("extra != 'b'", "extra == 'a'"));
3112        assert!(is_disjoint("extra != 'b'", "extra == 'b'"));
3113        assert!(is_disjoint("extra == 'b'", "extra != 'b'"));
3114    }
3115
3116    #[test]
3117    fn test_arbitrary_disjointness() {
3118        // `python_version == 'Linux'` is nonsense and ignored, thus the first marker
3119        // is always `true` and not disjoint.
3120        assert!(!is_disjoint(
3121            "python_version == 'Linux'",
3122            "python_full_version == '3.7.1'"
3123        ));
3124    }
3125
3126    #[test]
3127    fn test_version_disjointness() {
3128        assert!(!is_disjoint(
3129            "os_name == 'Linux'",
3130            "python_full_version == '3.7.1'"
3131        ));
3132
3133        test_version_bounds_disjointness("python_full_version");
3134
3135        assert!(!is_disjoint(
3136            "python_full_version == '3.7.*'",
3137            "python_full_version == '3.7.1'"
3138        ));
3139
3140        assert!(is_disjoint(
3141            "python_version == '3.7'",
3142            "python_full_version == '3.8'"
3143        ));
3144
3145        assert!(!is_disjoint(
3146            "python_version == '3.7'",
3147            "python_full_version == '3.7.2'"
3148        ));
3149
3150        assert!(is_disjoint(
3151            "python_version > '3.7'",
3152            "python_full_version == '3.7.1'"
3153        ));
3154
3155        assert!(!is_disjoint(
3156            "python_version <= '3.7'",
3157            "python_full_version == '3.7.1'"
3158        ));
3159    }
3160
3161    #[test]
3162    fn test_string_disjointness() {
3163        assert!(!is_disjoint(
3164            "os_name == 'Linux'",
3165            "platform_version == '3.7.1'"
3166        ));
3167        assert!(!is_disjoint(
3168            "implementation_version == '3.7.0'",
3169            "python_full_version == '3.7.1'"
3170        ));
3171
3172        // basic version bounds checking should still work with lexicographical comparisons
3173        test_version_bounds_disjointness("platform_version");
3174
3175        assert!(is_disjoint("os_name == 'Linux'", "os_name == 'OSX'"));
3176        assert!(is_disjoint("os_name <= 'Linux'", "os_name == 'OSX'"));
3177
3178        assert!(!is_disjoint(
3179            "os_name in 'OSXLinuxWindows'",
3180            "os_name == 'OSX'"
3181        ));
3182        assert!(!is_disjoint("'OSX' in os_name", "'Linux' in os_name"));
3183
3184        // complicated `in` intersections are not supported
3185        assert!(!is_disjoint("os_name in 'OSX'", "os_name in 'Linux'"));
3186        assert!(!is_disjoint(
3187            "os_name in 'OSXLinux'",
3188            "os_name == 'Windows'"
3189        ));
3190
3191        assert!(is_disjoint(
3192            "os_name in 'Windows'",
3193            "os_name not in 'Windows'"
3194        ));
3195        assert!(is_disjoint(
3196            "'Windows' in os_name",
3197            "'Windows' not in os_name"
3198        ));
3199
3200        assert!(!is_disjoint("'Windows' in os_name", "'Windows' in os_name"));
3201        assert!(!is_disjoint("'Linux' in os_name", "os_name not in 'Linux'"));
3202        assert!(!is_disjoint("'Linux' not in os_name", "os_name in 'Linux'"));
3203
3204        assert!(!is_disjoint(
3205            "os_name == 'Linux' and os_name != 'OSX'",
3206            "os_name == 'Linux'"
3207        ));
3208        assert!(is_disjoint(
3209            "os_name == 'Linux' and os_name != 'OSX'",
3210            "os_name == 'OSX'"
3211        ));
3212
3213        assert!(!is_disjoint(
3214            "extra == 'Linux' and extra != 'OSX'",
3215            "extra == 'Linux'"
3216        ));
3217        assert!(is_disjoint(
3218            "extra == 'Linux' and extra != 'OSX'",
3219            "extra == 'OSX'"
3220        ));
3221
3222        assert!(!is_disjoint(
3223            "extra == 'x1' and extra != 'x2'",
3224            "extra == 'x1'"
3225        ));
3226        assert!(is_disjoint(
3227            "extra == 'x1' and extra != 'x2'",
3228            "extra == 'x2'"
3229        ));
3230    }
3231
3232    #[test]
3233    fn is_disjoint_commutative() {
3234        let m1 = m("extra == 'Linux' and extra != 'OSX'");
3235        let m2 = m("extra == 'Linux'");
3236        assert!(!m2.is_disjoint(m1));
3237        assert!(!m1.is_disjoint(m2));
3238    }
3239
3240    #[test]
3241    fn test_combined_disjointness() {
3242        assert!(!is_disjoint(
3243            "os_name == 'a' and platform_version == '1'",
3244            "os_name == 'a'"
3245        ));
3246        assert!(!is_disjoint(
3247            "os_name == 'a' or platform_version == '1'",
3248            "os_name == 'a'"
3249        ));
3250
3251        assert!(is_disjoint(
3252            "os_name == 'a' and platform_version == '1'",
3253            "os_name == 'a' and platform_version == '2'"
3254        ));
3255        assert!(is_disjoint(
3256            "os_name == 'a' and platform_version == '1'",
3257            "'2' == platform_version and os_name == 'a'"
3258        ));
3259        assert!(!is_disjoint(
3260            "os_name == 'a' or platform_version == '1'",
3261            "os_name == 'a' or platform_version == '2'"
3262        ));
3263
3264        assert!(is_disjoint(
3265            "sys_platform == 'darwin' and implementation_name == 'pypy'",
3266            "sys_platform == 'bar' or implementation_name == 'foo'",
3267        ));
3268        assert!(is_disjoint(
3269            "sys_platform == 'bar' or implementation_name == 'foo'",
3270            "sys_platform == 'darwin' and implementation_name == 'pypy'",
3271        ));
3272
3273        assert!(is_disjoint(
3274            "python_version >= '3.7' and implementation_name == 'pypy'",
3275            "python_version < '3.7'"
3276        ));
3277        assert!(is_disjoint(
3278            "implementation_name == 'pypy' and python_version >= '3.7'",
3279            "implementation_name != 'pypy'"
3280        ));
3281        assert!(is_disjoint(
3282            "implementation_name != 'pypy' and python_version >= '3.7'",
3283            "implementation_name == 'pypy'"
3284        ));
3285    }
3286
3287    #[test]
3288    fn test_arbitrary() {
3289        assert!(m("'wat' == 'wat'").is_true());
3290        assert!(m("os_name ~= 'wat'").is_true());
3291        assert!(m("python_version == 'Linux'").is_true());
3292        assert!(m("os_name ~= 'wat' or 'wat' == 'wat' and python_version == 'Linux'").is_true());
3293    }
3294
3295    #[test]
3296    fn test_is_false() {
3297        assert!(m("python_version < '3.10' and python_version >= '3.10'").is_false());
3298        assert!(
3299            m("(python_version < '3.10' and python_version >= '3.10') \
3300              or (python_version < '3.9' and python_version >= '3.9')")
3301            .is_false()
3302        );
3303
3304        assert!(!m("python_version < '3.10'").is_false());
3305        assert!(!m("python_version < '0'").is_false());
3306        assert!(!m("python_version < '3.10' and python_version >= '3.9'").is_false());
3307        assert!(!m("python_version < '3.10' or python_version >= '3.11'").is_false());
3308    }
3309
3310    fn test_version_bounds_disjointness(version: &str) {
3311        assert!(!is_disjoint(
3312            format!("{version} > '2.7.0'"),
3313            format!("{version} == '3.6.0'")
3314        ));
3315        assert!(!is_disjoint(
3316            format!("{version} >= '3.7.0'"),
3317            format!("{version} == '3.7.1'")
3318        ));
3319        assert!(!is_disjoint(
3320            format!("{version} >= '3.7.0'"),
3321            format!("'3.7.1' == {version}")
3322        ));
3323
3324        assert!(is_disjoint(
3325            format!("{version} >= '3.7.1'"),
3326            format!("{version} == '3.7.0'")
3327        ));
3328        assert!(is_disjoint(
3329            format!("'3.7.1' <= {version}"),
3330            format!("{version} == '3.7.0'")
3331        ));
3332
3333        assert!(is_disjoint(
3334            format!("{version} < '3.7.0'"),
3335            format!("{version} == '3.7.0'")
3336        ));
3337        assert!(is_disjoint(
3338            format!("'3.7.0' > {version}"),
3339            format!("{version} == '3.7.0'")
3340        ));
3341        assert!(is_disjoint(
3342            format!("{version} < '3.7.0'"),
3343            format!("{version} == '3.7.1'")
3344        ));
3345
3346        assert!(is_disjoint(
3347            format!("{version} == '3.7.0'"),
3348            format!("{version} == '3.7.1'")
3349        ));
3350        assert!(is_disjoint(
3351            format!("{version} == '3.7.0'"),
3352            format!("{version} != '3.7.0'")
3353        ));
3354    }
3355
3356    fn assert_simplifies(left: &str, right: &str) {
3357        assert_eq!(m(left), m(right), "{left} != {right}");
3358        assert_eq!(m(left).try_to_string().unwrap(), right, "{left} != {right}");
3359    }
3360
3361    fn assert_true(marker: &str) {
3362        assert!(m(marker).is_true(), "{marker} != true");
3363    }
3364
3365    fn assert_false(marker: &str) {
3366        assert!(m(marker).is_false(), "{marker} != false");
3367    }
3368
3369    fn is_disjoint(left: impl AsRef<str>, right: impl AsRef<str>) -> bool {
3370        let (left, right) = (m(left.as_ref()), m(right.as_ref()));
3371        left.is_disjoint(right) && right.is_disjoint(left)
3372    }
3373
3374    fn implies(antecedent: &str, consequent: &str) -> bool {
3375        let mut marker = m(antecedent);
3376        marker.implies(m(consequent));
3377        marker.is_true()
3378    }
3379
3380    #[test]
3381    fn complexified_markers() {
3382        // Takes optional lower (inclusive) and upper (exclusive)
3383        // bounds representing `requires-python` and a "simplified"
3384        // marker, and returns the "complexified" marker. That is, a
3385        // marker that embeds the `requires-python` constraint into it.
3386        let complexify =
3387            |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
3388                let lower = lower
3389                    .map(|release| Bound::Included(Version::new(release)))
3390                    .unwrap_or(Bound::Unbounded);
3391                let upper = upper
3392                    .map(|release| Bound::Excluded(Version::new(release)))
3393                    .unwrap_or(Bound::Unbounded);
3394                m(marker).complexify_python_versions(lower.as_ref(), upper.as_ref())
3395            };
3396
3397        assert_eq!(
3398            complexify(None, None, "python_full_version < '3.10'"),
3399            m("python_full_version < '3.10'"),
3400        );
3401        assert_eq!(
3402            complexify(Some([3, 8]), None, "python_full_version < '3.10'"),
3403            m("python_full_version >= '3.8' and python_full_version < '3.10'"),
3404        );
3405        assert_eq!(
3406            complexify(None, Some([3, 8]), "python_full_version < '3.10'"),
3407            m("python_full_version < '3.8'"),
3408        );
3409        assert_eq!(
3410            complexify(Some([3, 8]), Some([3, 8]), "python_full_version < '3.10'"),
3411            // Kinda weird, but this normalizes to `false`, just like the above.
3412            m("python_full_version < '0' and python_full_version > '0'"),
3413        );
3414
3415        assert_eq!(
3416            complexify(Some([3, 11]), None, "python_full_version < '3.10'"),
3417            // Kinda weird, but this normalizes to `false`, just like the above.
3418            m("python_full_version < '0' and python_full_version > '0'"),
3419        );
3420        assert_eq!(
3421            complexify(Some([3, 11]), None, "python_full_version >= '3.10'"),
3422            m("python_full_version >= '3.11'"),
3423        );
3424        assert_eq!(
3425            complexify(Some([3, 11]), None, "python_full_version >= '3.12'"),
3426            m("python_full_version >= '3.12'"),
3427        );
3428
3429        assert_eq!(
3430            complexify(None, Some([3, 11]), "python_full_version > '3.12'"),
3431            // Kinda weird, but this normalizes to `false`, just like the above.
3432            m("python_full_version < '0' and python_full_version > '0'"),
3433        );
3434        assert_eq!(
3435            complexify(None, Some([3, 11]), "python_full_version <= '3.12'"),
3436            m("python_full_version < '3.11'"),
3437        );
3438        assert_eq!(
3439            complexify(None, Some([3, 11]), "python_full_version <= '3.10'"),
3440            m("python_full_version <= '3.10'"),
3441        );
3442
3443        assert_eq!(
3444            complexify(Some([3, 11]), None, "python_full_version == '3.8'"),
3445            // Kinda weird, but this normalizes to `false`, just like the above.
3446            m("python_full_version < '0' and python_full_version > '0'"),
3447        );
3448        assert_eq!(
3449            complexify(
3450                Some([3, 11]),
3451                None,
3452                "python_full_version == '3.8' or python_full_version == '3.12'"
3453            ),
3454            m("python_full_version == '3.12'"),
3455        );
3456        assert_eq!(
3457            complexify(
3458                Some([3, 11]),
3459                None,
3460                "python_full_version == '3.8' \
3461                 or python_full_version == '3.11' \
3462                 or python_full_version == '3.12'"
3463            ),
3464            m("python_full_version == '3.11' or python_full_version == '3.12'"),
3465        );
3466
3467        // Tests a tricky case where if a marker is always true, then
3468        // complexifying it will proceed correctly by adding the
3469        // requires-python constraint. This is a regression test for
3470        // an early implementation that special cased the "always
3471        // true" case to return "always true" regardless of the
3472        // requires-python bounds.
3473        assert_eq!(
3474            complexify(
3475                Some([3, 12]),
3476                None,
3477                "python_full_version < '3.10' or python_full_version >= '3.10'"
3478            ),
3479            m("python_full_version >= '3.12'"),
3480        );
3481    }
3482
3483    #[test]
3484    fn simplified_markers() {
3485        // Takes optional lower (inclusive) and upper (exclusive)
3486        // bounds representing `requires-python` and a "complexified"
3487        // marker, and returns the "simplified" marker. That is, a
3488        // marker that assumes `requires-python` is true.
3489        let simplify =
3490            |lower: Option<[u64; 2]>, upper: Option<[u64; 2]>, marker: &str| -> MarkerTree {
3491                let lower = lower
3492                    .map(|release| Bound::Included(Version::new(release)))
3493                    .unwrap_or(Bound::Unbounded);
3494                let upper = upper
3495                    .map(|release| Bound::Excluded(Version::new(release)))
3496                    .unwrap_or(Bound::Unbounded);
3497                m(marker).simplify_python_versions(lower.as_ref(), upper.as_ref())
3498            };
3499
3500        assert_eq!(
3501            simplify(
3502                Some([3, 8]),
3503                None,
3504                "python_full_version >= '3.8' and python_full_version < '3.10'"
3505            ),
3506            m("python_full_version < '3.10'"),
3507        );
3508        assert_eq!(
3509            simplify(Some([3, 8]), None, "python_full_version < '3.7'"),
3510            // Kinda weird, but this normalizes to `false`, just like the above.
3511            m("python_full_version < '0' and python_full_version > '0'"),
3512        );
3513        assert_eq!(
3514            simplify(
3515                Some([3, 8]),
3516                Some([3, 11]),
3517                "python_full_version == '3.7.*' \
3518                 or python_full_version == '3.8.*' \
3519                 or python_full_version == '3.10.*' \
3520                 or python_full_version == '3.11.*' \
3521                "
3522            ),
3523            // Given `requires-python = '>=3.8,<3.11'`, only `3.8.*`
3524            // and `3.10.*` can possibly be true. So this simplifies
3525            // to `!= 3.9.*`.
3526            m("python_full_version != '3.9.*'"),
3527        );
3528        assert_eq!(
3529            simplify(
3530                Some([3, 8]),
3531                None,
3532                "python_full_version >= '3.8' and sys_platform == 'win32'"
3533            ),
3534            m("sys_platform == 'win32'"),
3535        );
3536        assert_eq!(
3537            simplify(
3538                Some([3, 8]),
3539                None,
3540                "python_full_version >= '3.9' \
3541                 and (sys_platform == 'win32' or python_full_version >= '3.8')",
3542            ),
3543            m("python_full_version >= '3.9'"),
3544        );
3545    }
3546
3547    #[test]
3548    fn without_extras() {
3549        assert_eq!(
3550            m("os_name == 'Linux'").without_extras(),
3551            m("os_name == 'Linux'"),
3552        );
3553        assert!(m("extra == 'foo'").without_extras().is_true());
3554        assert_eq!(
3555            m("os_name == 'Linux' and extra == 'foo'").without_extras(),
3556            m("os_name == 'Linux'"),
3557        );
3558
3559        assert!(
3560            m("
3561                (os_name == 'Linux' and extra == 'foo')
3562                or (os_name != 'Linux' and extra == 'bar')")
3563            .without_extras()
3564            .is_true()
3565        );
3566
3567        assert_eq!(
3568            m("os_name == 'Linux' and extra != 'foo'").without_extras(),
3569            m("os_name == 'Linux'"),
3570        );
3571
3572        assert!(
3573            m("extra != 'extra-project-bar' and extra == 'extra-project-foo'")
3574                .without_extras()
3575                .is_true()
3576        );
3577
3578        assert_eq!(
3579            m("(os_name == 'Darwin' and extra == 'foo') \
3580               or (sys_platform == 'Linux' and extra != 'foo')")
3581            .without_extras(),
3582            m("os_name == 'Darwin' or sys_platform == 'Linux'"),
3583        );
3584    }
3585
3586    #[test]
3587    fn only_extras() {
3588        assert!(m("os_name == 'Linux'").only_extras().is_true());
3589        assert_eq!(m("extra == 'foo'").only_extras(), m("extra == 'foo'"));
3590        assert_eq!(
3591            m("os_name == 'Linux' and extra == 'foo'").only_extras(),
3592            m("extra == 'foo'"),
3593        );
3594        assert!(
3595            m("
3596                (os_name == 'foo' and extra == 'foo')
3597                or (os_name == 'bar' and extra != 'foo')")
3598            .only_extras()
3599            .is_true()
3600        );
3601        assert_eq!(
3602            m("
3603                (os_name == 'Linux' and extra == 'foo')
3604                or (os_name != 'Linux' and extra == 'bar')")
3605            .only_extras(),
3606            m("extra == 'foo' or extra == 'bar'"),
3607        );
3608
3609        assert_eq!(
3610            m("
3611                (implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'darwin')
3612                or (os_name == 'nt' and sys_platform == 'win32')")
3613            .only_extras(),
3614            m("os_name == 'Linux' or os_name != 'Linux'"),
3615        );
3616    }
3617
3618    #[test]
3619    fn visit_extras() {
3620        let collect = |m: MarkerTree| -> Vec<(&'static str, String)> {
3621            let mut collected = vec![];
3622            m.visit_extras(|op, extra| {
3623                let op = match op {
3624                    MarkerOperator::Equal => "==",
3625                    MarkerOperator::NotEqual => "!=",
3626                    _ => unreachable!(),
3627                };
3628                collected.push((op, extra.as_str().to_string()));
3629            });
3630            collected
3631        };
3632        assert_eq!(collect(m("os_name == 'Linux'")), vec![]);
3633        assert_eq!(
3634            collect(m("extra == 'foo'")),
3635            vec![("==", "foo".to_string())]
3636        );
3637        assert_eq!(
3638            collect(m("extra == 'Linux' and extra == 'foo'")),
3639            vec![("==", "foo".to_string()), ("==", "linux".to_string())]
3640        );
3641        assert_eq!(
3642            collect(m("os_name == 'Linux' and extra == 'foo'")),
3643            vec![("==", "foo".to_string())]
3644        );
3645
3646        let marker = "
3647            python_full_version >= '3.12'
3648            and sys_platform == 'darwin'
3649            and extra == 'extra-27-resolution-markers-for-days-cpu'
3650            and extra != 'extra-27-resolution-markers-for-days-cu124'
3651        ";
3652        assert_eq!(
3653            collect(m(marker)),
3654            vec![
3655                ("==", "extra-27-resolution-markers-for-days-cpu".to_string()),
3656                (
3657                    "!=",
3658                    "extra-27-resolution-markers-for-days-cu124".to_string()
3659                ),
3660            ]
3661        );
3662    }
3663
3664    /// Case a: There is no version `3` (no trailing zero) in the interner yet.
3665    #[test]
3666    fn marker_normalization_a() {
3667        let left_tree = m("python_version == '3.0.*'");
3668        let left = left_tree.try_to_string().unwrap();
3669        let right = "python_full_version == '3.0.*'";
3670        assert_eq!(left, right, "{left} != {right}");
3671    }
3672
3673    /// Case b: There is already a version `3` (no trailing zero) in the interner.
3674    #[test]
3675    fn marker_normalization_b() {
3676        m("python_version >= '3' and python_version <= '3.0'");
3677
3678        let left_tree = m("python_version == '3.0.*'");
3679        let left = left_tree.try_to_string().unwrap();
3680        let right = "python_full_version == '3.0.*'";
3681        assert_eq!(left, right, "{left} != {right}");
3682    }
3683
3684    #[test]
3685    fn marker_normalization_c() {
3686        let left_tree = MarkerTree::from_str("python_version == '3.10.0.*'").unwrap();
3687        let left = left_tree.try_to_string().unwrap();
3688        let right = "python_full_version == '3.10.*'";
3689        assert_eq!(left, right, "{left} != {right}");
3690    }
3691
3692    #[test]
3693    fn evaluate_only_extras() {
3694        let a = ExtraName::from_str("a").unwrap();
3695        let b = ExtraName::from_str("b").unwrap();
3696
3697        let marker = m("extra == 'a' and extra == 'b'");
3698        assert!(!marker.evaluate_only_extras(std::slice::from_ref(&a)));
3699        assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3700        assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3701
3702        let marker = m("(platform_machine == 'inapplicable' and extra == 'b') or extra == 'a'");
3703        assert!(marker.evaluate_only_extras(std::slice::from_ref(&a)));
3704        assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3705        assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3706
3707        let marker = m(
3708            "(platform_machine == 'inapplicable' and extra == 'a') or (platform_machine != 'inapplicable' and extra == 'b')",
3709        );
3710        assert!(!marker.evaluate_only_extras(std::slice::from_ref(&a)));
3711        assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
3712        assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
3713    }
3714}