From 91e482b46d43a24cd0a48ea1119b93105140cff2 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Fri, 27 Mar 2020 12:12:17 +0100
Subject: Replace if with if-let

---
 crates/ra_assists/src/doc_tests/generated.rs       |  26 +++++
 .../src/handlers/replace_let_with_if_let.rs        | 108 +++++++++++++++++++++
 crates/ra_assists/src/lib.rs                       |   2 +
 3 files changed, 136 insertions(+)
 create mode 100644 crates/ra_assists/src/handlers/replace_let_with_if_let.rs

(limited to 'crates/ra_assists/src')

diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 62dcb3808..c0486a8a9 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -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(
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
new file mode 100644
index 000000000..10e41f97e
--- /dev/null
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -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) {
+    }
+}
+            ",
+        )
+    }
+}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index bcc9b3f10..67a58fc1f 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -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 split_import;
 
@@ -153,6 +154,7 @@ mod handlers {
             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,
             split_import::split_import,
         ]
-- 
cgit v1.2.3