diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 78 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 6 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/early_return.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/flip_comma.rs | 26 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_is_method.rs (renamed from crates/ide_assists/src/handlers/generate_enum_match_method.rs) | 128 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_projection_method.rs | 331 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/invert_if.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs | 321 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_let_with_if_let.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 15 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 89 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 74 |
13 files changed, 983 insertions, 93 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/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index e93901cb3..dc38f90e9 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs | |||
@@ -33,9 +33,9 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
33 | // use super::AssistContext; | 33 | // use super::AssistContext; |
34 | // ``` | 34 | // ``` |
35 | // | 35 | // |
36 | // .Merge Behaviour | 36 | // .Merge Behavior |
37 | // | 37 | // |
38 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | 38 | // It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. |
39 | // It has the following configurations: | 39 | // It has the following configurations: |
40 | // | 40 | // |
41 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | 41 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the |
@@ -46,7 +46,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | |||
46 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | 46 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple |
47 | // paths. | 47 | // paths. |
48 | // | 48 | // |
49 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`. | 49 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehavior`. |
50 | // | 50 | // |
51 | // .Import Prefix | 51 | // .Import Prefix |
52 | // | 52 | // |
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/flip_comma.rs b/crates/ide_assists/src/handlers/flip_comma.rs index 18cf64a34..7f5e75d34 100644 --- a/crates/ide_assists/src/handlers/flip_comma.rs +++ b/crates/ide_assists/src/handlers/flip_comma.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use syntax::{algo::non_trivia_sibling, Direction, T}; | 1 | use syntax::{algo::non_trivia_sibling, Direction, SyntaxKind, T}; |
2 | 2 | ||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 3 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
@@ -28,6 +28,12 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
28 | return None; | 28 | return None; |
29 | } | 29 | } |
30 | 30 | ||
31 | // Don't apply a "flip" inside the macro call | ||
32 | // since macro input are just mere tokens | ||
33 | if comma.ancestors().any(|it| it.kind() == SyntaxKind::MACRO_CALL) { | ||
34 | return None; | ||
35 | } | ||
36 | |||
31 | acc.add( | 37 | acc.add( |
32 | AssistId("flip_comma", AssistKind::RefactorRewrite), | 38 | AssistId("flip_comma", AssistKind::RefactorRewrite), |
33 | "Flip comma", | 39 | "Flip comma", |
@@ -43,7 +49,7 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
43 | mod tests { | 49 | mod tests { |
44 | use super::*; | 50 | use super::*; |
45 | 51 | ||
46 | use crate::tests::{check_assist, check_assist_target}; | 52 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
47 | 53 | ||
48 | #[test] | 54 | #[test] |
49 | fn flip_comma_works_for_function_parameters() { | 55 | fn flip_comma_works_for_function_parameters() { |
@@ -81,4 +87,20 @@ mod tests { | |||
81 | ",", | 87 | ",", |
82 | ); | 88 | ); |
83 | } | 89 | } |
90 | |||
91 | #[test] | ||
92 | fn flip_comma_works() { | ||
93 | check_assist( | ||
94 | flip_comma, | ||
95 | r#"fn main() {((1, 2),$0 (3, 4));}"#, | ||
96 | r#"fn main() {((3, 4), (1, 2));}"#, | ||
97 | ) | ||
98 | } | ||
99 | |||
100 | #[test] | ||
101 | fn flip_comma_not_applicable_for_macro_input() { | ||
102 | // "Flip comma" assist shouldn't be applicable inside the macro call | ||
103 | // See https://github.com/rust-analyzer/rust-analyzer/issues/7693 | ||
104 | check_assist_not_applicable(flip_comma, r#"bar!(a,$0 b)"#); | ||
105 | } | ||
84 | } | 106 | } |
diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_is_method.rs index aeb887e71..7e181a480 100644 --- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_is_method.rs | |||
@@ -1,14 +1,13 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::to_lower_snake_case; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::VisibilityOwner; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | 3 | use syntax::ast::{self, AstNode, NameOwner}; |
4 | use test_utils::mark; | ||
5 | 4 | ||
6 | use crate::{ | 5 | use crate::{ |
7 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 6 | utils::{add_method_to_adt, find_struct_impl}, |
8 | AssistContext, AssistId, AssistKind, Assists, | 7 | AssistContext, AssistId, AssistKind, Assists, |
9 | }; | 8 | }; |
10 | 9 | ||
11 | // Assist: generate_enum_match_method | 10 | // Assist: generate_enum_is_method |
12 | // | 11 | // |
13 | // Generate an `is_` method for an enum variant. | 12 | // Generate an `is_` method for an enum variant. |
14 | // | 13 | // |
@@ -34,79 +33,52 @@ use crate::{ | |||
34 | // } | 33 | // } |
35 | // } | 34 | // } |
36 | // ``` | 35 | // ``` |
37 | pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 36 | pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | 37 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; |
39 | let variant_name = variant.name()?; | 38 | let variant_name = variant.name()?; |
40 | let parent_enum = variant.parent_enum(); | 39 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); |
41 | if !matches!(variant.kind(), ast::StructKind::Unit) { | 40 | let pattern_suffix = match variant.kind() { |
42 | mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 41 | ast::StructKind::Record(_) => " { .. }", |
43 | return None; | 42 | ast::StructKind::Tuple(_) => "(..)", |
44 | } | 43 | ast::StructKind::Unit => "", |
44 | }; | ||
45 | 45 | ||
46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | 46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); |
47 | let fn_name = to_lower_snake_case(&variant_name.to_string()); | 47 | let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); |
48 | 48 | ||
49 | // Return early if we've found an existing new fn | 49 | // Return early if we've found an existing new fn |
50 | let impl_def = find_struct_impl( | 50 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; |
51 | &ctx, | ||
52 | &ast::Adt::Enum(parent_enum.clone()), | ||
53 | format!("is_{}", fn_name).as_str(), | ||
54 | )?; | ||
55 | 51 | ||
56 | let target = variant.syntax().text_range(); | 52 | let target = variant.syntax().text_range(); |
57 | acc.add( | 53 | acc.add( |
58 | AssistId("generate_enum_match_method", AssistKind::Generate), | 54 | AssistId("generate_enum_is_method", AssistKind::Generate), |
59 | "Generate an `is_` method for an enum variant", | 55 | "Generate an `is_` method for an enum variant", |
60 | target, | 56 | target, |
61 | |builder| { | 57 | |builder| { |
62 | let mut buf = String::with_capacity(512); | ||
63 | |||
64 | if impl_def.is_some() { | ||
65 | buf.push('\n'); | ||
66 | } | ||
67 | |||
68 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | 58 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); |
69 | format_to!( | 59 | let method = format!( |
70 | buf, | ||
71 | " /// Returns `true` if the {} is [`{}`]. | 60 | " /// Returns `true` if the {} is [`{}`]. |
72 | {}fn is_{}(&self) -> bool {{ | 61 | {}fn {}(&self) -> bool {{ |
73 | matches!(self, Self::{}) | 62 | matches!(self, Self::{}{}) |
74 | }}", | 63 | }}", |
75 | enum_lowercase_name, | 64 | enum_lowercase_name, variant_name, vis, fn_name, variant_name, pattern_suffix, |
76 | variant_name, | ||
77 | vis, | ||
78 | fn_name, | ||
79 | variant_name | ||
80 | ); | 65 | ); |
81 | 66 | ||
82 | let start_offset = impl_def | 67 | add_method_to_adt(builder, &parent_enum, impl_def, &method); |
83 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
84 | .unwrap_or_else(|| { | ||
85 | buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf); | ||
86 | parent_enum.syntax().text_range().end() | ||
87 | }); | ||
88 | |||
89 | builder.insert(start_offset, buf); | ||
90 | }, | 68 | }, |
91 | ) | 69 | ) |
92 | } | 70 | } |
93 | 71 | ||
94 | #[cfg(test)] | 72 | #[cfg(test)] |
95 | mod tests { | 73 | mod tests { |
96 | use test_utils::mark; | ||
97 | |||
98 | use crate::tests::{check_assist, check_assist_not_applicable}; | 74 | use crate::tests::{check_assist, check_assist_not_applicable}; |
99 | 75 | ||
100 | use super::*; | 76 | use super::*; |
101 | 77 | ||
102 | fn check_not_applicable(ra_fixture: &str) { | ||
103 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) | ||
104 | } | ||
105 | |||
106 | #[test] | 78 | #[test] |
107 | fn test_generate_enum_match_from_variant() { | 79 | fn test_generate_enum_is_from_variant() { |
108 | check_assist( | 80 | check_assist( |
109 | generate_enum_match_method, | 81 | generate_enum_is_method, |
110 | r#" | 82 | r#" |
111 | enum Variant { | 83 | enum Variant { |
112 | Undefined, | 84 | Undefined, |
@@ -129,8 +101,9 @@ impl Variant { | |||
129 | } | 101 | } |
130 | 102 | ||
131 | #[test] | 103 | #[test] |
132 | fn test_generate_enum_match_already_implemented() { | 104 | fn test_generate_enum_is_already_implemented() { |
133 | check_not_applicable( | 105 | check_assist_not_applicable( |
106 | generate_enum_is_method, | ||
134 | r#" | 107 | r#" |
135 | enum Variant { | 108 | enum Variant { |
136 | Undefined, | 109 | Undefined, |
@@ -147,22 +120,59 @@ impl Variant { | |||
147 | } | 120 | } |
148 | 121 | ||
149 | #[test] | 122 | #[test] |
150 | fn test_add_from_impl_no_element() { | 123 | fn test_generate_enum_is_from_tuple_variant() { |
151 | mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 124 | check_assist( |
152 | check_not_applicable( | 125 | generate_enum_is_method, |
153 | r#" | 126 | r#" |
154 | enum Variant { | 127 | enum Variant { |
155 | Undefined, | 128 | Undefined, |
156 | Minor(u32)$0, | 129 | Minor(u32)$0, |
157 | Major, | 130 | Major, |
158 | }"#, | 131 | }"#, |
132 | r#"enum Variant { | ||
133 | Undefined, | ||
134 | Minor(u32), | ||
135 | Major, | ||
136 | } | ||
137 | |||
138 | impl Variant { | ||
139 | /// Returns `true` if the variant is [`Minor`]. | ||
140 | fn is_minor(&self) -> bool { | ||
141 | matches!(self, Self::Minor(..)) | ||
142 | } | ||
143 | }"#, | ||
144 | ); | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn test_generate_enum_is_from_record_variant() { | ||
149 | check_assist( | ||
150 | generate_enum_is_method, | ||
151 | r#" | ||
152 | enum Variant { | ||
153 | Undefined, | ||
154 | Minor { foo: i32 }$0, | ||
155 | Major, | ||
156 | }"#, | ||
157 | r#"enum Variant { | ||
158 | Undefined, | ||
159 | Minor { foo: i32 }, | ||
160 | Major, | ||
161 | } | ||
162 | |||
163 | impl Variant { | ||
164 | /// Returns `true` if the variant is [`Minor`]. | ||
165 | fn is_minor(&self) -> bool { | ||
166 | matches!(self, Self::Minor { .. }) | ||
167 | } | ||
168 | }"#, | ||
159 | ); | 169 | ); |
160 | } | 170 | } |
161 | 171 | ||
162 | #[test] | 172 | #[test] |
163 | fn test_generate_enum_match_from_variant_with_one_variant() { | 173 | fn test_generate_enum_is_from_variant_with_one_variant() { |
164 | check_assist( | 174 | check_assist( |
165 | generate_enum_match_method, | 175 | generate_enum_is_method, |
166 | r#"enum Variant { Undefi$0ned }"#, | 176 | r#"enum Variant { Undefi$0ned }"#, |
167 | r#" | 177 | r#" |
168 | enum Variant { Undefined } | 178 | enum Variant { Undefined } |
@@ -177,9 +187,9 @@ impl Variant { | |||
177 | } | 187 | } |
178 | 188 | ||
179 | #[test] | 189 | #[test] |
180 | fn test_generate_enum_match_from_variant_with_visibility_marker() { | 190 | fn test_generate_enum_is_from_variant_with_visibility_marker() { |
181 | check_assist( | 191 | check_assist( |
182 | generate_enum_match_method, | 192 | generate_enum_is_method, |
183 | r#" | 193 | r#" |
184 | pub(crate) enum Variant { | 194 | pub(crate) enum Variant { |
185 | Undefined, | 195 | Undefined, |
@@ -202,9 +212,9 @@ impl Variant { | |||
202 | } | 212 | } |
203 | 213 | ||
204 | #[test] | 214 | #[test] |
205 | fn test_multiple_generate_enum_match_from_variant() { | 215 | fn test_multiple_generate_enum_is_from_variant() { |
206 | check_assist( | 216 | check_assist( |
207 | generate_enum_match_method, | 217 | generate_enum_is_method, |
208 | r#" | 218 | r#" |
209 | enum Variant { | 219 | enum Variant { |
210 | Undefined, | 220 | Undefined, |
diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs new file mode 100644 index 000000000..871bcab50 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs | |||
@@ -0,0 +1,331 @@ | |||
1 | use itertools::Itertools; | ||
2 | use stdx::to_lower_snake_case; | ||
3 | use syntax::ast::VisibilityOwner; | ||
4 | use syntax::ast::{self, AstNode, NameOwner}; | ||
5 | |||
6 | use crate::{ | ||
7 | utils::{add_method_to_adt, find_struct_impl}, | ||
8 | AssistContext, AssistId, AssistKind, Assists, | ||
9 | }; | ||
10 | |||
11 | // Assist: generate_enum_try_into_method | ||
12 | // | ||
13 | // Generate an `try_into_` method for an enum variant. | ||
14 | // | ||
15 | // ``` | ||
16 | // enum Value { | ||
17 | // Number(i32), | ||
18 | // Text(String)$0, | ||
19 | // } | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // enum Value { | ||
24 | // Number(i32), | ||
25 | // Text(String), | ||
26 | // } | ||
27 | // | ||
28 | // impl Value { | ||
29 | // fn try_into_text(self) -> Result<String, Self> { | ||
30 | // if let Self::Text(v) = self { | ||
31 | // Ok(v) | ||
32 | // } else { | ||
33 | // Err(self) | ||
34 | // } | ||
35 | // } | ||
36 | // } | ||
37 | // ``` | ||
38 | pub(crate) fn generate_enum_try_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
39 | generate_enum_projection_method( | ||
40 | acc, | ||
41 | ctx, | ||
42 | "generate_enum_try_into_method", | ||
43 | "Generate an `try_into_` method for an enum variant", | ||
44 | ProjectionProps { | ||
45 | fn_name_prefix: "try_into", | ||
46 | self_param: "self", | ||
47 | return_prefix: "Result<", | ||
48 | return_suffix: ", Self>", | ||
49 | happy_case: "Ok", | ||
50 | sad_case: "Err(self)", | ||
51 | }, | ||
52 | ) | ||
53 | } | ||
54 | |||
55 | // Assist: generate_enum_as_method | ||
56 | // | ||
57 | // Generate an `as_` method for an enum variant. | ||
58 | // | ||
59 | // ``` | ||
60 | // enum Value { | ||
61 | // Number(i32), | ||
62 | // Text(String)$0, | ||
63 | // } | ||
64 | // ``` | ||
65 | // -> | ||
66 | // ``` | ||
67 | // enum Value { | ||
68 | // Number(i32), | ||
69 | // Text(String), | ||
70 | // } | ||
71 | // | ||
72 | // impl Value { | ||
73 | // fn as_text(&self) -> Option<&String> { | ||
74 | // if let Self::Text(v) = self { | ||
75 | // Some(v) | ||
76 | // } else { | ||
77 | // None | ||
78 | // } | ||
79 | // } | ||
80 | // } | ||
81 | // ``` | ||
82 | pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
83 | generate_enum_projection_method( | ||
84 | acc, | ||
85 | ctx, | ||
86 | "generate_enum_as_method", | ||
87 | "Generate an `as_` method for an enum variant", | ||
88 | ProjectionProps { | ||
89 | fn_name_prefix: "as", | ||
90 | self_param: "&self", | ||
91 | return_prefix: "Option<&", | ||
92 | return_suffix: ">", | ||
93 | happy_case: "Some", | ||
94 | sad_case: "None", | ||
95 | }, | ||
96 | ) | ||
97 | } | ||
98 | |||
99 | struct ProjectionProps { | ||
100 | fn_name_prefix: &'static str, | ||
101 | self_param: &'static str, | ||
102 | return_prefix: &'static str, | ||
103 | return_suffix: &'static str, | ||
104 | happy_case: &'static str, | ||
105 | sad_case: &'static str, | ||
106 | } | ||
107 | |||
108 | fn generate_enum_projection_method( | ||
109 | acc: &mut Assists, | ||
110 | ctx: &AssistContext, | ||
111 | assist_id: &'static str, | ||
112 | assist_description: &str, | ||
113 | props: ProjectionProps, | ||
114 | ) -> Option<()> { | ||
115 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | ||
116 | let variant_name = variant.name()?; | ||
117 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); | ||
118 | |||
119 | let (pattern_suffix, field_type, bound_name) = match variant.kind() { | ||
120 | ast::StructKind::Record(record) => { | ||
121 | let (field,) = record.fields().collect_tuple()?; | ||
122 | let name = field.name()?.to_string(); | ||
123 | let ty = field.ty()?; | ||
124 | let pattern_suffix = format!(" {{ {} }}", name); | ||
125 | (pattern_suffix, ty, name) | ||
126 | } | ||
127 | ast::StructKind::Tuple(tuple) => { | ||
128 | let (field,) = tuple.fields().collect_tuple()?; | ||
129 | let ty = field.ty()?; | ||
130 | ("(v)".to_owned(), ty, "v".to_owned()) | ||
131 | } | ||
132 | ast::StructKind::Unit => return None, | ||
133 | }; | ||
134 | |||
135 | let fn_name = format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(variant_name.text())); | ||
136 | |||
137 | // Return early if we've found an existing new fn | ||
138 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; | ||
139 | |||
140 | let target = variant.syntax().text_range(); | ||
141 | acc.add(AssistId(assist_id, AssistKind::Generate), assist_description, target, |builder| { | ||
142 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
143 | let method = format!( | ||
144 | " {0}fn {1}({2}) -> {3}{4}{5} {{ | ||
145 | if let Self::{6}{7} = self {{ | ||
146 | {8}({9}) | ||
147 | }} else {{ | ||
148 | {10} | ||
149 | }} | ||
150 | }}", | ||
151 | vis, | ||
152 | fn_name, | ||
153 | props.self_param, | ||
154 | props.return_prefix, | ||
155 | field_type.syntax(), | ||
156 | props.return_suffix, | ||
157 | variant_name, | ||
158 | pattern_suffix, | ||
159 | props.happy_case, | ||
160 | bound_name, | ||
161 | props.sad_case, | ||
162 | ); | ||
163 | |||
164 | add_method_to_adt(builder, &parent_enum, impl_def, &method); | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | #[cfg(test)] | ||
169 | mod tests { | ||
170 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
171 | |||
172 | use super::*; | ||
173 | |||
174 | #[test] | ||
175 | fn test_generate_enum_try_into_tuple_variant() { | ||
176 | check_assist( | ||
177 | generate_enum_try_into_method, | ||
178 | r#" | ||
179 | enum Value { | ||
180 | Number(i32), | ||
181 | Text(String)$0, | ||
182 | }"#, | ||
183 | r#"enum Value { | ||
184 | Number(i32), | ||
185 | Text(String), | ||
186 | } | ||
187 | |||
188 | impl Value { | ||
189 | fn try_into_text(self) -> Result<String, Self> { | ||
190 | if let Self::Text(v) = self { | ||
191 | Ok(v) | ||
192 | } else { | ||
193 | Err(self) | ||
194 | } | ||
195 | } | ||
196 | }"#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_generate_enum_try_into_already_implemented() { | ||
202 | check_assist_not_applicable( | ||
203 | generate_enum_try_into_method, | ||
204 | r#"enum Value { | ||
205 | Number(i32), | ||
206 | Text(String)$0, | ||
207 | } | ||
208 | |||
209 | impl Value { | ||
210 | fn try_into_text(self) -> Result<String, Self> { | ||
211 | if let Self::Text(v) = self { | ||
212 | Ok(v) | ||
213 | } else { | ||
214 | Err(self) | ||
215 | } | ||
216 | } | ||
217 | }"#, | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_generate_enum_try_into_unit_variant() { | ||
223 | check_assist_not_applicable( | ||
224 | generate_enum_try_into_method, | ||
225 | r#"enum Value { | ||
226 | Number(i32), | ||
227 | Text(String), | ||
228 | Unit$0, | ||
229 | }"#, | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn test_generate_enum_try_into_record_with_multiple_fields() { | ||
235 | check_assist_not_applicable( | ||
236 | generate_enum_try_into_method, | ||
237 | r#"enum Value { | ||
238 | Number(i32), | ||
239 | Text(String), | ||
240 | Both { first: i32, second: String }$0, | ||
241 | }"#, | ||
242 | ); | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn test_generate_enum_try_into_tuple_with_multiple_fields() { | ||
247 | check_assist_not_applicable( | ||
248 | generate_enum_try_into_method, | ||
249 | r#"enum Value { | ||
250 | Number(i32), | ||
251 | Text(String, String)$0, | ||
252 | }"#, | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_generate_enum_try_into_record_variant() { | ||
258 | check_assist( | ||
259 | generate_enum_try_into_method, | ||
260 | r#"enum Value { | ||
261 | Number(i32), | ||
262 | Text { text: String }$0, | ||
263 | }"#, | ||
264 | r#"enum Value { | ||
265 | Number(i32), | ||
266 | Text { text: String }, | ||
267 | } | ||
268 | |||
269 | impl Value { | ||
270 | fn try_into_text(self) -> Result<String, Self> { | ||
271 | if let Self::Text { text } = self { | ||
272 | Ok(text) | ||
273 | } else { | ||
274 | Err(self) | ||
275 | } | ||
276 | } | ||
277 | }"#, | ||
278 | ); | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn test_generate_enum_as_tuple_variant() { | ||
283 | check_assist( | ||
284 | generate_enum_as_method, | ||
285 | r#" | ||
286 | enum Value { | ||
287 | Number(i32), | ||
288 | Text(String)$0, | ||
289 | }"#, | ||
290 | r#"enum Value { | ||
291 | Number(i32), | ||
292 | Text(String), | ||
293 | } | ||
294 | |||
295 | impl Value { | ||
296 | fn as_text(&self) -> Option<&String> { | ||
297 | if let Self::Text(v) = self { | ||
298 | Some(v) | ||
299 | } else { | ||
300 | None | ||
301 | } | ||
302 | } | ||
303 | }"#, | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | #[test] | ||
308 | fn test_generate_enum_as_record_variant() { | ||
309 | check_assist( | ||
310 | generate_enum_as_method, | ||
311 | r#"enum Value { | ||
312 | Number(i32), | ||
313 | Text { text: String }$0, | ||
314 | }"#, | ||
315 | r#"enum Value { | ||
316 | Number(i32), | ||
317 | Text { text: String }, | ||
318 | } | ||
319 | |||
320 | impl Value { | ||
321 | fn as_text(&self) -> Option<&String> { | ||
322 | if let Self::Text { text } = self { | ||
323 | Some(text) | ||
324 | } else { | ||
325 | None | ||
326 | } | ||
327 | } | ||
328 | }"#, | ||
329 | ); | ||
330 | } | ||
331 | } | ||
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/handlers/replace_for_loop_with_for_each.rs b/crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs new file mode 100644 index 000000000..27da28bc0 --- /dev/null +++ b/crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs | |||
@@ -0,0 +1,321 @@ | |||
1 | use ast::LoopBodyOwner; | ||
2 | use hir::known; | ||
3 | use ide_db::helpers::FamousDefs; | ||
4 | use stdx::format_to; | ||
5 | use syntax::{ast, AstNode}; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
9 | |||
10 | // Assist: replace_for_loop_with_for_each | ||
11 | // | ||
12 | // Converts a for loop into a for_each loop on the Iterator. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn main() { | ||
16 | // let x = vec![1, 2, 3]; | ||
17 | // for$0 v in x { | ||
18 | // let y = v * 2; | ||
19 | // } | ||
20 | // } | ||
21 | // ``` | ||
22 | // -> | ||
23 | // ``` | ||
24 | // fn main() { | ||
25 | // let x = vec![1, 2, 3]; | ||
26 | // x.into_iter().for_each(|v| { | ||
27 | // let y = v * 2; | ||
28 | // }); | ||
29 | // } | ||
30 | // ``` | ||
31 | pub(crate) fn replace_for_loop_with_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
32 | let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?; | ||
33 | let iterable = for_loop.iterable()?; | ||
34 | let pat = for_loop.pat()?; | ||
35 | let body = for_loop.loop_body()?; | ||
36 | if body.syntax().text_range().start() < ctx.offset() { | ||
37 | mark::hit!(not_available_in_body); | ||
38 | return None; | ||
39 | } | ||
40 | |||
41 | acc.add( | ||
42 | AssistId("replace_for_loop_with_for_each", AssistKind::RefactorRewrite), | ||
43 | "Replace this for loop with `Iterator::for_each`", | ||
44 | for_loop.syntax().text_range(), | ||
45 | |builder| { | ||
46 | let mut buf = String::new(); | ||
47 | |||
48 | if let Some((expr_behind_ref, method)) = | ||
49 | is_ref_and_impls_iter_method(&ctx.sema, &iterable) | ||
50 | { | ||
51 | // We have either "for x in &col" and col implements a method called iter | ||
52 | // or "for x in &mut col" and col implements a method called iter_mut | ||
53 | format_to!(buf, "{}.{}()", expr_behind_ref, method); | ||
54 | } else if impls_core_iter(&ctx.sema, &iterable) { | ||
55 | format_to!(buf, "{}", iterable); | ||
56 | } else { | ||
57 | if let ast::Expr::RefExpr(_) = iterable { | ||
58 | format_to!(buf, "({}).into_iter()", iterable); | ||
59 | } else { | ||
60 | format_to!(buf, "{}.into_iter()", iterable); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | format_to!(buf, ".for_each(|{}| {});", pat, body); | ||
65 | |||
66 | builder.replace(for_loop.syntax().text_range(), buf) | ||
67 | }, | ||
68 | ) | ||
69 | } | ||
70 | |||
71 | /// If iterable is a reference where the expression behind the reference implements a method | ||
72 | /// returning an Iterator called iter or iter_mut (depending on the type of reference) then return | ||
73 | /// the expression behind the reference and the method name | ||
74 | fn is_ref_and_impls_iter_method( | ||
75 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
76 | iterable: &ast::Expr, | ||
77 | ) -> Option<(ast::Expr, hir::Name)> { | ||
78 | let ref_expr = match iterable { | ||
79 | ast::Expr::RefExpr(r) => r, | ||
80 | _ => return None, | ||
81 | }; | ||
82 | let wanted_method = if ref_expr.mut_token().is_some() { known::iter_mut } else { known::iter }; | ||
83 | let expr_behind_ref = ref_expr.expr()?; | ||
84 | let typ = sema.type_of_expr(&expr_behind_ref)?; | ||
85 | let scope = sema.scope(iterable.syntax()); | ||
86 | let krate = scope.module()?.krate(); | ||
87 | let traits_in_scope = scope.traits_in_scope(); | ||
88 | let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?; | ||
89 | let has_wanted_method = typ.iterate_method_candidates( | ||
90 | sema.db, | ||
91 | krate, | ||
92 | &traits_in_scope, | ||
93 | Some(&wanted_method), | ||
94 | |_, func| { | ||
95 | if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { | ||
96 | return Some(()); | ||
97 | } | ||
98 | None | ||
99 | }, | ||
100 | ); | ||
101 | has_wanted_method.and(Some((expr_behind_ref, wanted_method))) | ||
102 | } | ||
103 | |||
104 | /// Whether iterable implements core::Iterator | ||
105 | fn impls_core_iter(sema: &hir::Semantics<ide_db::RootDatabase>, iterable: &ast::Expr) -> bool { | ||
106 | let it_typ = if let Some(i) = sema.type_of_expr(iterable) { | ||
107 | i | ||
108 | } else { | ||
109 | return false; | ||
110 | }; | ||
111 | let module = if let Some(m) = sema.scope(iterable.syntax()).module() { | ||
112 | m | ||
113 | } else { | ||
114 | return false; | ||
115 | }; | ||
116 | let krate = module.krate(); | ||
117 | if let Some(iter_trait) = FamousDefs(sema, Some(krate)).core_iter_Iterator() { | ||
118 | return it_typ.impls_trait(sema.db, iter_trait, &[]); | ||
119 | } | ||
120 | false | ||
121 | } | ||
122 | |||
123 | #[cfg(test)] | ||
124 | mod tests { | ||
125 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
126 | |||
127 | use super::*; | ||
128 | |||
129 | const EMPTY_ITER_FIXTURE: &'static str = r" | ||
130 | //- /lib.rs deps:core crate:empty_iter | ||
131 | pub struct EmptyIter; | ||
132 | impl Iterator for EmptyIter { | ||
133 | type Item = usize; | ||
134 | fn next(&mut self) -> Option<Self::Item> { None } | ||
135 | } | ||
136 | |||
137 | pub struct Empty; | ||
138 | impl Empty { | ||
139 | pub fn iter(&self) -> EmptyIter { EmptyIter } | ||
140 | pub fn iter_mut(&self) -> EmptyIter { EmptyIter } | ||
141 | } | ||
142 | |||
143 | pub struct NoIterMethod; | ||
144 | "; | ||
145 | |||
146 | fn check_assist_with_fixtures(before: &str, after: &str) { | ||
147 | let before = &format!( | ||
148 | "//- /main.rs crate:main deps:core,empty_iter{}{}{}", | ||
149 | before, | ||
150 | FamousDefs::FIXTURE, | ||
151 | EMPTY_ITER_FIXTURE | ||
152 | ); | ||
153 | check_assist(replace_for_loop_with_for_each, before, after); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn test_not_for() { | ||
158 | check_assist_not_applicable( | ||
159 | replace_for_loop_with_for_each, | ||
160 | r" | ||
161 | let mut x = vec![1, 2, 3]; | ||
162 | x.iter_mut().$0for_each(|v| *v *= 2); | ||
163 | ", | ||
164 | ) | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn test_simple_for() { | ||
169 | check_assist( | ||
170 | replace_for_loop_with_for_each, | ||
171 | r" | ||
172 | fn main() { | ||
173 | let x = vec![1, 2, 3]; | ||
174 | for $0v in x { | ||
175 | v *= 2; | ||
176 | } | ||
177 | }", | ||
178 | r" | ||
179 | fn main() { | ||
180 | let x = vec![1, 2, 3]; | ||
181 | x.into_iter().for_each(|v| { | ||
182 | v *= 2; | ||
183 | }); | ||
184 | }", | ||
185 | ) | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn not_available_in_body() { | ||
190 | mark::check!(not_available_in_body); | ||
191 | check_assist_not_applicable( | ||
192 | replace_for_loop_with_for_each, | ||
193 | r" | ||
194 | fn main() { | ||
195 | let x = vec![1, 2, 3]; | ||
196 | for v in x { | ||
197 | $0v *= 2; | ||
198 | } | ||
199 | }", | ||
200 | ) | ||
201 | } | ||
202 | |||
203 | #[test] | ||
204 | fn test_for_borrowed() { | ||
205 | check_assist_with_fixtures( | ||
206 | r" | ||
207 | use empty_iter::*; | ||
208 | fn main() { | ||
209 | let x = Empty; | ||
210 | for $0v in &x { | ||
211 | let a = v * 2; | ||
212 | } | ||
213 | } | ||
214 | ", | ||
215 | r" | ||
216 | use empty_iter::*; | ||
217 | fn main() { | ||
218 | let x = Empty; | ||
219 | x.iter().for_each(|v| { | ||
220 | let a = v * 2; | ||
221 | }); | ||
222 | } | ||
223 | ", | ||
224 | ) | ||
225 | } | ||
226 | |||
227 | #[test] | ||
228 | fn test_for_borrowed_no_iter_method() { | ||
229 | check_assist_with_fixtures( | ||
230 | r" | ||
231 | use empty_iter::*; | ||
232 | fn main() { | ||
233 | let x = NoIterMethod; | ||
234 | for $0v in &x { | ||
235 | let a = v * 2; | ||
236 | } | ||
237 | } | ||
238 | ", | ||
239 | r" | ||
240 | use empty_iter::*; | ||
241 | fn main() { | ||
242 | let x = NoIterMethod; | ||
243 | (&x).into_iter().for_each(|v| { | ||
244 | let a = v * 2; | ||
245 | }); | ||
246 | } | ||
247 | ", | ||
248 | ) | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn test_for_borrowed_mut() { | ||
253 | check_assist_with_fixtures( | ||
254 | r" | ||
255 | use empty_iter::*; | ||
256 | fn main() { | ||
257 | let x = Empty; | ||
258 | for $0v in &mut x { | ||
259 | let a = v * 2; | ||
260 | } | ||
261 | } | ||
262 | ", | ||
263 | r" | ||
264 | use empty_iter::*; | ||
265 | fn main() { | ||
266 | let x = Empty; | ||
267 | x.iter_mut().for_each(|v| { | ||
268 | let a = v * 2; | ||
269 | }); | ||
270 | } | ||
271 | ", | ||
272 | ) | ||
273 | } | ||
274 | |||
275 | #[test] | ||
276 | fn test_for_borrowed_mut_behind_var() { | ||
277 | check_assist( | ||
278 | replace_for_loop_with_for_each, | ||
279 | r" | ||
280 | fn main() { | ||
281 | let x = vec![1, 2, 3]; | ||
282 | let y = &mut x; | ||
283 | for $0v in y { | ||
284 | *v *= 2; | ||
285 | } | ||
286 | }", | ||
287 | r" | ||
288 | fn main() { | ||
289 | let x = vec![1, 2, 3]; | ||
290 | let y = &mut x; | ||
291 | y.into_iter().for_each(|v| { | ||
292 | *v *= 2; | ||
293 | }); | ||
294 | }", | ||
295 | ) | ||
296 | } | ||
297 | |||
298 | #[test] | ||
299 | fn test_already_impls_iterator() { | ||
300 | check_assist_with_fixtures( | ||
301 | r#" | ||
302 | use empty_iter::*; | ||
303 | fn main() { | ||
304 | let x = Empty; | ||
305 | for$0 a in x.iter().take(1) { | ||
306 | println!("{}", a); | ||
307 | } | ||
308 | } | ||
309 | "#, | ||
310 | r#" | ||
311 | use empty_iter::*; | ||
312 | fn main() { | ||
313 | let x = Empty; | ||
314 | x.iter().take(1).for_each(|a| { | ||
315 | println!("{}", a); | ||
316 | }); | ||
317 | } | ||
318 | "#, | ||
319 | ); | ||
320 | } | ||
321 | } | ||
diff --git a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs index 5a27ada6b..be7e724b5 100644 --- a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use std::iter::once; | 1 | use std::iter::once; |
2 | 2 | ||
3 | use ide_db::ty_filter::TryEnum; | ||
3 | use syntax::{ | 4 | use syntax::{ |
4 | ast::{ | 5 | ast::{ |
5 | self, | 6 | self, |
@@ -10,7 +11,6 @@ use syntax::{ | |||
10 | }; | 11 | }; |
11 | 12 | ||
12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 13 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | use ide_db::ty_filter::TryEnum; | ||
14 | 14 | ||
15 | // Assist: replace_let_with_if_let | 15 | // Assist: replace_let_with_if_let |
16 | // | 16 | // |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index b9799b9d5..9c8148462 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -128,11 +128,12 @@ mod handlers { | |||
128 | mod flip_trait_bound; | 128 | mod flip_trait_bound; |
129 | mod generate_default_from_enum_variant; | 129 | mod generate_default_from_enum_variant; |
130 | mod generate_derive; | 130 | mod generate_derive; |
131 | mod generate_enum_match_method; | 131 | mod generate_enum_is_method; |
132 | mod generate_enum_projection_method; | ||
132 | mod generate_from_impl_for_enum; | 133 | mod generate_from_impl_for_enum; |
133 | mod generate_function; | 134 | mod generate_function; |
134 | mod generate_getter; | ||
135 | mod generate_getter_mut; | 135 | mod generate_getter_mut; |
136 | mod generate_getter; | ||
136 | mod generate_impl; | 137 | mod generate_impl; |
137 | mod generate_new; | 138 | mod generate_new; |
138 | mod generate_setter; | 139 | mod generate_setter; |
@@ -155,6 +156,7 @@ mod handlers { | |||
155 | mod reorder_fields; | 156 | mod reorder_fields; |
156 | mod reorder_impl; | 157 | mod reorder_impl; |
157 | mod replace_derive_with_manual_impl; | 158 | mod replace_derive_with_manual_impl; |
159 | mod replace_for_loop_with_for_each; | ||
158 | mod replace_if_let_with_match; | 160 | mod replace_if_let_with_match; |
159 | mod replace_impl_trait_with_generic; | 161 | mod replace_impl_trait_with_generic; |
160 | mod replace_let_with_if_let; | 162 | mod replace_let_with_if_let; |
@@ -180,7 +182,6 @@ mod handlers { | |||
180 | convert_comment_block::convert_comment_block, | 182 | convert_comment_block::convert_comment_block, |
181 | early_return::convert_to_guarded_return, | 183 | early_return::convert_to_guarded_return, |
182 | expand_glob_import::expand_glob_import, | 184 | expand_glob_import::expand_glob_import, |
183 | move_module_to_file::move_module_to_file, | ||
184 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 185 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
185 | fill_match_arms::fill_match_arms, | 186 | fill_match_arms::fill_match_arms, |
186 | fix_visibility::fix_visibility, | 187 | fix_visibility::fix_visibility, |
@@ -189,11 +190,13 @@ mod handlers { | |||
189 | flip_trait_bound::flip_trait_bound, | 190 | flip_trait_bound::flip_trait_bound, |
190 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 191 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
191 | generate_derive::generate_derive, | 192 | generate_derive::generate_derive, |
192 | generate_enum_match_method::generate_enum_match_method, | 193 | generate_enum_is_method::generate_enum_is_method, |
194 | generate_enum_projection_method::generate_enum_as_method, | ||
195 | generate_enum_projection_method::generate_enum_try_into_method, | ||
193 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 196 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
194 | generate_function::generate_function, | 197 | generate_function::generate_function, |
195 | generate_getter::generate_getter, | ||
196 | generate_getter_mut::generate_getter_mut, | 198 | generate_getter_mut::generate_getter_mut, |
199 | generate_getter::generate_getter, | ||
197 | generate_impl::generate_impl, | 200 | generate_impl::generate_impl, |
198 | generate_new::generate_new, | 201 | generate_new::generate_new, |
199 | generate_setter::generate_setter, | 202 | generate_setter::generate_setter, |
@@ -207,6 +210,7 @@ mod handlers { | |||
207 | move_bounds::move_bounds_to_where_clause, | 210 | move_bounds::move_bounds_to_where_clause, |
208 | move_guard::move_arm_cond_to_match_guard, | 211 | move_guard::move_arm_cond_to_match_guard, |
209 | move_guard::move_guard_to_arm_body, | 212 | move_guard::move_guard_to_arm_body, |
213 | move_module_to_file::move_module_to_file, | ||
210 | pull_assignment_up::pull_assignment_up, | 214 | pull_assignment_up::pull_assignment_up, |
211 | qualify_path::qualify_path, | 215 | qualify_path::qualify_path, |
212 | raw_string::add_hash, | 216 | raw_string::add_hash, |
@@ -218,6 +222,7 @@ mod handlers { | |||
218 | reorder_fields::reorder_fields, | 222 | reorder_fields::reorder_fields, |
219 | reorder_impl::reorder_impl, | 223 | reorder_impl::reorder_impl, |
220 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, | 224 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, |
225 | replace_for_loop_with_for_each::replace_for_loop_with_for_each, | ||
221 | replace_if_let_with_match::replace_if_let_with_match, | 226 | replace_if_let_with_match::replace_if_let_with_match, |
222 | replace_if_let_with_match::replace_match_with_if_let, | 227 | replace_if_let_with_match::replace_match_with_if_let, |
223 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, | 228 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 384eb7eee..b7f616760 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -190,8 +190,8 @@ fn assist_order_field_struct() { | |||
190 | let mut assists = assists.iter(); | 190 | let mut assists = assists.iter(); |
191 | 191 | ||
192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
193 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | ||
194 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); | 193 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); |
194 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | ||
195 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); |
196 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); | 196 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); |
197 | } | 197 | } |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 0516deaff..4f007aa48 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 | ) |
@@ -460,9 +460,38 @@ struct Point { | |||
460 | } | 460 | } |
461 | 461 | ||
462 | #[test] | 462 | #[test] |
463 | fn doctest_generate_enum_match_method() { | 463 | fn doctest_generate_enum_as_method() { |
464 | check_doc_test( | 464 | check_doc_test( |
465 | "generate_enum_match_method", | 465 | "generate_enum_as_method", |
466 | r#####" | ||
467 | enum Value { | ||
468 | Number(i32), | ||
469 | Text(String)$0, | ||
470 | } | ||
471 | "#####, | ||
472 | r#####" | ||
473 | enum Value { | ||
474 | Number(i32), | ||
475 | Text(String), | ||
476 | } | ||
477 | |||
478 | impl Value { | ||
479 | fn as_text(&self) -> Option<&String> { | ||
480 | if let Self::Text(v) = self { | ||
481 | Some(v) | ||
482 | } else { | ||
483 | None | ||
484 | } | ||
485 | } | ||
486 | } | ||
487 | "#####, | ||
488 | ) | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn doctest_generate_enum_is_method() { | ||
493 | check_doc_test( | ||
494 | "generate_enum_is_method", | ||
466 | r#####" | 495 | r#####" |
467 | enum Version { | 496 | enum Version { |
468 | Undefined, | 497 | Undefined, |
@@ -488,6 +517,35 @@ impl Version { | |||
488 | } | 517 | } |
489 | 518 | ||
490 | #[test] | 519 | #[test] |
520 | fn doctest_generate_enum_try_into_method() { | ||
521 | check_doc_test( | ||
522 | "generate_enum_try_into_method", | ||
523 | r#####" | ||
524 | enum Value { | ||
525 | Number(i32), | ||
526 | Text(String)$0, | ||
527 | } | ||
528 | "#####, | ||
529 | r#####" | ||
530 | enum Value { | ||
531 | Number(i32), | ||
532 | Text(String), | ||
533 | } | ||
534 | |||
535 | impl Value { | ||
536 | fn try_into_text(self) -> Result<String, Self> { | ||
537 | if let Self::Text(v) = self { | ||
538 | Ok(v) | ||
539 | } else { | ||
540 | Err(self) | ||
541 | } | ||
542 | } | ||
543 | } | ||
544 | "#####, | ||
545 | ) | ||
546 | } | ||
547 | |||
548 | #[test] | ||
491 | fn doctest_generate_from_impl_for_enum() { | 549 | fn doctest_generate_from_impl_for_enum() { |
492 | check_doc_test( | 550 | check_doc_test( |
493 | "generate_from_impl_for_enum", | 551 | "generate_from_impl_for_enum", |
@@ -1099,6 +1157,29 @@ impl Debug for S { | |||
1099 | } | 1157 | } |
1100 | 1158 | ||
1101 | #[test] | 1159 | #[test] |
1160 | fn doctest_replace_for_loop_with_for_each() { | ||
1161 | check_doc_test( | ||
1162 | "replace_for_loop_with_for_each", | ||
1163 | r#####" | ||
1164 | fn main() { | ||
1165 | let x = vec![1, 2, 3]; | ||
1166 | for$0 v in x { | ||
1167 | let y = v * 2; | ||
1168 | } | ||
1169 | } | ||
1170 | "#####, | ||
1171 | r#####" | ||
1172 | fn main() { | ||
1173 | let x = vec![1, 2, 3]; | ||
1174 | x.into_iter().for_each(|v| { | ||
1175 | let y = v * 2; | ||
1176 | }); | ||
1177 | } | ||
1178 | "#####, | ||
1179 | ) | ||
1180 | } | ||
1181 | |||
1182 | #[test] | ||
1102 | fn doctest_replace_if_let_with_match() { | 1183 | fn doctest_replace_if_let_with_match() { |
1103 | check_doc_test( | 1184 | check_doc_test( |
1104 | "replace_if_let_with_match", | 1185 | "replace_if_let_with_match", |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 0074da741..880ab6fe3 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::{ |
@@ -18,7 +21,7 @@ use syntax::{ | |||
18 | }; | 21 | }; |
19 | 22 | ||
20 | use crate::{ | 23 | use crate::{ |
21 | assist_context::AssistContext, | 24 | assist_context::{AssistBuilder, AssistContext}, |
22 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
23 | }; | 26 | }; |
24 | 27 | ||
@@ -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 | } |
@@ -432,3 +464,25 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str | |||
432 | 464 | ||
433 | buf | 465 | buf |
434 | } | 466 | } |
467 | |||
468 | pub(crate) fn add_method_to_adt( | ||
469 | builder: &mut AssistBuilder, | ||
470 | adt: &ast::Adt, | ||
471 | impl_def: Option<ast::Impl>, | ||
472 | method: &str, | ||
473 | ) { | ||
474 | let mut buf = String::with_capacity(method.len() + 2); | ||
475 | if impl_def.is_some() { | ||
476 | buf.push('\n'); | ||
477 | } | ||
478 | buf.push_str(method); | ||
479 | |||
480 | let start_offset = impl_def | ||
481 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
482 | .unwrap_or_else(|| { | ||
483 | buf = generate_impl_text(&adt, &buf); | ||
484 | adt.syntax().text_range().end() | ||
485 | }); | ||
486 | |||
487 | builder.insert(start_offset, buf); | ||
488 | } | ||