Repository: rust-analyzer/rust-analyzer
Pull request #6745 (2020-12-07 16:21:36)

Some more proc macro cleanups
* Remove `ProcMacroClient::dummy` and just use `Option<ProcMacroClient>` instead
* Remember the type of proc macros (later allows us to reject using an incorrect macro type)
* Prepare a few internals for procedural attribute macros

bors r+


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index f1847e7699..b73fb909c9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1187,6 +1187,7 @@ dependencies = [
 name = "proc_macro_api"
 version = "0.0.0"
 dependencies = [
+ "base_db",
  "crossbeam-channel 0.5.0",
  "jod-thread",
  "log",
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
index 7bcc318a99..7fd78e9d47 100644
--- a/crates/assists/src/handlers/early_return.rs
+++ b/crates/assists/src/handlers/early_return.rs
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
                         let then_branch =
                             make::block_expr(once(make::expr_stmt(early_expression).into()), None);
                         let cond = invert_boolean_expression(cond_expr);
-                        make::expr_if(make::condition(cond, None), then_branch, None)
+                        make::expr_if(make::condition(cond, None), then_branch)
                             .indent(if_indent_level)
                     };
                     replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
diff --git a/crates/assists/src/handlers/move_guard.rs b/crates/assists/src/handlers/move_guard.rs
index eaffd80ce3..e1855b63d4 100644
--- a/crates/assists/src/handlers/move_guard.rs
+++ b/crates/assists/src/handlers/move_guard.rs
@@ -42,7 +42,6 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
     let if_expr = make::expr_if(
         make::condition(guard_condition, None),
         make::block_expr(None, Some(arm_expr.clone())),
-        None,
     )
     .indent(arm_expr.indent_level());
 
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
index 4a355c66f0..9a49c48c1f 100644
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -1,6 +1,3 @@
-use std::iter;
-
-use ide_db::{ty_filter::TryEnum, RootDatabase};
 use syntax::{
     ast::{
         self,
@@ -11,6 +8,7 @@ use syntax::{
 };
 
 use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
+use ide_db::ty_filter::TryEnum;
 
 // Assist: replace_if_let_with_match
 //
@@ -81,91 +79,6 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
     )
 }
 
-// Assist: replace_match_with_if_let
-//
-// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
-//
-// ```
-// enum Action { Move { distance: u32 }, Stop }
-//
-// fn handle(action: Action) {
-//     <|>match action {
-//         Action::Move { distance } => foo(distance),
-//         _ => bar(),
-//     }
-// }
-// ```
-// ->
-// ```
-// enum Action { Move { distance: u32 }, Stop }
-//
-// fn handle(action: Action) {
-//     if let Action::Move { distance } = action {
-//         foo(distance)
-//     } else {
-//         bar()
-//     }
-// }
-// ```
-pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
-    let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
-    let mut arms = match_expr.match_arm_list()?.arms();
-    let first_arm = arms.next()?;
-    let second_arm = arms.next()?;
-    if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
-        return None;
-    }
-    let condition_expr = match_expr.expr()?;
-    let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
-    {
-        (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
-    } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
-        (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
-    } else {
-        return None;
-    };
-
-    let target = match_expr.syntax().text_range();
-    acc.add(
-        AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
-        "Replace with if let",
-        target,
-        move |edit| {
-            let condition = make::condition(condition_expr, Some(if_let_pat));
-            let then_block = match then_expr.reset_indent() {
-                ast::Expr::BlockExpr(block) => block,
-                expr => make::block_expr(iter::empty(), Some(expr)),
-            };
-            let else_expr = match else_expr {
-                ast::Expr::BlockExpr(block)
-                    if block.statements().count() == 0 && block.expr().is_none() =>
-                {
-                    None
-                }
-                ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
-                expr => Some(expr),
-            };
-            let if_let_expr = make::expr_if(
-                condition,
-                then_block,
-                else_expr.map(|else_expr| {
-                    ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
-                }),
-            )
-            .indent(IndentLevel::from_node(match_expr.syntax()));
-
-            edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
-        },
-    )
-}
-
-fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
-    sema.type_of_pat(&pat)
-        .and_then(|ty| TryEnum::from_ty(sema, &ty))
-        .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
-        .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -336,194 +249,6 @@ fn main() {
         }
     }
 }
-"#,
-        )
-    }
-
-    #[test]
-    fn test_replace_match_with_if_let_unwraps_simple_expressions() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-impl VariantData {
-    pub fn is_struct(&self) -> bool {
-        <|>match *self {
-            VariantData::Struct(..) => true,
-            _ => false,
-        }
-    }
-}           "#,
-            r#"
-impl VariantData {
-    pub fn is_struct(&self) -> bool {
-        if let VariantData::Struct(..) = *self {
-            true
-        } else {
-            false
-        }
-    }
-}           "#,
-        )
-    }
-
-    #[test]
-    fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-fn foo() {
-    <|>match a {
-        VariantData::Struct(..) => {
-            bar(
-                123
-            )
-        }
-        _ => false,
-    }
-}           "#,
-            r#"
-fn foo() {
-    if let VariantData::Struct(..) = a {
-        bar(
-            123
-        )
-    } else {
-        false
-    }
-}           "#,
-        )
-    }
-
-    #[test]
-    fn replace_match_with_if_let_target() {
-        check_assist_target(
-            replace_match_with_if_let,
-            r#"
-impl VariantData {
-    pub fn is_struct(&self) -> bool {
-        <|>match *self {
-            VariantData::Struct(..) => true,
-            _ => false,
-        }
-    }
-}           "#,
-            r#"match *self {
-            VariantData::Struct(..) => true,
-            _ => false,
-        }"#,
-        );
-    }
-
-    #[test]
-    fn special_case_option_match_to_if_let() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-enum Option<T> { Some(T), None }
-use Option::*;
-
-fn foo(x: Option<i32>) {
-    <|>match x {
-        Some(x) => println!("{}", x),
-        None => println!("none"),
-    }
-}
-           "#,
-            r#"
-enum Option<T> { Some(T), None }
-use Option::*;
-
-fn foo(x: Option<i32>) {
-    if let Some(x) = x {
-        println!("{}", x)
-    } else {
-        println!("none")
-    }
-}
-           "#,
-        );
-    }
-
-    #[test]
-    fn special_case_result_match_to_if_let() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-enum Result<T, E> { Ok(T), Err(E) }
-use Result::*;
-
-fn foo(x: Result<i32, ()>) {
-    <|>match x {
-        Ok(x) => println!("{}", x),
-        Err(_) => println!("none"),
-    }
-}
-           "#,
-            r#"
-enum Result<T, E> { Ok(T), Err(E) }
-use Result::*;
-
-fn foo(x: Result<i32, ()>) {
-    if let Ok(x) = x {
-        println!("{}", x)
-    } else {
-        println!("none")
-    }
-}
-           "#,
-        );
-    }
-
-    #[test]
-    fn nested_indent_match_to_if_let() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-fn main() {
-    if true {
-        <|>match path.strip_prefix(root_path) {
-            Ok(rel_path) => {
-                let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
-                Some((*id, rel_path))
-            }
-            _ => None,
-        }
-    }
-}
-"#,
-            r#"
-fn main() {
-    if true {
-        if let Ok(rel_path) = path.strip_prefix(root_path) {
-            let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
-            Some((*id, rel_path))
-        } else {
-            None
-        }
-    }
-}
-"#,
-        )
-    }
-
-    #[test]
-    fn replace_match_with_if_let_empty_wildcard_expr() {
-        check_assist(
-            replace_match_with_if_let,
-            r#"
-fn main() {
-    <|>match path.strip_prefix(root_path) {
-        Ok(rel_path) => println!("{}", rel_path),
-        _ => (),
-    }
-}
-"#,
-            r#"
-fn main() {
-    if let Ok(rel_path) = path.strip_prefix(root_path) {
-        println!("{}", rel_path)
-    }
-}
 "#,
         )
     }
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index 5970e283cf..69d3b08d36 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
             };
             let block =
                 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
-            let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, 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::WildcardPat::cast).unwrap();
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index b8ce7418d6..dfe6c27295 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -209,7 +209,6 @@ mod handlers {
             reorder_fields::reorder_fields,
             replace_derive_with_manual_impl::replace_derive_with_manual_impl,
             replace_if_let_with_match::replace_if_let_with_match,
-            replace_if_let_with_match::replace_match_with_if_let,
             replace_impl_trait_with_generic::replace_impl_trait_with_generic,
             replace_let_with_if_let::replace_let_with_if_let,
             replace_qualified_name_with_use::replace_qualified_name_with_use,
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 98ba372ad8..cda5e57dc7 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -143,9 +143,17 @@ impl CrateDisplayName {
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub struct ProcMacroId(pub u32);
 
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+pub enum ProcMacroKind {
+    CustomDerive,
+    FuncLike,
+    Attr,
+}
+
 #[derive(Debug, Clone)]
 pub struct ProcMacro {
     pub name: SmolStr,
+    pub kind: ProcMacroKind,
     pub expander: Arc<dyn TokenExpander>,
 }
 
@@ -198,11 +206,8 @@ impl CrateGraph {
         display_name: Option<CrateDisplayName>,
         cfg_options: CfgOptions,
         env: Env,
-        proc_macro: Vec<(SmolStr, Arc<dyn tt::TokenExpander>)>,
+        proc_macro: Vec<ProcMacro>,
     ) -> 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,
diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index ce75a5337a..5571af4954 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -14,7 +14,7 @@ pub use crate::{
     change::Change,
     input::{
         CrateData, CrateDisplayName, CrateGraph, CrateId, CrateName, Dependency, Edition, Env,
-        ProcMacroId, SourceRoot, SourceRootId,
+        ProcMacro, ProcMacroId, ProcMacroKind, SourceRoot, SourceRootId,
     },
 };
 pub use salsa;
diff --git a/crates/hir_def/src/docs.rs b/crates/hir_def/src/docs.rs
index 3e59a8f47f..e9a02b11bb 100644
--- a/crates/hir_def/src/docs.rs
+++ b/crates/hir_def/src/docs.rs
@@ -6,8 +6,7 @@
 use std::sync::Arc;
 
 use either::Either;
-use itertools::Itertools;
-use syntax::{ast, SmolStr};
+use syntax::ast;
 
 use crate::{
     db::DefDatabase,
@@ -94,7 +93,7 @@ fn merge_doc_comments_and_attrs(
 ) -> Option<String> {
     match (doc_comment_text, doc_attr_text) {
         (Some(mut comment_text), Some(attr_text)) => {
-            comment_text.push_str("\n");
+            comment_text.push_str("\n\n");
             comment_text.push_str(&attr_text);
             Some(comment_text)
         }
@@ -106,16 +105,17 @@ fn merge_doc_comments_and_attrs(
 
 fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option<String> {
     let mut docs = String::new();
-    owner
-        .attrs()
-        .filter_map(|attr| attr.as_simple_key_value().filter(|(key, _)| key == "doc"))
-        .map(|(_, value)| value)
-        .intersperse(SmolStr::new_inline("\n"))
-        // No FromIterator<SmolStr> for String
-        .for_each(|s| docs.push_str(s.as_str()));
+    for attr in owner.attrs() {
+        if let Some(("doc", value)) =
+            attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str()))
+        {
+            docs.push_str(value);
+            docs.push_str("\n\n");
+        }
+    }
     if docs.is_empty() {
         None
     } else {
-        Some(docs)
+        Some(docs.trim_end_matches("\n\n").to_owned())
     }
 }
diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs
index 97edf0fb6d..38882d2b68 100644
--- a/crates/hir_expand/src/proc_macro.rs
+++ b/crates/hir_expand/src/proc_macro.rs
@@ -26,7 +26,6 @@ impl ProcMacroExpander {
 
     pub fn dummy(krate: CrateId) -> Self {
         // FIXME: Should store the name for better errors
-        // FIXME: I think this is the second layer of "dummy" expansion, we should reduce that
         Self { krate, proc_macro_id: None }
     }
 
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index dc9621f464..462f5c2b8b 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -1525,7 +1525,9 @@ fn foo() { let bar = Ba<|>r; }
                 ---
 
                 bar docs 0
+
                 bar docs 1
+
                 bar docs 2
             "#]],
         );
diff --git a/crates/proc_macro_api/Cargo.toml b/crates/proc_macro_api/Cargo.toml
index 3863e51895..1af2bbca78 100644
--- a/crates/proc_macro_api/Cargo.toml
+++ b/crates/proc_macro_api/Cargo.toml
@@ -17,3 +17,4 @@ crossbeam-channel = "0.5.0"
 jod-thread = "0.1.1"
 
 tt = { path = "../tt", version = "0.0.0" }
+base_db = { path = "../base_db", version = "0.0.0" }
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs
index d5e87cf7d5..0d061fd536 100644
--- a/crates/proc_macro_api/src/lib.rs
+++ b/crates/proc_macro_api/src/lib.rs
@@ -16,6 +16,7 @@ use std::{
     sync::Arc,
 };
 
+use base_db::ProcMacro;
 use tt::{SmolStr, Subtree};
 
 use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread};
@@ -23,7 +24,7 @@ use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread};
 pub use rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind};
 
 #[derive(Debug, Clone)]
-pub struct ProcMacroProcessExpander {
+struct ProcMacroProcessExpander {
     process: Arc<ProcMacroProcessSrv>,
     dylib_path: PathBuf,
     name: SmolStr,
@@ -42,21 +43,24 @@ impl tt::TokenExpander for ProcMacroProcessExpander {
     fn expand(
         &self,
         subtree: &Subtree,
-        _attr: Option<&Subtree>,
+        attr: Option<&Subtree>,
     ) -> Result<Subtree, tt::ExpansionError> {
-        self.process.custom_derive(&self.dylib_path, subtree, &self.name)
-    }
-}
+        let task = ExpansionTask {
+            macro_body: subtree.clone(),
+            macro_name: self.name.to_string(),
+            attributes: attr.cloned(),
+            lib: self.dylib_path.to_path_buf(),
+        };
 
-#[derive(Debug)]
-enum ProcMacroClientKind {
-    Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread },
-    Dummy,
+        let result: ExpansionResult = self.process.send_task(msg::Request::ExpansionMacro(task))?;
+        Ok(result.expansion)
+    }
 }
 
 #[derive(Debug)]
 pub struct ProcMacroClient {
-    kind: ProcMacroClientKind,
+    process: Arc<ProcMacroProcessSrv>,
+    thread: ProcMacroProcessThread,
 }
 
 impl ProcMacroClient {
@@ -65,47 +69,35 @@ impl ProcMacroClient {
         args: impl IntoIterator<Item = impl AsRef<OsStr>>,
     ) -> io::Result<ProcMacroClient> {
         let (thread, process) = ProcMacroProcessSrv::run(process_path, args)?;
-        Ok(ProcMacroClient {
-            kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
-        })
+        Ok(ProcMacroClient { process: Arc::new(process), thread })
     }
 
-    pub fn dummy() -> ProcMacroClient {
-        ProcMacroClient { kind: ProcMacroClientKind::Dummy }
-    }
+    pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<ProcMacro> {
+        let macros = match self.process.find_proc_macros(dylib_path) {
+            Err(err) => {
+                eprintln!("Failed to find proc macros. Error: {:#?}", err);
+                return vec![];
+            }
+            Ok(macros) => macros,
+        };
 
-    pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<(SmolStr, Arc<dyn tt::TokenExpander>)> {
-        match &self.kind {
-            ProcMacroClientKind::Dummy => vec![],
-            ProcMacroClientKind::Process { process, .. } => {
-                let macros = match process.find_proc_macros(dylib_path) {
-                    Err(err) => {
-                        eprintln!("Failed to find proc macros. Error: {:#?}", err);
-                        return vec![];
-                    }
-                    Ok(macros) => macros,
+        macros
+            .into_iter()
+            .map(|(name, kind)| {
+                let name = SmolStr::new(&name);
+                let kind = match kind {
+                    ProcMacroKind::CustomDerive => base_db::ProcMacroKind::CustomDerive,
+                    ProcMacroKind::FuncLike => base_db::ProcMacroKind::FuncLike,
+                    ProcMacroKind::Attr => base_db::ProcMacroKind::Attr,
                 };
+                let expander: Arc<dyn tt::TokenExpander> = Arc::new(ProcMacroProcessExpander {
+                    process: self.process.clone(),
+                    name: name.clone(),
+                    dylib_path: dylib_path.into(),
+                });
 
-                macros
-                    .into_iter()
-                    .filter_map(|(name, kind)| {
-                        match kind {
-                            ProcMacroKind::CustomDerive | ProcMacroKind::FuncLike => {
-                                let name = SmolStr::new(&name);
-                                let expander: Arc<dyn tt::TokenExpander> =
-                                    Arc::new(ProcMacroProcessExpander {
-                                        process: process.clone(),
-                                        name: name.clone(),
-                                        dylib_path: dylib_path.into(),
-                                    });
-                                Some((name, expander))
-                            }
-                            // FIXME: Attribute macro are currently unsupported.
-                            ProcMacroKind::Attr => None,
-                        }
-                    })
-                    .collect()
-            }
-        }
+                ProcMacro { name, kind, expander }
+            })
+            .collect()
     }
 }
diff --git a/crates/proc_macro_api/src/process.rs b/crates/proc_macro_api/src/process.rs
index 301888a0e0..d68723ada3 100644
--- a/crates/proc_macro_api/src/process.rs
+++ b/crates/proc_macro_api/src/process.rs
@@ -10,11 +10,10 @@ use std::{
 };
 
 use crossbeam_channel::{bounded, Receiver, Sender};
-use tt::Subtree;
 
 use crate::{
     msg::{ErrorCode, Message, Request, Response, ResponseError},
-    rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind},
+    rpc::{ListMacrosResult, ListMacrosTask, ProcMacroKind},
 };
 
 #[derive(Debug, Default)]
@@ -58,23 +57,6 @@ impl ProcMacroProcessSrv {
         Ok(result.macros)
     }
 
-    pub(crate) fn custom_derive(
-        &self,
-        dylib_path: &Path,
-        subtree: &Subtree,
-        derive_name: &str,
-    ) -> Result<Subtree, tt::ExpansionError> {
-        let task = ExpansionTask {
-            macro_body: subtree.clone(),
-            macro_name: derive_name.to_string(),
-            attributes: None,
-            lib: dylib_path.to_path_buf(),
-        };
-
-        let result: ExpansionResult = self.send_task(Request::ExpansionMacro(task))?;
-        Ok(result.expansion)
-    }
-
     pub(crate) fn send_task<R>(&self, req: Request) -> Result<R, tt::ExpansionError>
     where
         R: TryFrom<Response, Error = &'static str>,
diff --git a/crates/proc_macro_api/src/rpc.rs b/crates/proc_macro_api/src/rpc.rs
index 203109ca40..b85f92eead 100644
--- a/crates/proc_macro_api/src/rpc.rs
+++ b/crates/proc_macro_api/src/rpc.rs
@@ -19,7 +19,7 @@ pub struct ListMacrosTask {
     pub lib: PathBuf,
 }
 
-#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
+#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
 pub enum ProcMacroKind {
     CustomDerive,
     FuncLike,
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs
index a71f96164d..ab5cbae11e 100644
--- a/crates/project_model/src/workspace.rs
+++ b/crates/project_model/src/workspace.rs
@@ -2,10 +2,14 @@
 //! metadata` or `rust-project.json`) into representation stored in the salsa
 //! database -- `CrateGraph`.
 
-use std::{fmt, fs, path::Component, process::Command};
+use std::{
+    fmt, fs,
+    path::{Component, Path},
+    process::Command,
+};
 
 use anyhow::{Context, Result};
-use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId};
+use base_db::{CrateDisplayName, CrateGraph, CrateId, CrateName, Edition, Env, FileId, ProcMacro};
 use cfg::CfgOptions;
 use paths::{AbsPath, AbsPathBuf};
 use proc_macro_api::ProcMacroClient;
@@ -194,15 +198,20 @@ impl ProjectWorkspace {
     pub fn to_crate_graph(
         &self,
         target: Option<&str>,
-        proc_macro_client: &ProcMacroClient,
+        proc_macro_client: Option<&ProcMacroClient>,
         load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     ) -> CrateGraph {
+        let proc_macro_loader = |path: &Path| match proc_macro_client {
+            Some(client) => client.by_dylib_path(path),
+            None => Vec::new(),
+        };
+
         let mut crate_graph = match self {
             ProjectWorkspace::Json { project, sysroot } => {
-                project_json_to_crate_graph(target, proc_macro_client, load, project, sysroot)
+                project_json_to_crate_graph(target, &proc_macro_loader, load, project, sysroot)
             }
             ProjectWorkspace::Cargo { cargo, sysroot, rustc } => {
-                cargo_to_crate_graph(target, proc_macro_client, load, cargo, sysroot, rustc)
+                cargo_to_crate_graph(target, &proc_macro_loader, load, cargo, sysroot, rustc)
             }
         };
         if crate_graph.patch_cfg_if() {
@@ -216,7 +225,7 @@ impl ProjectWorkspace {
 
 fn project_json_to_crate_graph(
     target: Option<&str>,
-    proc_macro_client: &ProcMacroClient,
+    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     project: &ProjectJson,
     sysroot: &Option<Sysroot>,
@@ -236,8 +245,7 @@ fn project_json_to_crate_graph(
         })
         .map(|(crate_id, krate, file_id)| {
             let env = krate.env.clone().into_iter().collect();
-            let proc_macro =
-                krate.proc_macro_dylib_path.clone().map(|it| proc_macro_client.by_dylib_path(&it));
+            let proc_macro = krate.proc_macro_dylib_path.clone().map(|it| proc_macro_loader(&it));
 
             let target = krate.target.as_deref().or(target);
             let target_cfgs =
@@ -279,7 +287,7 @@ fn project_json_to_crate_graph(
 
 fn cargo_to_crate_graph(
     target: Option<&str>,
-    proc_macro_client: &ProcMacroClient,
+    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
     load: &mut dyn FnMut(&AbsPath) -> Option<FileId>,
     cargo: &CargoWorkspace,
     sysroot: &Sysroot,
@@ -309,7 +317,7 @@ fn cargo_to_crate_graph(
                     &mut crate_graph,
                     &cargo[pkg],
                     &cfg_options,
-                    proc_macro_client,
+                    proc_macro_loader,
                     file_id,
                 );
                 if cargo[tgt].kind == TargetKind::Lib {
@@ -385,7 +393,7 @@ fn cargo_to_crate_graph(
                         &mut crate_graph,
                         &rustc_workspace[pkg],
                         &cfg_options,
-                        proc_macro_client,
+                        proc_macro_loader,
                         file_id,
                     );
                     pkg_to_lib_crate.insert(pkg, crate_id);
@@ -433,7 +441,7 @@ fn add_target_crate_root(
     crate_graph: &mut CrateGraph,
     pkg: &cargo_workspace::PackageData,
     cfg_options: &CfgOptions,
-    proc_macro_client: &ProcMacroClient,
+    proc_macro_loader: &dyn Fn(&Path) -> Vec<ProcMacro>,
     file_id: FileId,
 ) -> CrateId {
     let edition = pkg.edition;
@@ -452,11 +460,8 @@ fn add_target_crate_root(
             env.set("OUT_DIR", out_dir);
         }
     }
-    let proc_macro = pkg
-        .proc_macro_dylib_path
-        .as_ref()
-        .map(|it| proc_macro_client.by_dylib_path(&it))
-        .unwrap_or_default();
+    let proc_macro =
+        pkg.proc_macro_dylib_path.as_ref().map(|it| proc_macro_loader(&it)).unwrap_or_default();
 
     let display_name = CrateDisplayName::from_canonical_name(pkg.name.clone());
     let crate_id = crate_graph.add_crate_root(
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 76526c66c0..d51f4a93ad 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -33,12 +33,12 @@ pub fn load_cargo(
 
     let proc_macro_client = if with_proc_macro {
         let path = std::env::current_exe()?;
-        ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap()
+        Some(ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap())
     } else {
-        ProcMacroClient::dummy()
+        None
     };
 
-    let crate_graph = ws.to_crate_graph(None, &proc_macro_client, &mut |path: &AbsPath| {
+    let crate_graph = ws.to_crate_graph(None, proc_macro_client.as_ref(), &mut |path: &AbsPath| {
         let contents = loader.load_sync(path);
         let path = vfs::VfsPath::from(path.to_path_buf());
         vfs.set_file_contents(path.clone(), contents);
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index defe11c553..a27495d0d8 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -75,7 +75,7 @@ pub(crate) struct GlobalState {
     pub(crate) shutdown_requested: bool,
     pub(crate) status: Status,
     pub(crate) source_root_config: SourceRootConfig,
-    pub(crate) proc_macro_client: ProcMacroClient,
+    pub(crate) proc_macro_client: Option<ProcMacroClient>,
     pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
     latest_requests: Arc<RwLock<LatestRequests>>,
 }
@@ -127,7 +127,7 @@ impl GlobalState {
             shutdown_requested: false,
             status: Status::default(),
             source_root_config: SourceRootConfig::default(),
-            proc_macro_client: ProcMacroClient::dummy(),
+            proc_macro_client: None,
             workspaces: Arc::new(Vec::new()),
             latest_requests: Default::default(),
         }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index b2d35f5355..79e39e3a59 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -171,16 +171,16 @@ impl GlobalState {
         let project_folders = ProjectFolders::new(&workspaces);
 
         self.proc_macro_client = match &self.config.proc_macro_srv {
-            None => ProcMacroClient::dummy(),
+            None => None,
             Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) {
-                Ok(it) => it,
+                Ok(it) => Some(it),
                 Err(err) => {
                     log::error!(
                         "Failed to run proc_macro_srv from path {}, error: {:?}",
                         path.display(),
                         err
                     );
-                    ProcMacroClient::dummy()
+                    None
                 }
             },
         };
@@ -212,7 +212,7 @@ impl GlobalState {
             for ws in workspaces.iter() {
                 crate_graph.extend(ws.to_crate_graph(
                     self.config.cargo.target.as_deref(),
-                    &self.proc_macro_client,
+                    self.proc_macro_client.as_ref(),
                     &mut load,
                 ));
             }
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index cc09b77a59..876659a2b7 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -171,17 +171,8 @@ pub fn expr_return() -> ast::Expr {
 pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
     expr_from_text(&format!("match {} {}", expr, match_arm_list))
 }
-pub fn expr_if(
-    condition: ast::Condition,
-    then_branch: ast::BlockExpr,
-    else_branch: Option<ast::ElseBranch>,
-) -> ast::Expr {
-    let else_branch = match else_branch {
-        Some(ast::ElseBranch::Block(block)) => format!("else {}", block),
-        Some(ast::ElseBranch::IfExpr(if_expr)) => format!("else {}", if_expr),
-        None => String::new(),
-    };
-    expr_from_text(&format!("if {} {} {}", condition, then_branch, else_branch))
+pub fn expr_if(condition: ast::Condition, then_branch: ast::BlockExpr) -> ast::Expr {
+    expr_from_text(&format!("if {} {}", condition, then_branch))
 }
 pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
     let token = token(op);


Test patch:
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 853bde09ca..8d50c8791a 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -889,34 +889,6 @@ fn compute() -> Option<i32> { None }
     )
 }
 
-#[test]
-fn doctest_replace_match_with_if_let() {
-    check_doc_test(
-        "replace_match_with_if_let",
-        r#####"
-enum Action { Move { distance: u32 }, Stop }
-
-fn handle(action: Action) {
-    <|>match action {
-        Action::Move { distance } => foo(distance),
-        _ => bar(),
-    }
-}
-"#####,
-        r#####"
-enum Action { Move { distance: u32 }, Stop }
-
-fn handle(action: Action) {
-    if let Action::Move { distance } = action {
-        foo(distance)
-    } else {
-        bar()
-    }
-}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_replace_qualified_name_with_use() {
     check_doc_test(