mirror of https://github.com/astral-sh/uv
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:
parent
2d8dda34b4
commit
a3371867ac
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue