1 use std::{iter::once, ops::RangeInclusive};
4 algo::replace_children,
7 edit::{AstNodeEdit, IndentLevel},
11 SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression,
21 // Assist: convert_to_guarded_return
23 // Replace a large conditional with a guarded return.
43 pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
45 if if_expr.else_branch().is_some() {
49 let cond = if_expr.condition()?;
51 // Check if there is an IfLet that we can handle.
52 let if_let_pat = match cond.pat() {
53 None => None, // No IfLet, supported.
54 Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
55 let path = pat.path()?;
56 match path.qualifier() {
58 let bound_ident = pat.fields().next().unwrap();
59 if ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
60 Some((path, bound_ident))
65 Some(_) => return None,
68 Some(_) => return None, // Unsupported IfLet.
71 let cond_expr = cond.expr()?;
72 let then_block = if_expr.then_branch()?;
74 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
76 if parent_block.tail_expr()? != if_expr.clone().into() {
80 // check for early return and continue
81 let first_in_then_block = then_block.syntax().first_child()?;
82 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
83 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
84 || first_in_then_block
86 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
91 let parent_container = parent_block.syntax().parent()?;
93 let early_expression: ast::Expr = match parent_container.kind() {
94 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
95 FN => make::expr_return(None),
99 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
103 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
105 let target = if_expr.syntax().text_range();
107 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
108 "Convert to guarded return",
111 let if_indent_level = IndentLevel::from_node(if_expr.syntax());
112 let new_block = match if_let_pat {
117 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
118 let cond = invert_boolean_expression(cond_expr);
119 make::expr_if(make::condition(cond, None), then_branch, None)
120 .indent(if_indent_level)
122 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
124 Some((path, bound_ident)) => {
128 let pat = make::tuple_struct_pat(
130 once(make::ext::simple_ident_pat(make::name("it")).into()),
133 let path = make::ext::ident_path("it");
134 make::expr_path(path)
136 make::match_arm(once(pat.into()), None, expr)
139 let sad_arm = make::match_arm(
140 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
141 once(make::wildcard_pat().into()),
146 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
149 let let_stmt = make::let_stmt(bound_ident, None, Some(match_expr));
150 let let_stmt = let_stmt.indent(if_indent_level);
151 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
154 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
157 new_expr: &SyntaxNode,
158 then_block: &ast::BlockExpr,
159 parent_block: &ast::BlockExpr,
160 if_expr: &ast::IfExpr,
162 let then_block_items = then_block.dedent(IndentLevel(1));
163 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
165 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
166 end_of_then.prev_sibling_or_token().unwrap()
170 let mut then_statements = new_expr.children_with_tokens().chain(
173 .children_with_tokens()
175 .take_while(|i| *i != end_of_then),
178 parent_block.syntax(),
180 if_expr.clone().syntax().clone().into(),
181 if_expr.syntax().clone().into(),
183 &mut then_statements,
192 use crate::tests::{check_assist, check_assist_not_applicable};
197 fn convert_inside_fn() {
199 convert_to_guarded_return,
227 fn convert_let_inside_fn() {
229 convert_to_guarded_return,
231 fn main(n: Option<String>) {
233 if$0 let Some(n) = n {
242 fn main(n: Option<String>) {
258 fn convert_if_let_result() {
260 convert_to_guarded_return,
263 if$0 let Ok(x) = Err(92) {
270 let x = match Err(92) {
281 fn convert_let_ok_inside_fn() {
283 convert_to_guarded_return,
285 fn main(n: Option<String>) {
287 if$0 let Some(n) = n {
296 fn main(n: Option<String>) {
312 fn convert_let_mut_ok_inside_fn() {
314 convert_to_guarded_return,
316 fn main(n: Option<String>) {
318 if$0 let Some(mut n) = n {
327 fn main(n: Option<String>) {
329 let mut n = match n {
343 fn convert_let_ref_ok_inside_fn() {
345 convert_to_guarded_return,
347 fn main(n: Option<&str>) {
349 if$0 let Some(ref n) = n {
358 fn main(n: Option<&str>) {
360 let ref n = match n {
374 fn convert_inside_while() {
376 convert_to_guarded_return,
402 fn convert_let_inside_while() {
404 convert_to_guarded_return,
408 if$0 let Some(n) = n {
431 fn convert_inside_loop() {
433 convert_to_guarded_return,
459 fn convert_let_inside_loop() {
461 convert_to_guarded_return,
465 if$0 let Some(n) = n {
488 fn ignore_already_converted_if() {
489 check_assist_not_applicable(
490 convert_to_guarded_return,
502 fn ignore_already_converted_loop() {
503 check_assist_not_applicable(
504 convert_to_guarded_return,
519 check_assist_not_applicable(
520 convert_to_guarded_return,
532 fn ignore_else_branch() {
533 check_assist_not_applicable(
534 convert_to_guarded_return,
548 fn ignore_statements_aftert_if() {
549 check_assist_not_applicable(
550 convert_to_guarded_return,
563 fn ignore_statements_inside_if() {
564 check_assist_not_applicable(
565 convert_to_guarded_return,