Skip to content

Commit 8d6c49b

Browse files
committed
pep508: add MarkerTree::negate
It does what you think it does, for the most part.
1 parent da8a4a6 commit 8d6c49b

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

crates/pep440-rs/src/version.rs

+28
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,34 @@ pub enum Operator {
5959
}
6060

6161
impl Operator {
62+
/// Negates this operator, if a negation exists, so that it has the
63+
/// opposite meaning.
64+
///
65+
/// This returns a negated operator in every case except for the `~=`
66+
/// operator. In that case, `None` is returned and callers may need to
67+
/// handle its negation at a higher level. (For example, if it's negated
68+
/// in the context of a marker expression, then the "compatible" version
69+
/// constraint can be split into its component parts and turned into a
70+
/// disjunction of the negation of each of those parts.)
71+
///
72+
/// Note that this routine is not reversible in all cases. For example
73+
/// `Operator::ExactEqual` negates to `Operator::NotEqual`, and
74+
/// `Operator::NotEqual` in turn negates to `Operator::Equal`.
75+
pub fn negate(self) -> Option<Operator> {
76+
Some(match self {
77+
Operator::Equal => Operator::NotEqual,
78+
Operator::EqualStar => Operator::NotEqualStar,
79+
Operator::ExactEqual => Operator::NotEqual,
80+
Operator::NotEqual => Operator::Equal,
81+
Operator::NotEqualStar => Operator::EqualStar,
82+
Operator::TildeEqual => return None,
83+
Operator::LessThan => Operator::GreaterThanEqual,
84+
Operator::LessThanEqual => Operator::GreaterThan,
85+
Operator::GreaterThan => Operator::LessThanEqual,
86+
Operator::GreaterThanEqual => Operator::LessThan,
87+
})
88+
}
89+
6290
/// Returns true if and only if this operator can be used in a version
6391
/// specifier with a version containing a non-empty local segment.
6492
///

crates/pep508-rs/src/marker.rs

+252
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,24 @@ impl MarkerOperator {
251251
Self::NotIn => None,
252252
}
253253
}
254+
255+
/// Negates this marker operator.
256+
///
257+
/// If a negation doesn't exist, which is only the case for ~=, then this
258+
/// returns `None`.
259+
fn negate(self) -> Option<MarkerOperator> {
260+
Some(match self {
261+
Self::Equal => Self::NotEqual,
262+
Self::NotEqual => Self::Equal,
263+
Self::TildeEqual => return None,
264+
Self::LessThan => Self::GreaterEqual,
265+
Self::LessEqual => Self::GreaterThan,
266+
Self::GreaterThan => Self::LessEqual,
267+
Self::GreaterEqual => Self::LessThan,
268+
Self::In => Self::NotIn,
269+
Self::NotIn => Self::In,
270+
})
271+
}
254272
}
255273

256274
impl FromStr for MarkerOperator {
@@ -968,6 +986,13 @@ impl ExtraOperator {
968986
_ => None,
969987
}
970988
}
989+
990+
fn negate(&self) -> ExtraOperator {
991+
match *self {
992+
ExtraOperator::Equal => ExtraOperator::NotEqual,
993+
ExtraOperator::NotEqual => ExtraOperator::Equal,
994+
}
995+
}
971996
}
972997

973998
impl Display for ExtraOperator {
@@ -1285,6 +1310,118 @@ impl MarkerExpression {
12851310
}
12861311
}
12871312

1313+
/// Negates this marker expression.
1314+
///
1315+
/// In most cases, this returns a `MarkerTree::Expression`, but in some
1316+
/// cases it can be more complicated than that. For example, the negation
1317+
/// of a compatible version constraint is a disjunction.
1318+
///
1319+
/// Additionally, in some cases, the negation reflects the "spirit" of what
1320+
/// the marker expression is. For example, the negation of an "arbitrary"
1321+
/// expression will still result in an expression that is always false.
1322+
fn negate(&self) -> MarkerTree {
1323+
match *self {
1324+
MarkerExpression::Version {
1325+
ref key,
1326+
ref specifier,
1327+
} => {
1328+
let (op, version) = (specifier.operator(), specifier.version().clone());
1329+
match op.negate() {
1330+
None => negate_compatible_version(key.clone(), version),
1331+
Some(op) => {
1332+
// OK because this can only fail with either local versions,
1333+
// which we avoid by construction, or if the op is ~=, which
1334+
// is never the result of negating an op.
1335+
let specifier =
1336+
VersionSpecifier::from_version(op, version.without_local()).unwrap();
1337+
let expr = MarkerExpression::Version {
1338+
key: key.clone(),
1339+
specifier,
1340+
};
1341+
MarkerTree::Expression(expr)
1342+
}
1343+
}
1344+
}
1345+
MarkerExpression::VersionInverted {
1346+
ref version,
1347+
ref operator,
1348+
ref key,
1349+
} => {
1350+
let version = version.clone();
1351+
match operator.negate() {
1352+
None => negate_compatible_version(key.clone(), version),
1353+
Some(op) => {
1354+
let expr = MarkerExpression::VersionInverted {
1355+
version: version.without_local(),
1356+
operator: op,
1357+
key: key.clone(),
1358+
};
1359+
MarkerTree::Expression(expr)
1360+
}
1361+
}
1362+
}
1363+
MarkerExpression::String {
1364+
ref key,
1365+
ref operator,
1366+
ref value,
1367+
} => {
1368+
let expr = MarkerExpression::String {
1369+
key: key.clone(),
1370+
// negating ~= doesn't make sense in this context, but
1371+
// I believe it is technically allowed, so we just leave
1372+
// it as-is.
1373+
operator: operator.negate().unwrap_or(MarkerOperator::TildeEqual),
1374+
value: value.clone(),
1375+
};
1376+
MarkerTree::Expression(expr)
1377+
}
1378+
MarkerExpression::StringInverted {
1379+
ref value,
1380+
ref operator,
1381+
ref key,
1382+
} => {
1383+
let expr = MarkerExpression::StringInverted {
1384+
value: value.clone(),
1385+
// negating ~= doesn't make sense in this context, but
1386+
// I believe it is technically allowed, so we just leave
1387+
// it as-is.
1388+
operator: operator.negate().unwrap_or(MarkerOperator::TildeEqual),
1389+
key: key.clone(),
1390+
};
1391+
MarkerTree::Expression(expr)
1392+
}
1393+
MarkerExpression::Extra {
1394+
ref operator,
1395+
ref name,
1396+
} => {
1397+
let expr = MarkerExpression::Extra {
1398+
operator: operator.negate(),
1399+
name: name.clone(),
1400+
};
1401+
MarkerTree::Expression(expr)
1402+
}
1403+
// "arbitrary" expressions always return false, and while the
1404+
// negation logically implies they should always return true, we do
1405+
// not do that here because it violates the spirit of a meaningly
1406+
// or "arbitrary" marker. We flip the operator but do nothing else.
1407+
MarkerExpression::Arbitrary {
1408+
ref l_value,
1409+
ref operator,
1410+
ref r_value,
1411+
} => {
1412+
let expr = MarkerExpression::Arbitrary {
1413+
l_value: l_value.clone(),
1414+
// negating ~= doesn't make sense in this context, but
1415+
// I believe it is technically allowed, so we just leave
1416+
// it as-is.
1417+
operator: operator.negate().unwrap_or(MarkerOperator::TildeEqual),
1418+
r_value: r_value.clone(),
1419+
};
1420+
MarkerTree::Expression(expr)
1421+
}
1422+
}
1423+
}
1424+
12881425
/// Evaluate a <`marker_value`> <`marker_op`> <`marker_value`> expression
12891426
///
12901427
/// When `env` is `None`, all expressions that reference the environment
@@ -1858,6 +1995,28 @@ impl MarkerTree {
18581995
}
18591996
}
18601997

1998+
/// Returns a new marker tree that is the negation of this one.
1999+
#[must_use]
2000+
pub fn negate(&self) -> MarkerTree {
2001+
match *self {
2002+
MarkerTree::Expression(ref expr) => expr.negate(),
2003+
MarkerTree::And(ref trees) => {
2004+
let mut negated = MarkerTree::Or(Vec::with_capacity(trees.len()));
2005+
for tree in trees {
2006+
negated.or(tree.negate());
2007+
}
2008+
negated
2009+
}
2010+
MarkerTree::Or(ref trees) => {
2011+
let mut negated = MarkerTree::And(Vec::with_capacity(trees.len()));
2012+
for tree in trees {
2013+
negated.and(tree.negate());
2014+
}
2015+
negated
2016+
}
2017+
}
2018+
}
2019+
18612020
/// Combine this marker tree with the one given via a conjunction.
18622021
///
18632022
/// This does some shallow flattening. That is, if `self` is a conjunction
@@ -1954,6 +2113,43 @@ impl Display for MarkerTree {
19542113
}
19552114
}
19562115

