Support `extras` and `dependency_groups` markers in PEP 508 grammar (#14753)

## Summary

We always evaluate these to `false` right now, but we can at least parse
them.

See: https://peps.python.org/pep-0751/#dependency-groups.
This commit is contained in:
Charlie Marsh 2025-07-20 14:02:22 -04:00 committed by GitHub
parent 2d8dda34b4
commit a3371867ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 530 additions and 15 deletions

View File

@ -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 {

View File

@ -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<CanonicalMarkerValueDependencyGroup> 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),
}
}
}

View File

@ -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<T: Pep508Url>(
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<T: Pep508Url>(
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<T: Pep508Url>(
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 `<quoted PEP 440 version> <version op>` <version key>
@ -273,6 +278,12 @@ pub(crate) fn parse_marker_key_op_value<T: Pep508Url>(
}),
// `'...' == 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<T: Pep508Url>(
}
}
}
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<MarkerExpression> {
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<MarkerExpression> {
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(),
);

View File

@ -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()
}
}
}

View File

@ -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 <extra op> '...'` or `'...' <extra op> 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<ContainerOperator> {
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<Item = (bool, MarkerTree)> {
[(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<Ordering> {
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<Item = (bool, MarkerTree)> {
[(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<Ordering> {
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.

View File

@ -54,6 +54,16 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option<RequiresPythonRange> {
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);
}
}
}
}

View File

@ -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);
}
}
}
}