mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Fix substring marker expression disjointness checks (#4998)
## Summary Noticed a bug here, `'a' in env` and `env not in 'a'` are not disjoint given `env == 'ab'`.
This commit is contained in:
parent
abdb58d2df
commit
a1f71a36e7
1 changed files with 62 additions and 12 deletions
|
@ -49,7 +49,47 @@ pub(crate) fn is_disjoint(first: &MarkerTree, second: &MarkerTree) -> bool {
|
||||||
fn string_is_disjoint(this: &MarkerExpression, other: &MarkerExpression) -> bool {
|
fn string_is_disjoint(this: &MarkerExpression, other: &MarkerExpression) -> bool {
|
||||||
use MarkerOperator::*;
|
use MarkerOperator::*;
|
||||||
|
|
||||||
let (key, operator, value) = extract_string_expression(this).unwrap();
|
// The `in` and `not in` operators are not reversible, so we have to ensure the expressions
|
||||||
|
// match exactly. Notably, `'a' in env` and `env not in 'a'` are not disjoint given `env == 'ab'`.
|
||||||
|
match (this, other) {
|
||||||
|
(
|
||||||
|
MarkerExpression::String {
|
||||||
|
key,
|
||||||
|
operator,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
MarkerExpression::String {
|
||||||
|
key: key2,
|
||||||
|
operator: operator2,
|
||||||
|
value: value2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
MarkerExpression::StringInverted {
|
||||||
|
key,
|
||||||
|
operator,
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
MarkerExpression::StringInverted {
|
||||||
|
key: key2,
|
||||||
|
operator: operator2,
|
||||||
|
value: value2,
|
||||||
|
},
|
||||||
|
) if key == key2 => match (operator, operator2) {
|
||||||
|
// The only disjoint expressions involving these operators are `key in value`
|
||||||
|
// and `key not in value`, or reversed.
|
||||||
|
(In, NotIn) | (NotIn, In) => return value == value2,
|
||||||
|
// Anything else cannot be disjoint.
|
||||||
|
(In | NotIn, _) | (_, In | NotIn) => return false,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the normalized string expressions.
|
||||||
|
let Some((key, operator, value)) = extract_string_expression(this) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
let Some((key2, operator2, value2)) = extract_string_expression(other) else {
|
let Some((key2, operator2, value2)) = extract_string_expression(other) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -63,9 +103,6 @@ fn string_is_disjoint(this: &MarkerExpression, other: &MarkerExpression) -> bool
|
||||||
// The only disjoint expressions involving strict inequality are `key != value` and `key == value`.
|
// The only disjoint expressions involving strict inequality are `key != value` and `key == value`.
|
||||||
(NotEqual, Equal) | (Equal, NotEqual) => return value == value2,
|
(NotEqual, Equal) | (Equal, NotEqual) => return value == value2,
|
||||||
(NotEqual, _) | (_, NotEqual) => return false,
|
(NotEqual, _) | (_, NotEqual) => return false,
|
||||||
// Similarly for `in` and `not in`.
|
|
||||||
(In, NotIn) | (NotIn, In) => return value == value2,
|
|
||||||
(In | NotIn, _) | (_, In | NotIn) => return false,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,8 +597,8 @@ fn extract_string_expression(
|
||||||
operator,
|
operator,
|
||||||
key,
|
key,
|
||||||
} => {
|
} => {
|
||||||
// if the expression was inverted, we have to reverse the operator
|
// If the expression was inverted, we have to reverse the marker operator.
|
||||||
Some((key, reverse_marker_operator(*operator), value))
|
reverse_marker_operator(*operator).map(|operator| (key, operator, value.as_str()))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -675,6 +712,7 @@ fn keyed_range(expr: &MarkerExpression) -> Option<(&MarkerValueVersion, PubGrubR
|
||||||
/// Reverses a binary operator.
|
/// Reverses a binary operator.
|
||||||
fn reverse_operator(operator: Operator) -> Operator {
|
fn reverse_operator(operator: Operator) -> Operator {
|
||||||
use Operator::*;
|
use Operator::*;
|
||||||
|
|
||||||
match operator {
|
match operator {
|
||||||
LessThan => GreaterThan,
|
LessThan => GreaterThan,
|
||||||
LessThanEqual => GreaterThanEqual,
|
LessThanEqual => GreaterThanEqual,
|
||||||
|
@ -684,16 +722,21 @@ fn reverse_operator(operator: Operator) -> Operator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reverses a marker operator.
|
/// Reverses a marker operator, if possible.
|
||||||
fn reverse_marker_operator(operator: MarkerOperator) -> MarkerOperator {
|
fn reverse_marker_operator(operator: MarkerOperator) -> Option<MarkerOperator> {
|
||||||
use MarkerOperator::*;
|
use MarkerOperator::*;
|
||||||
match operator {
|
|
||||||
|
Some(match operator {
|
||||||
LessThan => GreaterThan,
|
LessThan => GreaterThan,
|
||||||
LessEqual => GreaterEqual,
|
LessEqual => GreaterEqual,
|
||||||
GreaterThan => LessThan,
|
GreaterThan => LessThan,
|
||||||
GreaterEqual => LessEqual,
|
GreaterEqual => LessEqual,
|
||||||
_ => operator,
|
Equal => Equal,
|
||||||
}
|
NotEqual => NotEqual,
|
||||||
|
TildeEqual => TildeEqual,
|
||||||
|
// The `in` and `not in` operators are not reversible.
|
||||||
|
In | NotIn => return None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -936,7 +979,14 @@ mod tests {
|
||||||
"os_name in 'Windows'",
|
"os_name in 'Windows'",
|
||||||
"os_name not in 'Windows'"
|
"os_name not in 'Windows'"
|
||||||
));
|
));
|
||||||
assert!(is_disjoint("'Linux' in os_name", "os_name not in 'Linux'"));
|
assert!(is_disjoint(
|
||||||
|
"'Windows' in os_name",
|
||||||
|
"'Windows' not in os_name"
|
||||||
|
));
|
||||||
|
|
||||||
|
assert!(!is_disjoint("'Windows' in os_name", "'Windows' in os_name"));
|
||||||
|
assert!(!is_disjoint("'Linux' in os_name", "os_name not in 'Linux'"));
|
||||||
|
assert!(!is_disjoint("'Linux' not in os_name", "os_name in 'Linux'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue