aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-02-24 12:19:22 +0000
committerGitHub <[email protected]>2021-02-24 12:19:22 +0000
commit0537510aef4059d5f79146a8dc2734ffdb27dc74 (patch)
tree6fa29e24165e07db8f77dad76d633b12eb79574f
parent18ac02ba411e2f9eeb38c3b246c619ac76846767 (diff)
parent694f7a7e9f90cc435afcaade23b5908728d17ed2 (diff)
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 <[email protected]> Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs78
-rw-r--r--crates/ide_assists/src/handlers/early_return.rs2
-rw-r--r--crates/ide_assists/src/handlers/invert_if.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs4
-rw-r--r--crates/ide_assists/src/utils.rs50
-rw-r--r--crates/ide_db/src/helpers.rs4
-rw-r--r--crates/ide_db/src/helpers/famous_defs_fixture.rs11
-rw-r--r--crates/syntax/src/ast/make.rs7
8 files changed, 137 insertions, 21 deletions
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs
index ed4d11455..6997ea048 100644
--- a/crates/ide_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ide_assists/src/handlers/apply_demorgan.rs
@@ -7,18 +7,17 @@ use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKin
7// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law]. 7// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
8// This transforms expressions of the form `!l || !r` into `!(l && r)`. 8// This transforms expressions of the form `!l || !r` into `!(l && r)`.
9// This also works with `&&`. This assist can only be applied with the cursor 9// 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. 10// on either `||` or `&&`.
11// This means something of the form `!x` or `x != y`.
12// 11//
13// ``` 12// ```
14// fn main() { 13// fn main() {
15// if x != 4 ||$0 !y {} 14// if x != 4 ||$0 y < 3.14 {}
16// } 15// }
17// ``` 16// ```
18// -> 17// ->
19// ``` 18// ```
20// fn main() { 19// fn main() {
21// if !(x == 4 && y) {} 20// if !(x == 4 && !(y < 3.14)) {}
22// } 21// }
23// ``` 22// ```
24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(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<(
33 32
34 let lhs = expr.lhs()?; 33 let lhs = expr.lhs()?;
35 let lhs_range = lhs.syntax().text_range(); 34 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);
37 36
38 let rhs = expr.rhs()?; 37 let rhs = expr.rhs()?;
39 let rhs_range = rhs.syntax().text_range(); 38 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);
41 40
42 acc.add( 41 acc.add(
43 AssistId("apply_demorgan", AssistKind::RefactorRewrite), 42 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
@@ -62,10 +61,77 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
62 61
63#[cfg(test)] 62#[cfg(test)]
64mod tests { 63mod tests {
64 use ide_db::helpers::FamousDefs;
65
65 use super::*; 66 use super::*;
66 67
67 use crate::tests::{check_assist, check_assist_not_applicable}; 68 use crate::tests::{check_assist, check_assist_not_applicable};
68 69
70 const ORDABLE_FIXTURE: &'static str = r"
71//- /lib.rs deps:core crate:ordable
72struct NonOrderable;
73struct Orderable;
74impl 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;
91fn f() {
92 Orderable < Orderable &&$0 Orderable <= Orderable
93}",
94 r"use ordable::Orderable;
95fn f() {
96 !(Orderable >= Orderable || Orderable > Orderable)
97}",
98 );
99 check(
100 r"use ordable::NonOrderable;
101fn f() {
102 NonOrderable < NonOrderable &&$0 NonOrderable <= NonOrderable
103}",
104 r"use ordable::NonOrderable;
105fn f() {
106 !(!(NonOrderable < NonOrderable) || !(NonOrderable <= NonOrderable))
107}",
108 );
109 }
110
111 #[test]
112 fn demorgan_handles_geq() {
113 check(
114 r"use ordable::Orderable;
115fn f() {
116 Orderable > Orderable &&$0 Orderable >= Orderable
117}",
118 r"use ordable::Orderable;
119fn f() {
120 !(Orderable <= Orderable || Orderable < Orderable)
121}",
122 );
123 check(
124 r"use ordable::NonOrderable;
125fn f() {
126 Orderable > Orderable &&$0 Orderable >= Orderable
127}",
128 r"use ordable::NonOrderable;
129fn f() {
130 !(!(Orderable > Orderable) || !(Orderable >= Orderable))
131}",
132 );
133 }
134
69 #[test] 135 #[test]
70 fn demorgan_turns_and_into_or() { 136 fn demorgan_turns_and_into_or() {
71 check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }") 137 check_assist(apply_demorgan, "fn f() { !x &&$0 !x }", "fn f() { !(x || x) }")
diff --git a/crates/ide_assists/src/handlers/early_return.rs b/crates/ide_assists/src/handlers/early_return.rs
index 6b87c3c05..9e0918477 100644
--- a/crates/ide_assists/src/handlers/early_return.rs
+++ b/crates/ide_assists/src/handlers/early_return.rs
@@ -111,7 +111,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
111 let new_expr = { 111 let new_expr = {
112 let then_branch = 112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 113 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);
115 make::expr_if(make::condition(cond, None), then_branch, None) 115 make::expr_if(make::condition(cond, None), then_branch, None)
116 .indent(if_indent_level) 116 .indent(if_indent_level)
117 }; 117 };
diff --git a/crates/ide_assists/src/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs
index 5b69dafd4..b131dc205 100644
--- a/crates/ide_assists/src/handlers/invert_if.rs
+++ b/crates/ide_assists/src/handlers/invert_if.rs
@@ -50,7 +50,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
50 }; 50 };
51 51
52 acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| { 52 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());
54 edit.replace_ast(cond, flip_cond); 54 edit.replace_ast(cond, flip_cond);
55 55
56 let else_node = else_block.syntax(); 56 let else_node = else_block.syntax();
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 0516deaff..701091a6b 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -147,12 +147,12 @@ fn doctest_apply_demorgan() {
147 "apply_demorgan", 147 "apply_demorgan",
148 r#####" 148 r#####"
149fn main() { 149fn main() {
150 if x != 4 ||$0 !y {} 150 if x != 4 ||$0 y < 3.14 {}
151} 151}
152"#####, 152"#####,
153 r#####" 153 r#####"
154fn main() { 154fn main() {
155 if !(x == 4 && y) {} 155 if !(x == 4 && !(y < 3.14)) {}
156} 156}
157"#####, 157"#####,
158 ) 158 )
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index 0074da741..276792bc1 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -3,8 +3,11 @@
3use std::ops; 3use std::ops;
4 4
5use ast::TypeBoundsOwner; 5use ast::TypeBoundsOwner;
6use hir::{Adt, HasSource}; 6use hir::{Adt, HasSource, Semantics};
7use ide_db::{helpers::SnippetCap, RootDatabase}; 7use ide_db::{
8 helpers::{FamousDefs, SnippetCap},
9 RootDatabase,
10};
8use itertools::Itertools; 11use itertools::Itertools;
9use stdx::format_to; 12use stdx::format_to;
10use syntax::{ 13use syntax::{
@@ -205,23 +208,36 @@ pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
205 .unwrap_or_else(|| node.text_range().start()) 208 .unwrap_or_else(|| node.text_range().start())
206} 209}
207 210
208pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { 211pub(crate) fn invert_boolean_expression(
209 if let Some(expr) = invert_special_case(&expr) { 212 sema: &Semantics<RootDatabase>,
213 expr: ast::Expr,
214) -> ast::Expr {
215 if let Some(expr) = invert_special_case(sema, &expr) {
210 return expr; 216 return expr;
211 } 217 }
212 make::expr_prefix(T![!], expr) 218 make::expr_prefix(T![!], expr)
213} 219}
214 220
215fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> { 221fn invert_special_case(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ast::Expr> {
216 match expr { 222 match expr {
217 ast::Expr::BinExpr(bin) => match bin.op_kind()? { 223 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
218 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()), 224 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
219 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()), 225 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
220 // Parenthesize composite boolean expressions before prefixing `!` 226 // Swap `<` with `>=`, `<=` with `>`, ... if operands `impl Ord`
221 ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => { 227 ast::BinOp::LesserTest if bin_impls_ord(sema, bin) => {
222 Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))) 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())
223 } 238 }
224 _ => None, 239 // Parenthesize other expressions before prefixing `!`
240 _ => Some(make::expr_prefix(T![!], make::expr_paren(expr.clone()))),
225 }, 241 },
226 ast::Expr::MethodCallExpr(mce) => { 242 ast::Expr::MethodCallExpr(mce) => {
227 let receiver = mce.receiver()?; 243 let receiver = mce.receiver()?;
@@ -250,6 +266,22 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
250 } 266 }
251} 267}
252 268
269fn 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
253pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { 285pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
254 [Direction::Next, Direction::Prev].iter().copied() 286 [Direction::Next, Direction::Prev].iter().copied()
255} 287}
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index bc7aee110..f9de8ce0e 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -45,6 +45,10 @@ impl FamousDefs<'_, '_> {
45 self.find_crate("core") 45 self.find_crate("core")
46 } 46 }
47 47
48 pub fn core_cmp_Ord(&self) -> Option<Trait> {
49 self.find_trait("core:cmp:Ord")
50 }
51
48 pub fn core_convert_From(&self) -> Option<Trait> { 52 pub fn core_convert_From(&self) -> Option<Trait> {
49 self.find_trait("core:convert:From") 53 self.find_trait("core:convert:From")
50 } 54 }
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs
index 5e88de64d..bb4e9666b 100644
--- a/crates/ide_db/src/helpers/famous_defs_fixture.rs
+++ b/crates/ide_db/src/helpers/famous_defs_fixture.rs
@@ -1,5 +1,15 @@
1//- /libcore.rs crate:core 1//- /libcore.rs crate:core
2//! Signatures of traits, types and functions from the core lib for use in tests. 2//! Signatures of traits, types and functions from the core lib for use in tests.
3pub 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
3pub mod convert { 13pub mod convert {
4 pub trait From<T> { 14 pub trait From<T> {
5 fn from(t: T) -> Self; 15 fn from(t: T) -> Self;
@@ -109,6 +119,7 @@ pub mod option {
109 119
110pub mod prelude { 120pub mod prelude {
111 pub use crate::{ 121 pub use crate::{
122 cmp::Ord,
112 convert::From, 123 convert::From,
113 default::Default, 124 default::Default,
114 iter::{IntoIterator, Iterator}, 125 iter::{IntoIterator, Iterator},
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 5eee33545..b6c5de658 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -527,8 +527,11 @@ pub mod tokens {
527 527
528 use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; 528 use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
529 529
530 pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = 530 pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
531 Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true, *p)\n;\n\n")); 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 });
532 535
533 pub fn single_space() -> SyntaxToken { 536 pub fn single_space() -> SyntaxToken {
534 SOURCE_FILE 537 SOURCE_FILE