Merge #3742
3742: Replace if with if-let r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
a4901fdcfd
@ -607,6 +607,32 @@ fn handle(action: Action) {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_let_with_if_let() {
|
||||
check(
|
||||
"replace_let_with_if_let",
|
||||
r#####"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main(action: Action) {
|
||||
<|>let x = compute();
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
"#####,
|
||||
r#####"
|
||||
enum Option<T> { Some(T), None }
|
||||
|
||||
fn main(action: Action) {
|
||||
if let Some(x) = compute() {
|
||||
}
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_replace_qualified_name_with_use() {
|
||||
check(
|
||||
|
108
crates/ra_assists/src/handlers/replace_let_with_if_let.rs
Normal file
108
crates/ra_assists/src/handlers/replace_let_with_if_let.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use hir::Adt;
|
||||
use ra_syntax::{
|
||||
ast::{self, make},
|
||||
AstNode, T,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
assist_ctx::{Assist, AssistCtx},
|
||||
AssistId,
|
||||
};
|
||||
use ast::edit::{AstNodeEdit, IndentLevel};
|
||||
use std::iter::once;
|
||||
|
||||
// Assist: replace_let_with_if_let
|
||||
//
|
||||
// Replaces `if let` with an else branch with a `match` expression.
|
||||
//
|
||||
// ```
|
||||
// # enum Option<T> { Some(T), None }
|
||||
//
|
||||
// fn main(action: Action) {
|
||||
// <|>let x = compute();
|
||||
// }
|
||||
//
|
||||
// fn compute() -> Option<i32> { None }
|
||||
// ```
|
||||
// ->
|
||||
// ```
|
||||
// # enum Option<T> { Some(T), None }
|
||||
//
|
||||
// fn main(action: Action) {
|
||||
// if let Some(x) = compute() {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn compute() -> Option<i32> { None }
|
||||
// ```
|
||||
pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
|
||||
let let_kw = ctx.find_token_at_offset(T![let])?;
|
||||
let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
|
||||
let init = let_stmt.initializer()?;
|
||||
let original_pat = let_stmt.pat()?;
|
||||
let ty = ctx.sema.type_of_expr(&init)?;
|
||||
let enum_ = match ty.as_adt() {
|
||||
Some(Adt::Enum(it)) => it,
|
||||
_ => return None,
|
||||
};
|
||||
let happy_case =
|
||||
[("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| {
|
||||
if &enum_.name(ctx.db).to_string() == known_type {
|
||||
return Some(happy_case);
|
||||
}
|
||||
None
|
||||
});
|
||||
|
||||
ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| {
|
||||
let with_placeholder: ast::Pat = match happy_case {
|
||||
None => make::placeholder_pat().into(),
|
||||
Some(var_name) => make::tuple_struct_pat(
|
||||
make::path_unqualified(make::path_segment(make::name_ref(var_name))),
|
||||
once(make::placeholder_pat().into()),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
let block =
|
||||
IndentLevel::from_node(let_stmt.syntax()).increase_indent(make::block_expr(None, None));
|
||||
let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
|
||||
let stmt = make::expr_stmt(if_);
|
||||
|
||||
let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
|
||||
let target_offset =
|
||||
let_stmt.syntax().text_range().start() + placeholder.syntax().text_range().start();
|
||||
let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
|
||||
|
||||
edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
|
||||
edit.target(let_kw.text_range());
|
||||
edit.set_cursor(target_offset);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::helpers::check_assist;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn replace_let_unknown_enum() {
|
||||
check_assist(
|
||||
replace_let_with_if_let,
|
||||
r"
|
||||
enum E<T> { X(T), Y(T) }
|
||||
|
||||
fn main() {
|
||||
<|>let x = E::X(92);
|
||||
}
|
||||
",
|
||||
r"
|
||||
enum E<T> { X(T), Y(T) }
|
||||
|
||||
fn main() {
|
||||
if let <|>x = E::X(92) {
|
||||
}
|
||||
}
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
@ -118,6 +118,7 @@ mod handlers {
|
||||
mod remove_dbg;
|
||||
mod remove_mut;
|
||||
mod replace_if_let_with_match;
|
||||
mod replace_let_with_if_let;
|
||||
mod replace_qualified_name_with_use;
|
||||
mod replace_unwrap_with_match;
|
||||
mod split_import;
|
||||
@ -154,6 +155,7 @@ pub(crate) fn all() -> &'static [AssistHandler] {
|
||||
remove_dbg::remove_dbg,
|
||||
remove_mut::remove_mut,
|
||||
replace_if_let_with_match::replace_if_let_with_match,
|
||||
replace_let_with_if_let::replace_let_with_if_let,
|
||||
replace_qualified_name_with_use::replace_qualified_name_with_use,
|
||||
replace_unwrap_with_match::replace_unwrap_with_match,
|
||||
split_import::split_import,
|
||||
|
@ -251,7 +251,7 @@ impl ast::UseItem {
|
||||
#[must_use]
|
||||
pub fn with_use_tree(&self, use_tree: ast::UseTree) -> ast::UseItem {
|
||||
if let Some(old) = self.use_tree() {
|
||||
return self.replace_descendants(iter::once((old, use_tree)));
|
||||
return self.replace_descendant(old, use_tree);
|
||||
}
|
||||
self.clone()
|
||||
}
|
||||
@ -283,7 +283,7 @@ impl ast::UseTree {
|
||||
#[must_use]
|
||||
pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
|
||||
if let Some(old) = self.path() {
|
||||
return self.replace_descendants(iter::once((old, path)));
|
||||
return self.replace_descendant(old, path);
|
||||
}
|
||||
self.clone()
|
||||
}
|
||||
@ -291,7 +291,7 @@ pub fn with_path(&self, path: ast::Path) -> ast::UseTree {
|
||||
#[must_use]
|
||||
pub fn with_use_tree_list(&self, use_tree_list: ast::UseTreeList) -> ast::UseTree {
|
||||
if let Some(old) = self.use_tree_list() {
|
||||
return self.replace_descendants(iter::once((old, use_tree_list)));
|
||||
return self.replace_descendant(old, use_tree_list);
|
||||
}
|
||||
self.clone()
|
||||
}
|
||||
@ -465,6 +465,11 @@ fn replace_children(
|
||||
Self::cast(new_syntax).unwrap()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn replace_descendant<D: AstNode>(&self, old: D, new: D) -> Self {
|
||||
self.replace_descendants(iter::once((old, new)))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn replace_descendants<D: AstNode>(
|
||||
&self,
|
||||
|
@ -127,7 +127,7 @@ pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition {
|
||||
match pattern {
|
||||
None => ast_from_text(&format!("const _: () = while {} {{}};", expr)),
|
||||
Some(pattern) => {
|
||||
ast_from_text(&format!("const _: () = while {} = {} {{}};", pattern, expr))
|
||||
ast_from_text(&format!("const _: () = while let {} = {} {{}};", pattern, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -245,7 +245,8 @@ pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetSt
|
||||
ast_from_text(&format!("fn f() {{ {} }}", text))
|
||||
}
|
||||
pub fn expr_stmt(expr: ast::Expr) -> ast::ExprStmt {
|
||||
ast_from_text(&format!("fn f() {{ {}; }}", expr))
|
||||
let semi = if expr.is_block_like() { "" } else { ";" };
|
||||
ast_from_text(&format!("fn f() {{ {}{} (); }}", expr, semi))
|
||||
}
|
||||
|
||||
pub fn token(kind: SyntaxKind) -> SyntaxToken {
|
||||
|
@ -583,6 +583,29 @@ fn handle(action: Action) {
|
||||
}
|
||||
```
|
||||
|
||||
## `replace_let_with_if_let`
|
||||
|
||||
Replaces `if let` with an else branch with a `match` expression.
|
||||
|
||||
```rust
|
||||
// BEFORE
|
||||
|
||||
fn main(action: Action) {
|
||||
┃let x = compute();
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
|
||||
// AFTER
|
||||
|
||||
fn main(action: Action) {
|
||||
if let Some(x) = compute() {
|
||||
}
|
||||
}
|
||||
|
||||
fn compute() -> Option<i32> { None }
|
||||
```
|
||||
|
||||
## `replace_qualified_name_with_use`
|
||||
|
||||
Adds a use statement for a given fully-qualified name.
|
||||
|
Loading…
Reference in New Issue
Block a user