ra_ap_proc_macro_api/
lib.rs

1//! Client-side Proc-Macro crate
2//!
3//! We separate proc-macro expanding logic to an extern program to allow
4//! different implementations (e.g. wasm or dylib loading). And this crate
5//! is used to provide basic infrastructure for communication between two
6//! processes: Client (RA itself), Server (the external program)
7
8pub mod legacy_protocol {
9    pub mod json;
10    pub mod msg;
11}
12mod process;
13
14use paths::{AbsPath, AbsPathBuf};
15use span::Span;
16use std::{fmt, io, sync::Arc};
17
18use crate::{
19    legacy_protocol::msg::{
20        deserialize_span_data_index_map, flat::serialize_span_data_index_map, ExpandMacro,
21        ExpandMacroData, ExpnGlobals, FlatTree, PanicMessage, Request, Response, SpanDataIndexMap,
22        HAS_GLOBAL_SPANS, RUST_ANALYZER_SPAN_SUPPORT,
23    },
24    process::ProcMacroServerProcess,
25};
26
27#[derive(Copy, Clone, Eq, PartialEq, Debug, serde_derive::Serialize, serde_derive::Deserialize)]
28pub enum ProcMacroKind {
29    CustomDerive,
30    Attr,
31    // This used to be called FuncLike, so that's what the server expects currently.
32    #[serde(alias = "Bang")]
33    #[serde(rename(serialize = "FuncLike", deserialize = "FuncLike"))]
34    Bang,
35}
36
37/// A handle to an external process which load dylibs with macros (.so or .dll)
38/// and runs actual macro expansion functions.
39#[derive(Debug)]
40pub struct ProcMacroClient {
41    /// Currently, the proc macro process expands all procedural macros sequentially.
42    ///
43    /// That means that concurrent salsa requests may block each other when expanding proc macros,
44    /// which is unfortunate, but simple and good enough for the time being.
45    process: Arc<ProcMacroServerProcess>,
46    path: AbsPathBuf,
47}
48
49pub struct MacroDylib {
50    path: AbsPathBuf,
51}
52
53impl MacroDylib {
54    pub fn new(path: AbsPathBuf) -> MacroDylib {
55        MacroDylib { path }
56    }
57}
58
59/// A handle to a specific proc-macro (a `#[proc_macro]` annotated function).
60///
61/// It exists within the context of a specific proc-macro server -- currently
62/// we share a single expander process for all macros within a workspace.
63#[derive(Debug, Clone)]
64pub struct ProcMacro {
65    process: Arc<ProcMacroServerProcess>,
66    dylib_path: Arc<AbsPathBuf>,
67    name: Box<str>,
68    kind: ProcMacroKind,
69}
70
71impl Eq for ProcMacro {}
72impl PartialEq for ProcMacro {
73    fn eq(&self, other: &Self) -> bool {
74        self.name == other.name
75            && self.kind == other.kind
76            && Arc::ptr_eq(&self.dylib_path, &other.dylib_path)
77            && Arc::ptr_eq(&self.process, &other.process)
78    }
79}
80
81#[derive(Clone, Debug)]
82pub struct ServerError {
83    pub message: String,
84    pub io: Option<Arc<io::Error>>,
85}
86
87impl fmt::Display for ServerError {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        self.message.fmt(f)?;
90        if let Some(io) = &self.io {
91            f.write_str(": ")?;
92            io.fmt(f)?;
93        }
94        Ok(())
95    }
96}
97
98impl ProcMacroClient {
99    /// Spawns an external process as the proc macro server and returns a client connected to it.
100    pub fn spawn(
101        process_path: &AbsPath,
102        env: impl IntoIterator<Item = (impl AsRef<std::ffi::OsStr>, impl AsRef<std::ffi::OsStr>)>
103            + Clone,
104    ) -> io::Result<ProcMacroClient> {
105        let process = ProcMacroServerProcess::run(process_path, env)?;
106        Ok(ProcMacroClient { process: Arc::new(process), path: process_path.to_owned() })
107    }
108
109    pub fn server_path(&self) -> &AbsPath {
110        &self.path
111    }
112
113    /// Loads a proc-macro dylib into the server process returning a list of `ProcMacro`s loaded.
114    pub fn load_dylib(&self, dylib: MacroDylib) -> Result<Vec<ProcMacro>, ServerError> {
115        let _p = tracing::info_span!("ProcMacroServer::load_dylib").entered();
116        let macros = self.process.find_proc_macros(&dylib.path)?;
117
118        let dylib_path = Arc::new(dylib.path);
119        match macros {
120            Ok(macros) => Ok(macros
121                .into_iter()
122                .map(|(name, kind)| ProcMacro {
123                    process: self.process.clone(),
124                    name: name.into(),
125                    kind,
126                    dylib_path: dylib_path.clone(),
127                })
128                .collect()),
129            Err(message) => Err(ServerError { message, io: None }),
130        }
131    }
132
133    pub fn exited(&self) -> Option<&ServerError> {
134        self.process.exited()
135    }
136}
137
138impl ProcMacro {
139    pub fn name(&self) -> &str {
140        &self.name
141    }
142
143    pub fn kind(&self) -> ProcMacroKind {
144        self.kind
145    }
146
147    pub fn expand(
148        &self,
149        subtree: tt::SubtreeView<'_, Span>,
150        attr: Option<tt::SubtreeView<'_, Span>>,
151        env: Vec<(String, String)>,
152        def_site: Span,
153        call_site: Span,
154        mixed_site: Span,
155        current_dir: Option<String>,
156    ) -> Result<Result<tt::TopSubtree<Span>, PanicMessage>, ServerError> {
157        let version = self.process.version();
158
159        let mut span_data_table = SpanDataIndexMap::default();
160        let def_site = span_data_table.insert_full(def_site).0;
161        let call_site = span_data_table.insert_full(call_site).0;
162        let mixed_site = span_data_table.insert_full(mixed_site).0;
163        let task = ExpandMacro {
164            data: ExpandMacroData {
165                macro_body: FlatTree::new(subtree, version, &mut span_data_table),
166                macro_name: self.name.to_string(),
167                attributes: attr
168                    .map(|subtree| FlatTree::new(subtree, version, &mut span_data_table)),
169                has_global_spans: ExpnGlobals {
170                    serialize: version >= HAS_GLOBAL_SPANS,
171                    def_site,
172                    call_site,
173                    mixed_site,
174                },
175                span_data_table: if version >= RUST_ANALYZER_SPAN_SUPPORT {
176                    serialize_span_data_index_map(&span_data_table)
177                } else {
178                    Vec::new()
179                },
180            },
181            lib: self.dylib_path.to_path_buf().into(),
182            env,
183            current_dir,
184        };
185
186        let response = self.process.send_task(Request::ExpandMacro(Box::new(task)))?;
187
188        match response {
189            Response::ExpandMacro(it) => {
190                Ok(it.map(|tree| FlatTree::to_subtree_resolved(tree, version, &span_data_table)))
191            }
192            Response::ExpandMacroExtended(it) => Ok(it.map(|resp| {
193                FlatTree::to_subtree_resolved(
194                    resp.tree,
195                    version,
196                    &deserialize_span_data_index_map(&resp.span_data_table),
197                )
198            })),
199            _ => Err(ServerError { message: "unexpected response".to_owned(), io: None }),
200        }
201    }
202}