3738: Implement ra_proc_macro client logic r=matklad a=edwin0cheng

This PR add the actual client logic for `ra_proc_macro` crate:

1. Define all necessary rpc serialization data structure, which include `ra_tt` related data and some task messages. Although adding `Serialize` and `Deserialize` trait to ra_tt directly seem to be much easier,   we deliberately duplicate the `ra_tt` struct with `#[serde(with = "XXDef")]` for separation of  code responsibility. 
2. Define a simplified version of lsp base protocol for rpc, which basically copy from lsp-server code base. 
3. Implement the actual `IO` for the client side progress spawning and message passing.

Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
This commit is contained in:
bors[bot] 2020-03-31 14:30:24 +00:00 committed by GitHub
commit 7a546490ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 697 additions and 24 deletions

5
Cargo.lock generated
View File

@ -1075,7 +1075,12 @@ dependencies = [
name = "ra_proc_macro"
version = "0.1.0"
dependencies = [
"crossbeam-channel",
"jod-thread",
"log",
"ra_tt",
"serde",
"serde_json",
]
[[package]]

View File

@ -9,6 +9,15 @@ pub struct ProcMacroExpander {
proc_macro_id: ProcMacroId,
}
macro_rules! err {
($fmt:literal, $($tt:tt),*) => {
mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown(format!($fmt, $($tt),*)))
};
($fmt:literal) => {
mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown($fmt.to_string()))
}
}
impl ProcMacroExpander {
pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander {
ProcMacroExpander { krate, proc_macro_id }
@ -25,8 +34,24 @@ impl ProcMacroExpander {
.proc_macro
.get(self.proc_macro_id.0 as usize)
.clone()
.ok_or_else(|| mbe::ExpandError::ConversionError)?;
.ok_or_else(|| err!("No derive macro found."))?;
let tt = remove_derive_atr(tt, &proc_macro.name)
.ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
}
}
fn remove_derive_atr(tt: &tt::Subtree, _name: &str) -> Option<tt::Subtree> {
// FIXME: proper handle the remove derive
// We assume the first 2 tokens are #[derive(name)]
if tt.token_trees.len() > 2 {
let mut tt = tt.clone();
tt.token_trees.remove(0);
tt.token_trees.remove(0);
return Some(tt);
}
None
}

View File

@ -10,3 +10,8 @@ doctest = false
[dependencies]
ra_tt = { path = "../ra_tt" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4.8"
crossbeam-channel = "0.4.0"
jod-thread = "0.1.1"

View File

@ -5,55 +5,104 @@
//! is used to provide basic infrastructure for communication between two
//! processes: Client (RA itself), Server (the external program)
mod rpc;
mod process;
pub mod msg;
use process::{ProcMacroProcessSrv, ProcMacroProcessThread};
use ra_tt::{SmolStr, Subtree};
use rpc::ProcMacroKind;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub use rpc::{ExpansionResult, ExpansionTask};
#[derive(Debug, Clone)]
pub struct ProcMacroProcessExpander {
process: Arc<ProcMacroProcessSrv>,
dylib_path: PathBuf,
name: SmolStr,
}
impl Eq for ProcMacroProcessExpander {}
impl PartialEq for ProcMacroProcessExpander {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.dylib_path == other.dylib_path
&& Arc::ptr_eq(&self.process, &other.process)
}
}
impl ra_tt::TokenExpander for ProcMacroProcessExpander {
fn expand(
&self,
_subtree: &Subtree,
subtree: &Subtree,
_attr: Option<&Subtree>,
) -> Result<Subtree, ra_tt::ExpansionError> {
// FIXME: do nothing for now
Ok(Subtree::default())
self.process.custom_derive(&self.dylib_path, subtree, &self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProcMacroProcessSrv {
path: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProcMacroClient {
Process { process: Arc<ProcMacroProcessSrv> },
#[derive(Debug)]
enum ProcMacroClientKind {
Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread },
Dummy,
}
#[derive(Debug)]
pub struct ProcMacroClient {
kind: ProcMacroClientKind,
}
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 extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> {
let (thread, process) = ProcMacroProcessSrv::run(process_path)?;
Ok(ProcMacroClient {
kind: ProcMacroClientKind::Process { process: Arc::new(process), thread },
})
}
pub fn dummy() -> ProcMacroClient {
ProcMacroClient::Dummy
ProcMacroClient { kind: ProcMacroClientKind::Dummy }
}
pub fn by_dylib_path(
&self,
_dylib_path: &Path,
dylib_path: &Path,
) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> {
// FIXME: return empty for now
vec![]
match &self.kind {
ProcMacroClientKind::Dummy => vec![],
ProcMacroClientKind::Process { process, .. } => {
let macros = match process.find_proc_macros(dylib_path) {
Err(err) => {
eprintln!("Fail to find proc macro. Error: {:#?}", err);
return vec![];
}
Ok(macros) => macros,
};
macros
.into_iter()
.filter_map(|(name, kind)| {
// FIXME: Support custom derive only for now.
match kind {
ProcMacroKind::CustomDerive => {
let name = SmolStr::new(&name);
let expander: Arc<dyn ra_tt::TokenExpander> =
Arc::new(ProcMacroProcessExpander {
process: process.clone(),
name: name.clone(),
dylib_path: dylib_path.into(),
});
Some((name, expander))
}
_ => None,
}
})
.collect()
}
}
}
}

View File

@ -0,0 +1,93 @@
//! Defines messages for cross-process message based on `ndjson` wire protocol
use std::{
convert::TryFrom,
io::{self, BufRead, Write},
};
use crate::{
rpc::{ListMacrosResult, ListMacrosTask},
ExpansionResult, ExpansionTask,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Request {
ListMacro(ListMacrosTask),
ExpansionMacro(ExpansionTask),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Response {
Error(ResponseError),
ListMacro(ListMacrosResult),
ExpansionMacro(ExpansionResult),
}
macro_rules! impl_try_from_response {
($ty:ty, $tag:ident) => {
impl TryFrom<Response> for $ty {
type Error = &'static str;
fn try_from(value: Response) -> Result<Self, Self::Error> {
match value {
Response::$tag(res) => Ok(res),
_ => Err("Fail to convert from response"),
}
}
}
};
}
impl_try_from_response!(ListMacrosResult, ListMacro);
impl_try_from_response!(ExpansionResult, ExpansionMacro);
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ResponseError {
pub code: ErrorCode,
pub message: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ErrorCode {
ServerErrorEnd,
ExpansionError,
}
pub trait Message: Sized + Serialize + DeserializeOwned {
fn read(r: &mut impl BufRead) -> io::Result<Option<Self>> {
let text = match read_json(r)? {
None => return Ok(None),
Some(text) => text,
};
let msg = serde_json::from_str(&text)?;
Ok(Some(msg))
}
fn write(self, w: &mut impl Write) -> io::Result<()> {
let text = serde_json::to_string(&self)?;
write_json(w, &text)
}
}
impl Message for Request {}
impl Message for Response {}
fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> {
let mut buf = String::new();
if inp.read_line(&mut buf)? == 0 {
return Ok(None);
}
// Remove ending '\n'
let buf = &buf[..buf.len() - 1];
if buf.is_empty() {
return Ok(None);
}
Ok(Some(buf.to_string()))
}
fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> {
log::debug!("> {}", msg);
out.write_all(msg.as_bytes())?;
out.write_all(b"\n")?;
out.flush()?;
Ok(())
}

View File

@ -0,0 +1,196 @@
//! Handle process life-time and message passing for proc-macro client
use crossbeam_channel::{bounded, Receiver, Sender};
use ra_tt::Subtree;
use crate::msg::{ErrorCode, Message, Request, Response, ResponseError};
use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind};
use io::{BufRead, BufReader};
use std::{
convert::{TryFrom, TryInto},
io::{self, Write},
path::{Path, PathBuf},
process::{Child, Command, Stdio},
sync::{Arc, Weak},
};
#[derive(Debug, Default)]
pub(crate) struct ProcMacroProcessSrv {
inner: Option<Weak<Sender<Task>>>,
}
#[derive(Debug)]
pub(crate) struct ProcMacroProcessThread {
// XXX: drop order is significant
sender: Arc<Sender<Task>>,
handle: jod_thread::JoinHandle<()>,
}
struct Task {
req: Request,
result_tx: Sender<Option<Response>>,
}
struct Process {
path: PathBuf,
child: Child,
}
impl Drop for Process {
fn drop(&mut self) {
let _ = self.child.kill();
}
}
impl Process {
fn run(process_path: &Path) -> Result<Process, io::Error> {
let child = Command::new(process_path.clone())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
Ok(Process { path: process_path.into(), child })
}
fn restart(&mut self) -> Result<(), io::Error> {
let _ = self.child.kill();
self.child = Command::new(self.path.clone())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
Ok(())
}
fn stdio(&mut self) -> Option<(impl Write, impl BufRead)> {
let stdin = self.child.stdin.take()?;
let stdout = self.child.stdout.take()?;
let read = BufReader::new(stdout);
Some((stdin, read))
}
}
impl ProcMacroProcessSrv {
pub fn run(
process_path: &Path,
) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> {
let process = Process::run(process_path)?;
let (task_tx, task_rx) = bounded(0);
let handle = jod_thread::spawn(move || {
client_loop(task_rx, process);
});
let task_tx = Arc::new(task_tx);
let srv = ProcMacroProcessSrv { inner: Some(Arc::downgrade(&task_tx)) };
let thread = ProcMacroProcessThread { handle, sender: task_tx };
Ok((thread, srv))
}
pub fn find_proc_macros(
&self,
dylib_path: &Path,
) -> Result<Vec<(String, ProcMacroKind)>, ra_tt::ExpansionError> {
let task = ListMacrosTask { lib: dylib_path.to_path_buf() };
let result: ListMacrosResult = self.send_task(Request::ListMacro(task))?;
Ok(result.macros)
}
pub fn custom_derive(
&self,
dylib_path: &Path,
subtree: &Subtree,
derive_name: &str,
) -> Result<Subtree, ra_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 fn send_task<R>(&self, req: Request) -> Result<R, ra_tt::ExpansionError>
where
R: TryFrom<Response, Error = &'static str>,
{
let sender = match &self.inner {
None => return Err(ra_tt::ExpansionError::Unknown("No sender is found.".to_string())),
Some(it) => it,
};
let (result_tx, result_rx) = bounded(0);
let sender = match sender.upgrade() {
None => {
return Err(ra_tt::ExpansionError::Unknown("Proc macro process is closed.".into()))
}
Some(it) => it,
};
sender.send(Task { req: req.into(), result_tx }).unwrap();
let res = result_rx
.recv()
.map_err(|_| ra_tt::ExpansionError::Unknown("Proc macro thread is closed.".into()))?;
match res {
Some(Response::Error(err)) => {
return Err(ra_tt::ExpansionError::ExpansionError(err.message));
}
Some(res) => Ok(res.try_into().map_err(|err| {
ra_tt::ExpansionError::Unknown(format!(
"Fail to get response, reason : {:#?} ",
err
))
})?),
None => Err(ra_tt::ExpansionError::Unknown("Empty result".into())),
}
}
}
fn client_loop(task_rx: Receiver<Task>, mut process: Process) {
let (mut stdin, mut stdout) = match process.stdio() {
None => return,
Some(it) => it,
};
for task in task_rx {
let Task { req, result_tx } = task;
match send_request(&mut stdin, &mut stdout, req) {
Ok(res) => result_tx.send(res).unwrap(),
Err(_err) => {
let res = Response::Error(ResponseError {
code: ErrorCode::ServerErrorEnd,
message: "Server closed".into(),
});
result_tx.send(res.into()).unwrap();
// Restart the process
if process.restart().is_err() {
break;
}
let stdio = match process.stdio() {
None => break,
Some(it) => it,
};
stdin = stdio.0;
stdout = stdio.1;
}
}
}
}
fn send_request(
mut writer: &mut impl Write,
mut reader: &mut impl BufRead,
req: Request,
) -> Result<Option<Response>, io::Error> {
req.write(&mut writer)?;
Ok(Response::read(&mut reader)?)
}

View File

@ -0,0 +1,266 @@
//! Data struture serialization related stuffs for RPC
//!
//! Define all necessary rpc serialization data structure,
//! which include ra_tt related data and some task messages.
//! Although adding Serialize and Deserialize trait to ra_tt directly seem to be much easier,
//! we deliberately duplicate the ra_tt struct with #[serde(with = "XXDef")]
//! for separation of code responsibility.
use ra_tt::{
Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId,
TokenTree,
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ListMacrosTask {
pub lib: PathBuf,
}
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub enum ProcMacroKind {
CustomDerive,
FuncLike,
Attr,
}
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ListMacrosResult {
pub macros: Vec<(String, ProcMacroKind)>,
}
#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
pub struct ExpansionTask {
/// Argument of macro call.
///
/// In custom derive that would be a struct or enum; in attribute-like macro - underlying
/// item; in function-like macro - the macro body.
#[serde(with = "SubtreeDef")]
pub macro_body: Subtree,
/// Names of macros to expand.
///
/// In custom derive those are names of derived traits (`Serialize`, `Getters`, etc.). In
/// attribute-like and functiona-like macros - single name of macro itself (`show_streams`).
pub macro_name: String,
/// Possible attributes for the attribute-like macros.
#[serde(with = "opt_subtree_def")]
pub attributes: Option<Subtree>,
pub lib: PathBuf,
}
#[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct ExpansionResult {
#[serde(with = "SubtreeDef")]
pub expansion: Subtree,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "DelimiterKind")]
enum DelimiterKindDef {
Parenthesis,
Brace,
Bracket,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenId")]
struct TokenIdDef(u32);
#[derive(Serialize, Deserialize)]
#[serde(remote = "Delimiter")]
struct DelimiterDef {
#[serde(with = "TokenIdDef")]
pub id: TokenId,
#[serde(with = "DelimiterKindDef")]
pub kind: DelimiterKind,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Subtree")]
struct SubtreeDef {
#[serde(default, with = "opt_delimiter_def")]
pub delimiter: Option<Delimiter>,
#[serde(with = "vec_token_tree")]
pub token_trees: Vec<TokenTree>,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "TokenTree")]
enum TokenTreeDef {
#[serde(with = "LeafDef")]
Leaf(Leaf),
#[serde(with = "SubtreeDef")]
Subtree(Subtree),
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Leaf")]
enum LeafDef {
#[serde(with = "LiteralDef")]
Literal(Literal),
#[serde(with = "PunctDef")]
Punct(Punct),
#[serde(with = "IdentDef")]
Ident(Ident),
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Literal")]
struct LiteralDef {
pub text: SmolStr,
#[serde(with = "TokenIdDef")]
pub id: TokenId,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Punct")]
struct PunctDef {
pub char: char,
#[serde(with = "SpacingDef")]
pub spacing: Spacing,
#[serde(with = "TokenIdDef")]
pub id: TokenId,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Spacing")]
enum SpacingDef {
Alone,
Joint,
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "Ident")]
struct IdentDef {
pub text: SmolStr,
#[serde(with = "TokenIdDef")]
pub id: TokenId,
}
mod opt_delimiter_def {
use super::{Delimiter, DelimiterDef};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter);
value.as_ref().map(Helper).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper(#[serde(with = "DelimiterDef")] Delimiter);
let helper = Option::deserialize(deserializer)?;
Ok(helper.map(|Helper(external)| external))
}
}
mod opt_subtree_def {
use super::{Subtree, SubtreeDef};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree);
value.as_ref().map(Helper).serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper(#[serde(with = "SubtreeDef")] Subtree);
let helper = Option::deserialize(deserializer)?;
Ok(helper.map(|Helper(external)| external))
}
}
mod vec_token_tree {
use super::{TokenTree, TokenTreeDef};
use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree);
let items: Vec<_> = value.iter().map(Helper).collect();
let mut seq = serializer.serialize_seq(Some(items.len()))?;
for element in items {
seq.serialize_element(&element)?;
}
seq.end()
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper(#[serde(with = "TokenTreeDef")] TokenTree);
let helper = Vec::deserialize(deserializer)?;
Ok(helper.into_iter().map(|Helper(external)| external).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture_token_tree() -> Subtree {
let mut subtree = Subtree::default();
subtree
.token_trees
.push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into()));
subtree
.token_trees
.push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into()));
subtree.token_trees.push(TokenTree::Subtree(
Subtree {
delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }),
token_trees: vec![],
}
.into(),
));
subtree
}
#[test]
fn test_proc_macro_rpc_works() {
let tt = fixture_token_tree();
let task = ExpansionTask {
macro_body: tt.clone(),
macro_name: Default::default(),
attributes: None,
lib: Default::default(),
};
let json = serde_json::to_string(&task).unwrap();
let back: ExpansionTask = serde_json::from_str(&json).unwrap();
assert_eq!(task.macro_body, back.macro_body);
let result = ExpansionResult { expansion: tt.clone() };
let json = serde_json::to_string(&result).unwrap();
let back: ExpansionResult = serde_json::from_str(&json).unwrap();
assert_eq!(result, back);
}
}

View File

@ -1,6 +1,7 @@
//! FIXME: write short doc here
use std::{
ffi::OsStr,
ops,
path::{Path, PathBuf},
};
@ -299,7 +300,10 @@ pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures)
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) {
// Skip rmeta file
if let Some(filename) =
message.filenames.iter().filter(|name| is_dylib(name)).next()
{
acc.proc_dylib_paths.insert(package_id, filename.clone());
}
}
@ -316,3 +320,11 @@ pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures)
acc
}
// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
None => false,
Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
}
}

