Skip to content

Commit 65a6441

Browse files
committed
Auto merge of #16424 - dfireBird:let-stmt-guarded-return-assist, r=Veykril
implement convert to guarded return assist for `let` statement with type that implements `std::ops::Try` I've tried to implement the assist that #16390 talked about If there are any improvements that I can make in implementation, please suggest them. ![Peek 2024-02-05 19-01](https://github.com/rust-lang/rust-analyzer/assets/40687700/d6af3222-4f23-4ade-a930-8a78cc75e821)
2 parents d24bb7e + 53db37f commit 65a6441

File tree

1 file changed

+173
-2
lines changed

1 file changed

+173
-2
lines changed

crates/ide-assists/src/handlers/convert_to_guarded_return.rs

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::iter::once;
22

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+
};
47
use syntax::{
58
ast::{
69
self,
@@ -41,13 +44,35 @@ use crate::{
4144
// }
4245
// ```
4346
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<()> {
4561
if if_expr.else_branch().is_some() {
4662
return None;
4763
}
4864

4965
let cond = if_expr.condition()?;
5066

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+
5176
// Check if there is an IfLet that we can handle.
5277
let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
5378
let let_ = single_let(cond)?;
@@ -148,6 +173,65 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext<'
148173
)
149174
}
150175

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+
151235
#[cfg(test)]
152236
mod tests {
153237
use crate::tests::{check_assist, check_assist_not_applicable};
@@ -450,6 +534,62 @@ fn main() {
450534
);
451535
}
452536

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+
453593
#[test]
454594
fn convert_arbitrary_if_let_patterns() {
455595
check_assist(
@@ -591,6 +731,37 @@ fn main() {
591731
}
592732
}
593733
}
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+
}
594765
"#,
595766
);
596767
}

0 commit comments

Comments
 (0)