internal: make invert binary op more robust
Previously, we only inverted comparison operators (< and the like) if the type implemented Ord. This doesn't make sense: if `<` works, then `>=` will work as well! Extra semantic checks greatly reduce robustness and predictability of the assist, it's better to keep things simple.
This commit is contained in:
parent
49dbb74a0b
commit
beca92b245
@ -19,7 +19,7 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
|
||||
// ->
|
||||
// ```
|
||||
// fn main() {
|
||||
// if !(x == 4 && !(y < 3.14)) {}
|
||||
// if !(x == 4 && y >= 3.14) {}
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
@ -99,7 +99,7 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
|
||||
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);
|
||||
let not_term = invert_boolean_expression(term);
|
||||
|
||||
edit.replace(range, not_term.syntax().text());
|
||||
}
|
||||
@ -114,21 +114,21 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
|
||||
} else {
|
||||
if let Some(lhs) = terms.pop_front() {
|
||||
let lhs_range = lhs.syntax().text_range();
|
||||
let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
|
||||
let not_lhs = invert_boolean_expression(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);
|
||||
let not_rhs = invert_boolean_expression(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);
|
||||
let not_term = invert_boolean_expression(term);
|
||||
edit.replace(term_range, not_term.syntax().text());
|
||||
}
|
||||
}
|
||||
@ -156,40 +156,12 @@ mod tests {
|
||||
check_assist(
|
||||
apply_demorgan,
|
||||
r#"
|
||||
//- minicore: ord, derive
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
S < S &&$0 S <= S
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
!(S >= S || S > S)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
apply_demorgan,
|
||||
r#"
|
||||
//- minicore: ord, derive
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
S < S &&$0 S <= S
|
||||
}
|
||||
fn f() { S < S &&$0 S <= S }
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
!(!(S < S) || !(S <= S))
|
||||
}
|
||||
fn f() { !(S >= S || S > S) }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
@ -199,39 +171,12 @@ fn f() {
|
||||
check_assist(
|
||||
apply_demorgan,
|
||||
r#"
|
||||
//- minicore: ord, derive
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
S > S &&$0 S >= S
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
!(S <= S || S < S)
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
apply_demorgan,
|
||||
r#"
|
||||
//- minicore: ord, derive
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
S > S &&$0 S >= S
|
||||
}
|
||||
fn f() { S > S &&$0 S >= S }
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
|
||||
fn f() {
|
||||
!(!(S > S) || !(S >= S))
|
||||
}
|
||||
fn f() { !(S <= S || S < S) }
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext) ->
|
||||
e => e,
|
||||
};
|
||||
|
||||
let cond = if invert_cond { invert_boolean_expression(&ctx.sema, cond) } else { cond };
|
||||
let cond = if invert_cond { invert_boolean_expression(cond) } else { cond };
|
||||
let arg_list = make::arg_list(Some(make::expr_closure(None, closure_body)));
|
||||
let mcall = make::expr_method_call(cond, make::name_ref("then"), arg_list);
|
||||
builder.replace(target, mcall.to_string());
|
||||
|
@ -115,7 +115,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
|
||||
let new_expr = {
|
||||
let then_branch =
|
||||
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
|
||||
let cond = invert_boolean_expression(&ctx.sema, cond_expr);
|
||||
let cond = invert_boolean_expression(cond_expr);
|
||||
make::expr_if(make::condition(cond, None), then_branch, None)
|
||||
.indent(if_indent_level)
|
||||
};
|
||||
|
@ -47,7 +47,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
};
|
||||
|
||||
acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
|
||||
let flip_cond = invert_boolean_expression(&ctx.sema, cond.clone());
|
||||
let flip_cond = invert_boolean_expression(cond.clone());
|
||||
edit.replace_ast(cond, flip_cond);
|
||||
|
||||
let else_node = else_block.syntax();
|
||||
|
@ -151,7 +151,7 @@ fn main() {
|
||||
"#####,
|
||||
r#####"
|
||||
fn main() {
|
||||
if !(x == 4 && !(y < 3.14)) {}
|
||||
if !(x == 4 && y >= 3.14) {}
|
||||
}
|
||||
"#####,
|
||||
)
|
||||
|
@ -5,12 +5,8 @@ mod gen_trait_fn_body;
|
||||
|
||||
use std::ops;
|
||||
|
||||
use hir::{Adt, HasSource, Semantics};
|
||||
use ide_db::{
|
||||
helpers::{FamousDefs, SnippetCap},
|
||||
path_transform::PathTransform,
|
||||
RootDatabase,
|
||||
};
|
||||
use hir::{Adt, HasSource};
|
||||
use ide_db::{helpers::SnippetCap, path_transform::PathTransform, RootDatabase};
|
||||
use itertools::Itertools;
|
||||
use stdx::format_to;
|
||||
use syntax::{
|
||||
@ -207,31 +203,19 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
|
||||
.unwrap_or_else(|| node.text_range().start())
|
||||
}
|
||||
|
||||
pub(crate) fn invert_boolean_expression(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
expr: ast::Expr,
|
||||
) -> ast::Expr {
|
||||
invert_special_case(sema, &expr).unwrap_or_else(|| make::expr_prefix(T![!], expr))
|
||||
pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
|
||||
invert_special_case(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr))
|
||||
}
|
||||
|
||||
fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ast::Expr> {
|
||||
fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
|
||||
match expr {
|
||||
ast::Expr::BinExpr(bin) => match bin.op_kind()? {
|
||||
ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
|
||||
ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
|
||||
// Swap `<` with `>=`, `<=` with `>`, ... if operands `impl Ord`
|
||||
ast::BinOp::LesserTest if bin_impls_ord(sema, bin) => {
|
||||
bin.replace_op(T![>=]).map(|it| it.into())
|
||||
}
|
||||
ast::BinOp::LesserEqualTest if bin_impls_ord(sema, bin) => {
|
||||
bin.replace_op(T![>]).map(|it| it.into())
|
||||
}
|
||||
ast::BinOp::GreaterTest if bin_impls_ord(sema, bin) => {
|
||||
bin.replace_op(T![<=]).map(|it| it.into())
|
||||
}
|
||||
ast::BinOp::GreaterEqualTest if bin_impls_ord(sema, bin) => {
|
||||
bin.replace_op(T![<]).map(|it| it.into())
|
||||
}
|
||||
ast::BinOp::LesserTest => bin.replace_op(T![>=]).map(|it| it.into()),
|
||||
ast::BinOp::LesserEqualTest => bin.replace_op(T![>]).map(|it| it.into()),
|
||||
ast::BinOp::GreaterTest => bin.replace_op(T![<=]).map(|it| it.into()),
|
||||
ast::BinOp::GreaterEqualTest => bin.replace_op(T![<]).map(|it| it.into()),
|
||||
// Parenthesize other expressions before prefixing `!`
|
||||
_ => Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))),
|
||||
},
|
||||
@ -267,22 +251,6 @@ fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Opti
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_impls_ord(sema: &Semantics<RootDatabase>, bin: &ast::BinExpr) -> bool {
|
||||
match (
|
||||
bin.lhs().and_then(|lhs| sema.type_of_expr(&lhs)).map(hir::TypeInfo::adjusted),
|
||||
bin.rhs().and_then(|rhs| sema.type_of_expr(&rhs)).map(hir::TypeInfo::adjusted),
|
||||
) {
|
||||
(Some(lhs_ty), Some(rhs_ty)) if lhs_ty == rhs_ty => {
|
||||
let krate = sema.scope(bin.syntax()).module().map(|it| it.krate());
|
||||
let ord_trait = FamousDefs(sema, krate).core_cmp_Ord();
|
||||
ord_trait.map_or(false, |ord_trait| {
|
||||
lhs_ty.autoderef(sema.db).any(|ty| ty.impls_trait(sema.db, ord_trait, &[]))
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
|
||||
[Direction::Next, Direction::Prev].iter().copied()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user