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:
bors[bot] 2020-03-27 11:21:57 +00:00 committed by GitHub
commit a4901fdcfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 170 additions and 5 deletions

View File

@ -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(

View 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) {
}
}
",
)
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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.