diff --git a/crates/uv-pep508/src/marker/algebra.rs b/crates/uv-pep508/src/marker/algebra.rs index 2a3f82f27..d1a369491 100644 --- a/crates/uv-pep508/src/marker/algebra.rs +++ b/crates/uv-pep508/src/marker/algebra.rs @@ -59,8 +59,10 @@ use uv_pep440::{Operator, Version, VersionSpecifier, release_specifier_to_range} use crate::marker::MarkerValueExtra; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerValueVersion, }; +use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerValueString, MarkerValueVersion, }; @@ -328,11 +330,53 @@ impl InternerGuard<'_> { Variable::Extra(CanonicalMarkerValueExtra::Extra(extra)), Edges::from_bool(false), ), - // Invalid extras are always `false`. + // Invalid `extra` names are always `false`. MarkerExpression::Extra { name: MarkerValueExtra::Arbitrary(_), .. } => return NodeId::FALSE, + // A variable representing the existence or absence of a particular extra, in the + // context of a PEP 751 lockfile. + MarkerExpression::Extras { + name: MarkerValueExtra::Extra(extra), + operator: ContainerOperator::In, + } => ( + Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), + Edges::from_bool(true), + ), + MarkerExpression::Extras { + name: MarkerValueExtra::Extra(extra), + operator: ContainerOperator::NotIn, + } => ( + Variable::Extras(CanonicalMarkerValueExtra::Extra(extra)), + Edges::from_bool(false), + ), + // Invalid `extras` names are always `false`. + MarkerExpression::Extras { + name: MarkerValueExtra::Arbitrary(_), + .. + } => return NodeId::FALSE, + // A variable representing the existence or absence of a particular extra, in the + // context of a PEP 751 lockfile. + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Group(group), + operator: ContainerOperator::In, + } => ( + Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), + Edges::from_bool(true), + ), + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Group(group), + operator: ContainerOperator::NotIn, + } => ( + Variable::DependencyGroups(CanonicalMarkerValueDependencyGroup::Group(group)), + Edges::from_bool(false), + ), + // Invalid `dependency_group` names are always `false`. + MarkerExpression::DependencyGroups { + name: MarkerValueDependencyGroup::Arbitrary(_), + .. + } => return NodeId::FALSE, }; self.create_node(var, children) @@ -1046,6 +1090,18 @@ pub(crate) enum Variable { /// We keep extras at the leaves of the tree, so when simplifying extras we can /// trivially remove the leaves without having to reconstruct the entire tree. Extra(CanonicalMarkerValueExtra), + /// A variable representing the existence or absence of a given extra, in the context of a + /// PEP 751 lockfile marker. + /// + /// We keep extras at the leaves of the tree, so when simplifying extras we can + /// trivially remove the leaves without having to reconstruct the entire tree. + Extras(CanonicalMarkerValueExtra), + /// A variable representing the existence or absence of a given dependency group, in the context of a + /// PEP 751 lockfile marker. + /// + /// We keep groups at the leaves of the tree, so when simplifying groups we can + /// trivially remove the leaves without having to reconstruct the entire tree. + DependencyGroups(CanonicalMarkerValueDependencyGroup), } impl Variable { diff --git a/crates/uv-pep508/src/marker/lowering.rs b/crates/uv-pep508/src/marker/lowering.rs index 16139a65d..dadfeac53 100644 --- a/crates/uv-pep508/src/marker/lowering.rs +++ b/crates/uv-pep508/src/marker/lowering.rs @@ -1,7 +1,8 @@ use std::fmt::{Display, Formatter}; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; +use crate::marker::tree::MarkerValueDependencyGroup; use crate::{MarkerValueExtra, MarkerValueString, MarkerValueVersion}; /// Those environment markers with a PEP 440 version as value such as `python_version` @@ -128,7 +129,7 @@ impl Display for CanonicalMarkerValueString { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum CanonicalMarkerValueExtra { /// A valid [`ExtraName`]. @@ -159,3 +160,35 @@ impl Display for CanonicalMarkerValueExtra { } } } + +/// The [`GroupName`] value used in `dependency_group` markers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum CanonicalMarkerValueDependencyGroup { + /// A valid [`GroupName`]. + Group(GroupName), +} + +impl CanonicalMarkerValueDependencyGroup { + /// Returns the [`GroupName`] value. + pub fn group(&self) -> &GroupName { + match self { + Self::Group(group) => group, + } + } +} + +impl From for MarkerValueDependencyGroup { + fn from(value: CanonicalMarkerValueDependencyGroup) -> Self { + match value { + CanonicalMarkerValueDependencyGroup::Group(group) => Self::Group(group), + } + } +} + +impl Display for CanonicalMarkerValueDependencyGroup { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Group(group) => group.fmt(f), + } + } +} diff --git a/crates/uv-pep508/src/marker/parse.rs b/crates/uv-pep508/src/marker/parse.rs index 13620662b..9c361c19d 100644 --- a/crates/uv-pep508/src/marker/parse.rs +++ b/crates/uv-pep508/src/marker/parse.rs @@ -1,10 +1,11 @@ use arcstr::ArcStr; use std::str::FromStr; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionPattern, VersionSpecifier}; use crate::cursor::Cursor; use crate::marker::MarkerValueExtra; +use crate::marker::tree::{ContainerOperator, MarkerValueDependencyGroup}; use crate::{ ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, MarkerWarningKind, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, @@ -208,6 +209,8 @@ pub(crate) fn parse_marker_key_op_value( MarkerValue::MarkerEnvString(key) => { let value = match r_value { MarkerValue::Extra + | MarkerValue::Extras + | MarkerValue::DependencyGroups | MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) => { reporter.report( @@ -242,7 +245,9 @@ pub(crate) fn parse_marker_key_op_value( let value = match r_value { MarkerValue::MarkerEnvVersion(_) | MarkerValue::MarkerEnvString(_) - | MarkerValue::Extra => { + | MarkerValue::Extra + | MarkerValue::Extras + | MarkerValue::DependencyGroups => { reporter.report( MarkerWarningKind::ExtraInvalidComparison, "Comparing extra with something other than a quoted string is wrong, @@ -257,7 +262,7 @@ pub(crate) fn parse_marker_key_op_value( parse_extra_expr(operator, &value, reporter) } - // This is either MarkerEnvVersion, MarkerEnvString or Extra inverted + // This is either MarkerEnvVersion, MarkerEnvString, Extra (inverted), or Extras MarkerValue::QuotedString(l_string) => { match r_value { // The only sound choice for this is ` ` @@ -273,6 +278,12 @@ pub(crate) fn parse_marker_key_op_value( }), // `'...' == extra` MarkerValue::Extra => parse_extra_expr(operator, &l_string, reporter), + // `'...' in extras` + MarkerValue::Extras => parse_extras_expr(operator, &l_string, reporter), + // `'...' in dependency_groups` + MarkerValue::DependencyGroups => { + parse_dependency_groups_expr(operator, &l_string, reporter) + } // `'...' == '...'`, doesn't make much sense MarkerValue::QuotedString(_) => { // Not even pypa/packaging 22.0 supports this @@ -289,6 +300,26 @@ pub(crate) fn parse_marker_key_op_value( } } } + MarkerValue::Extras => { + reporter.report( + MarkerWarningKind::Pep440Error, + format!( + "The `extras` marker must be used as '...' in extras' or '... not in extras', + found `{l_value} {operator} {r_value}`, will be ignored" + ), + ); + return Ok(None); + } + MarkerValue::DependencyGroups => { + reporter.report( + MarkerWarningKind::Pep440Error, + format!( + "The `dependency_groups` marker must be used as '...' in dependency_groups' or '... not in dependency_groups', + found `{l_value} {operator} {r_value}`, will be ignored" + ), + ); + return Ok(None); + } }; Ok(expr) @@ -491,8 +522,69 @@ fn parse_extra_expr( reporter.report( MarkerWarningKind::ExtraInvalidComparison, - "Comparing extra with something other than a quoted string is wrong, - will be ignored" + "Comparing `extra` with any operator other than `==` or `!=` is wrong and will be ignored" + .to_string(), + ); + + None +} + +/// Creates an instance of [`MarkerExpression::Extras`] with the given values, falling back to +/// [`MarkerExpression::Arbitrary`] on failure. +fn parse_extras_expr( + operator: MarkerOperator, + value: &str, + reporter: &mut impl Reporter, +) -> Option { + let name = match ExtraName::from_str(value) { + Ok(name) => MarkerValueExtra::Extra(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{value}`): {err}"), + ); + MarkerValueExtra::Arbitrary(value.to_string()) + } + }; + + if let Some(operator) = ContainerOperator::from_marker_operator(operator) { + return Some(MarkerExpression::Extras { operator, name }); + } + + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" + .to_string(), + ); + + None +} + +/// Creates an instance of [`MarkerExpression::DependencyGroups`] with the given values, falling +/// back to [`MarkerExpression::Arbitrary`] on failure. +fn parse_dependency_groups_expr( + operator: MarkerOperator, + value: &str, + reporter: &mut impl Reporter, +) -> Option { + let name = match GroupName::from_str(value) { + Ok(name) => MarkerValueDependencyGroup::Group(name), + Err(err) => { + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + format!("Expected extra name (found `{value}`): {err}"), + ); + MarkerValueDependencyGroup::Arbitrary(value.to_string()) + } + }; + + if let Some(operator) = ContainerOperator::from_marker_operator(operator) { + return Some(MarkerExpression::DependencyGroups { operator, name }); + } + + reporter.report( + MarkerWarningKind::ExtrasInvalidComparison, + "Comparing `extras` with any operator other than `in` or `not in` is wrong and will be ignored" .to_string(), ); diff --git a/crates/uv-pep508/src/marker/simplify.rs b/crates/uv-pep508/src/marker/simplify.rs index 3dc03693a..6897615c4 100644 --- a/crates/uv-pep508/src/marker/simplify.rs +++ b/crates/uv-pep508/src/marker/simplify.rs @@ -9,6 +9,7 @@ use version_ranges::Ranges; use uv_pep440::{Version, VersionSpecifier}; +use crate::marker::tree::ContainerOperator; use crate::{ExtraOperator, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeKind}; /// Returns a simplified DNF expression for a given marker tree. @@ -174,6 +175,42 @@ fn collect_dnf( operator, }; + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } + MarkerTreeKind::Extras(marker) => { + for (value, tree) in marker.children() { + let operator = if value { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }; + + let expr = MarkerExpression::Extras { + name: marker.name().clone().into(), + operator, + }; + + path.push(expr); + collect_dnf(tree, dnf, path); + path.pop(); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (value, tree) in marker.children() { + let operator = if value { + ContainerOperator::In + } else { + ContainerOperator::NotIn + }; + + let expr = MarkerExpression::DependencyGroups { + name: marker.name().clone().into(), + operator, + }; + path.push(expr); collect_dnf(tree, dnf, path); path.pop(); @@ -440,5 +477,27 @@ fn is_negation(left: &MarkerExpression, right: &MarkerExpression) -> bool { name == name2 && operator.negate() == *operator2 } + MarkerExpression::Extras { name, operator } => { + let MarkerExpression::Extras { + name: name2, + operator: operator2, + } = right + else { + return false; + }; + + name == name2 && *operator == operator2.negate() + } + MarkerExpression::DependencyGroups { name, operator } => { + let MarkerExpression::DependencyGroups { + name: name2, + operator: operator2, + } = right + else { + return false; + }; + + name == name2 && *operator == operator2.negate() + } } } diff --git a/crates/uv-pep508/src/marker/tree.rs b/crates/uv-pep508/src/marker/tree.rs index 5739d7c98..594b81723 100644 --- a/crates/uv-pep508/src/marker/tree.rs +++ b/crates/uv-pep508/src/marker/tree.rs @@ -9,14 +9,15 @@ use itertools::Itertools; use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; use version_ranges::Ranges; -use uv_normalize::ExtraName; +use uv_normalize::{ExtraName, GroupName}; use uv_pep440::{Version, VersionParseError, VersionSpecifier}; use super::algebra::{Edges, INTERNER, NodeId, Variable}; use super::simplify; use crate::cursor::Cursor; use crate::marker::lowering::{ - CanonicalMarkerValueExtra, CanonicalMarkerValueString, CanonicalMarkerValueVersion, + CanonicalMarkerValueDependencyGroup, CanonicalMarkerValueExtra, CanonicalMarkerValueString, + CanonicalMarkerValueVersion, }; use crate::marker::parse; use crate::{ @@ -32,6 +33,12 @@ pub enum MarkerWarningKind { /// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as /// `extra > "perf"` or `extra == os_name` ExtraInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as + /// `extras > "perf"` or `extras == os_name` + ExtrasInvalidComparison, + /// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`, + /// such as `dependency_groups > "perf"` or `dependency_groups == os_name` + DependencyGroupsInvalidComparison, /// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"` LexicographicComparison, /// Comparing two markers, such as `os_name != sys_implementation` @@ -128,8 +135,12 @@ pub enum MarkerValue { MarkerEnvVersion(MarkerValueVersion), /// Those environment markers with an arbitrary string as value such as `sys_platform` MarkerEnvString(MarkerValueString), - /// `extra`. This one is special because it's a list and not env but user given + /// `extra`. This one is special because it's a list, and user-provided Extra, + /// `extras`. This one is special because it's a list, and user-provided + Extras, + /// `dependency_groups`. This one is special because it's a list, and user-provided + DependencyGroups, /// Not a constant, but a user given quoted string with a value inside such as '3.8' or "windows" QuotedString(ArcStr), } @@ -170,6 +181,8 @@ impl FromStr for MarkerValue { "sys_platform" => Self::MarkerEnvString(MarkerValueString::SysPlatform), "sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated), "extra" => Self::Extra, + "extras" => Self::Extras, + "dependency_groups" => Self::DependencyGroups, _ => return Err(format!("Invalid key: {s}")), }; Ok(value) @@ -182,6 +195,8 @@ impl Display for MarkerValue { Self::MarkerEnvVersion(marker_value_version) => marker_value_version.fmt(f), Self::MarkerEnvString(marker_value_string) => marker_value_string.fmt(f), Self::Extra => f.write_str("extra"), + Self::Extras => f.write_str("extras"), + Self::DependencyGroups => f.write_str("dependency_groups"), Self::QuotedString(value) => write!(f, "'{value}'"), } } @@ -433,7 +448,7 @@ impl Deref for StringVersion { } } -/// The [`ExtraName`] value used in `extra` markers. +/// The [`ExtraName`] value used in `extra` and `extras` markers. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum MarkerValueExtra { /// A valid [`ExtraName`]. @@ -469,6 +484,24 @@ impl Display for MarkerValueExtra { } } +/// The [`GroupName`] value used in `dependency_group` markers. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum MarkerValueDependencyGroup { + /// A valid [`GroupName`]. + Group(GroupName), + /// An invalid name, preserved as an arbitrary string. + Arbitrary(String), +} + +impl Display for MarkerValueDependencyGroup { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Group(group) => group.fmt(f), + Self::Arbitrary(string) => string.fmt(f), + } + } +} + /// Represents one clause such as `python_version > "3.8"`. #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] #[allow(missing_docs)] @@ -504,8 +537,18 @@ pub enum MarkerExpression { }, /// `extra '...'` or `'...' extra`. Extra { - operator: ExtraOperator, name: MarkerValueExtra, + operator: ExtraOperator, + }, + /// `'...' in extras` + Extras { + name: MarkerValueExtra, + operator: ContainerOperator, + }, + /// `'...' in dependency_groups` + DependencyGroups { + name: MarkerValueDependencyGroup, + operator: ContainerOperator, }, } @@ -520,6 +563,10 @@ pub(crate) enum MarkerExpressionKind { String(MarkerValueString), /// An extra expression, e.g. `extra == '...'`. Extra, + /// An extras expression, e.g. `'...' in extras`. + Extras, + /// A dependency groups expression, e.g. `'...' in dependency_groups`. + DependencyGroups, } /// The operator for an extra expression, either '==' or '!='. @@ -561,6 +608,45 @@ impl Display for ExtraOperator { } } +/// The operator for a container expression, either 'in' or 'not in'. +#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum ContainerOperator { + /// `in` + In, + /// `not in` + NotIn, +} + +impl ContainerOperator { + /// Creates a [`ContainerOperator`] from an equivalent [`MarkerOperator`]. + /// + /// Returns `None` if the operator is not supported for containers. + pub(crate) fn from_marker_operator(operator: MarkerOperator) -> Option { + match operator { + MarkerOperator::In => Some(ContainerOperator::In), + MarkerOperator::NotIn => Some(ContainerOperator::NotIn), + _ => None, + } + } + + /// Negates this operator. + pub(crate) fn negate(&self) -> ContainerOperator { + match *self { + ContainerOperator::In => ContainerOperator::NotIn, + ContainerOperator::NotIn => ContainerOperator::In, + } + } +} + +impl Display for ContainerOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::In => "in", + Self::NotIn => "not in", + }) + } +} + impl MarkerExpression { /// Parse a [`MarkerExpression`] from a string with the given reporter. pub fn parse_reporter( @@ -600,6 +686,8 @@ impl MarkerExpression { MarkerExpression::VersionIn { key, .. } => MarkerExpressionKind::VersionIn(*key), MarkerExpression::String { key, .. } => MarkerExpressionKind::String(*key), MarkerExpression::Extra { .. } => MarkerExpressionKind::Extra, + MarkerExpression::Extras { .. } => MarkerExpressionKind::Extras, + MarkerExpression::DependencyGroups { .. } => MarkerExpressionKind::DependencyGroups, } } } @@ -641,6 +729,12 @@ impl Display for MarkerExpression { MarkerExpression::Extra { operator, name } => { write!(f, "extra {operator} '{name}'") } + MarkerExpression::Extras { operator, name } => { + write!(f, "'{name}' {operator} extras") + } + MarkerExpression::DependencyGroups { operator, name } => { + write!(f, "'{name}' {operator} dependency_groups") + } } } } @@ -862,6 +956,26 @@ impl MarkerTree { low: low.negate(self.0), }) } + Variable::Extras(name) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::Extras(ExtrasMarkerTree { + name, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } + Variable::DependencyGroups(name) => { + let Edges::Boolean { low, high } = node.children else { + unreachable!() + }; + MarkerTreeKind::DependencyGroups(DependencyGroupsMarkerTree { + name, + high: high.negate(self.0), + low: low.negate(self.0), + }) + } } } @@ -962,6 +1076,10 @@ impl MarkerTree { .edge(extras.contains(marker.name().extra())) .evaluate_reporter_impl(env, extras, reporter); } + // TODO(charlie): Add support for evaluating container extras in PEP 751 lockfiles. + MarkerTreeKind::Extras(..) | MarkerTreeKind::DependencyGroups(..) => { + return false; + } } false @@ -989,6 +1107,12 @@ impl MarkerTree { MarkerTreeKind::Extra(marker) => marker .edge(extras.contains(marker.name().extra())) .evaluate_extras(extras), + MarkerTreeKind::Extras(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), + MarkerTreeKind::DependencyGroups(marker) => marker + .children() + .any(|(_, tree)| tree.evaluate_extras(extras)), } } @@ -1226,6 +1350,16 @@ impl MarkerTree { imp(tree, f); } } + MarkerTreeKind::Extras(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } + MarkerTreeKind::DependencyGroups(kind) => { + for (_, tree) in kind.children() { + imp(tree, f); + } + } } } imp(self, &mut f); @@ -1348,6 +1482,36 @@ impl MarkerTree { write!(f, "extra != {} -> ", kind.name())?; kind.edge(false).fmt_graph(f, level + 1)?; } + MarkerTreeKind::Extras(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in extras -> ", kind.name())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in extras -> ", kind.name())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } + MarkerTreeKind::DependencyGroups(kind) => { + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} in dependency_groups -> ", kind.name())?; + kind.edge(true).fmt_graph(f, level + 1)?; + + writeln!(f)?; + for _ in 0..level { + write!(f, " ")?; + } + write!(f, "{} not in dependency_groups -> ", kind.name())?; + kind.edge(false).fmt_graph(f, level + 1)?; + } } Ok(()) @@ -1417,8 +1581,12 @@ pub enum MarkerTreeKind<'a> { In(InMarkerTree<'a>), /// A string expression with the `contains` operator. Contains(ContainsMarkerTree<'a>), - /// A string expression. + /// A string expression (e.g., `extra == 'dev'`). Extra(ExtraMarkerTree<'a>), + /// A string expression (e.g., `'dev' in extras`). + Extras(ExtrasMarkerTree<'a>), + /// A string expression (e.g., `'dev' in dependency_groups`). + DependencyGroups(DependencyGroupsMarkerTree<'a>), } /// A version marker node, such as `python_version < '3.7'`. @@ -1636,6 +1804,93 @@ impl Ord for ExtraMarkerTree<'_> { } } +/// A node representing the existence or absence of a given extra, such as `'bar' in extras`. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct ExtrasMarkerTree<'a> { + name: &'a CanonicalMarkerValueExtra, + high: NodeId, + low: NodeId, +} + +impl ExtrasMarkerTree<'_> { + /// Returns the name of the extra in this expression. + pub fn name(&self) -> &CanonicalMarkerValueExtra { + self.name + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for ExtrasMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ExtrasMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.name() + .cmp(other.name()) + .then_with(|| self.children().cmp(other.children())) + } +} + +/// A node representing the existence or absence of a given dependency group, such as +/// `'bar' in dependency_groups`. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct DependencyGroupsMarkerTree<'a> { + name: &'a CanonicalMarkerValueDependencyGroup, + high: NodeId, + low: NodeId, +} + +impl DependencyGroupsMarkerTree<'_> { + /// Returns the name of the group in this expression. + pub fn name(&self) -> &CanonicalMarkerValueDependencyGroup { + self.name + } + + /// The edges of this node, corresponding to the boolean evaluation of the expression. + pub fn children(&self) -> impl Iterator { + [(true, MarkerTree(self.high)), (false, MarkerTree(self.low))].into_iter() + } + + /// Returns the subtree associated with the given edge value. + pub fn edge(&self, value: bool) -> MarkerTree { + if value { + MarkerTree(self.high) + } else { + MarkerTree(self.low) + } + } +} + +impl PartialOrd for DependencyGroupsMarkerTree<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for DependencyGroupsMarkerTree<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.name() + .cmp(other.name()) + .then_with(|| self.children().cmp(other.children())) + } +} + /// A marker tree that contains at least one expression. /// /// See [`MarkerTree::contents`] for details. diff --git a/crates/uv-resolver/src/marker.rs b/crates/uv-resolver/src/marker.rs index b63d51401..5a2203f9b 100644 --- a/crates/uv-resolver/src/marker.rs +++ b/crates/uv-resolver/src/marker.rs @@ -54,6 +54,16 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option { collect_python_markers(tree, markers, range); } } + MarkerTreeKind::Extras(marker) => { + for (_, tree) in marker.children() { + collect_python_markers(tree, markers, range); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (_, tree) in marker.children() { + collect_python_markers(tree, markers, range); + } + } } } diff --git a/crates/uv-resolver/src/resolution/output.rs b/crates/uv-resolver/src/resolution/output.rs index dd2b3388f..2afbf2c6b 100644 --- a/crates/uv-resolver/src/resolution/output.rs +++ b/crates/uv-resolver/src/resolution/output.rs @@ -698,6 +698,16 @@ impl ResolverOutput { add_marker_params_from_tree(tree, set); } } + MarkerTreeKind::Extras(marker) => { + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } + MarkerTreeKind::DependencyGroups(marker) => { + for (_, tree) in marker.children() { + add_marker_params_from_tree(tree, set); + } + } } }