mirror of https://github.com/astral-sh/ruff
Use an enum to represent composition kind (#2945)
This commit is contained in:
parent
1bc37110d4
commit
b6587e51ee
|
|
@ -233,23 +233,58 @@ pub fn assert_in_exception_handler(handlers: &[Excepthandler]) -> Vec<Diagnostic
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the test expression is a composite condition.
|
enum CompositionKind {
|
||||||
/// For example, `a and b` or `not (a or b)`. The latter is equivalent
|
// E.g., `a or b or c`.
|
||||||
/// to `not a and not b` by De Morgan's laws.
|
None,
|
||||||
const fn is_composite_condition(test: &Expr) -> bool {
|
// E.g., `a and b` or `not (a or b)`.
|
||||||
|
Simple,
|
||||||
|
// E.g., `not (a and b or c)`.
|
||||||
|
Mixed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the test expression is a composite condition, and whether it can
|
||||||
|
/// be split into multiple independent conditions.
|
||||||
|
///
|
||||||
|
/// For example, `a and b` or `not (a or b)`. The latter is equivalent to
|
||||||
|
/// `not a and not b` by De Morgan's laws.
|
||||||
|
fn is_composite_condition(test: &Expr) -> CompositionKind {
|
||||||
match &test.node {
|
match &test.node {
|
||||||
ExprKind::BoolOp {
|
ExprKind::BoolOp {
|
||||||
op: Boolop::And, ..
|
op: Boolop::And, ..
|
||||||
} => true,
|
} => {
|
||||||
|
return CompositionKind::Simple;
|
||||||
|
}
|
||||||
ExprKind::UnaryOp {
|
ExprKind::UnaryOp {
|
||||||
op: Unaryop::Not,
|
op: Unaryop::Not,
|
||||||
operand,
|
operand,
|
||||||
} => matches!(&operand.node, ExprKind::BoolOp { op: Boolop::Or, .. }),
|
} => {
|
||||||
_ => false,
|
if let ExprKind::BoolOp {
|
||||||
|
op: Boolop::Or,
|
||||||
|
values,
|
||||||
|
} = &operand.node
|
||||||
|
{
|
||||||
|
// Only split cases without mixed `and` and `or`.
|
||||||
|
return if values.iter().all(|expr| {
|
||||||
|
!matches!(
|
||||||
|
expr.node,
|
||||||
|
ExprKind::BoolOp {
|
||||||
|
op: Boolop::And,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
CompositionKind::Simple
|
||||||
|
} else {
|
||||||
|
CompositionKind::Mixed
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
CompositionKind::None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Negate condition, i.e. `a` => `not a` and `not a` => `a`
|
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
|
||||||
fn negate(f: Expr) -> Expr {
|
fn negate(f: Expr) -> Expr {
|
||||||
match f.node {
|
match f.node {
|
||||||
ExprKind::UnaryOp {
|
ExprKind::UnaryOp {
|
||||||
|
|
@ -263,37 +298,8 @@ fn negate(f: Expr) -> Expr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the condition appears to be a fixable composite condition.
|
|
||||||
fn is_fixable_composite_condition(test: &Expr) -> bool {
|
|
||||||
let ExprKind::UnaryOp {
|
|
||||||
op: Unaryop::Not,
|
|
||||||
operand,
|
|
||||||
} = &test.node else {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
let ExprKind::BoolOp {
|
|
||||||
op: Boolop::Or,
|
|
||||||
values,
|
|
||||||
} = &operand.node else {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
// Only take cases without mixed `and` and `or`
|
|
||||||
values.iter().all(|expr| {
|
|
||||||
!matches!(
|
|
||||||
expr.node,
|
|
||||||
ExprKind::BoolOp {
|
|
||||||
op: Boolop::And,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace composite condition `assert a == "hello" and b == "world"` with two statements
|
/// Replace composite condition `assert a == "hello" and b == "world"` with two statements
|
||||||
/// `assert a == "hello"` and `assert b == "world"`.
|
/// `assert a == "hello"` and `assert b == "world"`.
|
||||||
///
|
|
||||||
/// It's assumed that the condition is fixable, i.e., that `is_fixable_composite_condition`
|
|
||||||
/// returns `true`.
|
|
||||||
fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
||||||
let mut conditions: Vec<Expr> = vec![];
|
let mut conditions: Vec<Expr> = vec![];
|
||||||
match &test.node {
|
match &test.node {
|
||||||
|
|
@ -301,7 +307,7 @@ fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
||||||
op: Boolop::And,
|
op: Boolop::And,
|
||||||
values,
|
values,
|
||||||
} => {
|
} => {
|
||||||
// Compound, so split (Split)
|
// Compound, so split.
|
||||||
conditions.extend(values.clone());
|
conditions.extend(values.clone());
|
||||||
}
|
}
|
||||||
ExprKind::UnaryOp {
|
ExprKind::UnaryOp {
|
||||||
|
|
@ -313,17 +319,11 @@ fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
||||||
op: Boolop::Or,
|
op: Boolop::Or,
|
||||||
values,
|
values,
|
||||||
} => {
|
} => {
|
||||||
// `not (a or b)` equals `not a and not b`
|
// Split via `not (a or b)` equals `not a and not b`.
|
||||||
let vals = values
|
conditions.extend(values.iter().map(|f| negate(f.clone())));
|
||||||
.iter()
|
|
||||||
.map(|f| negate(f.clone()))
|
|
||||||
.collect::<Vec<Expr>>();
|
|
||||||
|
|
||||||
// Compound, so split (Split)
|
|
||||||
conditions.extend(vals);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Do not split
|
// Do not split.
|
||||||
conditions.push(*operand.clone());
|
conditions.push(*operand.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -331,7 +331,7 @@ fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// for each condition create `assert condition`
|
// For each condition, create an `assert condition` statement.
|
||||||
let mut content: Vec<String> = Vec::with_capacity(conditions.len());
|
let mut content: Vec<String> = Vec::with_capacity(conditions.len());
|
||||||
for condition in conditions {
|
for condition in conditions {
|
||||||
content.push(unparse_stmt(
|
content.push(unparse_stmt(
|
||||||
|
|
@ -349,8 +349,9 @@ fn fix_composite_condition(stylist: &Stylist, stmt: &Stmt, test: &Expr) -> Fix {
|
||||||
|
|
||||||
/// PT018
|
/// PT018
|
||||||
pub fn composite_condition(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
|
pub fn composite_condition(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
|
||||||
if is_composite_condition(test) {
|
let composite = is_composite_condition(test);
|
||||||
let fixable = msg.is_none() && is_fixable_composite_condition(test);
|
if matches!(composite, CompositionKind::Simple | CompositionKind::Mixed) {
|
||||||
|
let fixable = matches!(composite, CompositionKind::Simple) && msg.is_none();
|
||||||
let mut diagnostic =
|
let mut diagnostic =
|
||||||
Diagnostic::new(CompositeAssertion { fixable }, Range::from_located(stmt));
|
Diagnostic::new(CompositeAssertion { fixable }, Range::from_located(stmt));
|
||||||
if fixable && checker.patch(diagnostic.kind.rule()) {
|
if fixable && checker.patch(diagnostic.kind.rule()) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue