Skip to content

Commit 0537510

Browse files
bors[bot]lbrandeVeykril
authored
Merge #7719
7719: De Morgan's Law assist now correctly parenthesizes binary expressions. r=Veykril a=lbrande Closes #7694 by parenthesizing binary expressions that are negated. Co-authored-by: lbrande <lovbra00@gmail.com> Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents 18ac02b + 694f7a7 commit 0537510

File tree

8 files changed

+137
-21
lines changed

8 files changed

+137
-21
lines changed

crates/ide_assists/src/handlers/apply_demorgan.rs

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
77
// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
88
// This transforms expressions of the form `!l || !r` into `!(l && r)`.
99
// This also works with `&&`. This assist can only be applied with the cursor
10-
// on either `||` or `&&`, with both operands being a negation of some kind.
11-
// This means something of the form `!x` or `x != y`.
10+
// on either `||` or `&&`.
1211
//
1312
// ```
1413
// fn main() {
15-
// if x != 4 ||$0 !y {}
14+
// if x != 4 ||$0 y < 3.14 {}
1615
// }
1716
// ```
1817
// ->
1918
// ```
2019
// fn main() {
21-
// if !(x == 4 && y) {}
20+
// if !(x == 4 && !(y < 3.14)) {}
2221
// }
2322
// ```
2423
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -33,11 +32,11 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
3332

3433
let lhs = expr.lhs()?;
3534
let lhs_range = lhs.syntax().text_range();
36-
let not_lhs = invert_boolean_expression(lhs);
35+
let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
3736

3837
let rhs = expr.rhs()?;
3938
let rhs_range = rhs.syntax().text_range();
40-
let not_rhs = invert_boolean_expression(rhs);
39+
let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
4140

