diff options
Diffstat (limited to 'crates/ide_assists')
-rw-r--r-- | crates/ide_assists/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/add_turbo_fish.rs | 33 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 87 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs | 248 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | 255 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 59 |
7 files changed, 673 insertions, 15 deletions
diff --git a/crates/ide_assists/Cargo.toml b/crates/ide_assists/Cargo.toml index dd9aa27c6..a83acb191 100644 --- a/crates/ide_assists/Cargo.toml +++ b/crates/ide_assists/Cargo.toml | |||
@@ -10,7 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | cov-mark = "1.1" | 13 | cov-mark = { version = "1.1", features = ["thread-local"] } |
14 | rustc-hash = "1.1.0" | 14 | rustc-hash = "1.1.0" |
15 | itertools = "0.10.0" | 15 | itertools = "0.10.0" |
16 | either = "1.6.1" | 16 | either = "1.6.1" |
diff --git a/crates/ide_assists/src/handlers/add_turbo_fish.rs b/crates/ide_assists/src/handlers/add_turbo_fish.rs index 3b6efbab4..ee879c151 100644 --- a/crates/ide_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide_assists/src/handlers/add_turbo_fish.rs | |||
@@ -56,13 +56,20 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
56 | if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() { | 56 | if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() { |
57 | if let_stmt.colon_token().is_none() { | 57 | if let_stmt.colon_token().is_none() { |
58 | let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end(); | 58 | let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end(); |
59 | let semi_pos = let_stmt.syntax().last_token()?.text_range().end(); | ||
60 | |||
59 | acc.add( | 61 | acc.add( |
60 | AssistId("add_type_ascription", AssistKind::RefactorRewrite), | 62 | AssistId("add_type_ascription", AssistKind::RefactorRewrite), |
61 | "Add `: _` before assignment operator", | 63 | "Add `: _` before assignment operator", |
62 | ident.text_range(), | 64 | ident.text_range(), |
63 | |builder| match ctx.config.snippet_cap { | 65 | |builder| { |
64 | Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"), | 66 | if let_stmt.semicolon_token().is_none() { |
65 | None => builder.insert(type_pos, ": _"), | 67 | builder.insert(semi_pos, ";"); |
68 | } | ||
69 | match ctx.config.snippet_cap { | ||
70 | Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"), | ||
71 | None => builder.insert(type_pos, ": _"), | ||
72 | } | ||
66 | }, | 73 | }, |
67 | )? | 74 | )? |
68 | } else { | 75 | } else { |
@@ -265,4 +272,24 @@ fn main() { | |||
265 | "#, | 272 | "#, |
266 | ); | 273 | ); |
267 | } | 274 | } |
275 | |||
276 | #[test] | ||
277 | fn add_type_ascription_append_semicolon() { | ||
278 | check_assist_by_label( | ||
279 | add_turbo_fish, | ||
280 | r#" | ||
281 | fn make<T>() -> T {} | ||
282 | fn main() { | ||
283 | let x = make$0() | ||
284 | } | ||
285 | "#, | ||
286 | r#" | ||
287 | fn make<T>() -> T {} | ||
288 | fn main() { | ||
289 | let x: ${0:_} = make(); | ||
290 | } | ||
291 | "#, | ||
292 | "Add `: _` before assignment operator", | ||
293 | ); | ||
294 | } | ||
268 | } | 295 | } |
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs index a1c339603..5c936a510 100644 --- a/crates/ide_assists/src/handlers/apply_demorgan.rs +++ b/crates/ide_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | use std::collections::VecDeque; | ||
2 | |||
1 | use syntax::ast::{self, AstNode}; | 3 | use syntax::ast::{self, AstNode}; |
2 | 4 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; | 5 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; |
@@ -30,19 +32,52 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
30 | return None; | 32 | return None; |
31 | } | 33 | } |
32 | 34 | ||
33 | let lhs = expr.lhs()?; | 35 | let mut expr = expr; |
34 | let lhs_range = lhs.syntax().text_range(); | 36 | |
35 | let not_lhs = invert_boolean_expression(&ctx.sema, lhs); | 37 | // Walk up the tree while we have the same binary operator |
38 | while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) { | ||
39 | if let Some(parent_op) = expr.op_kind() { | ||
40 | if parent_op == op { | ||
41 | expr = parent_expr | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | |||
46 | let mut expr_stack = vec![expr.clone()]; | ||
47 | let mut terms = Vec::new(); | ||
48 | let mut op_ranges = Vec::new(); | ||
49 | |||
50 | // Find all the children with the same binary operator | ||
51 | while let Some(expr) = expr_stack.pop() { | ||
52 | let mut traverse_bin_expr_arm = |expr| { | ||
53 | if let ast::Expr::BinExpr(bin_expr) = expr { | ||
54 | if let Some(expr_op) = bin_expr.op_kind() { | ||
55 | if expr_op == op { | ||
56 | expr_stack.push(bin_expr); | ||
57 | } else { | ||
58 | terms.push(ast::Expr::BinExpr(bin_expr)); | ||
59 | } | ||
60 | } else { | ||
61 | terms.push(ast::Expr::BinExpr(bin_expr)); | ||
62 | } | ||
63 | } else { | ||
64 | terms.push(expr); | ||
65 | } | ||
66 | }; | ||
36 | 67 | ||
37 | let rhs = expr.rhs()?; | 68 | op_ranges.extend(expr.op_token().map(|t| t.text_range())); |
38 | let rhs_range = rhs.syntax().text_range(); | 69 | traverse_bin_expr_arm(expr.lhs()?); |
39 | let not_rhs = invert_boolean_expression(&ctx.sema, rhs); | 70 | traverse_bin_expr_arm(expr.rhs()?); |
71 | } | ||
40 | 72 | ||
41 | acc.add( | 73 | acc.add( |
42 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), | 74 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), |
43 | "Apply De Morgan's law", | 75 | "Apply De Morgan's law", |
44 | op_range, | 76 | op_range, |
45 | |edit| { | 77 | |edit| { |
78 | terms.sort_by_key(|t| t.syntax().text_range().start()); | ||
79 | let mut terms = VecDeque::from(terms); | ||
80 | |||
46 | let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent)); | 81 | let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent)); |
47 | 82 | ||
48 | let neg_expr = paren_expr | 83 | let neg_expr = paren_expr |
@@ -57,11 +92,18 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
57 | } | 92 | } |
58 | }); | 93 | }); |
59 | 94 | ||
60 | edit.replace(op_range, opposite_op); | 95 | for op_range in op_ranges { |
96 | edit.replace(op_range, opposite_op); | ||
97 | } | ||
61 | 98 | ||
62 | if let Some(paren_expr) = paren_expr { | 99 | if let Some(paren_expr) = paren_expr { |
63 | edit.replace(lhs_range, not_lhs.syntax().text()); | 100 | for term in terms { |
64 | edit.replace(rhs_range, not_rhs.syntax().text()); | 101 | let range = term.syntax().text_range(); |
102 | let not_term = invert_boolean_expression(&ctx.sema, term); | ||
103 | |||
104 | edit.replace(range, not_term.syntax().text()); | ||
105 | } | ||
106 | |||
65 | if let Some(neg_expr) = neg_expr { | 107 | if let Some(neg_expr) = neg_expr { |
66 | cov_mark::hit!(demorgan_double_negation); | 108 | cov_mark::hit!(demorgan_double_negation); |
67 | edit.replace(neg_expr.op_token().unwrap().text_range(), ""); | 109 | edit.replace(neg_expr.op_token().unwrap().text_range(), ""); |
@@ -70,8 +112,25 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
70 | edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); | 112 | edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); |
71 | } | 113 | } |
72 | } else { | 114 | } else { |
73 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 115 | if let Some(lhs) = terms.pop_front() { |
74 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 116 | let lhs_range = lhs.syntax().text_range(); |
117 | let not_lhs = invert_boolean_expression(&ctx.sema, lhs); | ||
118 | |||
119 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | ||
120 | } | ||
121 | |||
122 | if let Some(rhs) = terms.pop_back() { | ||
123 | let rhs_range = rhs.syntax().text_range(); | ||
124 | let not_rhs = invert_boolean_expression(&ctx.sema, rhs); | ||
125 | |||
126 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | ||
127 | } | ||
128 | |||
129 | for term in terms { | ||
130 | let term_range = term.syntax().text_range(); | ||
131 | let not_term = invert_boolean_expression(&ctx.sema, term); | ||
132 | edit.replace(term_range, not_term.syntax().text()); | ||
133 | } | ||
75 | } | 134 | } |
76 | }, | 135 | }, |
77 | ) | 136 | ) |
@@ -180,6 +239,12 @@ fn f() { | |||
180 | } | 239 | } |
181 | 240 | ||
182 | #[test] | 241 | #[test] |
242 | fn demorgan_multiple_terms() { | ||
243 | check_assist(apply_demorgan, "fn f() { x ||$0 y || z }", "fn f() { !(!x && !y && !z) }"); | ||
244 | check_assist(apply_demorgan, "fn f() { x || y ||$0 z }", "fn f() { !(!x && !y && !z) }"); | ||
245 | } | ||
246 | |||
247 | #[test] | ||
183 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { | 248 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { |
184 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") | 249 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") |
185 | } | 250 | } |
diff --git a/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs b/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs new file mode 100644 index 000000000..4e75a7b14 --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs | |||
@@ -0,0 +1,248 @@ | |||
1 | use ide_db::helpers::FamousDefs; | ||
2 | use syntax::{ | ||
3 | ast::{self, edit::AstNodeEdit, make, ArgListOwner}, | ||
4 | AstNode, | ||
5 | }; | ||
6 | |||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
8 | |||
9 | // Assist: convert_iter_for_each_to_for | ||
10 | // | ||
11 | // Converts an Iterator::for_each function into a for loop. | ||
12 | // | ||
13 | // ``` | ||
14 | // # //- /lib.rs crate:core | ||
15 | // # pub mod iter { pub mod traits { pub mod iterator { pub trait Iterator {} } } } | ||
16 | // # pub struct SomeIter; | ||
17 | // # impl self::iter::traits::iterator::Iterator for SomeIter {} | ||
18 | // # //- /lib.rs crate:main deps:core | ||
19 | // # use core::SomeIter; | ||
20 | // fn main() { | ||
21 | // let iter = SomeIter; | ||
22 | // iter.for_each$0(|(x, y)| { | ||
23 | // println!("x: {}, y: {}", x, y); | ||
24 | // }); | ||
25 | // } | ||
26 | // ``` | ||
27 | // -> | ||
28 | // ``` | ||
29 | // # use core::SomeIter; | ||
30 | // fn main() { | ||
31 | // let iter = SomeIter; | ||
32 | // for (x, y) in iter { | ||
33 | // println!("x: {}, y: {}", x, y); | ||
34 | // } | ||
35 | // } | ||
36 | // ``` | ||
37 | |||
38 | pub(crate) fn convert_iter_for_each_to_for(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
39 | let method = ctx.find_node_at_offset::<ast::MethodCallExpr>()?; | ||
40 | |||
41 | let closure = match method.arg_list()?.args().next()? { | ||
42 | ast::Expr::ClosureExpr(expr) => expr, | ||
43 | _ => return None, | ||
44 | }; | ||
45 | |||
46 | let (method, receiver) = validate_method_call_expr(ctx, method)?; | ||
47 | |||
48 | let param_list = closure.param_list()?; | ||
49 | let param = param_list.params().next()?.pat()?; | ||
50 | let body = closure.body()?; | ||
51 | |||
52 | let stmt = method.syntax().parent().and_then(ast::ExprStmt::cast); | ||
53 | let syntax = stmt.as_ref().map_or(method.syntax(), |stmt| stmt.syntax()); | ||
54 | |||
55 | acc.add( | ||
56 | AssistId("convert_iter_for_each_to_for", AssistKind::RefactorRewrite), | ||
57 | "Replace this `Iterator::for_each` with a for loop", | ||
58 | syntax.text_range(), | ||
59 | |builder| { | ||
60 | let indent = stmt.as_ref().map_or(method.indent_level(), |stmt| stmt.indent_level()); | ||
61 | |||
62 | let block = match body { | ||
63 | ast::Expr::BlockExpr(block) => block, | ||
64 | _ => make::block_expr(Vec::new(), Some(body)), | ||
65 | } | ||
66 | .reset_indent() | ||
67 | .indent(indent); | ||
68 | |||
69 | let expr_for_loop = make::expr_for_loop(param, receiver, block); | ||
70 | builder.replace(syntax.text_range(), expr_for_loop.syntax().text()) | ||
71 | }, | ||
72 | ) | ||
73 | } | ||
74 | |||
75 | fn validate_method_call_expr( | ||
76 | ctx: &AssistContext, | ||
77 | expr: ast::MethodCallExpr, | ||
78 | ) -> Option<(ast::Expr, ast::Expr)> { | ||
79 | let name_ref = expr.name_ref()?; | ||
80 | if name_ref.syntax().text_range().intersect(ctx.frange.range).is_none() | ||
81 | || name_ref.text() != "for_each" | ||
82 | { | ||
83 | return None; | ||
84 | } | ||
85 | |||
86 | let sema = &ctx.sema; | ||
87 | |||
88 | let receiver = expr.receiver()?; | ||
89 | let expr = ast::Expr::MethodCallExpr(expr); | ||
90 | |||
91 | let it_type = sema.type_of_expr(&receiver)?; | ||
92 | let module = sema.scope(receiver.syntax()).module()?; | ||
93 | let krate = module.krate(); | ||
94 | |||
95 | let iter_trait = FamousDefs(sema, Some(krate)).core_iter_Iterator()?; | ||
96 | it_type.impls_trait(sema.db, iter_trait, &[]).then(|| (expr, receiver)) | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use crate::tests::{self, check_assist}; | ||
102 | |||
103 | use super::*; | ||
104 | |||
105 | const EMPTY_ITER_FIXTURE: &'static str = r" | ||
106 | //- /lib.rs deps:core crate:empty_iter | ||
107 | pub struct EmptyIter; | ||
108 | impl Iterator for EmptyIter { | ||
109 | type Item = usize; | ||
110 | fn next(&mut self) -> Option<Self::Item> { None } | ||
111 | } | ||
112 | pub struct Empty; | ||
113 | impl Empty { | ||
114 | pub fn iter(&self) -> EmptyIter { EmptyIter } | ||
115 | } | ||
116 | "; | ||
117 | |||
118 | fn check_assist_with_fixtures(before: &str, after: &str) { | ||
119 | let before = &format!( | ||
120 | "//- /main.rs crate:main deps:core,empty_iter{}{}{}", | ||
121 | before, | ||
122 | EMPTY_ITER_FIXTURE, | ||
123 | FamousDefs::FIXTURE, | ||
124 | ); | ||
125 | check_assist(convert_iter_for_each_to_for, before, after); | ||
126 | } | ||
127 | |||
128 | fn check_assist_not_applicable(before: &str) { | ||
129 | let before = &format!( | ||
130 | "//- /main.rs crate:main deps:core,empty_iter{}{}{}", | ||
131 | before, | ||
132 | EMPTY_ITER_FIXTURE, | ||
133 | FamousDefs::FIXTURE, | ||
134 | ); | ||
135 | tests::check_assist_not_applicable(convert_iter_for_each_to_for, before); | ||
136 | } | ||
137 | |||
138 | #[test] | ||
139 | fn test_for_each_in_method_stmt() { | ||
140 | check_assist_with_fixtures( | ||
141 | r#" | ||
142 | use empty_iter::*; | ||
143 | fn main() { | ||
144 | let x = Empty; | ||
145 | x.iter().$0for_each(|(x, y)| { | ||
146 | println!("x: {}, y: {}", x, y); | ||
147 | }); | ||
148 | }"#, | ||
149 | r#" | ||
150 | use empty_iter::*; | ||
151 | fn main() { | ||
152 | let x = Empty; | ||
153 | for (x, y) in x.iter() { | ||
154 | println!("x: {}, y: {}", x, y); | ||
155 | } | ||
156 | } | ||
157 | "#, | ||
158 | ) | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn test_for_each_in_method() { | ||
163 | check_assist_with_fixtures( | ||
164 | r#" | ||
165 | use empty_iter::*; | ||
166 | fn main() { | ||
167 | let x = Empty; | ||
168 | x.iter().$0for_each(|(x, y)| { | ||
169 | println!("x: {}, y: {}", x, y); | ||
170 | }) | ||
171 | }"#, | ||
172 | r#" | ||
173 | use empty_iter::*; | ||
174 | fn main() { | ||
175 | let x = Empty; | ||
176 | for (x, y) in x.iter() { | ||
177 | println!("x: {}, y: {}", x, y); | ||
178 | } | ||
179 | } | ||
180 | "#, | ||
181 | ) | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn test_for_each_in_iter_stmt() { | ||
186 | check_assist_with_fixtures( | ||
187 | r#" | ||
188 | use empty_iter::*; | ||
189 | fn main() { | ||
190 | let x = Empty.iter(); | ||
191 | x.$0for_each(|(x, y)| { | ||
192 | println!("x: {}, y: {}", x, y); | ||
193 | }); | ||
194 | }"#, | ||
195 | r#" | ||
196 | use empty_iter::*; | ||
197 | fn main() { | ||
198 | let x = Empty.iter(); | ||
199 | for (x, y) in x { | ||
200 | println!("x: {}, y: {}", x, y); | ||
201 | } | ||
202 | } | ||
203 | "#, | ||
204 | ) | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn test_for_each_without_braces_stmt() { | ||
209 | check_assist_with_fixtures( | ||
210 | r#" | ||
211 | use empty_iter::*; | ||
212 | fn main() { | ||
213 | let x = Empty; | ||
214 | x.iter().$0for_each(|(x, y)| println!("x: {}, y: {}", x, y)); | ||
215 | }"#, | ||
216 | r#" | ||
217 | use empty_iter::*; | ||
218 | fn main() { | ||
219 | let x = Empty; | ||
220 | for (x, y) in x.iter() { | ||
221 | println!("x: {}, y: {}", x, y) | ||
222 | } | ||
223 | } | ||
224 | "#, | ||
225 | ) | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn test_for_each_not_applicable() { | ||
230 | check_assist_not_applicable( | ||
231 | r#" | ||
232 | fn main() { | ||
233 | ().$0for_each(|x| println!("{}", x)); | ||
234 | }"#, | ||
235 | ) | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn test_for_each_not_applicable_invalid_cursor_pos() { | ||
240 | check_assist_not_applicable( | ||
241 | r#" | ||
242 | use empty_iter::*; | ||
243 | fn main() { | ||
244 | Empty.iter().for_each(|(x, y)| $0println!("x: {}, y: {}", x, y)); | ||
245 | }"#, | ||
246 | ) | ||
247 | } | ||
248 | } | ||
diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs new file mode 100644 index 000000000..aa7072f25 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | |||
@@ -0,0 +1,255 @@ | |||
1 | use hir::{known, HasSource, Name}; | ||
2 | use syntax::{ | ||
3 | ast::{self, NameOwner}, | ||
4 | AstNode, TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::{ | ||
8 | assist_context::{AssistContext, Assists}, | ||
9 | AssistId, AssistKind, | ||
10 | }; | ||
11 | |||
12 | // Assist: generate_is_empty_from_len | ||
13 | // | ||
14 | // Generates is_empty implementation from the len method. | ||
15 | // | ||
16 | // ``` | ||
17 | // struct MyStruct { data: Vec<String> } | ||
18 | // | ||
19 | // impl MyStruct { | ||
20 | // p$0ub fn len(&self) -> usize { | ||
21 | // self.data.len() | ||
22 | // } | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // struct MyStruct { data: Vec<String> } | ||
28 | // | ||
29 | // impl MyStruct { | ||
30 | // pub fn len(&self) -> usize { | ||
31 | // self.data.len() | ||
32 | // } | ||
33 | // | ||
34 | // pub fn is_empty(&self) -> bool { | ||
35 | // self.len() == 0 | ||
36 | // } | ||
37 | // } | ||
38 | // ``` | ||
39 | pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let fn_node = ctx.find_node_at_offset::<ast::Fn>()?; | ||
41 | let fn_name = fn_node.name()?; | ||
42 | |||
43 | if fn_name.text() != "len" { | ||
44 | cov_mark::hit!(len_function_not_present); | ||
45 | return None; | ||
46 | } | ||
47 | |||
48 | if fn_node.param_list()?.params().next().is_some() { | ||
49 | cov_mark::hit!(len_function_with_parameters); | ||
50 | return None; | ||
51 | } | ||
52 | |||
53 | let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; | ||
54 | if get_impl_method(ctx, &impl_, &known::is_empty).is_some() { | ||
55 | cov_mark::hit!(is_empty_already_implemented); | ||
56 | return None; | ||
57 | } | ||
58 | |||
59 | let range = get_text_range_of_len_function(ctx, &impl_)?; | ||
60 | |||
61 | acc.add( | ||
62 | AssistId("generate_is_empty_from_len", AssistKind::Generate), | ||
63 | "Generate a is_empty impl from a len function", | ||
64 | range, | ||
65 | |builder| { | ||
66 | let code = r#" | ||
67 | |||
68 | pub fn is_empty(&self) -> bool { | ||
69 | self.len() == 0 | ||
70 | }"# | ||
71 | .to_string(); | ||
72 | builder.insert(range.end(), code) | ||
73 | }, | ||
74 | ) | ||
75 | } | ||
76 | |||
77 | fn get_impl_method( | ||
78 | ctx: &AssistContext, | ||
79 | impl_: &ast::Impl, | ||
80 | fn_name: &Name, | ||
81 | ) -> Option<hir::Function> { | ||
82 | let db = ctx.sema.db; | ||
83 | let impl_def: hir::Impl = ctx.sema.to_def(impl_)?; | ||
84 | |||
85 | let scope = ctx.sema.scope(impl_.syntax()); | ||
86 | let krate = impl_def.module(db).krate(); | ||
87 | let ty = impl_def.target_ty(db); | ||
88 | let traits_in_scope = scope.traits_in_scope(); | ||
89 | ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) | ||
90 | } | ||
91 | |||
92 | fn get_text_range_of_len_function(ctx: &AssistContext, impl_: &ast::Impl) -> Option<TextRange> { | ||
93 | let db = ctx.sema.db; | ||
94 | let func = get_impl_method(ctx, impl_, &known::len)?; | ||
95 | let node = func.source(db)?; | ||
96 | Some(node.syntax().value.text_range()) | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
102 | |||
103 | use super::*; | ||
104 | |||
105 | #[test] | ||
106 | fn len_function_not_present() { | ||
107 | cov_mark::check!(len_function_not_present); | ||
108 | check_assist_not_applicable( | ||
109 | generate_is_empty_from_len, | ||
110 | r#" | ||
111 | struct MyStruct { data: Vec<String> } | ||
112 | |||
113 | impl MyStruct { | ||
114 | p$0ub fn test(&self) -> usize { | ||
115 | self.data.len() | ||
116 | } | ||
117 | } | ||
118 | "#, | ||
119 | ); | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn len_function_with_parameters() { | ||
124 | cov_mark::check!(len_function_with_parameters); | ||
125 | check_assist_not_applicable( | ||
126 | generate_is_empty_from_len, | ||
127 | r#" | ||
128 | struct MyStruct { data: Vec<String> } | ||
129 | |||
130 | impl MyStruct { | ||
131 | p$0ub fn len(&self, _i: bool) -> usize { | ||
132 | self.data.len() | ||
133 | } | ||
134 | } | ||
135 | "#, | ||
136 | ); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn is_empty_already_implemented() { | ||
141 | cov_mark::check!(is_empty_already_implemented); | ||
142 | check_assist_not_applicable( | ||
143 | generate_is_empty_from_len, | ||
144 | r#" | ||
145 | struct MyStruct { data: Vec<String> } | ||
146 | |||
147 | impl MyStruct { | ||
148 | p$0ub fn len(&self) -> usize { | ||
149 | self.data.len() | ||
150 | } | ||
151 | |||
152 | pub fn is_empty(&self) -> bool { | ||
153 | self.len() == 0 | ||
154 | } | ||
155 | } | ||
156 | "#, | ||
157 | ); | ||
158 | } | ||
159 | |||
160 | #[test] | ||
161 | fn generate_is_empty() { | ||
162 | check_assist( | ||
163 | generate_is_empty_from_len, | ||
164 | r#" | ||
165 | struct MyStruct { data: Vec<String> } | ||
166 | |||
167 | impl MyStruct { | ||
168 | p$0ub fn len(&self) -> usize { | ||
169 | self.data.len() | ||
170 | } | ||
171 | } | ||
172 | "#, | ||
173 | r#" | ||
174 | struct MyStruct { data: Vec<String> } | ||
175 | |||
176 | impl MyStruct { | ||
177 | pub fn len(&self) -> usize { | ||
178 | self.data.len() | ||
179 | } | ||
180 | |||
181 | pub fn is_empty(&self) -> bool { | ||
182 | self.len() == 0 | ||
183 | } | ||
184 | } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn multiple_functions_in_impl() { | ||
191 | check_assist( | ||
192 | generate_is_empty_from_len, | ||
193 | r#" | ||
194 | struct MyStruct { data: Vec<String> } | ||
195 | |||
196 | impl MyStruct { | ||
197 | pub fn new() -> Self { | ||
198 | Self { data: 0 } | ||
199 | } | ||
200 | |||
201 | p$0ub fn len(&self) -> usize { | ||
202 | self.data.len() | ||
203 | } | ||
204 | |||
205 | pub fn work(&self) -> Option<usize> { | ||
206 | |||
207 | } | ||
208 | } | ||
209 | "#, | ||
210 | r#" | ||
211 | struct MyStruct { data: Vec<String> } | ||
212 | |||
213 | impl MyStruct { | ||
214 | pub fn new() -> Self { | ||
215 | Self { data: 0 } | ||
216 | } | ||
217 | |||
218 | pub fn len(&self) -> usize { | ||
219 | self.data.len() | ||
220 | } | ||
221 | |||
222 | pub fn is_empty(&self) -> bool { | ||
223 | self.len() == 0 | ||
224 | } | ||
225 | |||
226 | pub fn work(&self) -> Option<usize> { | ||
227 | |||
228 | } | ||
229 | } | ||
230 | "#, | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | #[test] | ||
235 | fn multiple_impls() { | ||
236 | check_assist_not_applicable( | ||
237 | generate_is_empty_from_len, | ||
238 | r#" | ||
239 | struct MyStruct { data: Vec<String> } | ||
240 | |||
241 | impl MyStruct { | ||
242 | p$0ub fn len(&self) -> usize { | ||
243 | self.data.len() | ||
244 | } | ||
245 | } | ||
246 | |||
247 | impl MyStruct { | ||
248 | pub fn is_empty(&self) -> bool { | ||
249 | self.len() == 0 | ||
250 | } | ||
251 | } | ||
252 | "#, | ||
253 | ); | ||
254 | } | ||
255 | } | ||
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index ea62d5f5d..8c068a6c0 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -116,6 +116,7 @@ mod handlers { | |||
116 | mod change_visibility; | 116 | mod change_visibility; |
117 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
118 | mod convert_comment_block; | 118 | mod convert_comment_block; |
119 | mod convert_iter_for_each_to_for; | ||
119 | mod early_return; | 120 | mod early_return; |
120 | mod expand_glob_import; | 121 | mod expand_glob_import; |
121 | mod extract_function; | 122 | mod extract_function; |
@@ -128,6 +129,7 @@ mod handlers { | |||
128 | mod flip_trait_bound; | 129 | mod flip_trait_bound; |
129 | mod generate_default_from_enum_variant; | 130 | mod generate_default_from_enum_variant; |
130 | mod generate_default_from_new; | 131 | mod generate_default_from_new; |
132 | mod generate_is_empty_from_len; | ||
131 | mod generate_derive; | 133 | mod generate_derive; |
132 | mod generate_enum_is_method; | 134 | mod generate_enum_is_method; |
133 | mod generate_enum_projection_method; | 135 | mod generate_enum_projection_method; |
@@ -181,6 +183,7 @@ mod handlers { | |||
181 | change_visibility::change_visibility, | 183 | change_visibility::change_visibility, |
182 | convert_integer_literal::convert_integer_literal, | 184 | convert_integer_literal::convert_integer_literal, |
183 | convert_comment_block::convert_comment_block, | 185 | convert_comment_block::convert_comment_block, |
186 | convert_iter_for_each_to_for::convert_iter_for_each_to_for, | ||
184 | early_return::convert_to_guarded_return, | 187 | early_return::convert_to_guarded_return, |
185 | expand_glob_import::expand_glob_import, | 188 | expand_glob_import::expand_glob_import, |
186 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 189 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
@@ -191,6 +194,7 @@ mod handlers { | |||
191 | flip_trait_bound::flip_trait_bound, | 194 | flip_trait_bound::flip_trait_bound, |
192 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 195 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
193 | generate_default_from_new::generate_default_from_new, | 196 | generate_default_from_new::generate_default_from_new, |
197 | generate_is_empty_from_len::generate_is_empty_from_len, | ||
194 | generate_derive::generate_derive, | 198 | generate_derive::generate_derive, |
195 | generate_enum_is_method::generate_enum_is_method, | 199 | generate_enum_is_method::generate_enum_is_method, |
196 | generate_enum_projection_method::generate_enum_as_method, | 200 | generate_enum_projection_method::generate_enum_as_method, |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 304b5798f..736027ff0 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -206,6 +206,36 @@ const _: i32 = 0b1010; | |||
206 | } | 206 | } |
207 | 207 | ||
208 | #[test] | 208 | #[test] |
209 | fn doctest_convert_iter_for_each_to_for() { | ||
210 | check_doc_test( | ||
211 | "convert_iter_for_each_to_for", | ||
212 | r#####" | ||
213 | //- /lib.rs crate:core | ||
214 | pub mod iter { pub mod traits { pub mod iterator { pub trait Iterator {} } } } | ||
215 | pub struct SomeIter; | ||
216 | impl self::iter::traits::iterator::Iterator for SomeIter {} | ||
217 | //- /lib.rs crate:main deps:core | ||
218 | use core::SomeIter; | ||
219 | fn main() { | ||
220 | let iter = SomeIter; | ||
221 | iter.for_each$0(|(x, y)| { | ||
222 | println!("x: {}, y: {}", x, y); | ||
223 | }); | ||
224 | } | ||
225 | "#####, | ||
226 | r#####" | ||
227 | use core::SomeIter; | ||
228 | fn main() { | ||
229 | let iter = SomeIter; | ||
230 | for (x, y) in iter { | ||
231 | println!("x: {}, y: {}", x, y); | ||
232 | } | ||
233 | } | ||
234 | "#####, | ||
235 | ) | ||
236 | } | ||
237 | |||
238 | #[test] | ||
209 | fn doctest_convert_to_guarded_return() { | 239 | fn doctest_convert_to_guarded_return() { |
210 | check_doc_test( | 240 | check_doc_test( |
211 | "convert_to_guarded_return", | 241 | "convert_to_guarded_return", |
@@ -692,6 +722,35 @@ impl<T: Clone> Ctx<T> { | |||
692 | } | 722 | } |
693 | 723 | ||
694 | #[test] | 724 | #[test] |
725 | fn doctest_generate_is_empty_from_len() { | ||
726 | check_doc_test( | ||
727 | "generate_is_empty_from_len", | ||
728 | r#####" | ||
729 | struct MyStruct { data: Vec<String> } | ||
730 | |||
731 | impl MyStruct { | ||
732 | p$0ub fn len(&self) -> usize { | ||
733 | self.data.len() | ||
734 | } | ||
735 | } | ||
736 | "#####, | ||
737 | r#####" | ||
738 | struct MyStruct { data: Vec<String> } | ||
739 | |||
740 | impl MyStruct { | ||
741 | pub fn len(&self) -> usize { | ||
742 | self.data.len() | ||
743 | } | ||
744 | |||
745 | pub fn is_empty(&self) -> bool { | ||
746 | self.len() == 0 | ||
747 | } | ||
748 | } | ||
749 | "#####, | ||
750 | ) | ||
751 | } | ||
752 | |||
753 | #[test] | ||
695 | fn doctest_generate_new() { | 754 | fn doctest_generate_new() { |
696 | check_doc_test( | 755 | check_doc_test( |
697 | "generate_new", | 756 | "generate_new", |