1#![cfg(feature = "sysroot-abi")]
14#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
15#![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)]
16#![allow(
17 unreachable_pub,
18 internal_features,
19 clippy::disallowed_types,
20 clippy::print_stderr,
21 unused_crate_dependencies,
22 unused_features
23)]
24#![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
25
26#[cfg(not(feature = "in-rust-tree"))]
27extern crate proc_macro as rustc_proc_macro;
28#[cfg(feature = "in-rust-tree")]
29extern crate rustc_driver as _;
30#[cfg(feature = "in-rust-tree")]
31extern crate rustc_proc_macro;
32
33#[cfg(not(feature = "in-rust-tree"))]
34extern crate ra_ap_rustc_lexer as rustc_lexer;
35#[cfg(feature = "in-rust-tree")]
36extern crate rustc_lexer;
37
38mod bridge;
39mod dylib;
40mod server_impl;
41mod token_stream;
42
43use std::{
44 collections::{HashMap, hash_map::Entry},
45 env,
46 ffi::OsString,
47 fs,
48 ops::Range,
49 path::{Path, PathBuf},
50 sync::{Arc, Mutex, PoisonError},
51 thread,
52};
53
54use paths::{Utf8Path, Utf8PathBuf};
55use span::Span;
56use temp_dir::TempDir;
57
58pub use crate::server_impl::token_id::SpanId;
59
60pub use rustc_proc_macro::Delimiter;
61pub use span;
62
63pub use crate::bridge::*;
64pub use crate::server_impl::literal_from_str;
65pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
66
67#[derive(Copy, Clone, Eq, PartialEq, Debug)]
68pub enum ProcMacroKind {
69 CustomDerive,
70 Attr,
71 Bang,
72}
73
74pub const RUSTC_VERSION_STRING: &str = env!("RUSTC_VERSION");
75
76pub struct ProcMacroSrv<'env> {
77 expanders: Mutex<HashMap<Utf8PathBuf, Arc<dylib::Expander>>>,
78 env: &'env EnvSnapshot,
79 temp_dir: TempDir,
80}
81
82impl<'env> ProcMacroSrv<'env> {
83 pub fn new(env: &'env EnvSnapshot) -> Self {
84 Self {
85 expanders: Default::default(),
86 env,
87 temp_dir: TempDir::with_prefix("proc-macro-srv").unwrap(),
88 }
89 }
90
91 pub fn join_spans(&self, first: Span, second: Span) -> Option<Span> {
92 first.join(second, |_, _| {
93 None
96 })
97 }
98}
99
100#[derive(Debug)]
101pub enum ProcMacroClientError {
102 Cancelled { reason: String },
103 Io(std::io::Error),
104 Protocol(String),
105 Eof,
106}
107
108#[derive(Debug)]
109pub enum ProcMacroPanicMarker {
110 Cancelled { reason: String },
111 Internal { reason: String },
112}
113
114pub type ProcMacroClientHandle<'a> = &'a mut (dyn ProcMacroClientInterface + Sync + Send);
115
116pub trait ProcMacroClientInterface {
117 fn file(&mut self, file_id: span::FileId) -> String;
118 fn source_text(&mut self, span: Span) -> Option<String>;
119 fn local_file(&mut self, file_id: span::FileId) -> Option<String>;
120 fn line_column(&mut self, span: Span) -> Option<(u32, u32)>;
122
123 fn byte_range(&mut self, span: Span) -> Range<usize>;
124 fn span_source(&mut self, span: Span) -> Span;
125 fn span_parent(&mut self, span: Span) -> Option<Span>;
126}
127
128const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
129
130pub enum ExpandError {
131 Panic(PanicMessage),
132 Cancelled { reason: Option<String> },
133 Internal { reason: Option<String> },
134}
135
136impl ExpandError {
137 pub fn into_string(self) -> Option<String> {
138 match self {
139 ExpandError::Panic(panic_message) => panic_message.into_string(),
140 ExpandError::Cancelled { reason } => reason,
141 ExpandError::Internal { reason } => reason,
142 }
143 }
144}
145
146impl ProcMacroSrv<'_> {
147 pub fn expand<S: ProcMacroSrvSpan>(
148 &self,
149 lib: impl AsRef<Utf8Path>,
150 env: &[(String, String)],
151 current_dir: Option<impl AsRef<Path>>,
152 macro_name: &str,
153 macro_body: token_stream::TokenStream<S>,
154 attribute: Option<token_stream::TokenStream<S>>,
155 def_site: S,
156 call_site: S,
157 mixed_site: S,
158 callback: Option<ProcMacroClientHandle<'_>>,
159 ) -> Result<token_stream::TokenStream<S>, ExpandError> {
160 let snapped_env = self.env;
161 let expander = self.expander(lib.as_ref()).map_err(|err| ExpandError::Internal {
162 reason: Some(format!("failed to load macro: {err}")),
163 })?;
164
165 let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref));
166
167 let result = thread::scope(|s| {
170 let thread = thread::Builder::new()
171 .stack_size(EXPANDER_STACK_SIZE)
172 .name(macro_name.to_owned())
173 .spawn_scoped(s, move || {
174 expander.expand(
175 macro_name, macro_body, attribute, def_site, call_site, mixed_site,
176 callback,
177 )
178 });
179 match thread.unwrap().join() {
180 Ok(res) => res.map_err(ExpandError::Panic),
181
182 Err(payload) => {
183 if let Some(marker) = payload.downcast_ref::<ProcMacroPanicMarker>() {
184 return match marker {
185 ProcMacroPanicMarker::Cancelled { reason } => {
186 Err(ExpandError::Cancelled { reason: Some(reason.clone()) })
187 }
188 ProcMacroPanicMarker::Internal { reason } => {
189 Err(ExpandError::Internal { reason: Some(reason.clone()) })
190 }
191 };
192 }
193
194 std::panic::resume_unwind(payload)
195 }
196 }
197 });
198 prev_env.rollback();
199
200 result
201 }
202
203 pub fn list_macros(
204 &self,
205 dylib_path: &Utf8Path,
206 ) -> Result<Vec<(String, ProcMacroKind)>, String> {
207 let expander = self.expander(dylib_path)?;
208 Ok(expander.list_macros().map(|(k, v)| (k.to_owned(), v)).collect())
209 }
210
211 fn expander(&self, path: &Utf8Path) -> Result<Arc<dylib::Expander>, String> {
212 let expander = || {
213 let expander = dylib::Expander::new(&self.temp_dir, path)
214 .map_err(|err| format!("Cannot create expander for {path}: {err}",));
215 expander.map(Arc::new)
216 };
217
218 Ok(
219 match self
220 .expanders
221 .lock()
222 .unwrap_or_else(PoisonError::into_inner)
223 .entry(path.to_path_buf())
224 {
225 Entry::Vacant(v) => v.insert(expander()?).clone(),
226 Entry::Occupied(mut e) => {
227 let time = fs::metadata(path).and_then(|it| it.modified()).ok();
228 if Some(e.get().modified_time()) != time {
229 e.insert(expander()?);
230 }
231 e.get().clone()
232 }
233 },
234 )
235 }
236}
237
238pub trait ProcMacroSrvSpan: Copy + Send + Sync {
239 type Server<'a>: rustc_proc_macro::bridge::server::Server<
240 TokenStream = crate::token_stream::TokenStream<Self>,
241 >;
242 fn make_server<'a>(
243 call_site: Self,
244 def_site: Self,
245 mixed_site: Self,
246 callback: Option<ProcMacroClientHandle<'a>>,
247 ) -> Self::Server<'a>;
248}
249
250impl ProcMacroSrvSpan for SpanId {
251 type Server<'a> = server_impl::token_id::SpanIdServer<'a>;
252
253 fn make_server<'a>(
254 call_site: Self,
255 def_site: Self,
256 mixed_site: Self,
257 callback: Option<ProcMacroClientHandle<'a>>,
258 ) -> Self::Server<'a> {
259 Self::Server {
260 call_site,
261 def_site,
262 mixed_site,
263 callback,
264 tracked_env_vars: Default::default(),
265 tracked_paths: Default::default(),
266 }
267 }
268}
269
270impl ProcMacroSrvSpan for Span {
271 type Server<'a> = server_impl::rust_analyzer_span::RaSpanServer<'a>;
272 fn make_server<'a>(
273 call_site: Self,
274 def_site: Self,
275 mixed_site: Self,
276 callback: Option<ProcMacroClientHandle<'a>>,
277 ) -> Self::Server<'a> {
278 Self::Server {
279 call_site,
280 def_site,
281 mixed_site,
282 callback,
283 tracked_env_vars: Default::default(),
284 tracked_paths: Default::default(),
285 }
286 }
287}
288
289#[derive(Debug, Clone)]
290pub struct PanicMessage {
291 message: Option<String>,
292}
293
294impl PanicMessage {
295 pub fn into_string(self) -> Option<String> {
296 self.message
297 }
298}
299
300pub struct EnvSnapshot {
301 vars: HashMap<OsString, OsString>,
302}
303
304impl Default for EnvSnapshot {
305 fn default() -> EnvSnapshot {
306 EnvSnapshot { vars: env::vars_os().collect() }
307 }
308}
309
310static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
311
312struct EnvChange<'snap> {
313 changed_vars: Vec<&'snap str>,
314 prev_working_dir: Option<PathBuf>,
315 snap: &'snap EnvSnapshot,
316 _guard: std::sync::MutexGuard<'snap, ()>,
317}
318
319impl<'snap> EnvChange<'snap> {
320 fn apply(
321 snap: &'snap EnvSnapshot,
322 new_vars: &'snap [(String, String)],
323 current_dir: Option<&Path>,
324 ) -> EnvChange<'snap> {
325 let guard = ENV_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
326 let prev_working_dir = match current_dir {
327 Some(dir) => {
328 let prev_working_dir = std::env::current_dir().ok();
329 if let Err(err) = std::env::set_current_dir(dir) {
330 eprintln!(
331 "Failed to change the current working dir to {}. Error: {err:?}",
332 dir.display()
333 )
334 }
335 prev_working_dir
336 }
337 None => None,
338 };
339 EnvChange {
340 snap,
341 changed_vars: new_vars
342 .iter()
343 .map(|(k, v)| {
344 unsafe { env::set_var(k, v) };
346 &**k
347 })
348 .collect(),
349 prev_working_dir,
350 _guard: guard,
351 }
352 }
353
354 fn rollback(self) {}
355}
356
357impl Drop for EnvChange<'_> {
358 fn drop(&mut self) {
359 for name in self.changed_vars.drain(..) {
360 unsafe {
362 match self.snap.vars.get::<std::ffi::OsStr>(name.as_ref()) {
363 Some(prev_val) => env::set_var(name, prev_val),
364 None => env::remove_var(name),
365 }
366 }
367 }
368
369 if let Some(dir) = &self.prev_working_dir
370 && let Err(err) = std::env::set_current_dir(dir)
371 {
372 eprintln!(
373 "Failed to change the current working dir back to {}. Error: {:?}",
374 dir.display(),
375 err
376 )
377 }
378 }
379}
380
381#[cfg(test)]
382mod tests;
383
384#[cfg(test)]
385pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf {
386 proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
387}