View File

@ -189,7 +189,12 @@ impl Subtree {
pub mod buffer;
#[derive(Debug, PartialEq, Eq)]
pub enum ExpansionError {}
pub enum ExpansionError {
IOError(String),
JsonError(String),
Unknown(String),
ExpansionError(String),
}
pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe {
fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>)

View File

@ -109,6 +109,7 @@ fn get_config(
},
rustfmt_args: config.rustfmt_args.clone(),
vscode_lldb: config.vscode_lldb,
proc_macro_srv: None, // FIXME: get this from config
}
}

View File

@ -58,6 +58,7 @@ pub struct Config {
pub rustfmt_args: Vec<String>,
pub check: CheckConfig,
pub vscode_lldb: bool,
pub proc_macro_srv: Option<String>,
}
/// `WorldState` is the primary mutable state of the language server
@ -167,8 +168,23 @@ impl WorldState {
vfs_file.map(|f| FileId(f.0))
};
let proc_macro_client =
ProcMacroClient::extern_process(std::path::Path::new("ra_proc_macro_srv"));
let proc_macro_client = match &config.proc_macro_srv {
None => ProcMacroClient::dummy(),
Some(srv) => {
let path = Path::new(&srv);
match ProcMacroClient::extern_process(path) {
Ok(it) => it,
Err(err) => {
log::error!(
"Fail to run ra_proc_macro_srv from path {}, error : {}",
path.to_string_lossy(),
err
);
ProcMacroClient::dummy()
}
}
}
};
workspaces
.iter()