4241
acc.add(
4342
AssistId("apply_demorgan", AssistKind::RefactorRewrite),
@@ -62,10 +61,77 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
6261

6362
#[cfg(test)]
6463
mod tests {
64+
use ide_db::helpers::FamousDefs;
65+
6566
use super::*;
6667

6768
use crate::tests::{check_assist, check_assist_not_applicable};
6869

70+
const ORDABLE_FIXTURE: &'static str = r"
71+
//- /lib.rs deps:core crate:ordable
72+
struct NonOrderable;
73+
struct Orderable;
74+
impl core::cmp::Ord for Orderable {}
75+
";
76+
77+
fn check(ra_fixture_before: &str, ra_fixture_after: &str) {
78+
let before = &format!(
79+
"//- /main.rs crate:main deps:core,ordable\n{}\n{}{}",
80+
ra_fixture_before,
81+
FamousDefs::FIXTURE,
82+
ORDABLE_FIXTURE
83+
);
84+
check_assist(apply_demorgan, before, &format!("{}\n", ra_fixture_after));
85+
}
86+
87+
#[test]
88+
fn demorgan_handles_leq() {
89+
check(
90+
r"use ordable::Orderable;
91+
fn f() {
92+
Orderable < Orderable &&$0 Orderable <= Orderable
93+
}",
94+
r"use ordable::Orderable;
95+
fn f() {
96+
!(Orderable >= Orderable || Orderable > Orderable)
97+
}",
98+
);
99+
check(
100+
r"use ordable::NonOrderable;
101+
fn f() {
102+
NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
103+
}",
104+
r"use ordable::NonOrderable;
105+
fn f() {
106+
!(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
107+
}",
108+
);
109+
}
110+
111+
#[test]
112+
fn demorgan_handles_geq() {
113+
check(
114+
r"use ordable::Orderable;
115+
fn f() {
116+
Orderable > Orderable &&$0 Orderable >= Orderable
117+
}",
118+
r"use ordable::Orderable;
119+
fn f() {
120+
!(Orderable <= Orderable || Orderable < Orderable)
121+
}",
122+
);
123+
check(
124+
r"use ordable::NonOrderable;
125+
fn f() {
126+
Orderable > Orderable &&$0 Orderable >= Orderable
127+
}",
128+
r"use ordable::NonOrderable;
129+
fn f() {
130+
!(!(Orderable > Orderable) || !(Orderable >= Orderable))
131+
}",
132+
);
133+
}
134+
69135
#[test]
70136
fn demorgan_turns_and_into_or() {
71137
check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")

crates/ide_assists/src/handlers/early_return.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
111111
let new_expr = {
112112
let then_branch =
113113
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114-
let cond = invert_boolean_expression(cond_expr);
114+
let cond = invert_boolean_expression(&ctx.sema, cond_expr);
115115
make::expr_if(make::condition(cond, None), then_branch, None)
116116
.indent(if_indent_level)
117117
};

crates/ide_assists/src/handlers/invert_if.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
5050
};
5151

5252
acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
53-
let flip_cond = invert_boolean_expression(cond.clone());
53+
let flip_cond = invert_boolean_expression(&ctx.sema, cond.clone());
5454
edit.replace_ast(cond, flip_cond);
5555

5656
let else_node = else_block.syntax();

crates/ide_assists/src/tests/generated.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,12 @@ fn doctest_apply_demorgan() {
147147
"apply_demorgan",
148148
r#####"
149149
fn main() {
150-
if x != 4 ||$0 !y {}
150+
if x != 4 ||$0 y < 3.14 {}
151151
}
152152
"#####,
153153
r#####"
154154
fn main() {
155-
if !(x == 4 && y) {}
155+
if !(x == 4 && !(y < 3.14)) {}
156156
}
157157
"#####,
158158
)

crates/ide_assists/src/utils.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
use std::ops;
44

55
use ast::TypeBoundsOwner;
6-
use hir::{Adt, HasSource};
7-
use ide_db::{helpers::SnippetCap, RootDatabase};
6+
use hir::{Adt, HasSource, Semantics};
7+
use ide_db::{
8+
helpers::{FamousDefs, SnippetCap},
9+
RootDatabase,
10+
};
811
use itertools::Itertools;
912
use stdx::format_to;
1013
use syntax::{
@@ -205,23 +208,36 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
205208
.unwrap_or_else(|| node.text_range().start())
206209
}
207210

208-
pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
209-
if let Some(expr) = invert_special_case(&expr) {
211+
pub(crate) fn invert_boolean_expression(
212+
sema: &Semantics<RootDatabase>,
213+
expr: ast::Expr,
214+
) -> ast::Expr {
215+
if let Some(expr) = invert_special_case(sema, &expr) {
210216
return expr;
211217
}
212218
make::expr_prefix(T![!], expr)
213219
}
214220

215-
fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
221+
fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ast::Expr> {
216222
match expr {
217223
ast::Expr::BinExpr(bin) => match bin.op_kind()? {
218224
ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
219225
ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
220-
// Parenthesize composite boolean expressions before prefixing `!`
221-
ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => {
222-
Some(make::expr_prefix(T![!], make::expr_paren(expr.clone())))
226+
// Swap `<` with `>=`, `<=` with `>`, ... if operands `impl Ord`
227+
ast::BinOp::LesserTest if bin_impls_ord(sema, bin) => {
228+
bin.replace_op(T![>=]).map(|it| it.into())
229+
}
230+
ast::BinOp::LesserEqualTest if bin_impls_ord(sema, bin) => {
231+
bin.replace_op(T![>]).map(|it| it.into())
232+
}
233+
ast::BinOp::GreaterTest if bin_impls_ord(sema, bin) => {
234+
bin.replace_op(T![<=]).map(|it| it.into())
235+
}
236+
ast::BinOp::GreaterEqualTest if bin_impls_ord(sema, bin) => {
237+
bin.replace_op(T![<]).map(|it| it.into())
223238
}
224-
_ => None,
239+
// Parenthesize other expressions before prefixing `!`
240+
_ => Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))),
225241
},
226242
ast::Expr::MethodCallExpr(mce) => {
227243
let receiver = mce.receiver()?;
@@ -250,6 +266,22 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
250266
}
251267
}
252268

269+
fn bin_impls_ord(sema: &Semantics<RootDatabase>, bin: &ast::BinExpr) -> bool {
270+
match (
271+
bin.lhs().and_then(|lhs| sema.type_of_expr(&lhs)),
272+
bin.rhs().and_then(|rhs| sema.type_of_expr(&rhs)),
273+
) {
274+
(Some(lhs_ty), Some(rhs_ty)) if lhs_ty == rhs_ty => {
275+
let krate = sema.scope(bin.syntax()).module().map(|it| it.krate());
276+
let ord_trait = FamousDefs(sema, krate).core_cmp_Ord();
277+
ord_trait.map_or(false, |ord_trait| {
278+
lhs_ty.autoderef(sema.db).any(|ty| ty.impls_trait(sema.db, ord_trait, &[]))
279+
})
280+
}
281+
_ => false,
282+
}
283+
}
284+
253285
pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
254286
[Direction::Next, Direction::Prev].iter().copied()
255287
}

crates/ide_db/src/helpers.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ impl FamousDefs<'_, '_> {
4545
self.find_crate("core")
4646
}
4747

48+
pub fn core_cmp_Ord(&self) -> Option<Trait> {
49+
self.find_trait("core:cmp:Ord")
50+
}
51+
4852
pub fn core_convert_From(&self) -> Option<Trait> {
4953
self.find_trait("core:convert:From")
5054
}

crates/ide_db/src/helpers/famous_defs_fixture.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
//- /libcore.rs crate:core
22
//! Signatures of traits, types and functions from the core lib for use in tests.
3+
pub mod cmp {
4+
5+
pub trait Ord {
6+
fn cmp(&self, other: &Self) -> Ordering;
7+
fn max(self, other: Self) -> Self;
8+
fn min(self, other: Self) -> Self;
9+
fn clamp(self, min: Self, max: Self) -> Self;
10+
}
11+
}
12+
313
pub mod convert {
414
pub trait From<T> {
515
fn from(t: T) -> Self;
@@ -109,6 +119,7 @@ pub mod option {
109119

110120
pub mod prelude {
111121
pub use crate::{
122+
cmp::Ord,
112123
convert::From,
113124
default::Default,
114125
iter::{IntoIterator, Iterator},

crates/syntax/src/ast/make.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,8 +527,11 @@ pub mod tokens {
527527

528528
use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
529529

530-
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> =
531-
Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true, *p)\n;\n\n"));
530+
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
531+
SourceFile::parse(
532+
"const C: <()>::Item = (1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p)\n;\n\n",
533+
)
534+
});
532535

533536
pub fn single_space() -> SyntaxToken {
534537
SOURCE_FILE

0 commit comments

Comments
 (0)