aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/assists/early_return.rs219
-rw-r--r--crates/ra_syntax/src/ast/make.rs17
2 files changed, 207 insertions, 29 deletions
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index ad6c5695a..570a07a20 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -3,9 +3,10 @@ use std::ops::RangeInclusive;
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4use ra_syntax::{ 4use ra_syntax::{
5 algo::replace_children, 5 algo::replace_children,
6 ast::{self, edit::IndentLevel, make}, 6 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat},
7 AstNode, 7 AstNode,
8 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, 8 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
9 SyntaxNode,
9}; 10};
10 11
11use crate::{ 12use crate::{
@@ -37,7 +38,23 @@ use crate::{
37// ``` 38// ```
38pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
39 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
40 let expr = if_expr.condition()?.expr()?; 41 let cond = if_expr.condition()?;
42 let mut if_let_ident: Option<String> = None;
43
44 // Check if there is an IfLet that we can handle.
45 match cond.pat() {
46 None => {} // No IfLet, supported.
47 Some(TupleStructPat(ref pat)) if pat.args().count() == 1usize => match &pat.path() {
48 Some(p) => match p.qualifier() {
49 None => if_let_ident = Some(p.syntax().text().to_string()),
50 _ => return None,
51 },
52 _ => return None,
53 },
54 _ => return None, // Unsupported IfLet.
55 };
56
57 let expr = cond.expr()?;
41 let then_block = if_expr.then_branch()?.block()?; 58 let then_block = if_expr.then_branch()?.block()?;
42 if if_expr.else_branch().is_some() { 59 if if_expr.else_branch().is_some() {
43 return None; 60 return None;
@@ -63,8 +80,8 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
63 let parent_container = parent_block.syntax().parent()?.parent()?; 80 let parent_container = parent_block.syntax().parent()?.parent()?;
64 81
65 let early_expression = match parent_container.kind() { 82 let early_expression = match parent_container.kind() {
66 WHILE_EXPR | LOOP_EXPR => Some("continue;"), 83 WHILE_EXPR | LOOP_EXPR => Some("continue"),
67 FN_DEF => Some("return;"), 84 FN_DEF => Some("return"),
68 _ => None, 85 _ => None,
69 }?; 86 }?;
70 87
@@ -77,34 +94,58 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
77 94
78 ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { 95 ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| {
79 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 96 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
80 let new_if_expr = 97 let new_block = match if_let_ident {
81 if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); 98 None => {
82 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); 99 // If.
83 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); 100 let early_expression = &(early_expression.to_owned() + ";");
84 let end_of_then = 101 let new_expr =
85 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { 102 if_indent_level.increase_indent(make::if_expression(&expr, early_expression));
86 end_of_then.prev_sibling_or_token().unwrap() 103 replace(new_expr, &then_block, &parent_block, &if_expr)
87 } else { 104 }
88 end_of_then 105 Some(if_let_ident) => {
89 }; 106 // If-let.
90 let mut new_if_and_then_statements = new_if_expr.syntax().children_with_tokens().chain( 107 let new_expr = if_indent_level.increase_indent(make::let_match_early(
91 then_block_items 108 expr,
92 .syntax() 109 &if_let_ident,
93 .children_with_tokens() 110 early_expression,
94 .skip(1) 111 ));
95 .take_while(|i| *i != end_of_then), 112 replace(new_expr, &then_block, &parent_block, &if_expr)
96 ); 113 }
97 let new_block = replace_children( 114 };
98 &parent_block.syntax(),
99 RangeInclusive::new(
100 if_expr.clone().syntax().clone().into(),
101 if_expr.syntax().clone().into(),
102 ),
103 &mut new_if_and_then_statements,
104 );
105 edit.target(if_expr.syntax().text_range()); 115 edit.target(if_expr.syntax().text_range());
106 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); 116 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
107 edit.set_cursor(cursor_position); 117 edit.set_cursor(cursor_position);
118
119 fn replace(
120 new_expr: impl AstNode,
121 then_block: &Block,
122 parent_block: &Block,
123 if_expr: &ast::IfExpr,
124 ) -> SyntaxNode {
125 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
126 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
127 let end_of_then =
128 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
129 end_of_then.prev_sibling_or_token().unwrap()
130 } else {
131 end_of_then
132 };
133 let mut then_statements = new_expr.syntax().children_with_tokens().chain(
134 then_block_items
135 .syntax()
136 .children_with_tokens()
137 .skip(1)
138 .take_while(|i| *i != end_of_then),
139 );
140 replace_children(
141 &parent_block.syntax(),
142 RangeInclusive::new(
143 if_expr.clone().syntax().clone().into(),
144 if_expr.syntax().clone().into(),
145 ),
146 &mut then_statements,
147 )
148 }
108 }) 149 })
109} 150}
110 151
@@ -144,6 +185,68 @@ mod tests {
144 } 185 }
145 186
146 #[test] 187 #[test]
188 fn convert_let_inside_fn() {
189 check_assist(
190 convert_to_guarded_return,
191 r#"
192 fn main(n: Option<String>) {
193 bar();
194 if<|> let Some(n) = n {
195 foo(n);
196
197 //comment
198 bar();
199 }
200 }
201 "#,
202 r#"
203 fn main(n: Option<String>) {
204 bar();
205 le<|>t n = match n {
206 Some(it) => it,
207 None => return,
208 };
209 foo(n);
210
211 //comment
212 bar();
213 }
214 "#,
215 );
216 }
217
218 #[test]
219 fn convert_let_ok_inside_fn() {
220 check_assist(
221 convert_to_guarded_return,
222 r#"
223 fn main(n: Option<String>) {
224 bar();
225 if<|> let Ok(n) = n {
226 foo(n);
227
228 //comment
229 bar();
230 }
231 }
232 "#,
233 r#"
234 fn main(n: Option<String>) {
235 bar();
236 le<|>t n = match n {
237 Ok(it) => it,
238 None => return,
239 };
240 foo(n);
241
242 //comment
243 bar();
244 }
245 "#,
246 );
247 }
248
249 #[test]
147 fn convert_inside_while() { 250 fn convert_inside_while() {
148 check_assist( 251 check_assist(
149 convert_to_guarded_return, 252 convert_to_guarded_return,
@@ -172,6 +275,35 @@ mod tests {
172 } 275 }
173 276
174 #[test] 277 #[test]
278 fn convert_let_inside_while() {
279 check_assist(
280 convert_to_guarded_return,
281 r#"
282 fn main() {
283 while true {
284 if<|> let Some(n) = n {
285 foo(n);
286 bar();
287 }
288 }
289 }
290 "#,
291 r#"
292 fn main() {
293 while true {
294 le<|>t n = match n {
295 Some(it) => it,
296 None => continue,
297 };
298 foo(n);
299 bar();
300 }
301 }
302 "#,
303 );
304 }
305
306 #[test]
175 fn convert_inside_loop() { 307 fn convert_inside_loop() {
176 check_assist( 308 check_assist(
177 convert_to_guarded_return, 309 convert_to_guarded_return,
@@ -200,6 +332,35 @@ mod tests {
200 } 332 }
201 333
202 #[test] 334 #[test]
335 fn convert_let_inside_loop() {
336 check_assist(
337 convert_to_guarded_return,
338 r#"
339 fn main() {
340 loop {
341 if<|> let Some(n) = n {
342 foo(n);
343 bar();
344 }
345 }
346 }
347 "#,
348 r#"
349 fn main() {
350 loop {
351 le<|>t n = match n {
352 Some(it) => it,
353 None => continue,
354 };
355 foo(n);
356 bar();
357 }
358 }
359 "#,
360 );
361 }
362
363 #[test]
203 fn ignore_already_converted_if() { 364 fn ignore_already_converted_if() {
204 check_assist_not_applicable( 365 check_assist_not_applicable(
205 convert_to_guarded_return, 366 convert_to_guarded_return,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 3d5f18bfa..95062ef6c 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -110,6 +110,23 @@ pub fn match_arm_list(arms: impl Iterator<Item = ast::MatchArm>) -> ast::MatchAr
110 } 110 }
111} 111}
112 112
113pub fn let_match_early(expr: ast::Expr, path: &str, early_expression: &str) -> ast::LetStmt {
114 return from_text(&format!(
115 r#"let {} = match {} {{
116 {}(it) => it,
117 None => {},
118}};"#,
119 expr.syntax().text(),
120 expr.syntax().text(),
121 path,
122 early_expression
123 ));
124
125 fn from_text(text: &str) -> ast::LetStmt {
126 ast_from_text(&format!("fn f() {{ {} }}", text))
127 }
128}
129
113pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>) -> ast::WherePred { 130pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>) -> ast::WherePred {
114 let bounds = bounds.map(|b| b.syntax().to_string()).join(" + "); 131 let bounds = bounds.map(|b| b.syntax().to_string()).join(" + ");
115 return from_text(&format!("{}: {}", path.syntax(), bounds)); 132 return from_text(&format!("{}: {}", path.syntax(), bounds));