3 use ide_db::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(),
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)) => {
134 let pat = make::tuple_struct_pat(
136 once(make::ext::simple_ident_pat(make::name("it")).into()),
139 let path = make::ext::ident_path("it");
140 make::expr_path(path)
142 make::match_arm(once(pat.into()), None, expr)
145 let sad_arm = make::match_arm(
146 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
147 once(make::wildcard_pat().into()),
152 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
155 let let_stmt = make::let_stmt(bound_ident, None, Some(match_expr));
156 let let_stmt = let_stmt.indent(if_indent_level);
157 let_stmt.syntax().clone_for_update()
161 let then_block_items = then_block.dedent(IndentLevel(1)).clone_for_update();
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()
171 let then_statements = replacement
172 .children_with_tokens()
176 .children_with_tokens()
178 .take_while(|i| *i != end_of_then),
182 ted::replace_with_many(if_expr.syntax(), then_statements)
189 use crate::tests::{check_assist, check_assist_not_applicable};
194 fn convert_inside_fn() {
196 convert_to_guarded_return,
224 fn convert_let_inside_fn() {
226 convert_to_guarded_return,
228 fn main(n: Option<String>) {
230 if$0 let Some(n) = n {
239 fn main(n: Option<String>) {
255 fn convert_if_let_result() {
257 convert_to_guarded_return,
260 if$0 let Ok(x) = Err(92) {
267 let x = match Err(92) {
278 fn convert_let_ok_inside_fn() {
280 convert_to_guarded_return,
282 fn main(n: Option<String>) {
284 if$0 let Some(n) = n {
293 fn main(n: Option<String>) {
309 fn convert_let_mut_ok_inside_fn() {
311 convert_to_guarded_return,
313 fn main(n: Option<String>) {
315 if$0 let Some(mut n) = n {
324 fn main(n: Option<String>) {
326 let mut n = match n {
340 fn convert_let_ref_ok_inside_fn() {
342 convert_to_guarded_return,
344 fn main(n: Option<&str>) {
346 if$0 let Some(ref n) = n {
355 fn main(n: Option<&str>) {
357 let ref n = match n {
371 fn convert_inside_while() {
373 convert_to_guarded_return,
399 fn convert_let_inside_while() {
401 convert_to_guarded_return,
405 if$0 let Some(n) = n {
428 fn convert_inside_loop() {
430 convert_to_guarded_return,
456 fn convert_let_inside_loop() {
458 convert_to_guarded_return,
462 if$0 let Some(n) = n {
485 fn ignore_already_converted_if() {
486 check_assist_not_applicable(
487 convert_to_guarded_return,
499 fn ignore_already_converted_loop() {
500 check_assist_not_applicable(
501 convert_to_guarded_return,
516 check_assist_not_applicable(
517 convert_to_guarded_return,
529 fn ignore_else_branch() {
530 check_assist_not_applicable(
531 convert_to_guarded_return,
545 fn ignore_statements_aftert_if() {
546 check_assist_not_applicable(
547 convert_to_guarded_return,
560 fn ignore_statements_inside_if() {
561 check_assist_not_applicable(
562 convert_to_guarded_return,