mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-28 18:43:01 +00:00
Add let-chain support for convert_to_guarded_return
- And add early expression `None` in function `Option` return
Example
---
```rust
fn main() {
if$0 let Ok(x) = Err(92)
&& x < 30
&& let Some(y) = Some(8)
{
foo(x, y);
}
}
```
->
```rust
fn main() {
let Ok(x) = Err(92) else { return };
if x >= 30 {
return;
}
let Some(y) = Some(8) else { return };
foo(x, y);
}
```
This commit is contained in:
parent
bbb6459dc4
commit
a98da9f795
1 changed files with 179 additions and 38 deletions
|
|
@ -1,13 +1,11 @@
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
use ide_db::{
|
use hir::Semantics;
|
||||||
syntax_helpers::node_ext::{is_pattern_cond, single_let},
|
use ide_db::{RootDatabase, ty_filter::TryEnum};
|
||||||
ty_filter::TryEnum,
|
|
||||||
};
|
|
||||||
use syntax::{
|
use syntax::{
|
||||||
AstNode,
|
AstNode,
|
||||||
SyntaxKind::{FN, FOR_EXPR, LOOP_EXPR, WHILE_EXPR, WHITESPACE},
|
SyntaxKind::{FN, FOR_EXPR, LOOP_EXPR, WHILE_EXPR, WHITESPACE},
|
||||||
T,
|
SyntaxNode, T,
|
||||||
ast::{
|
ast::{
|
||||||
self,
|
self,
|
||||||
edit::{AstNodeEdit, IndentLevel},
|
edit::{AstNodeEdit, IndentLevel},
|
||||||
|
|
@ -73,13 +71,7 @@ fn if_expr_to_guarded_return(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is an IfLet that we can handle.
|
let let_chains = flat_let_chain(cond);
|
||||||
let (if_let_pat, cond_expr) = if is_pattern_cond(cond.clone()) {
|
|
||||||
let let_ = single_let(cond)?;
|
|
||||||
(Some(let_.pat()?), let_.expr()?)
|
|
||||||
} else {
|
|
||||||
(None, cond)
|
|
||||||
};
|
|
||||||
|
|
||||||
let then_block = if_expr.then_branch()?;
|
let then_block = if_expr.then_branch()?;
|
||||||
let then_block = then_block.stmt_list()?;
|
let then_block = then_block.stmt_list()?;
|
||||||
|
|
@ -106,11 +98,7 @@ fn if_expr_to_guarded_return(
|
||||||
|
|
||||||
let parent_container = parent_block.syntax().parent()?;
|
let parent_container = parent_block.syntax().parent()?;
|
||||||
|
|
||||||
let early_expression: ast::Expr = match parent_container.kind() {
|
let early_expression: ast::Expr = early_expression(parent_container, &ctx.sema)?;
|
||||||
WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None),
|
|
||||||
FN => make::expr_return(None),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
then_block.syntax().first_child_or_token().map(|t| t.kind() == T!['{'])?;
|
then_block.syntax().first_child_or_token().map(|t| t.kind() == T!['{'])?;
|
||||||
|
|
||||||
|
|
@ -132,32 +120,42 @@ fn if_expr_to_guarded_return(
|
||||||
target,
|
target,
|
||||||
|edit| {
|
|edit| {
|
||||||
let if_indent_level = IndentLevel::from_node(if_expr.syntax());
|
let if_indent_level = IndentLevel::from_node(if_expr.syntax());
|
||||||
let replacement = match if_let_pat {
|
let replacement = let_chains.into_iter().map(|expr| {
|
||||||
None => {
|
if let ast::Expr::LetExpr(let_expr) = &expr
|
||||||
// If.
|
&& let (Some(pat), Some(expr)) = (let_expr.pat(), let_expr.expr())
|
||||||
let new_expr = {
|
{
|
||||||
let then_branch =
|
|
||||||
make::block_expr(once(make::expr_stmt(early_expression).into()), None);
|
|
||||||
let cond = invert_boolean_expression_legacy(cond_expr);
|
|
||||||
make::expr_if(cond, then_branch, None).indent(if_indent_level)
|
|
||||||
};
|
|
||||||
new_expr.syntax().clone()
|
|
||||||
}
|
|
||||||
Some(pat) => {
|
|
||||||
// If-let.
|
// If-let.
|
||||||
let let_else_stmt = make::let_else_stmt(
|
let let_else_stmt = make::let_else_stmt(
|
||||||
pat,
|
pat,
|
||||||
None,
|
None,
|
||||||
cond_expr,
|
expr,
|
||||||
ast::make::tail_only_block_expr(early_expression),
|
ast::make::tail_only_block_expr(early_expression.clone()),
|
||||||
);
|
);
|
||||||
let let_else_stmt = let_else_stmt.indent(if_indent_level);
|
let let_else_stmt = let_else_stmt.indent(if_indent_level);
|
||||||
let_else_stmt.syntax().clone()
|
let_else_stmt.syntax().clone()
|
||||||
|
} else {
|
||||||
|
// If.
|
||||||
|
let new_expr = {
|
||||||
|
let then_branch = make::block_expr(
|
||||||
|
once(make::expr_stmt(early_expression.clone()).into()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let cond = invert_boolean_expression_legacy(expr);
|
||||||
|
make::expr_if(cond, then_branch, None).indent(if_indent_level)
|
||||||
|
};
|
||||||
|
new_expr.syntax().clone()
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
|
let newline = &format!("\n{if_indent_level}");
|
||||||
let then_statements = replacement
|
let then_statements = replacement
|
||||||
.children_with_tokens()
|
.enumerate()
|
||||||
|
.flat_map(|(i, node)| {
|
||||||
|
(i != 0)
|
||||||
|
.then(|| make::tokens::whitespace(newline).into())
|
||||||
|
.into_iter()
|
||||||
|
.chain(node.children_with_tokens())
|
||||||
|
})
|
||||||
.chain(
|
.chain(
|
||||||
then_block_items
|
then_block_items
|
||||||
.syntax()
|
.syntax()
|
||||||
|
|
@ -201,11 +199,7 @@ fn let_stmt_to_guarded_return(
|
||||||
let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
|
let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
|
||||||
let parent_container = parent_block.syntax().parent()?;
|
let parent_container = parent_block.syntax().parent()?;
|
||||||
|
|
||||||
match parent_container.kind() {
|
early_expression(parent_container, &ctx.sema)?
|
||||||
WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None),
|
|
||||||
FN => make::expr_return(None),
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
|
|
@ -232,6 +226,44 @@ fn let_stmt_to_guarded_return(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn early_expression(
|
||||||
|
parent_container: SyntaxNode,
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
) -> Option<ast::Expr> {
|
||||||
|
if let Some(fn_) = ast::Fn::cast(parent_container.clone())
|
||||||
|
&& let Some(fn_def) = sema.to_def(&fn_)
|
||||||
|
&& let Some(TryEnum::Option) = TryEnum::from_ty(sema, &fn_def.ret_type(sema.db))
|
||||||
|
{
|
||||||
|
let none_expr = make::expr_path(make::ext::ident_path("None"));
|
||||||
|
return Some(make::expr_return(Some(none_expr)));
|
||||||
|
}
|
||||||
|
Some(match parent_container.kind() {
|
||||||
|
WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None),
|
||||||
|
FN => make::expr_return(None),
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flat_let_chain(mut expr: ast::Expr) -> Vec<ast::Expr> {
|
||||||
|
let mut chains = vec![];
|
||||||
|
|
||||||
|
while let ast::Expr::BinExpr(bin_expr) = &expr
|
||||||
|
&& bin_expr.op_kind() == Some(ast::BinaryOp::LogicOp(ast::LogicOp::And))
|
||||||
|
&& let (Some(lhs), Some(rhs)) = (bin_expr.lhs(), bin_expr.rhs())
|
||||||
|
{
|
||||||
|
if let Some(last) = chains.pop_if(|last| !matches!(last, ast::Expr::LetExpr(_))) {
|
||||||
|
chains.push(make::expr_bin_op(rhs, ast::BinaryOp::LogicOp(ast::LogicOp::And), last));
|
||||||
|
} else {
|
||||||
|
chains.push(rhs);
|
||||||
|
}
|
||||||
|
expr = lhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
chains.push(expr);
|
||||||
|
chains.reverse();
|
||||||
|
chains
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||||
|
|
@ -268,6 +300,37 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_inside_fn_return_option() {
|
||||||
|
check_assist(
|
||||||
|
convert_to_guarded_return,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn ret_option() -> Option<()> {
|
||||||
|
bar();
|
||||||
|
if$0 true {
|
||||||
|
foo();
|
||||||
|
|
||||||
|
// comment
|
||||||
|
bar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn ret_option() -> Option<()> {
|
||||||
|
bar();
|
||||||
|
if false {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
foo();
|
||||||
|
|
||||||
|
// comment
|
||||||
|
bar();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn convert_let_inside_fn() {
|
fn convert_let_inside_fn() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
|
@ -316,6 +379,58 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_if_let_chain_result() {
|
||||||
|
check_assist(
|
||||||
|
convert_to_guarded_return,
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
if$0 let Ok(x) = Err(92)
|
||||||
|
&& x < 30
|
||||||
|
&& let Some(y) = Some(8)
|
||||||
|
{
|
||||||
|
foo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let Ok(x) = Err(92) else { return };
|
||||||
|
if x >= 30 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(y) = Some(8) else { return };
|
||||||
|
foo(x, y);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_assist(
|
||||||
|
convert_to_guarded_return,
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
if$0 let Ok(x) = Err(92)
|
||||||
|
&& x < 30
|
||||||
|
&& y < 20
|
||||||
|
&& let Some(y) = Some(8)
|
||||||
|
{
|
||||||
|
foo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let Ok(x) = Err(92) else { return };
|
||||||
|
if !(x < 30 && y < 20) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(y) = Some(8) else { return };
|
||||||
|
foo(x, y);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn convert_let_ok_inside_fn() {
|
fn convert_let_ok_inside_fn() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
|
@ -560,6 +675,32 @@ fn main() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_let_stmt_inside_fn_return_option() {
|
||||||
|
check_assist(
|
||||||
|
convert_to_guarded_return,
|
||||||
|
r#"
|
||||||
|
//- minicore: option
|
||||||
|
fn foo() -> Option<i32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ret_option() -> Option<i32> {
|
||||||
|
let x$0 = foo();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
r#"
|
||||||
|
fn foo() -> Option<i32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ret_option() -> Option<i32> {
|
||||||
|
let Some(x) = foo() else { return None };
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn convert_let_stmt_inside_loop() {
|
fn convert_let_stmt_inside_loop() {
|
||||||
check_assist(
|
check_assist(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue