Repository: rust-analyzer/rust-analyzer
Pull request #3742 (2020-03-27 11:21:41)

Replace if with if-let


bors r+
🤖


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 3bb1df05b8..10ea555505 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -928,7 +928,6 @@ dependencies = [
  "ra_cfg",
  "ra_prof",
  "ra_syntax",
- "ra_tt",
  "relative-path",
  "rustc-hash",
  "salsa",
@@ -1082,13 +1081,6 @@ dependencies = [
  "drop_bomb",
 ]
 
-[[package]]
-name = "ra_proc_macro"
-version = "0.1.0"
-dependencies = [
- "ra_tt",
-]
-
 [[package]]
 name = "ra_prof"
 version = "0.1.0"
@@ -1110,7 +1102,6 @@ dependencies = [
  "ra_cargo_watch",
  "ra_cfg",
  "ra_db",
- "ra_proc_macro",
  "rustc-hash",
  "serde",
  "serde_json",
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 0000000000..10e41f97e3
--- /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/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
deleted file mode 100644
index 62cb7a7631..0000000000
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ /dev/null
@@ -1,177 +0,0 @@
-use std::iter;
-
-use ra_syntax::{
-    ast::{self, make},
-    AstNode,
-};
-
-use crate::{Assist, AssistCtx, AssistId};
-use ast::edit::IndentLevel;
-
-// Assist: replace_unwrap_with_match
-//
-// Replaces `unwrap` a `match` expression. Works for Result and Option.
-//
-// ```
-// enum Result<T, E> { Ok(T), Err(E) }
-// fn main() {
-//     let x: Result<i32, i32> = Result::Ok(92);
-//     let y = x.<|>unwrap();
-// }
-// ```
-// ->
-// ```
-// enum Result<T, E> { Ok(T), Err(E) }
-// fn main() {
-//     let x: Result<i32, i32> = Result::Ok(92);
-//     let y = match x {
-//         Ok(a) => a,
-//         _ => unreachable!(),
-//     };
-// }
-// ```
-pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
-    let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
-    let name = method_call.name_ref()?;
-    if name.text() != "unwrap" {
-        return None;
-    }
-    let caller = method_call.expr()?;
-    let ty = ctx.sema.type_of_expr(&caller)?;
-
-    let type_name = ty.as_adt()?.name(ctx.sema.db).to_string();
-
-    for (unwrap_type, variant_name) in [("Result", "Ok"), ("Option", "Some")].iter() {
-        if &type_name == unwrap_type {
-            return ctx.add_assist(
-                AssistId("replace_unwrap_with_match"),
-                "Replace unwrap with match",
-                |edit| {
-                    let ok_path =
-                        make::path_unqualified(make::path_segment(make::name_ref(variant_name)));
-                    let it = make::bind_pat(make::name("a")).into();
-                    let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
-
-                    let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
-                    let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
-
-                    let unreachable_call = make::unreachable_macro_call().into();
-                    let err_arm = make::match_arm(
-                        iter::once(make::placeholder_pat().into()),
-                        unreachable_call,
-                    );
-
-                    let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
-                    let match_expr = make::expr_match(caller.clone(), match_arm_list);
-                    let match_expr =
-                        IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr);
-
-                    edit.target(method_call.syntax().text_range());
-                    edit.set_cursor(caller.syntax().text_range().start());
-                    edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
-                },
-            );
-        }
-    }
-    None
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::helpers::{check_assist, check_assist_target};
-
-    #[test]
-    fn test_replace_result_unwrap_with_match() {
-        check_assist(
-            replace_unwrap_with_match,
-            r"
-enum Result<T, E> { Ok(T), Err(E) }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = i(x).<|>unwrap();
-}
-            ",
-            r"
-enum Result<T, E> { Ok(T), Err(E) }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = <|>match i(x) {
-        Ok(a) => a,
-        _ => unreachable!(),
-    };
-}
-            ",
-        )
-    }
-
-    #[test]
-    fn test_replace_option_unwrap_with_match() {
-        check_assist(
-            replace_unwrap_with_match,
-            r"
-enum Option<T> { Some(T), None }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x = Option::Some(92);
-    let y = i(x).<|>unwrap();
-}
-            ",
-            r"
-enum Option<T> { Some(T), None }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x = Option::Some(92);
-    let y = <|>match i(x) {
-        Some(a) => a,
-        _ => unreachable!(),
-    };
-}
-            ",
-        );
-    }
-
-    #[test]
-    fn test_replace_result_unwrap_with_match_chaining() {
-        check_assist(
-            replace_unwrap_with_match,
-            r"
-enum Result<T, E> { Ok(T), Err(E) }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = i(x).<|>unwrap().count_zeroes();
-}
-            ",
-            r"
-enum Result<T, E> { Ok(T), Err(E) }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = <|>match i(x) {
-        Ok(a) => a,
-        _ => unreachable!(),
-    }.count_zeroes();
-}
-            ",
-        )
-    }
-
-    #[test]
-    fn replace_unwrap_with_match_target() {
-        check_assist_target(
-            replace_unwrap_with_match,
-            r"
-enum Option<T> { Some(T), None }
-fn i<T>(a: T) -> T { a }
-fn main() {
-    let x = Option::Some(92);
-    let y = i(x).<|>unwrap();
-}
-            ",
-            r"i(x).unwrap()",
-        );
-    }
-}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index becd5e99da..67a58fc1fb 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -118,8 +118,8 @@ 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;
 
     pub(crate) fn all() -> &'static [AssistHandler] {
@@ -154,8 +154,8 @@ 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,
-            replace_unwrap_with_match::replace_unwrap_with_match,
             split_import::split_import,
         ]
     }
diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml
index 8ab409158c..878c22ba98 100644
--- a/crates/ra_db/Cargo.toml
+++ b/crates/ra_db/Cargo.toml
@@ -15,5 +15,4 @@ rustc-hash = "1.1.0"
 ra_syntax = { path = "../ra_syntax" }
 ra_cfg = { path = "../ra_cfg" }
 ra_prof = { path = "../ra_prof" }
-ra_tt = { path = "../ra_tt" }
 test_utils = { path = "../test_utils" }
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 7777ce81e7..5e3e9203cd 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -70,7 +70,6 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
             meta.cfg,
             meta.env,
             Default::default(),
-            Default::default(),
         );
         crate_graph
     } else {
@@ -82,7 +81,6 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         crate_graph
     };
@@ -132,7 +130,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
                 meta.cfg,
                 meta.env,
                 Default::default(),
-                Default::default(),
             );
             let prev = crates.insert(krate.clone(), crate_id);
             assert!(prev.is_none());
@@ -170,7 +167,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
     } else {
         for (from, to) in crate_deps {
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index 5ddce98c65..e371f849df 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -10,7 +10,6 @@ use std::{
     fmt, ops,
     path::{Path, PathBuf},
     str::FromStr,
-    sync::Arc,
 };
 
 use ra_cfg::CfgOptions;
@@ -20,7 +19,6 @@ use rustc_hash::FxHashSet;
 
 use crate::{RelativePath, RelativePathBuf};
 use fmt::Display;
-use ra_tt::TokenExpander;
 
 /// `FileId` is an integer which uniquely identifies a file. File paths are
 /// messy and system-dependent, so most of the code should work directly with
@@ -117,22 +115,6 @@ impl Display for CrateName {
     }
 }
 
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-pub struct ProcMacroId(pub u32);
-
-#[derive(Debug, Clone)]
-pub struct ProcMacro {
-    pub name: SmolStr,
-    pub expander: Arc<dyn TokenExpander>,
-}
-
-impl Eq for ProcMacro {}
-impl PartialEq for ProcMacro {
-    fn eq(&self, other: &ProcMacro) -> bool {
-        self.name == other.name && Arc::ptr_eq(&self.expander, &other.expander)
-    }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct CrateData {
     pub root_file_id: FileId,
@@ -145,7 +127,6 @@ pub struct CrateData {
     pub env: Env,
     pub extern_source: ExternSource,
     pub dependencies: Vec<Dependency>,
-    pub proc_macro: Vec<ProcMacro>,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -185,11 +166,7 @@ impl CrateGraph {
         cfg_options: CfgOptions,
         env: Env,
         extern_source: ExternSource,
-        proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>,
     ) -> CrateId {
-        let proc_macro =
-            proc_macro.into_iter().map(|(name, it)| ProcMacro { name, expander: it }).collect();
-
         let data = CrateData {
             root_file_id: file_id,
             edition,
@@ -197,7 +174,6 @@ impl CrateGraph {
             cfg_options,
             env,
             extern_source,
-            proc_macro,
             dependencies: Vec::new(),
         };
         let crate_id = CrateId(self.arena.len() as u32);
@@ -369,7 +345,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let crate2 = graph.add_crate_root(
             FileId(2u32),
@@ -378,7 +353,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let crate3 = graph.add_crate_root(
             FileId(3u32),
@@ -387,7 +361,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
         assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
@@ -404,7 +377,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let crate2 = graph.add_crate_root(
             FileId(2u32),
@@ -413,7 +385,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let crate3 = graph.add_crate_root(
             FileId(3u32),
@@ -422,7 +393,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
         assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
@@ -438,7 +408,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let crate2 = graph.add_crate_root(
             FileId(2u32),
@@ -447,7 +416,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         assert!(graph
             .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index a06f59c140..bac24e2186 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -12,7 +12,7 @@ pub use crate::{
     cancellation::Canceled,
     input::{
         CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId,
-        FileId, ProcMacroId, SourceRoot, SourceRootId,
+        FileId, SourceRoot, SourceRootId,
     },
 };
 pub use relative_path::{RelativePath, RelativePathBuf};
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index 8fe3f8617f..9c125f32f3 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -11,7 +11,7 @@ use hir_expand::{
     HirFileId, MacroCallId, MacroDefId, MacroDefKind,
 };
 use ra_cfg::CfgOptions;
-use ra_db::{CrateId, FileId, ProcMacroId};
+use ra_db::{CrateId, FileId};
 use ra_syntax::ast;
 use rustc_hash::FxHashMap;
 use test_utils::tested_by;
@@ -53,16 +53,6 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> Cr
     }
 
     let cfg_options = &crate_graph[def_map.krate].cfg_options;
-    let proc_macros = &crate_graph[def_map.krate].proc_macro;
-    let proc_macros = proc_macros
-        .iter()
-        .enumerate()
-        .map(|(idx, it)| {
-            // FIXME: a hacky way to create a Name from string.
-            let name = tt::Ident { text: it.name.clone(), id: tt::TokenId::unspecified() };
-            (name.as_name(), ProcMacroExpander::new(def_map.krate, ProcMacroId(idx as u32)))
-        })
-        .collect();
 
     let mut collector = DefCollector {
         db,
@@ -75,7 +65,9 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> Cr
         unexpanded_attribute_macros: Vec::new(),
         mod_dirs: FxHashMap::default(),
         cfg_options,
-        proc_macros,
+
+        // FIXME: pass proc-macro from crate-graph
+        proc_macros: Default::default(),
     };
     collector.collect();
     collector.finish()
diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs
index 4d270e0def..a8dee20526 100644
--- a/crates/ra_hir_expand/src/proc_macro.rs
+++ b/crates/ra_hir_expand/src/proc_macro.rs
@@ -1,32 +1,33 @@
 //! Proc Macro Expander stub
 
-use crate::{db::AstDatabase, LazyMacroId};
-use ra_db::{CrateId, ProcMacroId};
+use crate::{db::AstDatabase, LazyMacroId, MacroCallKind, MacroCallLoc};
+use ra_db::CrateId;
 
 #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
 pub struct ProcMacroExpander {
     krate: CrateId,
-    proc_macro_id: ProcMacroId,
 }
 
 impl ProcMacroExpander {
-    pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander {
-        ProcMacroExpander { krate, proc_macro_id }
+    pub fn new(krate: CrateId) -> ProcMacroExpander {
+        ProcMacroExpander { krate }
     }
 
     pub fn expand(
         &self,
         db: &dyn AstDatabase,
-        _id: LazyMacroId,
-        tt: &tt::Subtree,
+        id: LazyMacroId,
+        _tt: &tt::Subtree,
     ) -> Result<tt::Subtree, mbe::ExpandError> {
-        let krate_graph = db.crate_graph();
-        let proc_macro = krate_graph[self.krate]
-            .proc_macro
-            .get(self.proc_macro_id.0 as usize)
-            .clone()
-            .ok_or_else(|| mbe::ExpandError::ConversionError)?;
+        let loc: MacroCallLoc = db.lookup_intern_macro(id);
+        let name = match loc.kind {
+            MacroCallKind::FnLike(_) => return Err(mbe::ExpandError::ConversionError),
+            MacroCallKind::Attr(_, name) => name,
+        };
 
-        proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
+        log::debug!("Proc-macro-expanding name = {}", name);
+
+        // Return nothing for now
+        return Ok(tt::Subtree::default());
     }
 }
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index e43414985f..5ab06c6cfd 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -213,7 +213,6 @@ impl Analysis {
             cfg_options,
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
         change.set_crate_graph(crate_graph);
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 2c13f206a1..2cf77a31f5 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -103,7 +103,6 @@ impl MockAnalysis {
                     cfg_options,
                     Env::default(),
                     Default::default(),
-                    Default::default(),
                 ));
             } else if path.ends_with("/lib.rs") {
                 let crate_name = path.parent().unwrap().file_name().unwrap();
@@ -114,7 +113,6 @@ impl MockAnalysis {
                     cfg_options,
                     Env::default(),
                     Default::default(),
-                    Default::default(),
                 );
                 if let Some(root_crate) = root_crate {
                     crate_graph
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs
index 958b92bed5..76d130b9b9 100644
--- a/crates/ra_ide/src/parent_module.rs
+++ b/crates/ra_ide/src/parent_module.rs
@@ -137,7 +137,6 @@ mod tests {
             CfgOptions::default(),
             Env::default(),
             Default::default(),
-            Default::default(),
         );
         let mut change = AnalysisChange::new();
         change.set_crate_graph(crate_graph);
diff --git a/crates/ra_mbe/src/lib.rs b/crates/ra_mbe/src/lib.rs
index 535b7daa07..6a9037bfc8 100644
--- a/crates/ra_mbe/src/lib.rs
+++ b/crates/ra_mbe/src/lib.rs
@@ -28,13 +28,6 @@ pub enum ExpandError {
     BindingError(String),
     ConversionError,
     InvalidRepeat,
-    ProcMacroError(tt::ExpansionError),
-}
-
-impl From<tt::ExpansionError> for ExpandError {
-    fn from(it: tt::ExpansionError) -> Self {
-        ExpandError::ProcMacroError(it)
-    }
 }
 
 pub use crate::syntax_bridge::{
diff --git a/crates/ra_proc_macro/Cargo.toml b/crates/ra_proc_macro/Cargo.toml
deleted file mode 100644
index bc2c37296d..0000000000
--- a/crates/ra_proc_macro/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-edition = "2018"
-name = "ra_proc_macro"
-version = "0.1.0"
-authors = ["rust-analyzer developers"]
-publish = false
-
-[lib]
-doctest = false
-
-[dependencies]
-ra_tt = { path = "../ra_tt" }
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs
deleted file mode 100644
index 5e21dd4872..0000000000
--- a/crates/ra_proc_macro/src/lib.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-//! Client-side Proc-Macro crate
-//!
-//! We separate proc-macro expanding logic to an extern program to allow
-//! different implementations (e.g. wasm or dylib loading). And this crate
-//! is used to provide basic infrastructure  for communication between two
-//! processes: Client (RA itself), Server (the external program)
-
-use ra_tt::{SmolStr, Subtree};
-use std::{
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ProcMacroProcessExpander {
-    process: Arc<ProcMacroProcessSrv>,
-    name: SmolStr,
-}
-
-impl ra_tt::TokenExpander for ProcMacroProcessExpander {
-    fn expand(
-        &self,
-        _subtree: &Subtree,
-        _attr: Option<&Subtree>,
-    ) -> Result<Subtree, ra_tt::ExpansionError> {
-        // FIXME: do nothing for now
-        Ok(Subtree::default())
-    }
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ProcMacroProcessSrv {
-    path: PathBuf,
-}
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum ProcMacroClient {
-    Process { process: Arc<ProcMacroProcessSrv> },
-    Dummy,
-}
-
-impl ProcMacroClient {
-    pub fn extern_process(process_path: &Path) -> ProcMacroClient {
-        let process = ProcMacroProcessSrv { path: process_path.into() };
-        ProcMacroClient::Process { process: Arc::new(process) }
-    }
-
-    pub fn dummy() -> ProcMacroClient {
-        ProcMacroClient::Dummy
-    }
-
-    pub fn by_dylib_path(
-        &self,
-        _dylib_path: &Path,
-    ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> {
-        // FIXME: return empty for now
-        vec![]
-    }
-}
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml
index cdcdd63c91..22300548a7 100644
--- a/crates/ra_project_model/Cargo.toml
+++ b/crates/ra_project_model/Cargo.toml
@@ -17,7 +17,6 @@ ra_arena = { path = "../ra_arena" }
 ra_db = { path = "../ra_db" }
 ra_cfg = { path = "../ra_cfg" }
 ra_cargo_watch = { path = "../ra_cargo_watch" }
-ra_proc_macro =  { path = "../ra_proc_macro" }
 
 serde = { version = "1.0.104", features = ["derive"] }
 serde_json = "1.0.48"
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs
index 291594e2a2..c7f9bd873e 100644
--- a/crates/ra_project_model/src/cargo_workspace.rs
+++ b/crates/ra_project_model/src/cargo_workspace.rs
@@ -83,7 +83,6 @@ pub struct PackageData {
     pub edition: Edition,
     pub features: Vec<String>,
     pub out_dir: Option<PathBuf>,
-    pub proc_macro_dylib_path: Option<PathBuf>,
 }
 
 #[derive(Debug, Clone)]
@@ -159,11 +158,8 @@ impl CargoWorkspace {
         })?;
 
         let mut out_dir_by_id = FxHashMap::default();
-        let mut proc_macro_dylib_paths = FxHashMap::default();
         if cargo_features.load_out_dirs_from_check {
-            let resources = load_extern_resources(cargo_toml, cargo_features);
-            out_dir_by_id = resources.out_dirs;
-            proc_macro_dylib_paths = resources.proc_dylib_paths;
+            out_dir_by_id = load_out_dirs(cargo_toml, cargo_features);
         }
 
         let mut pkg_by_id = FxHashMap::default();
@@ -187,7 +183,6 @@ impl CargoWorkspace {
                 dependencies: Vec::new(),
                 features: Vec::new(),
                 out_dir: out_dir_by_id.get(&id).cloned(),
-                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
             });
             let pkg_data = &mut packages[pkg];
             pkg_by_id.insert(id, pkg);
@@ -251,13 +246,10 @@ impl CargoWorkspace {
     }
 }
 
-#[derive(Debug, Clone, Default)]
-pub struct ExternResources {
-    out_dirs: FxHashMap<PackageId, PathBuf>,
-    proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
-}
-
-pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources {
+pub fn load_out_dirs(
+    cargo_toml: &Path,
+    cargo_features: &CargoFeatures,
+) -> FxHashMap<PackageId, PathBuf> {
     let mut args: Vec<String> = vec![
         "check".to_string(),
         "--message-format=json".to_string(),
@@ -275,21 +267,14 @@ pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures)
         args.extend(cargo_features.features.iter().cloned());
     }
 
-    let mut acc = ExternResources::default();
+    let mut acc = FxHashMap::default();
     let res = run_cargo(&args, cargo_toml.parent(), &mut |message| {
         match message {
             Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => {
-                acc.out_dirs.insert(package_id, out_dir);
+                acc.insert(package_id, out_dir);
             }
 
-            Message::CompilerArtifact(message) => {
-                if message.target.kind.contains(&"proc-macro".to_string()) {
-                    let package_id = message.package_id;
-                    if let Some(filename) = message.filenames.get(0) {
-                        acc.proc_dylib_paths.insert(package_id, filename.clone());
-                    }
-                }
-            }
+            Message::CompilerArtifact(_) => (),
             Message::CompilerMessage(_) => (),
             Message::Unknown => (),
         }
diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs
index b030c8a6a1..336446e58c 100644
--- a/crates/ra_project_model/src/json_project.rs
+++ b/crates/ra_project_model/src/json_project.rs
@@ -23,7 +23,6 @@ pub struct Crate {
     pub(crate) atom_cfgs: FxHashSet<String>,
     pub(crate) key_value_cfgs: FxHashMap<String, String>,
     pub(crate) out_dir: Option<PathBuf>,
-    pub(crate) proc_macro_dylib_path: Option<PathBuf>,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize)]
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index 444d3bb3f0..a3ef9acdc6 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -23,7 +23,6 @@ pub use crate::{
     json_project::JsonProject,
     sysroot::Sysroot,
 };
-pub use ra_proc_macro::ProcMacroClient;
 
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
 pub struct CargoTomlNotFoundError {
@@ -174,29 +173,6 @@ impl ProjectWorkspace {
         }
     }
 
-    pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> {
-        match self {
-            ProjectWorkspace::Json { project } => {
-                let mut proc_macro_dylib_paths = Vec::with_capacity(project.crates.len());
-                for krate in &project.crates {
-                    if let Some(out_dir) = &krate.proc_macro_dylib_path {
-                        proc_macro_dylib_paths.push(out_dir.to_path_buf());
-                    }
-                }
-                proc_macro_dylib_paths
-            }
-            ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => {
-                let mut proc_macro_dylib_paths = Vec::with_capacity(cargo.packages().len());
-                for pkg in cargo.packages() {
-                    if let Some(dylib_path) = &cargo[pkg].proc_macro_dylib_path {
-                        proc_macro_dylib_paths.push(dylib_path.to_path_buf());
-                    }
-                }
-                proc_macro_dylib_paths
-            }
-        }
-    }
-
     pub fn n_packages(&self) -> usize {
         match self {
             ProjectWorkspace::Json { project } => project.crates.len(),
@@ -210,7 +186,6 @@ impl ProjectWorkspace {
         &self,
         default_cfg_options: &CfgOptions,
         extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>,
-        proc_macro_client: &ProcMacroClient,
         load: &mut dyn FnMut(&Path) -> Option<FileId>,
     ) -> CrateGraph {
         let mut crate_graph = CrateGraph::default();
@@ -244,10 +219,7 @@ impl ProjectWorkspace {
                                 extern_source.set_extern_path(&out_dir, extern_source_id);
                             }
                         }
-                        let proc_macro = krate
-                            .proc_macro_dylib_path
-                            .clone()
-                            .map(|it| proc_macro_client.by_dylib_path(&it));
+
                         // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env
                         crates.insert(
                             crate_id,
@@ -259,7 +231,6 @@ impl ProjectWorkspace {
                                 cfg_options,
                                 env,
                                 extern_source,
-                                proc_macro.unwrap_or_default(),
                             ),
                         );
                     }
@@ -299,8 +270,6 @@ impl ProjectWorkspace {
 
                         let env = Env::default();
                         let extern_source = ExternSource::default();
-                        let proc_macro = vec![];
-
                         let crate_id = crate_graph.add_crate_root(
                             file_id,
                             Edition::Edition2018,
@@ -311,7 +280,6 @@ impl ProjectWorkspace {
                             cfg_options,
                             env,
                             extern_source,
-                            proc_macro,
                         );
                         sysroot_crates.insert(krate, crate_id);
                     }
@@ -359,12 +327,6 @@ impl ProjectWorkspace {
                                     extern_source.set_extern_path(&out_dir, extern_source_id);
                                 }
                             }
-                            let proc_macro = cargo[pkg]
-                                .proc_macro_dylib_path
-                                .as_ref()
-                                .map(|it| proc_macro_client.by_dylib_path(&it))
-                                .unwrap_or_default();
-
                             let crate_id = crate_graph.add_crate_root(
                                 file_id,
                                 edition,
@@ -372,7 +334,6 @@ impl ProjectWorkspace {
                                 cfg_options,
                                 env,
                                 extern_source,
-                                proc_macro.clone(),
                             );
                             if cargo[tgt].kind == TargetKind::Lib {
                                 lib_tgt = Some((crate_id, cargo[tgt].name.clone()));
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index f74c9f9c62..bdaecdc43d 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -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 @@ impl 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 @@ pub trait AstNodeEdit: AstNode + Sized {
         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,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 69bacf224f..c818bba555 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -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 {
@@ -258,10 +259,6 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken {
         .unwrap_or_else(|| panic!("unhandled token: {:?}", kind))
 }
 
-pub fn unreachable_macro_call() -> ast::MacroCall {
-    ast_from_text(&format!("unreachable!()"))
-}
-
 fn ast_from_text<N: AstNode>(text: &str) -> N {
     let parse = SourceFile::parse(text);
     let node = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/crates/ra_tt/src/lib.rs b/crates/ra_tt/src/lib.rs
index 1015ce0a60..1e2fb8b913 100644
--- a/crates/ra_tt/src/lib.rs
+++ b/crates/ra_tt/src/lib.rs
@@ -14,12 +14,9 @@ macro_rules! impl_froms {
     }
 }
 
-use std::{
-    fmt::{self, Debug},
-    panic::RefUnwindSafe,
-};
+use std::fmt;
 
-pub use smol_str::SmolStr;
+use smol_str::SmolStr;
 
 /// Represents identity of the token.
 ///
@@ -187,11 +184,3 @@ impl Subtree {
 }
 
 pub mod buffer;
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum ExpansionError {}
-
-pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe {
-    fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>)
-        -> Result<Subtree, ExpansionError>;
-}
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 832f04226c..54e2fa1a7c 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -7,9 +7,7 @@ use anyhow::Result;
 use crossbeam_channel::{unbounded, Receiver};
 use ra_db::{ExternSourceId, FileId, SourceRootId};
 use ra_ide::{AnalysisChange, AnalysisHost};
-use ra_project_model::{
-    get_rustc_cfg_options, CargoFeatures, PackageRoot, ProcMacroClient, ProjectWorkspace,
-};
+use ra_project_model::{get_rustc_cfg_options, CargoFeatures, PackageRoot, ProjectWorkspace};
 use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
 use rustc_hash::{FxHashMap, FxHashSet};
 
@@ -69,9 +67,7 @@ pub(crate) fn load_cargo(
             (source_root_id, project_root)
         })
         .collect::<FxHashMap<_, _>>();
-
-    let proc_macro_client = ProcMacroClient::dummy();
-    let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client);
+    let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs);
     Ok((host, source_roots))
 }
 
@@ -81,7 +77,6 @@ pub(crate) fn load(
     vfs: &mut Vfs,
     receiver: Receiver<VfsTask>,
     extern_dirs: FxHashSet<PathBuf>,
-    proc_macro_client: &ProcMacroClient,
 ) -> AnalysisHost {
     let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
     let mut host = AnalysisHost::new(lru_cap);
@@ -148,16 +143,12 @@ pub(crate) fn load(
         opts
     };
 
-    let crate_graph = ws.to_crate_graph(
-        &default_cfg_options,
-        &extern_source_roots,
-        proc_macro_client,
-        &mut |path: &Path| {
+    let crate_graph =
+        ws.to_crate_graph(&default_cfg_options, &extern_source_roots, &mut |path: &Path| {
             let vfs_file = vfs.load(path);
             log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
             vfs_file.map(vfs_file_to_id)
-        },
-    );
+        });
     log::debug!("crate graph: {:?}", crate_graph);
     analysis_change.set_crate_graph(crate_graph);
 
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index de85bb0178..c4244fee24 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -16,7 +16,7 @@ use ra_ide::{
     Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsOptions, LibraryData,
     SourceRootId,
 };
-use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace};
+use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
 use relative_path::RelativePathBuf;
 
@@ -150,19 +150,9 @@ impl WorldState {
             vfs_file.map(|f| FileId(f.0))
         };
 
-        let proc_macro_client =
-            ProcMacroClient::extern_process(std::path::Path::new("ra_proc_macro_srv"));
-
         workspaces
             .iter()
-            .map(|ws| {
-                ws.to_crate_graph(
-                    &default_cfg_options,
-                    &extern_source_roots,
-                    &proc_macro_client,
-                    &mut load,
-                )
-            })
+            .map(|ws| ws.to_crate_graph(&default_cfg_options, &extern_source_roots, &mut load))
             .for_each(|graph| {
                 crate_graph.extend(graph);
             });
diff --git a/docs/user/assists.md b/docs/user/assists.md
index b2568a954f..3b7467a26d 100644
--- a/docs/user/assists.md
+++ b/docs/user/assists.md
@@ -583,41 +583,41 @@ fn handle(action: Action) {
 }
 ```
 
-## `replace_qualified_name_with_use`
+## `replace_let_with_if_let`
 
-Adds a use statement for a given fully-qualified name.
+Replaces `if let` with an else branch with a `match` expression.
 
 ```rust
 // BEFORE
-fn process(map: std::collections::┃HashMap<String, String>) {}
+
+fn main(action: Action) {
+    ┃let x = compute();
+}
+
+fn compute() -> Option<i32> { None }
 
 // AFTER
-use std::collections::HashMap;
 
-fn process(map: HashMap<String, String>) {}
+fn main(action: Action) {
+    if let Some(x) = compute() {
+    }
+}
+
+fn compute() -> Option<i32> { None }
 ```
 
-## `replace_unwrap_with_match`
+## `replace_qualified_name_with_use`
 
-Replaces `unwrap` a `match` expression. Works for Result and Option.
+Adds a use statement for a given fully-qualified name.
 
 ```rust
 // BEFORE
-enum Result<T, E> { Ok(T), Err(E) }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = x.┃unwrap();
-}
+fn process(map: std::collections::┃HashMap<String, String>) {}
 
 // AFTER
-enum Result<T, E> { Ok(T), Err(E) }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = match x {
-        Ok(a) => a,
-        _ => unreachable!(),
-    };
-}
+use std::collections::HashMap;
+
+fn process(map: HashMap<String, String>) {}
 ```
 
 ## `split_import`
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts
index d64ac726e2..03528b8252 100644
--- a/editors/code/src/commands/server_version.ts
+++ b/editors/code/src/commands/server_version.ts
@@ -4,12 +4,7 @@ import { Ctx, Cmd } from '../ctx';
 
 export function serverVersion(ctx: Ctx): Cmd {
     return async () => {
-        const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
-        const commitHash = stdout.slice(`rust-analyzer `.length).trim();
-        const { releaseTag } = ctx.config.package;
-
-        void vscode.window.showInformationMessage(
-            `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
-        );
+        const version = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" }).stdout;
+        vscode.window.showInformationMessage('rust-analyzer version : ' + version);
     };
 }
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index e77462c1b0..637aea27da 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -21,7 +21,7 @@ export class Config {
 
     readonly package: {
         version: string;
-        releaseTag: string | null;
+        releaseTag: string | undefined;
         enableProposedApi: boolean | undefined;
     } = vscode.extensions.getExtension(this.extensionId)!.packageJSON;
 
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 980ed925b4..de27d95353 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -48,19 +48,21 @@ export async function activate(context: vscode.ExtensionContext) {
     ctx = await Ctx.create(config, context, serverPath);
 
     // Commands which invokes manually via command palette, shortcut, etc.
-
-    // Reloading is inspired by @DanTup maneuver: https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895
-    ctx.registerCommand('reload', _ => async () => {
-        void vscode.window.showInformationMessage('Reloading rust-analyzer...');
-        await deactivate();
-        while (context.subscriptions.length > 0) {
-            try {
-                context.subscriptions.pop()!.dispose();
-            } catch (err) {
-                log.error("Dispose error:", err);
+    ctx.registerCommand('reload', (ctx) => {
+        return async () => {
+            vscode.window.showInformationMessage('Reloading rust-analyzer...');
+            // @DanTup maneuver
+            // https://github.com/microsoft/vscode/issues/45774#issuecomment-373423895
+            await deactivate();
+            for (const sub of ctx.subscriptions) {
+                try {
+                    sub.dispose();
+                } catch (e) {
+                    log.error(e);
+                }
             }
-        }
-        await activate(context).catch(log.error);
+            await activate(context);
+        };
     });
 
     ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
@@ -94,7 +96,7 @@ export async function activate(context: vscode.ExtensionContext) {
 }
 
 export async function deactivate() {
-    await ctx?.client.stop();
+    await ctx?.client?.stop();
     ctx = undefined;
 }
 
@@ -108,13 +110,11 @@ async function bootstrap(config: Config, state: PersistentState): Promise<string
 }
 
 async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> {
-    if (config.package.releaseTag === null) return;
+    if (config.package.releaseTag === undefined) return;
     if (config.channel === "stable") {
         if (config.package.releaseTag === NIGHTLY_TAG) {
-            void vscode.window.showWarningMessage(
-                `You are running a nightly version of rust-analyzer extension. ` +
-                `To switch to stable, uninstall the extension and re-install it from the marketplace`
-            );
+            vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension.
+To switch to stable, uninstall the extension and re-install it from the marketplace`);
         }
         return;
     };
@@ -169,7 +169,9 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise<
     log.debug("Checked binary availability via --version", res);
     log.debug(res, "--version output:", res.output);
     if (res.status !== 0) {
-        throw new Error(`Failed to execute ${path} --version`);
+        throw new Error(
+            `Failed to execute ${path} --version`
+        );
     }
 
     return path;
@@ -183,7 +185,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
         }
         return explicitPath;
     };
-    if (config.package.releaseTag === null) return "rust-analyzer";
+    if (config.package.releaseTag === undefined) return "rust-analyzer";
 
     let binaryName: string | undefined = undefined;
     if (process.arch === "x64" || process.arch === "ia32") {


Test patch:
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index 543224232f..c0486a8a91 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -608,40 +608,42 @@ fn handle(action: Action) {
 }
 
 #[test]
-fn doctest_replace_qualified_name_with_use() {
+fn doctest_replace_let_with_if_let() {
     check(
-        "replace_qualified_name_with_use",
+        "replace_let_with_if_let",
         r#####"
-fn process(map: std::collections::<|>HashMap<String, String>) {}
+enum Option<T> { Some(T), None }
+
+fn main(action: Action) {
+    <|>let x = compute();
+}
+
+fn compute() -> Option<i32> { None }
 "#####,
         r#####"
-use std::collections::HashMap;
+enum Option<T> { Some(T), None }
 
-fn process(map: HashMap<String, String>) {}
+fn main(action: Action) {
+    if let Some(x) = compute() {
+    }
+}
+
+fn compute() -> Option<i32> { None }
 "#####,
     )
 }
 
 #[test]
-fn doctest_replace_unwrap_with_match() {
+fn doctest_replace_qualified_name_with_use() {
     check(
-        "replace_unwrap_with_match",
+        "replace_qualified_name_with_use",
         r#####"
-enum Result<T, E> { Ok(T), Err(E) }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = x.<|>unwrap();
-}
+fn process(map: std::collections::<|>HashMap<String, String>) {}
 "#####,
         r#####"
-enum Result<T, E> { Ok(T), Err(E) }
-fn main() {
-    let x: Result<i32, i32> = Result::Ok(92);
-    let y = match x {
-        Ok(a) => a,
-        _ => unreachable!(),
-    };
-}
+use std::collections::HashMap;
+
+fn process(map: HashMap<String, String>) {}
 "#####,
     )
 }