Skip to main content

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
8#![cfg_attr(not(feature = "in-rust-tree"), allow(unused_crate_dependencies))]
9#![cfg_attr(
10    feature = "in-rust-tree",
11    feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span, rustc_private)
12)]
13#![allow(internal_features, unused_features)]
14
15#[cfg(feature = "in-rust-tree")]
16extern crate rustc_driver as _;
17
18pub mod bidirectional_protocol;
19pub mod legacy_protocol;
20pub mod pool;
21pub mod process;
22pub mod transport;
23
24use paths::{AbsPath, AbsPathBuf};
25use semver::Version;
26use span::{ErasedFileAstId, FIXUP_ERASED_FILE_AST_ID_MARKER, Span};
27use std::{fmt, io, sync::Arc, time::SystemTime};
28
29use crate::{
30    bidirectional_protocol::SubCallback, pool::ProcMacroServerPool, process::ProcMacroServerProcess,
31};
32
33/// The versions of the server protocol
34pub mod version {
35    pub const NO_VERSION_CHECK_VERSION: u32 = 0;
36    pub const VERSION_CHECK_VERSION: u32 = 1;
37    pub const ENCODE_CLOSE_SPAN_VERSION: u32 = 2;
38    pub const HAS_GLOBAL_SPANS: u32 = 3;
39    pub const RUST_ANALYZER_SPAN_SUPPORT: u32 = 4;
40    /// Whether literals encode their kind as an additional u32 field and idents their rawness as a u32 field.
41    pub const EXTENDED_LEAF_DATA: u32 = 5;
42    pub const HASHED_AST_ID: u32 = 6;
43
44    /// Current API version of the proc-macro protocol.
45    pub const CURRENT_API_VERSION: u32 = HASHED_AST_ID;
46}
47
48/// Protocol format for communication between client and server.
49#[derive(Copy, Clone, Debug, PartialEq, Eq)]
50pub enum ProtocolFormat {
51    /// JSON-based legacy protocol (newline-delimited JSON).
52    JsonLegacy,
53    /// Bidirectional postcard protocol with sub-request support.
54    BidirectionalPostcardPrototype,
55}
56
57impl fmt::Display for ProtocolFormat {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            ProtocolFormat::JsonLegacy => write!(f, "json-legacy"),
61            ProtocolFormat::BidirectionalPostcardPrototype => {
62                write!(f, "bidirectional-postcard-prototype")
63            }
64        }
65    }
66}
67
68/// Represents different kinds of procedural macros that can be expanded by the external server.
69#[derive(Copy, Clone, Eq, PartialEq, Debug, serde_derive::Serialize, serde_derive::Deserialize)]
70pub enum ProcMacroKind {
71    /// A macro that derives implementations for a struct or enum.
72    CustomDerive,
73    /// An attribute-like procedural macro.
74    Attr,
75    // This used to be called FuncLike, so that's what the server expects currently.
76    #[serde(alias = "Bang")]
77    #[serde(rename(serialize = "FuncLike", deserialize = "FuncLike"))]
78    Bang,
79}
80
81/// A handle to an external process which load dylibs with macros (.so or .dll)
82/// and runs actual macro expansion functions.
83#[derive(Debug)]
84pub struct ProcMacroClient {
85    /// Currently, the proc macro process expands all procedural macros sequentially.
86    ///
87    /// That means that concurrent salsa requests may block each other when expanding proc macros,
88    /// which is unfortunate, but simple and good enough for the time being.
89    pool: Arc<ProcMacroServerPool>,
90    path: AbsPathBuf,
91}
92
93/// Represents a dynamically loaded library containing procedural macros.
94pub struct MacroDylib {
95    path: AbsPathBuf,
96}
97
98impl MacroDylib {
99    /// Creates a new MacroDylib instance with the given path.
100    pub fn new(path: AbsPathBuf) -> MacroDylib {
101        MacroDylib { path }
102    }
103}
104
105/// A handle to a specific proc-macro (a `#[proc_macro]` annotated function).
106///
107/// It exists within the context of a specific proc-macro server -- currently
108/// we share a single expander process for all macros within a workspace.
109#[derive(Debug, Clone)]
110pub struct ProcMacro {
111    pool: ProcMacroServerPool,
112    dylib_path: Arc<AbsPathBuf>,
113    name: Box<str>,
114    kind: ProcMacroKind,
115    dylib_last_modified: Option<SystemTime>,
116}
117
118impl Eq for ProcMacro {}
119impl PartialEq for ProcMacro {
120    fn eq(&self, other: &Self) -> bool {
121        self.name == other.name
122            && self.kind == other.kind
123            && self.dylib_path == other.dylib_path
124            && self.dylib_last_modified == other.dylib_last_modified
125    }
126}
127
128/// Represents errors encountered when communicating with the proc-macro server.
129#[derive(Clone, Debug)]
130pub struct ServerError {
131    pub message: String,
132    pub io: Option<Arc<io::Error>>,
133}
134
135impl fmt::Display for ServerError {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        self.message.fmt(f)?;
138        if let Some(io) = &self.io {
139            f.write_str(": ")?;
140            io.fmt(f)?;
141        }
142        Ok(())
143    }
144}
145
146impl ProcMacroClient {
147    /// Spawns an external process as the proc macro server and returns a client connected to it.
148    pub fn spawn<'a>(
149        process_path: &AbsPath,
150        env: impl IntoIterator<
151            Item = (impl AsRef<std::ffi::OsStr>, &'a Option<impl 'a + AsRef<std::ffi::OsStr>>),
152        > + Clone,
153        version: Option<&Version>,
154        num_process: usize,
155    ) -> io::Result<ProcMacroClient> {
156        let pool_size = num_process;
157        let mut workers = Vec::with_capacity(pool_size);
158        for _ in 0..pool_size {
159            let worker = ProcMacroServerProcess::spawn(process_path, env.clone(), version)?;
160            workers.push(worker);
161        }
162
163        let pool = ProcMacroServerPool::new(workers);
164        Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() })
165    }
166
167    /// Invokes `spawn` and returns a client connected to the resulting read and write handles.
168    ///
169    /// The `process_path` is used for `Self::server_path`. This function is mainly used for testing.
170    pub fn with_io_channels(
171        process_path: &AbsPath,
172        spawn: impl Fn(
173            Option<ProtocolFormat>,
174        ) -> io::Result<(
175            Box<dyn process::ProcessExit>,
176            Box<dyn io::Write + Send + Sync>,
177            Box<dyn io::BufRead + Send + Sync>,
178        )> + Clone,
179        version: Option<&Version>,
180        num_process: usize,
181    ) -> io::Result<ProcMacroClient> {
182        let pool_size = num_process;
183        let mut workers = Vec::with_capacity(pool_size);
184        for _ in 0..pool_size {
185            let worker =
186                ProcMacroServerProcess::run(spawn.clone(), version, || "<unknown>".to_owned())?;
187            workers.push(worker);
188        }
189
190        let pool = ProcMacroServerPool::new(workers);
191        Ok(ProcMacroClient { pool: Arc::new(pool), path: process_path.to_owned() })
192    }
193
194    /// Returns the absolute path to the proc-macro server.
195    pub fn server_path(&self) -> &AbsPath {
196        &self.path
197    }
198
199    /// Loads a proc-macro dylib into the server process returning a list of `ProcMacro`s loaded.
200    pub fn load_dylib(&self, dylib: MacroDylib) -> Result<Vec<ProcMacro>, ServerError> {
201        self.pool.load_dylib(&dylib)
202    }
203
204    /// Checks if the proc-macro server has exited.
205    pub fn exited(&self) -> Option<&ServerError> {
206        self.pool.exited()
207    }
208}
209
210impl ProcMacro {
211    /// Returns the name of the procedural macro.
212    pub fn name(&self) -> &str {
213        &self.name
214    }
215
216    /// Returns the type of procedural macro.
217    pub fn kind(&self) -> ProcMacroKind {
218        self.kind
219    }
220
221    fn needs_fixup_change(&self) -> bool {
222        let version = self.pool.version();
223        (version::RUST_ANALYZER_SPAN_SUPPORT..version::HASHED_AST_ID).contains(&version)
224    }
225
226    /// On some server versions, the fixup ast id is different than ours. So change it to match.
227    fn change_fixup_to_match_old_server(&self, tt: &mut tt::TopSubtree) {
228        const OLD_FIXUP_AST_ID: ErasedFileAstId = ErasedFileAstId::from_raw(!0 - 1);
229        tt.change_every_ast_id(|ast_id| {
230            if *ast_id == FIXUP_ERASED_FILE_AST_ID_MARKER {
231                *ast_id = OLD_FIXUP_AST_ID;
232            } else if *ast_id == OLD_FIXUP_AST_ID {
233                // Swap between them, that means no collision plus the change can be reversed by doing itself.
234                *ast_id = FIXUP_ERASED_FILE_AST_ID_MARKER;
235            }
236        });
237    }
238
239    /// Expands the procedural macro by sending an expansion request to the server.
240    /// This includes span information and environmental context.
241    pub fn expand(
242        &self,
243        subtree: tt::SubtreeView<'_>,
244        attr: Option<tt::SubtreeView<'_>>,
245        env: Vec<(String, String)>,
246        def_site: Span,
247        call_site: Span,
248        mixed_site: Span,
249        current_dir: String,
250        callback: Option<SubCallback<'_>>,
251    ) -> Result<Result<tt::TopSubtree, String>, ServerError> {
252        let (mut subtree, mut attr) = (subtree, attr);
253        let (mut subtree_changed, mut attr_changed);
254        if self.needs_fixup_change() {
255            subtree_changed = tt::TopSubtree::from_subtree(subtree);
256            self.change_fixup_to_match_old_server(&mut subtree_changed);
257            subtree = subtree_changed.view();
258
259            if let Some(attr) = &mut attr {
260                attr_changed = tt::TopSubtree::from_subtree(*attr);
261                self.change_fixup_to_match_old_server(&mut attr_changed);
262                *attr = attr_changed.view();
263            }
264        }
265
266        self.pool.pick_process()?.expand(
267            self,
268            subtree,
269            attr,
270            env,
271            def_site,
272            call_site,
273            mixed_site,
274            current_dir,
275            callback,
276        )
277    }
278}