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)]
23#![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
24
25extern crate proc_macro;
26#[cfg(feature = "in-rust-tree")]
27extern crate rustc_driver as _;
28
29#[cfg(not(feature = "in-rust-tree"))]
30extern crate ra_ap_rustc_lexer as rustc_lexer;
31#[cfg(feature = "in-rust-tree")]
32extern crate rustc_lexer;
33
34mod bridge;
35mod dylib;
36mod server_impl;
37mod token_stream;
38
39use std::{
40 collections::{HashMap, hash_map::Entry},
41 env,
42 ffi::OsString,
43 fs,
44 path::{Path, PathBuf},
45 sync::{Arc, Mutex, PoisonError},
46 thread,
47};
48
49use paths::{Utf8Path, Utf8PathBuf};
50use span::Span;
51use temp_dir::TempDir;
52
53pub use crate::server_impl::token_id::SpanId;
54
55pub use proc_macro::Delimiter;
56
57pub use crate::bridge::*;
58pub use crate::server_impl::literal_from_str;
59pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
60
61#[derive(Copy, Clone, Eq, PartialEq, Debug)]
62pub enum ProcMacroKind {
63 CustomDerive,
64 Attr,
65 Bang,
66}
67
68pub const RUSTC_VERSION_STRING: &str = env!("RUSTC_VERSION");
69
70pub struct ProcMacroSrv<'env> {
71 expanders: Mutex<HashMap<Utf8PathBuf, Arc<dylib::Expander>>>,
72 env: &'env EnvSnapshot,
73 temp_dir: TempDir,
74}
75
76impl<'env> ProcMacroSrv<'env> {
77 pub fn new(env: &'env EnvSnapshot) -> Self {
78 Self {
79 expanders: Default::default(),
80 env,
81 temp_dir: TempDir::with_prefix("proc-macro-srv").unwrap(),
82 }
83 }
84
85 pub fn join_spans(&self, first: Span, second: Span) -> Option<Span> {
86 first.join(second, |_, _| {
87 None
90 })
91 }
92}
93
94const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
95
96impl ProcMacroSrv<'_> {
97 pub fn expand<S: ProcMacroSrvSpan>(
98 &self,
99 lib: impl AsRef<Utf8Path>,
100 env: &[(String, String)],
101 current_dir: Option<impl AsRef<Path>>,
102 macro_name: &str,
103 macro_body: token_stream::TokenStream<S>,
104 attribute: Option<token_stream::TokenStream<S>>,
105 def_site: S,
106 call_site: S,
107 mixed_site: S,
108 ) -> Result<token_stream::TokenStream<S>, PanicMessage> {
109 let snapped_env = self.env;
110 let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage {
111 message: Some(format!("failed to load macro: {err}")),
112 })?;
113
114 let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref));
115
116 let result = thread::scope(|s| {
119 let thread = thread::Builder::new()
120 .stack_size(EXPANDER_STACK_SIZE)
121 .name(macro_name.to_owned())
122 .spawn_scoped(s, move || {
123 expander
124 .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site)
125 });
126 match thread.unwrap().join() {
127 Ok(res) => res,
128 Err(e) => std::panic::resume_unwind(e),
129 }
130 });
131 prev_env.rollback();
132
133 result
134 }
135
136 pub fn list_macros(
137 &self,
138 dylib_path: &Utf8Path,
139 ) -> Result<Vec<(String, ProcMacroKind)>, String> {
140 let expander = self.expander(dylib_path)?;
141 Ok(expander.list_macros().map(|(k, v)| (k.to_owned(), v)).collect())
142 }
143
144 fn expander(&self, path: &Utf8Path) -> Result<Arc<dylib::Expander>, String> {
145 let expander = || {
146 let expander = dylib::Expander::new(&self.temp_dir, path)
147 .map_err(|err| format!("Cannot create expander for {path}: {err}",));
148 expander.map(Arc::new)
149 };
150
151 Ok(
152 match self
153 .expanders
154 .lock()
155 .unwrap_or_else(PoisonError::into_inner)
156 .entry(path.to_path_buf())
157 {
158 Entry::Vacant(v) => v.insert(expander()?).clone(),
159 Entry::Occupied(mut e) => {
160 let time = fs::metadata(path).and_then(|it| it.modified()).ok();
161 if Some(e.get().modified_time()) != time {
162 e.insert(expander()?);
163 }
164 e.get().clone()
165 }
166 },
167 )
168 }
169}
170
171pub trait ProcMacroSrvSpan: Copy + Send + Sync {
172 type Server: proc_macro::bridge::server::Server<TokenStream = crate::token_stream::TokenStream<Self>>;
173 fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server;
174}
175
176impl ProcMacroSrvSpan for SpanId {
177 type Server = server_impl::token_id::SpanIdServer;
178
179 fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
180 Self::Server {
181 call_site,
182 def_site,
183 mixed_site,
184 tracked_env_vars: Default::default(),
185 tracked_paths: Default::default(),
186 }
187 }
188}
189impl ProcMacroSrvSpan for Span {
190 type Server = server_impl::rust_analyzer_span::RaSpanServer;
191 fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
192 Self::Server {
193 call_site,
194 def_site,
195 mixed_site,
196 tracked_env_vars: Default::default(),
197 tracked_paths: Default::default(),
198 }
199 }
200}
201
202#[derive(Debug, Clone)]
203pub struct PanicMessage {
204 message: Option<String>,
205}
206
207impl PanicMessage {
208 pub fn into_string(self) -> Option<String> {
209 self.message
210 }
211}
212
213pub struct EnvSnapshot {
214 vars: HashMap<OsString, OsString>,
215}
216
217impl Default for EnvSnapshot {
218 fn default() -> EnvSnapshot {
219 EnvSnapshot { vars: env::vars_os().collect() }
220 }
221}
222
223static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
224
225struct EnvChange<'snap> {
226 changed_vars: Vec<&'snap str>,
227 prev_working_dir: Option<PathBuf>,
228 snap: &'snap EnvSnapshot,
229 _guard: std::sync::MutexGuard<'snap, ()>,
230}
231
232impl<'snap> EnvChange<'snap> {
233 fn apply(
234 snap: &'snap EnvSnapshot,
235 new_vars: &'snap [(String, String)],
236 current_dir: Option<&Path>,
237 ) -> EnvChange<'snap> {
238 let guard = ENV_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
239 let prev_working_dir = match current_dir {
240 Some(dir) => {
241 let prev_working_dir = std::env::current_dir().ok();
242 if let Err(err) = std::env::set_current_dir(dir) {
243 eprintln!(
244 "Failed to set the current working dir to {}. Error: {err:?}",
245 dir.display()
246 )
247 }
248 prev_working_dir
249 }
250 None => None,
251 };
252 EnvChange {
253 snap,
254 changed_vars: new_vars
255 .iter()
256 .map(|(k, v)| {
257 unsafe { env::set_var(k, v) };
259 &**k
260 })
261 .collect(),
262 prev_working_dir,
263 _guard: guard,
264 }
265 }
266
267 fn rollback(self) {}
268}
269
270impl Drop for EnvChange<'_> {
271 fn drop(&mut self) {
272 for name in self.changed_vars.drain(..) {
273 unsafe {
275 match self.snap.vars.get::<std::ffi::OsStr>(name.as_ref()) {
276 Some(prev_val) => env::set_var(name, prev_val),
277 None => env::remove_var(name),
278 }
279 }
280 }
281
282 if let Some(dir) = &self.prev_working_dir
283 && let Err(err) = std::env::set_current_dir(dir)
284 {
285 eprintln!(
286 "Failed to set the current working dir to {}. Error: {:?}",
287 dir.display(),
288 err
289 )
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests;
296
297#[cfg(test)]
298pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf {
299 proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
300}