2116+
/// Negates a compatible version marker expression, from its component parts.
2117+
///
2118+
/// Here, we consider `key ~= V.N` to be equivalent to
2119+
/// `key >= V.N and key == V.*`. So the negation returned is
2120+
/// `key < V.N or key != V.*`.
2121+
fn negate_compatible_version(key: MarkerValueVersion, version: Version) -> MarkerTree {
2122+
assert!(
2123+
version.release().len() > 1,
2124+
"~= requires more than 1 release version number"
2125+
);
2126+
// I believe we're already guaranteed that this is true,
2127+
// because we're only here if this version was combined
2128+
// with ~=, which cannot be used with local versions anyway.
2129+
// But this ensures correctness and should be pretty cheap.
2130+
let version = version.without_local();
2131+
let pattern = VersionPattern::wildcard(Version::new(
2132+
&version.release()[..version.release().len() - 1],
2133+
));
2134+
// OK because this can only fail for local versions or when using
2135+
// ~=, but neither is the case here.
2136+
let disjunct1 = VersionSpecifier::from_version(pep440_rs::Operator::LessThan, version).unwrap();
2137+
// And this is OK because it only fails if the above would fail
2138+
// (which we know it doesn't) or if the operator is not compatible
2139+
// with wildcards, but != is.
2140+
let disjunct2 = VersionSpecifier::from_pattern(pep440_rs::Operator::NotEqual, pattern).unwrap();
2141+
MarkerTree::Or(vec![
2142+
MarkerTree::Expression(MarkerExpression::Version {
2143+
key: key.clone(),
2144+
specifier: disjunct1,
2145+
}),
2146+
MarkerTree::Expression(MarkerExpression::Version {
2147+
key,
2148+
specifier: disjunct2,
2149+
}),
2150+
])
2151+
}
2152+
19572153
/// ```text
19582154
/// version_cmp = wsp* <'<=' | '<' | '!=' | '==' | '>=' | '>' | '~=' | '==='>
19592155
/// marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in')
@@ -2265,6 +2461,62 @@ mod test {
22652461
}
22662462
}
22672463

2464+
#[test]
2465+
fn test_marker_negation() {
2466+
let neg = |marker_string: &str| -> String {
2467+
let tree: MarkerTree = marker_string.parse().unwrap();
2468+
tree.negate().to_string()
2469+
};
2470+
2471+
assert_eq!(neg("python_version > '3.6'"), "python_version <= '3.6'");
2472+
assert_eq!(neg("'3.6' < python_version"), "'3.6' >= python_version");
2473+
2474+
assert_eq!(
2475+
neg("python_version == '3.6.*'"),
2476+
"python_version != '3.6.*'"
2477+
);
2478+
assert_eq!(
2479+
neg("python_version != '3.6.*'"),
2480+
"python_version == '3.6.*'"
2481+
);
2482+
2483+
assert_eq!(
2484+
neg("python_version ~= '3.6'"),
2485+
"python_version < '3.6' or python_version != '3.*'"
2486+
);
2487+
assert_eq!(
2488+
neg("'3.6' ~= python_version"),
2489+
"python_version < '3.6' or python_version != '3.*'"
2490+
);
2491+
assert_eq!(
2492+
neg("python_version ~= '3.6.2'"),
2493+
"python_version < '3.6.2' or python_version != '3.6.*'"
2494+
);
2495+
2496+
assert_eq!(neg("sys_platform == 'linux'"), "sys_platform != 'linux'");
2497+
assert_eq!(neg("'linux' == sys_platform"), "'linux' != sys_platform");
2498+
2499+
// ~= is nonsense on string markers. Evaluation always returns false
2500+
// in this case, so technically negation would be an expression that
2501+
// always returns true. But, as we do with "arbitrary" markers, we
2502+
// don't let the negation of nonsense become sensible.
2503+
assert_eq!(neg("sys_platform ~= 'linux'"), "sys_platform ~= 'linux'");
2504+
2505+
// As above, arbitrary exprs remain arbitrary.
2506+
assert_eq!(neg("'foo' == 'bar'"), "'foo' != 'bar'");
2507+
2508+
// Conjunctions
2509+
assert_eq!(
2510+
neg("os_name == 'bar' and os_name == 'foo'"),
2511+
"os_name != 'bar' or os_name != 'foo'"
2512+
);
2513+
// Disjunctions
2514+
assert_eq!(
2515+
neg("os_name == 'bar' or os_name == 'foo'"),
2516+
"os_name != 'bar' and os_name != 'foo'"
2517+
);
2518+
}
2519+
22682520
#[test]
22692521
fn test_marker_evaluation() {
22702522
let env27 = MarkerEnvironment::try_from(MarkerEnvironmentBuilder {

0 commit comments

Comments
 (0)