aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r--crates/ide_assists/src/handlers/add_turbo_fish.rs33
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs87
-rw-r--r--crates/ide_assists/src/handlers/convert_iter_for_each_to_for.rs248
-rw-r--r--crates/ide_assists/src/handlers/generate_is_empty_from_len.rs255
4 files changed, 609 insertions, 14 deletions
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#"
281fn make<T>() -> T {}
282fn main() {
283 let x = make$0()
284}
285"#,
286 r#"
287fn make<T>() -> T {}
288fn 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 @@
1use std::collections::VecDeque;
2
1use syntax::ast::{self, AstNode}; 3use syntax::ast::{self, AstNode};
2 4
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; 5use 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 @@
1use ide_db::helpers::FamousDefs;
2use syntax::{
3 ast::{self, edit::AstNodeEdit, make, ArgListOwner},
4 AstNode,
5};
6
7use 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
38pub(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
75fn 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)]
100mod 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
107pub struct EmptyIter;
108impl Iterator for EmptyIter {
109 type Item = usize;
110 fn next(&mut self) -> Option<Self::Item> { None }
111}
112pub struct Empty;
113impl 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#"
142use empty_iter::*;
143fn main() {
144 let x = Empty;
145 x.iter().$0for_each(|(x, y)| {
146 println!("x: {}, y: {}", x, y);
147 });
148}"#,
149 r#"
150use empty_iter::*;
151fn 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#"
165use empty_iter::*;
166fn main() {
167 let x = Empty;
168 x.iter().$0for_each(|(x, y)| {
169 println!("x: {}, y: {}", x, y);
170 })
171}"#,
172 r#"
173use empty_iter::*;
174fn 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#"
188use empty_iter::*;
189fn main() {
190 let x = Empty.iter();
191 x.$0for_each(|(x, y)| {
192 println!("x: {}, y: {}", x, y);
193 });
194}"#,
195 r#"
196use empty_iter::*;
197fn 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#"
211use empty_iter::*;
212fn main() {
213 let x = Empty;
214 x.iter().$0for_each(|(x, y)| println!("x: {}, y: {}", x, y));
215}"#,
216 r#"
217use empty_iter::*;
218fn 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#"
232fn 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#"
242use empty_iter::*;
243fn 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 @@
1use hir::{known, HasSource, Name};
2use syntax::{
3 ast::{self, NameOwner},
4 AstNode, TextRange,
5};
6
7use 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// ```
39pub(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
77fn 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
92fn 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)]
100mod 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#"
111struct MyStruct { data: Vec<String> }
112
113impl 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#"
128struct MyStruct { data: Vec<String> }
129
130impl 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#"
145struct MyStruct { data: Vec<String> }
146
147impl 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#"
165struct MyStruct { data: Vec<String> }
166
167impl MyStruct {
168 p$0ub fn len(&self) -> usize {
169 self.data.len()
170 }
171}
172"#,
173 r#"
174struct MyStruct { data: Vec<String> }
175
176impl 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#"
194struct MyStruct { data: Vec<String> }
195
196impl 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#"
211struct MyStruct { data: Vec<String> }
212
213impl 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#"
239struct MyStruct { data: Vec<String> }
240
241impl MyStruct {
242 p$0ub fn len(&self) -> usize {
243 self.data.len()
244 }
245}
246
247impl MyStruct {
248 pub fn is_empty(&self) -> bool {
249 self.len() == 0
250 }
251}
252"#,
253 );
254 }
255}