Repository: rust-analyzer/rust-analyzer
Pull request #3958 (2020-04-12 16:11:52)

Add proc-macro related config and tests
This PR do the following things:

1. Add cli argument `proc-macro` for running proc-macro server.
2. Added support for proc-macro in bench and analysis-stats
3. Added typescript config for proc-macros
4. Added an heavy test for proc-macros. 

To test it out: 

1. run `cargo xtask install --proc-macro`
2. add `"rust-analyzer.cargo.loadOutDirsFromCheck": true"` and `"rust-analyzer.procMacro.enabled": true"` in vs code config.

[Edit] Change to use `rust-analyzer proc-macro` for running proc-macro standalone process.

lnicola: Maybe it doesn't make sense, but could we put the functionality in the same binary to simplify deployment (`e.g. rust-analyzer proc-macro-server`)?
edwin0cheng: > Maybe it doesn't make sense, but could we put the functionality in the same binary to simplify deployment (e.g. rust-analyzer proc-macro-server)?

Oh... sounds possible ? I like this idea and could simplify some code too. but I have to check whether it is possible in Windows. 
matklad: >Maybe it doesn't make sense, but could we put the functionality in the same binary to simplify deployment (e.g. rust-analyzer proc-macro-server)?

Yeah, I'd probably go with this way as well. Just let rust-analyzer spawn another rust-analyzer, and be careful not to create a fork-bomb accidentally :)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0e0f1af81f..8e204d39ff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -114,7 +114,7 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 [[package]]
 name = "chalk-derive"
 version = "0.1.0"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -125,7 +125,7 @@ dependencies = [
 [[package]]
 name = "chalk-engine"
 version = "0.9.0"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "chalk-macros",
  "rustc-hash",
@@ -134,7 +134,7 @@ dependencies = [
 [[package]]
 name = "chalk-ir"
 version = "0.1.0"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "chalk-derive",
  "chalk-engine",
@@ -144,7 +144,7 @@ dependencies = [
 [[package]]
 name = "chalk-macros"
 version = "0.1.1"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "lazy_static",
 ]
@@ -152,7 +152,7 @@ dependencies = [
 [[package]]
 name = "chalk-rust-ir"
 version = "0.1.0"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "chalk-derive",
  "chalk-engine",
@@ -163,7 +163,7 @@ dependencies = [
 [[package]]
 name = "chalk-solve"
 version = "0.1.0"
-source = "git+https://github.com/rust-lang/chalk.git?rev=28cef6ff403d403e6ad2f3d27d944e9ffac1bce8#28cef6ff403d403e6ad2f3d27d944e9ffac1bce8"
+source = "git+https://github.com/rust-lang/chalk.git?rev=6222e416b96892b2a86bc08de7dbc9826ff1acea#6222e416b96892b2a86bc08de7dbc9826ff1acea"
 dependencies = [
  "chalk-derive",
  "chalk-engine",
@@ -1341,6 +1341,7 @@ dependencies = [
  "ra_hir_def",
  "ra_hir_ty",
  "ra_ide",
+ "ra_proc_macro_srv",
  "ra_prof",
  "ra_project_model",
  "ra_syntax",
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 177bdbcb0e..e891d733fc 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -27,9 +27,9 @@ test_utils = { path = "../test_utils" }
 
 scoped-tls = "1"
 
-chalk-solve =   { git = "https://github.com/rust-lang/chalk.git", rev = "28cef6ff403d403e6ad2f3d27d944e9ffac1bce8" }
-chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "28cef6ff403d403e6ad2f3d27d944e9ffac1bce8" }
-chalk-ir =      { git = "https://github.com/rust-lang/chalk.git", rev = "28cef6ff403d403e6ad2f3d27d944e9ffac1bce8" }
+chalk-solve =   { git = "https://github.com/rust-lang/chalk.git", rev = "6222e416b96892b2a86bc08de7dbc9826ff1acea" }
+chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "6222e416b96892b2a86bc08de7dbc9826ff1acea" }
+chalk-ir =      { git = "https://github.com/rust-lang/chalk.git", rev = "6222e416b96892b2a86bc08de7dbc9826ff1acea" }
 
 [dev-dependencies]
 insta = "0.16.0"
diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs
index b43e2a539e..55eb0ffcb0 100644
--- a/crates/ra_hir_ty/src/traits/chalk.rs
+++ b/crates/ra_hir_ty/src/traits/chalk.rs
@@ -795,9 +795,8 @@ impl<'a> chalk_solve::RustIrDatabase<Interner> for ChalkContext<'a> {
     fn well_known_trait_id(
         &self,
         _well_known_trait: chalk_rust_ir::WellKnownTrait,
-    ) -> Option<chalk_ir::TraitId<Interner>> {
-        // FIXME tell Chalk about well-known traits (here and in trait_datum)
-        None
+    ) -> chalk_ir::TraitId<Interner> {
+        unimplemented!()
     }
 }
 
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs
index 63da9f1b45..b200fd1263 100644
--- a/crates/ra_proc_macro/src/lib.rs
+++ b/crates/ra_proc_macro/src/lib.rs
@@ -12,6 +12,7 @@ pub mod msg;
 use process::{ProcMacroProcessSrv, ProcMacroProcessThread};
 use ra_tt::{SmolStr, Subtree};
 use std::{
+    ffi::OsStr,
     path::{Path, PathBuf},
     sync::Arc,
 };
@@ -56,8 +57,15 @@ pub struct ProcMacroClient {
 }
 
 impl ProcMacroClient {
-    pub fn extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> {
-        let (thread, process) = ProcMacroProcessSrv::run(process_path)?;
+    pub fn extern_process<I, S>(
+        process_path: &Path,
+        args: I,
+    ) -> Result<ProcMacroClient, std::io::Error>
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        let (thread, process) = ProcMacroProcessSrv::run(process_path, args)?;
         Ok(ProcMacroClient {
             kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
         })
diff --git a/crates/ra_proc_macro/src/process.rs b/crates/ra_proc_macro/src/process.rs
index e8c85be38a..f851570bca 100644
--- a/crates/ra_proc_macro/src/process.rs
+++ b/crates/ra_proc_macro/src/process.rs
@@ -9,6 +9,7 @@ use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTas
 use io::{BufRead, BufReader};
 use std::{
     convert::{TryFrom, TryInto},
+    ffi::OsStr,
     io::{self, Write},
     path::{Path, PathBuf},
     process::{Child, Command, Stdio},
@@ -44,8 +45,13 @@ impl Drop for Process {
 }
 
 impl Process {
-    fn run(process_path: &Path) -> Result<Process, io::Error> {
+    fn run<I, S>(process_path: &Path, args: I) -> Result<Process, io::Error>
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
         let child = Command::new(process_path.clone())
+            .args(args)
             .stdin(Stdio::piped())
             .stdout(Stdio::piped())
             .stderr(Stdio::null())
@@ -74,10 +80,15 @@ impl Process {
 }
 
 impl ProcMacroProcessSrv {
-    pub fn run(
+    pub fn run<I, S>(
         process_path: &Path,
-    ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> {
-        let process = Process::run(process_path)?;
+        args: I,
+    ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error>
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        let process = Process::run(process_path, args)?;
 
         let (task_tx, task_rx) = bounded(0);
         let handle = jod_thread::spawn(move || {
diff --git a/crates/ra_proc_macro_srv/src/main.rs b/crates/ra_proc_macro_srv/src/cli.rs
similarity index 96%
rename from crates/ra_proc_macro_srv/src/main.rs
rename to crates/ra_proc_macro_srv/src/cli.rs
index 70743c1f40..c771f2b389 100644
--- a/crates/ra_proc_macro_srv/src/main.rs
+++ b/crates/ra_proc_macro_srv/src/cli.rs
@@ -1,7 +1,7 @@
 //! Driver for proc macro server
 
+use crate::{expand_task, list_macros};
 use ra_proc_macro::msg::{self, Message};
-use ra_proc_macro_srv::{expand_task, list_macros};
 
 use std::io;
 
@@ -24,7 +24,8 @@ fn write_response(res: Result<msg::Response, String>) -> Result<(), io::Error> {
     let mut stdout = stdout.lock();
     msg.write(&mut stdout)
 }
-fn main() {
+
+pub fn run() {
     loop {
         let req = match read_request() {
             Err(err) => {
diff --git a/crates/ra_proc_macro_srv/src/lib.rs b/crates/ra_proc_macro_srv/src/lib.rs
index 59716cbb3b..c62b0ed893 100644
--- a/crates/ra_proc_macro_srv/src/lib.rs
+++ b/crates/ra_proc_macro_srv/src/lib.rs
@@ -22,7 +22,7 @@ mod dylib;
 use proc_macro::bridge::client::TokenStream;
 use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
 
-pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
+pub(crate) fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
     let expander = dylib::Expander::new(&task.lib)
         .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
 
@@ -39,7 +39,7 @@ pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
     }
 }
 
-pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
+pub(crate) fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
     let expander = dylib::Expander::new(&task.lib)
         .expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
 
@@ -53,5 +53,7 @@ pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
     }
 }
 
+pub mod cli;
+
 #[cfg(test)]
 mod tests;
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index f5f7734325..cee0248b62 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -46,7 +46,7 @@ ra_db = { path = "../ra_db" }
 hir = { path = "../ra_hir", package = "ra_hir" }
 hir_def = { path = "../ra_hir_def", package = "ra_hir_def" }
 hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" }
-
+ra_proc_macro_srv = { path = "../ra_proc_macro_srv" }
 
 [target.'cfg(windows)'.dependencies]
 winapi = "0.3.8"
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
index f5981588ab..5e19253a65 100644
--- a/crates/rust-analyzer/src/bin/args.rs
+++ b/crates/rust-analyzer/src/bin/args.rs
@@ -29,19 +29,23 @@ pub(crate) enum Command {
         with_deps: bool,
         path: PathBuf,
         load_output_dirs: bool,
+        with_proc_macro: bool,
     },
     Bench {
         path: PathBuf,
         what: BenchWhat,
         load_output_dirs: bool,
+        with_proc_macro: bool,
     },
     Diagnostics {
         path: PathBuf,
         load_output_dirs: bool,
+        with_proc_macro: bool,
         /// Include files which are not modules. In rust-analyzer
         /// this would include the parser test files.
         all: bool,
     },
+    ProcMacro,
     RunServer,
     Version,
 }
@@ -148,6 +152,7 @@ FLAGS:
     -h, --help              Prints help information
         --memory-usage
         --load-output-dirs  Load OUT_DIR values by running `cargo check` before analysis
+        --with-proc-macro    Use ra-proc-macro-srv for proc-macro expanding
     -v, --verbose
     -q, --quiet
 
@@ -165,6 +170,7 @@ ARGS:
                 let only: Option<String> = matches.opt_value_from_str(["-o", "--only"])?;
                 let with_deps: bool = matches.contains("--with-deps");
                 let load_output_dirs = matches.contains("--load-output-dirs");
+                let with_proc_macro = matches.contains("--with-proc-macro");
                 let path = {
                     let mut trailing = matches.free()?;
                     if trailing.len() != 1 {
@@ -173,7 +179,15 @@ ARGS:
                     trailing.pop().unwrap().into()
                 };
 
-                Command::Stats { randomize, memory_usage, only, with_deps, path, load_output_dirs }
+                Command::Stats {
+                    randomize,
+                    memory_usage,
+                    only,
+                    with_deps,
+                    path,
+                    load_output_dirs,
+                    with_proc_macro,
+                }
             }
             "analysis-bench" => {
                 if matches.contains(["-h", "--help"]) {
@@ -187,6 +201,7 @@ USAGE:
 FLAGS:
     -h, --help          Prints help information
     --load-output-dirs  Load OUT_DIR values by running `cargo check` before analysis
+    --with-proc-macro    Use ra-proc-macro-srv for proc-macro expanding
     -v, --verbose
 
 OPTIONS:
@@ -214,7 +229,8 @@ ARGS:
                     ),
                 };
                 let load_output_dirs = matches.contains("--load-output-dirs");
-                Command::Bench { path, what, load_output_dirs }
+                let with_proc_macro = matches.contains("--with-proc-macro");
+                Command::Bench { path, what, load_output_dirs, with_proc_macro }
             }
             "diagnostics" => {
                 if matches.contains(["-h", "--help"]) {
@@ -237,6 +253,7 @@ ARGS:
                 }
 
                 let load_output_dirs = matches.contains("--load-output-dirs");
+                let with_proc_macro = matches.contains("--with-proc-macro");
                 let all = matches.contains("--all");
                 let path = {
                     let mut trailing = matches.free()?;
@@ -246,8 +263,9 @@ ARGS:
                     trailing.pop().unwrap().into()
                 };
 
-                Command::Diagnostics { path, load_output_dirs, all }
+                Command::Diagnostics { path, load_output_dirs, with_proc_macro, all }
             }
+            "proc-macro" => Command::ProcMacro,
             _ => {
                 eprintln!(
                     "\
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 7cfc44f01f..28b67cfe2a 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -25,6 +25,7 @@ fn main() -> Result<()> {
             with_deps,
             path,
             load_output_dirs,
+            with_proc_macro,
         } => cli::analysis_stats(
             args.verbosity,
             memory_usage,
@@ -33,16 +34,24 @@ fn main() -> Result<()> {
             with_deps,
             randomize,
             load_output_dirs,
+            with_proc_macro,
         )?,
 
-        args::Command::Bench { path, what, load_output_dirs } => {
-            cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)?
+        args::Command::Bench { path, what, load_output_dirs, with_proc_macro } => {
+            cli::analysis_bench(
+                args.verbosity,
+                path.as_ref(),
+                what,
+                load_output_dirs,
+                with_proc_macro,
+            )?
         }
 
-        args::Command::Diagnostics { path, load_output_dirs, all } => {
-            cli::diagnostics(path.as_ref(), load_output_dirs, all)?
+        args::Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } => {
+            cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro, all)?
         }
 
+        args::Command::ProcMacro => run_proc_macro_sv()?,
         args::Command::RunServer => run_server()?,
         args::Command::Version => println!("rust-analyzer {}", env!("REV")),
     }
@@ -56,6 +65,11 @@ fn setup_logging() -> Result<()> {
     Ok(())
 }
 
+fn run_proc_macro_sv() -> Result<()> {
+    ra_proc_macro_srv::cli::run();
+    Ok(())
+}
+
 fn run_server() -> Result<()> {
     log::info!("lifecycle: server started");
 
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 7667873d5f..6147ae2074 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -47,12 +47,13 @@ pub fn analysis_bench(
     path: &Path,
     what: BenchWhat,
     load_output_dirs: bool,
+    with_proc_macro: bool,
 ) -> Result<()> {
     ra_prof::init();
 
     let start = Instant::now();
     eprint!("loading: ");
-    let (mut host, roots) = load_cargo(path, load_output_dirs)?;
+    let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
     let db = host.raw_database();
     eprintln!("{:?}\n", start.elapsed());
 
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index e9ee0b888f..d442cbd63e 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -25,9 +25,10 @@ pub fn analysis_stats(
     with_deps: bool,
     randomize: bool,
     load_output_dirs: bool,
+    with_proc_macro: bool,
 ) -> Result<()> {
     let db_load_time = Instant::now();
-    let (mut host, roots) = load_cargo(path, load_output_dirs)?;
+    let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
     let db = host.raw_database();
     println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
     let analysis_time = Instant::now();
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 92664b415b..60daefa3ef 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -9,8 +9,13 @@ use std::{collections::HashSet, path::Path};
 use crate::cli::{load_cargo::load_cargo, Result};
 use hir::Semantics;
 
-pub fn diagnostics(path: &Path, load_output_dirs: bool, all: bool) -> Result<()> {
-    let (host, roots) = load_cargo(path, load_output_dirs)?;
+pub fn diagnostics(
+    path: &Path,
+    load_output_dirs: bool,
+    with_proc_macro: bool,
+    all: bool,
+) -> Result<()> {
+    let (host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
     let db = host.raw_database();
     let analysis = host.analysis();
     let semantics = Semantics::new(db);
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index 43062ea105..32a9ee339a 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -25,6 +25,7 @@ fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
 pub(crate) fn load_cargo(
     root: &Path,
     load_out_dirs_from_check: bool,
+    with_proc_macro: bool,
 ) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
     let root = std::env::current_dir()?.join(root);
     let ws = ProjectWorkspace::discover(
@@ -69,7 +70,14 @@ pub(crate) fn load_cargo(
         })
         .collect::<FxHashMap<_, _>>();
 
-    let proc_macro_client = ProcMacroClient::dummy();
+    let proc_macro_client = if !with_proc_macro {
+        ProcMacroClient::dummy()
+    } else {
+        let mut path = std::env::current_exe()?;
+        path.pop();
+        path.push("rust-analyzer");
+        ProcMacroClient::extern_process(&path, &["proc-macro"]).unwrap()
+    };
     let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client);
     Ok((host, source_roots))
 }
@@ -175,7 +183,7 @@ mod tests {
     #[test]
     fn test_loading_rust_analyzer() {
         let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
-        let (host, _roots) = load_cargo(path, false).unwrap();
+        let (host, _roots) = load_cargo(path, false, false).unwrap();
         let n_crates = Crate::all(host.raw_database()).len();
         // RA has quite a few crates, but the exact count doesn't matter
         assert!(n_crates > 20);
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 4734df16ac..2b45f13103 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -20,7 +20,7 @@ pub struct Config {
     pub with_sysroot: bool,
     pub publish_diagnostics: bool,
     pub lru_capacity: Option<usize>,
-    pub proc_macro_srv: Option<String>,
+    pub proc_macro_srv: Option<(String, Vec<String>)>,
     pub files: FilesConfig,
     pub notifications: NotificationsConfig,
 
@@ -131,6 +131,18 @@ impl Config {
         set(value, "/cargo/allFeatures", &mut self.cargo.all_features);
         set(value, "/cargo/features", &mut self.cargo.features);
         set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
+
+        match get::<bool>(value, "/procMacro/enabled") {
+            Some(true) => {
+                if let Ok(mut path) = std::env::current_exe() {
+                    path.pop();
+                    path.push("rust-analyzer");
+                    self.proc_macro_srv = Some((path.to_string_lossy().to_string(), vec!["proc-macro".to_string()]));
+                }
+            }
+            _ => self.proc_macro_srv = None,
+        }
+
         match get::<Vec<String>>(value, "/rustfmt/overrideCommand") {
             Some(mut args) if !args.is_empty() => {
                 let command = args.remove(0);
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 6c42e1d760..f2ad453faf 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -64,6 +64,7 @@ pub struct WorldState {
     pub latest_requests: Arc<RwLock<LatestRequests>>,
     pub flycheck: Option<Flycheck>,
     pub diagnostics: DiagnosticCollection,
+    pub proc_macro_client: ProcMacroClient,
 }
 
 /// An immutable snapshot of the world's state at a point in time.
@@ -147,9 +148,9 @@ impl WorldState {
 
         let proc_macro_client = match &config.proc_macro_srv {
             None => ProcMacroClient::dummy(),
-            Some(srv) => {
-                let path = Path::new(&srv);
-                match ProcMacroClient::extern_process(path) {
+            Some((path, args)) => {
+                let path = std::path::Path::new(path);
+                match ProcMacroClient::extern_process(path, args) {
                     Ok(it) => it,
                     Err(err) => {
                         log::error!(
@@ -192,6 +193,7 @@ impl WorldState {
             latest_requests: Default::default(),
             flycheck,
             diagnostics: Default::default(),
+            proc_macro_client,
         }
     }
 
diff --git a/editors/code/package.json b/editors/code/package.json
index 5f73c8d838..5ce59e54a9 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -388,6 +388,11 @@
                     "description": "Enable logging of VS Code extensions itself",
                     "type": "boolean",
                     "default": false
+                },
+                "rust-analyzer.procMacro.enabled": {
+                    "description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
+                    "type": "boolean",
+                    "default": false
                 }
             }
         },
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 35a05131c8..3b2eec8baa 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -12,6 +12,7 @@ export class Config {
     private readonly requiresReloadOpts = [
         "serverPath",
         "cargo",
+        "procMacro",
         "files",
         "highlighting",
         "updates.channel",


Test patch:
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 6388133112..1dd2676b6e 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -9,7 +9,7 @@ use lsp_types::{
 };
 use rust_analyzer::req::{
     CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
-    Formatting, GotoDefinition, OnEnter, Runnables, RunnablesParams,
+    Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams,
 };
 use serde_json::json;
 use tempfile::TempDir;
@@ -625,3 +625,92 @@ fn main() { message(); }
     ));
     assert!(format!("{}", res).contains("hello.rs"));
 }
+
+#[test]
+fn resolve_proc_macro() {
+    if skip_slow_tests() {
+        return;
+    }
+    let server = Project::with_fixture(
+        r###"
+//- foo/Cargo.toml
+[package]
+name = "foo"
+version = "0.0.0"
+edition = "2018"
+[dependencies]
+bar = {path = "../bar"}
+
+//- foo/src/main.rs
+use bar::Bar;
+trait Bar {
+  fn bar();
+}
+#[derive(Bar)]
+struct Foo {}
+fn main() {
+  Foo::bar();
+}
+
+//- bar/Cargo.toml
+[package]
+name = "bar"
+version = "0.0.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+//- bar/src/lib.rs
+extern crate proc_macro;
+use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
+macro_rules! t {
+    ($n:literal) => {
+        TokenTree::from(Ident::new($n, Span::call_site()))
+    };
+    ({}) => {
+        TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
+    };
+    (()) => {
+        TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
+    };
+}
+#[proc_macro_derive(Bar)]
+pub fn foo(_input: TokenStream) -> TokenStream {
+    // We hard code the output here for preventing to use any deps
+    let mut res = TokenStream::new();
+
+    // impl Bar for Foo { fn bar() {} }
+    let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
+    let mut fn_stream = TokenStream::new();
+    fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
+    tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
+    res.extend(tokens);
+    res
+}
+
+"###,
+    )
+    .with_config(|config| {
+        // FIXME: Use env!("CARGO_BIN_EXE_ra-analyzer") instead after
+        // https://github.com/rust-lang/cargo/pull/7697 landed
+        let macro_srv_path = std::path::Path::new(std::env!("CARGO_MANIFEST_DIR"))
+            .join("../../target/debug/rust-analyzer")
+            .to_string_lossy()
+            .to_string();
+
+        config.cargo.load_out_dirs_from_check = true;
+        config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".to_string()]));
+    })
+    .root("foo")
+    .root("bar")
+    .server();
+    server.wait_until_workspace_is_loaded();
+    let res = server.send_request::<HoverRequest>(TextDocumentPositionParams::new(
+        server.doc_id("foo/src/main.rs"),
+        Position::new(7, 9),
+    ));
+
+    let value = res.get("contents").unwrap().get("value").unwrap().to_string();
+    assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)
+}