diff options
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 78 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/early_return.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/invert_if.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 4 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 50 | ||||
-rw-r--r-- | crates/ide_db/src/helpers.rs | 4 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/famous_defs_fixture.rs | 11 | ||||
-rw-r--r-- | crates/syntax/src/ast/make.rs | 7 |
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 | // ``` |
24 | pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 23 | 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<( | |||
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)] |
64 | mod tests { | 63 | mod 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 | ||
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 | |||
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#####" |
149 | fn main() { | 149 | fn main() { |
150 | if x != 4 ||$0 !y {} | 150 | if x != 4 ||$0 y < 3.14 {} |
151 | } | 151 | } |
152 | "#####, | 152 | "#####, |
153 | r#####" | 153 | r#####" |
154 | fn main() { | 154 | fn 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 @@ | |||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use ast::TypeBoundsOwner; | 5 | use ast::TypeBoundsOwner; |
6 | use hir::{Adt, HasSource}; | 6 | use hir::{Adt, HasSource, Semantics}; |
7 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 7 | use ide_db::{ |
8 | helpers::{FamousDefs, SnippetCap}, | ||
9 | RootDatabase, | ||
10 | }; | ||
8 | use itertools::Itertools; | 11 | use itertools::Itertools; |
9 | use stdx::format_to; | 12 | use stdx::format_to; |
10 | use syntax::{ | 13 | use 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 | ||
208 | pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { | 211 | pub(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 | ||
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> { |
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 | ||
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 | |||
253 | pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { | 285 | pub(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. |
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 | |||
3 | pub mod convert { | 13 | pub 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 | ||
110 | pub mod prelude { | 120 | pub 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 |