1 //! RA Proc Macro Server
2 //!
3 //! This library is able to call compiled Rust custom derive dynamic libraries on arbitrary code.
4 //! The general idea here is based on <https://github.com/fedochet/rust-proc-macro-expander>.
5 //!
6 //! But we adapt it to better fit RA needs:
7 //!
8 //! * We use `tt` for proc-macro `TokenStream` server, it is easier to manipulate and interact with
9 //!   RA than `proc-macro2` token stream.
10 //! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable`
11 //!   rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)…
12 #![allow(unreachable_pub)]
13 
14 mod dylib;
15 mod abis;
16 
17 use std::{
18     collections::{hash_map::Entry, HashMap},
19     env, fs,
20     path::{Path, PathBuf},
21     time::SystemTime,
22 };
23 
24 use proc_macro_api::{
25     msg::{ExpandMacro, FlatTree, PanicMessage},
26     ProcMacroKind,
27 };
28 
29 #[derive(Default)]
30 pub(crate) struct ProcMacroSrv {
31     expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
32 }
33 
34 impl ProcMacroSrv {
expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage>35     pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
36         let expander = self.expander(task.lib.as_ref()).map_err(|err| {
37             debug_assert!(false, "should list macros before asking to expand");
38             PanicMessage(format!("failed to load macro: {}", err))
39         })?;
40 
41         let mut prev_env = HashMap::new();
42         for (k, v) in &task.env {
43             prev_env.insert(k.as_str(), env::var_os(k));
44             env::set_var(k, v);
45         }
46 
47         let macro_body = task.macro_body.to_subtree();
48         let attributes = task.attributes.map(|it| it.to_subtree());
49         let result = expander
50             .expand(&task.macro_name, &macro_body, attributes.as_ref())
51             .map(|it| FlatTree::new(&it));
52 
53         for (k, _) in &task.env {
54             match &prev_env[k.as_str()] {
55                 Some(v) => env::set_var(k, v),
56                 None => env::remove_var(k),
57             }
58         }
59 
60         result.map_err(PanicMessage)
61     }
62 
list_macros( &mut self, dylib_path: &Path, ) -> Result<Vec<(String, ProcMacroKind)>, String>63     pub(crate) fn list_macros(
64         &mut self,
65         dylib_path: &Path,
66     ) -> Result<Vec<(String, ProcMacroKind)>, String> {
67         let expander = self.expander(dylib_path)?;
68         Ok(expander.list_macros())
69     }
70 
expander(&mut self, path: &Path) -> Result<&dylib::Expander, String>71     fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
72         let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| {
73             format!("Failed to get file metadata for {}: {:?}", path.display(), err)
74         })?;
75 
76         Ok(match self.expanders.entry((path.to_path_buf(), time)) {
77             Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| {
78                 format!("Cannot create expander for {}: {:?}", path.display(), err)
79             })?),
80             Entry::Occupied(e) => e.into_mut(),
81         })
82     }
83 }
84 
85 pub mod cli;
86 
87 #[cfg(test)]
88 mod tests;
89