rust/crates/ide_assists/src/handlers/apply_demorgan.rs

264 lines
8.0 KiB
Rust
Raw Normal View History

use std::collections::VecDeque;
2020-08-12 11:26:51 -05:00
use syntax::ast::{self, AstNode};
2020-06-28 17:36:05 -05:00
use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
2019-10-25 15:38:15 -05:00
// Assist: apply_demorgan
2019-10-26 09:27:47 -05:00
//
2020-08-12 06:56:58 -05:00
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
2019-10-25 15:38:15 -05:00
// This transforms expressions of the form `!l || !r` into `!(l && r)`.
// This also works with `&&`. This assist can only be applied with the cursor
// on either `||` or `&&`.
2019-10-26 09:27:47 -05:00
//
2019-10-25 15:38:15 -05:00
// ```
// fn main() {
// if x != 4 ||$0 y < 3.14 {}
2019-10-25 15:38:15 -05:00
// }
// ```
// ->
// ```
// fn main() {
// if !(x == 4 && !(y < 3.14)) {}
2019-10-25 15:38:15 -05:00
// }
// ```
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
let op = expr.op_kind()?;
let op_range = expr.op_token()?.text_range();
let opposite_op = opposite_logic_op(op)?;
2020-04-24 16:40:41 -05:00
let cursor_in_range = op_range.contains_range(ctx.frange.range);
if !cursor_in_range {
return None;
}
let mut expr = expr;
// Walk up the tree while we have the same binary operator
while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
if let Some(parent_op) = expr.op_kind() {
if parent_op == op {
expr = parent_expr
}
}
}
let mut expr_stack = vec![expr.clone()];
let mut terms = Vec::new();
let mut op_ranges = Vec::new();
// Find all the children with the same binary operator
while let Some(expr) = expr_stack.pop() {
let mut traverse_bin_expr_arm = |expr| {
if let ast::Expr::BinExpr(bin_expr) = expr {
if let Some(expr_op) = bin_expr.op_kind() {
if expr_op == op {
expr_stack.push(bin_expr);
} else {
terms.push(ast::Expr::BinExpr(bin_expr));
}
} else {
terms.push(ast::Expr::BinExpr(bin_expr));
}
} else {
terms.push(expr);
}
};
op_ranges.extend(expr.op_token().map(|t| t.text_range()));
traverse_bin_expr_arm(expr.lhs()?);
traverse_bin_expr_arm(expr.rhs()?);
}
2020-06-28 17:36:05 -05:00
acc.add(
2020-07-02 16:48:35 -05:00
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
2020-06-28 17:36:05 -05:00
"Apply De Morgan's law",
op_range,
|edit| {
terms.sort_by_key(|t| t.syntax().text_range().start());
let mut terms = VecDeque::from(terms);
2021-06-12 22:59:36 -05:00
let paren_expr = expr.syntax().parent().and_then(ast::ParenExpr::cast);
let neg_expr = paren_expr
.clone()
.and_then(|paren_expr| paren_expr.syntax().parent())
2021-06-12 22:59:36 -05:00
.and_then(ast::PrefixExpr::cast)
.and_then(|prefix_expr| {
if prefix_expr.op_kind().unwrap() == ast::PrefixOp::Not {
Some(prefix_expr)
} else {
None
}
});
for op_range in op_ranges {
edit.replace(op_range, opposite_op);
}
if let Some(paren_expr) = paren_expr {
for term in terms {
let range = term.syntax().text_range();
let not_term = invert_boolean_expression(&ctx.sema, term);
edit.replace(range, not_term.syntax().text());
}
if let Some(neg_expr) = neg_expr {
2021-03-08 14:19:44 -06:00
cov_mark::hit!(demorgan_double_negation);
edit.replace(neg_expr.op_token().unwrap().text_range(), "");
} else {
2021-03-08 14:19:44 -06:00
cov_mark::hit!(demorgan_double_parens);
edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
}
} else {
if let Some(lhs) = terms.pop_front() {
let lhs_range = lhs.syntax().text_range();
let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
}
if let Some(rhs) = terms.pop_back() {
let rhs_range = rhs.syntax().text_range();
let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
}
for term in terms {
let term_range = term.syntax().text_range();
let not_term = invert_boolean_expression(&ctx.sema, term);
edit.replace(term_range, not_term.syntax().text());
}
}
2020-06-28 17:36:05 -05:00
},
)
}
// Return the opposite text for a given logical operator, if it makes sense
fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
match kind {
ast::BinOp::BooleanOr => Some("&&"),
ast::BinOp::BooleanAnd => Some("||"),
_ => None,
}
}
#[cfg(test)]
mod tests {
2021-02-24 04:42:32 -06:00
use ide_db::helpers::FamousDefs;
use super::*;
2020-05-06 03:16:55 -05:00
use crate::tests::{check_assist, check_assist_not_applicable};
2021-02-24 04:42:32 -06:00
const ORDABLE_FIXTURE: &'static str = r"
//- /lib.rs deps:core crate:ordable
struct NonOrderable;
struct Orderable;
impl core::cmp::Ord for Orderable {}
";
fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
let before = &format!(
"//- /main.rs crate:main deps:core,ordable\n{}\n{}{}",
ra_fixture_before,
FamousDefs::FIXTURE,
ORDABLE_FIXTURE
);
check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after));
}
#[test]
fn demorgan_handles_leq() {
check(
r"use ordable::Orderable;
fn f() {
Orderable < Orderable &&$0 Orderable <= Orderable
}",
r"use ordable::Orderable;
fn f() {
!(Orderable >= Orderable || Orderable > Orderable)
}",
);
check(
r"use ordable::NonOrderable;
fn f() {
NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
}",
r"use ordable::NonOrderable;
fn f() {
!(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
}",
);
}
#[test]
fn demorgan_handles_geq() {
check(
r"use ordable::Orderable;
fn f() {
Orderable > Orderable &&$0 Orderable >= Orderable
}",
r"use ordable::Orderable;
fn f() {
!(Orderable <= Orderable || Orderable < Orderable)
}",
);
check(
r"use ordable::NonOrderable;
fn f() {
Orderable > Orderable &&$0 Orderable >= Orderable
}",
r"use ordable::NonOrderable;
fn f() {
!(!(Orderable > Orderable) || !(Orderable >= Orderable))
}",
);
}
#[test]
fn demorgan_turns_and_into_or() {
2021-01-06 14:15:48 -06:00
check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
}
#[test]
fn demorgan_turns_or_into_and() {
2021-01-06 14:15:48 -06:00
check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
}
#[test]
fn demorgan_removes_inequality() {
2021-01-06 14:15:48 -06:00
check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
}
#[test]
fn demorgan_general_case() {
2021-01-06 14:15:48 -06:00
check_assist(apply_demorgan, "fn f() { x ||$0 x }", "fn f() { !(!x && !x) }")
}
#[test]
fn demorgan_multiple_terms() {
check_assist(apply_demorgan, "fn f() { x ||$0 y || z }", "fn f() { !(!x && !y && !z) }");
check_assist(apply_demorgan, "fn f() { x || y ||$0 z }", "fn f() { !(!x && !y && !z) }");
}
#[test]
fn demorgan_doesnt_apply_with_cursor_not_on_op() {
2021-01-06 14:15:48 -06:00
check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
}
#[test]
fn demorgan_doesnt_double_negation() {
2021-03-08 14:19:44 -06:00
cov_mark::check!(demorgan_double_negation);
check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }")
}
#[test]
fn demorgan_doesnt_double_parens() {
2021-03-08 14:19:44 -06:00
cov_mark::check!(demorgan_double_parens);
check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }")
}
}