|
1 | 1 | use std::iter::once;
|
2 | 2 |
|
3 |
| -use ide_db::syntax_helpers::node_ext::{is_pattern_cond, single_let}; |
| 3 | +use ide_db::{ |
| 4 | + syntax_helpers::node_ext::{is_pattern_cond, single_let}, |
| 5 | + ty_filter::TryEnum, |
| 6 | +}; |
4 | 7 | use syntax::{
|
5 | 8 | ast::{
|
6 | 9 | self,
|
@@ -41,13 +44,35 @@ use crate::{
|
41 | 44 | // }
|
42 | 45 | // ```
|
43 | 46 | pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
44 |
| - let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
| 47 | + if let Some(let_stmt) = ctx.find_node_at_offset() { |
| 48 | + let_stmt_to_guarded_return(let_stmt, acc, ctx) |
| 49 | + } else if let Some(if_expr) = ctx.find_node_at_offset() { |
| 50 | + if_expr_to_guarded_return(if_expr, acc, ctx) |
| 51 | + } else { |
| 52 | + None |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +fn if_expr_to_guarded_return( |
| 57 | + if_expr: ast::IfExpr, |
| 58 | + acc: &mut Assists, |
| 59 | + ctx: &AssistContext<'_>, |
| 60 | +) -> Option<()> { |
45 | 61 | if if_expr.else_branch().is_some() {
|
46 | 62 | return None;
|
47 | 63 | }
|
48 | 64 |
|
49 | 65 | let cond = if_expr.condition()?;
|
50 | 66 |
|
| 67 | + let if_token_range = if_expr.if_token()?.text_range(); |
| 68 | + let if_cond_range = cond.syntax().text_range(); |
| 69 | + |
| 70 | + let cursor_in_range = |
| 71 | + if_token_range.cover(if_cond_range).contains_range(ctx.selection_trimmed()); |
| 72 | + if !cursor_in_range { |
| 73 | + return None; |
| 74 | + } |
| 75 | + |
51 | 76 | // Check if there is an IfLet that we can handle.
|
52 | 77 | let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
|
53 | 78 | let let_ = single_let(cond)?;
|
@@ -148,6 +173,65 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
|
148 | 173 | )
|
149 | 174 | }
|
150 | 175 |
|
| 176 | +fn let_stmt_to_guarded_return( |
| 177 | + let_stmt: ast::LetStmt, |
| 178 | + acc: &mut Assists, |
| 179 | + ctx: &AssistContext<'_>, |
| 180 | +) -> Option<()> { |
| 181 | + let pat = let_stmt.pat()?; |
| 182 | + let expr = let_stmt.initializer()?; |
| 183 | + |
| 184 | + let let_token_range = let_stmt.let_token()?.text_range(); |
| 185 | + let let_pattern_range = pat.syntax().text_range(); |
| 186 | + let cursor_in_range = |
| 187 | + let_token_range.cover(let_pattern_range).contains_range(ctx.selection_trimmed()); |
| 188 | + |
| 189 | + if !cursor_in_range { |
| 190 | + return None; |
| 191 | + } |
| 192 | + |
| 193 | + let try_enum = |
| 194 | + ctx.sema.type_of_expr(&expr).and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty.adjusted()))?; |
| 195 | + |
| 196 | + let happy_pattern = try_enum.happy_pattern(pat); |
| 197 | + let target = let_stmt.syntax().text_range(); |
| 198 | + |
| 199 | + let early_expression: ast::Expr = { |
| 200 | + let parent_block = |
| 201 | + let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; |
| 202 | + let parent_container = parent_block.syntax().parent()?; |
| 203 | + |
| 204 | + match parent_container.kind() { |
| 205 | + WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None), |
| 206 | + FN => make::expr_return(None), |
| 207 | + _ => return None, |
| 208 | + } |
| 209 | + }; |
| 210 | + |
| 211 | + acc.add( |
| 212 | + AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite), |
| 213 | + "Convert to guarded return", |
| 214 | + target, |
| 215 | + |edit| { |
| 216 | + let let_stmt = edit.make_mut(let_stmt); |
| 217 | + let let_indent_level = IndentLevel::from_node(let_stmt.syntax()); |
| 218 | + |
| 219 | + let replacement = { |
| 220 | + let let_else_stmt = make::let_else_stmt( |
| 221 | + happy_pattern, |
| 222 | + let_stmt.ty(), |
| 223 | + expr, |
| 224 | + ast::make::tail_only_block_expr(early_expression), |
| 225 | + ); |
| 226 | + let let_else_stmt = let_else_stmt.indent(let_indent_level); |
| 227 | + let_else_stmt.syntax().clone_for_update() |
| 228 | + }; |
| 229 | + |
| 230 | + ted::replace(let_stmt.syntax(), replacement) |
| 231 | + }, |
| 232 | + ) |
| 233 | +} |
| 234 | + |
151 | 235 | #[cfg(test)]
|
152 | 236 | mod tests {
|
153 | 237 | use crate::tests::{check_assist, check_assist_not_applicable};
|
@@ -450,6 +534,62 @@ fn main() {
|
450 | 534 | );
|
451 | 535 | }
|
452 | 536 |
|
| 537 | + #[test] |
| 538 | + fn convert_let_stmt_inside_fn() { |
| 539 | + check_assist( |
| 540 | + convert_to_guarded_return, |
| 541 | + r#" |
| 542 | +//- minicore: option |
| 543 | +fn foo() -> Option<i32> { |
| 544 | + None |
| 545 | +} |
| 546 | +
|
| 547 | +fn main() { |
| 548 | + let x$0 = foo(); |
| 549 | +} |
| 550 | +"#, |
| 551 | + r#" |
| 552 | +fn foo() -> Option<i32> { |
| 553 | + None |
| 554 | +} |
| 555 | +
|
| 556 | +fn main() { |
| 557 | + let Some(x) = foo() else { return }; |
| 558 | +} |
| 559 | +"#, |
| 560 | + ); |
| 561 | + } |
| 562 | + |
| 563 | + #[test] |
| 564 | + fn convert_let_stmt_inside_loop() { |
| 565 | + check_assist( |
| 566 | + convert_to_guarded_return, |
| 567 | + r#" |
| 568 | +//- minicore: option |
| 569 | +fn foo() -> Option<i32> { |
| 570 | + None |
| 571 | +} |
| 572 | +
|
| 573 | +fn main() { |
| 574 | + loop { |
| 575 | + let x$0 = foo(); |
| 576 | + } |
| 577 | +} |
| 578 | +"#, |
| 579 | + r#" |
| 580 | +fn foo() -> Option<i32> { |
| 581 | + None |
| 582 | +} |
| 583 | +
|
| 584 | +fn main() { |
| 585 | + loop { |
| 586 | + let Some(x) = foo() else { continue }; |
| 587 | + } |
| 588 | +} |
| 589 | +"#, |
| 590 | + ); |
| 591 | + } |
| 592 | + |
453 | 593 | #[test]
|
454 | 594 | fn convert_arbitrary_if_let_patterns() {
|
455 | 595 | check_assist(
|
@@ -591,6 +731,37 @@ fn main() {
|
591 | 731 | }
|
592 | 732 | }
|
593 | 733 | }
|
| 734 | +"#, |
| 735 | + ); |
| 736 | + } |
| 737 | + |
| 738 | + #[test] |
| 739 | + fn ignore_inside_if_stmt() { |
| 740 | + check_assist_not_applicable( |
| 741 | + convert_to_guarded_return, |
| 742 | + r#" |
| 743 | +fn main() { |
| 744 | + if false { |
| 745 | + foo()$0; |
| 746 | + } |
| 747 | +} |
| 748 | +"#, |
| 749 | + ); |
| 750 | + } |
| 751 | + |
| 752 | + #[test] |
| 753 | + fn ignore_inside_let_initializer() { |
| 754 | + check_assist_not_applicable( |
| 755 | + convert_to_guarded_return, |
| 756 | + r#" |
| 757 | +//- minicore: option |
| 758 | +fn foo() -> Option<i32> { |
| 759 | + None |
| 760 | +} |
| 761 | +
|
| 762 | +fn main() { |
| 763 | + let x = foo()$0; |
| 764 | +} |
594 | 765 | "#,
|
595 | 766 | );
|
596 | 767 | }
|
|
0 commit comments