3 use ide_db::syntax_helpers::node_ext::{is_pattern_cond, single_let};
7 edit::{AstNodeEdit, IndentLevel},
11 SyntaxKind::{FN, LOOP_EXPR, 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, cond_expr) = if is_pattern_cond(cond.clone()) {
53 let let_ = single_let(cond)?;
55 Some(ast::Pat::TupleStructPat(pat)) if pat.fields().count() == 1 => {
56 let path = pat.path()?;
57 if path.qualifier().is_some() {
61 let bound_ident = pat.fields().next().unwrap();
62 if !ast::IdentPat::can_cast(bound_ident.syntax().kind()) {
66 (Some((path, bound_ident)), let_.expr()?)
68 _ => return None, // Unsupported IfLet.
74 let then_block = if_expr.then_branch()?;
75 let then_block = then_block.stmt_list()?;
77 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
79 if parent_block.tail_expr()? != if_expr.clone().into() {
83 // FIXME: This relies on untyped syntax tree and casts to much. It should be
84 // rewritten to use strongly-typed APIs.
86 // check for early return and continue
87 let first_in_then_block = then_block.syntax().first_child()?;
88 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
89 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
90 || first_in_then_block
92 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
97 let parent_container = parent_block.syntax().parent()?;
99 let early_expression: ast::Expr = match parent_container.kind() {
100 WHILE_EXPR | LOOP_EXPR => make::expr_continue(None),
101 FN => make::expr_return(None),
105 if then_block.syntax().first_child_or_token().map(|t| t.kind() == T!['{']).is_none() {
109 then_block.syntax().last_child_or_token().filter(|t| t.kind() == T!['}'])?;
111 let target = if_expr.syntax().text_range();
113 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
114 "Convert to guarded return",
117 let if_expr = edit.make_mut(if_expr);
118 let if_indent_level = IndentLevel::from_node(if_expr.syntax());
119 let replacement = match if_let_pat {
124 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
125 let cond = invert_boolean_expression(cond_expr);
126 make::expr_if(cond, then_branch, None).indent(if_indent_level)
128 new_expr.syntax().clone_for_update()
130 Some((path, bound_ident)) => {
132 let pat = make::tuple_struct_pat(path, once(bound_ident));
133 let let_else_stmt = make::let_else_stmt(
137 ast::make::tail_only_block_expr(early_expression),
139 let let_else_stmt = let_else_stmt.indent(if_indent_level);
140 let_else_stmt.syntax().clone_for_update()
144 let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update();
146 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
148 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
149 end_of_then.prev_sibling_or_token().unwrap()
154 let then_statements = replacement
155 .children_with_tokens()
159 .children_with_tokens()
161 .take_while(|i| *i != end_of_then),
165 ted::replace_with_many(if_expr.syntax(), then_statements)
172 use crate::tests::{check_assist, check_assist_not_applicable};
177 fn convert_inside_fn() {
179 convert_to_guarded_return,
207 fn convert_let_inside_fn() {
209 convert_to_guarded_return,
211 fn main(n: Option<String>) {
213 if$0 let Some(n) = n {
222 fn main(n: Option<String>) {
224 let Some(n) = n else { return };
235 fn convert_if_let_result() {
237 convert_to_guarded_return,
240 if$0 let Ok(x) = Err(92) {
247 let Ok(x) = Err(92) else { return };
255 fn convert_let_ok_inside_fn() {
257 convert_to_guarded_return,
259 fn main(n: Option<String>) {
261 if$0 let Some(n) = n {
270 fn main(n: Option<String>) {
272 let Some(n) = n else { return };
283 fn convert_let_mut_ok_inside_fn() {
285 convert_to_guarded_return,
287 fn main(n: Option<String>) {
289 if$0 let Some(mut n) = n {
298 fn main(n: Option<String>) {
300 let Some(mut n) = n else { return };
311 fn convert_let_ref_ok_inside_fn() {
313 convert_to_guarded_return,
315 fn main(n: Option<&str>) {
317 if$0 let Some(ref n) = n {
326 fn main(n: Option<&str>) {
328 let Some(ref n) = n else { return };
339 fn convert_inside_while() {
341 convert_to_guarded_return,
367 fn convert_let_inside_while() {
369 convert_to_guarded_return,
373 if$0 let Some(n) = n {
383 let Some(n) = n else { continue };
393 fn convert_inside_loop() {
395 convert_to_guarded_return,
421 fn convert_let_inside_loop() {
423 convert_to_guarded_return,
427 if$0 let Some(n) = n {
437 let Some(n) = n else { continue };
447 fn ignore_already_converted_if() {
448 check_assist_not_applicable(
449 convert_to_guarded_return,
461 fn ignore_already_converted_loop() {
462 check_assist_not_applicable(
463 convert_to_guarded_return,
478 check_assist_not_applicable(
479 convert_to_guarded_return,
491 fn ignore_else_branch() {
492 check_assist_not_applicable(
493 convert_to_guarded_return,
507 fn ignore_statements_aftert_if() {
508 check_assist_not_applicable(
509 convert_to_guarded_return,
522 fn ignore_statements_inside_if() {
523 check_assist_not_applicable(
524 convert_to_guarded_return,