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
3 files changed, 354 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}