1 //! RA Proc Macro Server
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>.
6 //! But we adapt it to better fit RA needs:
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)…
13 #![cfg(any(feature = "sysroot-abi", rust_analyzer))]
14 #![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)]
15 #![warn(rust_2018_idioms, unused_lifetimes)]
16 #![allow(unreachable_pub, internal_features)]
18 extern crate proc_macro
;
25 collections
::{hash_map::Entry, HashMap}
,
29 path
::{Path, PathBuf}
,
35 msg
::{self, ExpnGlobals, TokenId, CURRENT_API_VERSION}
,
40 pub use proc_macro_api
::msg
::TokenId
;
44 pub type Subtree
= ::tt
::Subtree
<TokenId
>;
45 pub type TokenTree
= ::tt
::TokenTree
<TokenId
>;
46 pub type Delimiter
= ::tt
::Delimiter
<TokenId
>;
47 pub type Leaf
= ::tt
::Leaf
<TokenId
>;
48 pub type Literal
= ::tt
::Literal
<TokenId
>;
49 pub type Punct
= ::tt
::Punct
<TokenId
>;
50 pub type Ident
= ::tt
::Ident
<TokenId
>;
54 include
!(concat
!(env
!("OUT_DIR"), "/rustc_version.rs"));
57 pub struct ProcMacroSrv
{
58 expanders
: HashMap
<(PathBuf
, SystemTime
), dylib
::Expander
>,
61 const EXPANDER_STACK_SIZE
: usize = 8 * 1024 * 1024;
64 pub fn expand(&mut self, task
: msg
::ExpandMacro
) -> Result
<msg
::FlatTree
, msg
::PanicMessage
> {
65 let expander
= self.expander(task
.lib
.as_ref()).map_err(|err
| {
66 debug_assert
!(false, "should list macros before asking to expand");
67 msg
::PanicMessage(format
!("failed to load macro: {err}"))
70 let prev_env
= EnvSnapshot
::new();
71 for (k
, v
) in &task
.env
{
74 let prev_working_dir
= match task
.current_dir
{
76 let prev_working_dir
= std
::env
::current_dir().ok();
77 if let Err(err
) = std
::env
::set_current_dir(&dir
) {
78 eprintln
!("Failed to set the current working dir to {dir}. Error: {err:?}")
85 let ExpnGlobals { def_site, call_site, mixed_site, .. }
= task
.has_global_spans
;
86 let def_site
= TokenId(def_site
as u32);
87 let call_site
= TokenId(call_site
as u32);
88 let mixed_site
= TokenId(mixed_site
as u32);
90 let macro_body
= task
.macro_body
.to_subtree_unresolved(CURRENT_API_VERSION
);
91 let attributes
= task
.attributes
.map(|it
| it
.to_subtree_unresolved(CURRENT_API_VERSION
));
92 let result
= thread
::scope(|s
| {
93 let thread
= thread
::Builder
::new()
94 .stack_size(EXPANDER_STACK_SIZE
)
95 .name(task
.macro_name
.clone())
106 .map(|it
| msg
::FlatTree
::new_raw(&it
, CURRENT_API_VERSION
))
108 let res
= match thread
{
109 Ok(handle
) => handle
.join(),
110 Err(e
) => std
::panic
::resume_unwind(Box
::new(e
)),
115 Err(e
) => std
::panic
::resume_unwind(e
),
121 if let Some(dir
) = prev_working_dir
{
122 if let Err(err
) = std
::env
::set_current_dir(&dir
) {
124 "Failed to set the current working dir to {}. Error: {:?}",
131 result
.map_err(msg
::PanicMessage
)
137 ) -> Result
<Vec
<(String
, ProcMacroKind
)>, String
> {
138 let expander
= self.expander(dylib_path
)?
;
139 Ok(expander
.list_macros())
142 fn expander(&mut self, path
: &Path
) -> Result
<&dylib
::Expander
, String
> {
143 let time
= fs
::metadata(path
)
144 .and_then(|it
| it
.modified())
145 .map_err(|err
| format
!("Failed to get file metadata for {}: {err}", path
.display()))?
;
147 Ok(match self.expanders
.entry((path
.to_path_buf(), time
)) {
148 Entry
::Vacant(v
) => {
149 v
.insert(dylib
::Expander
::new(path
).map_err(|err
| {
150 format
!("Cannot create expander for {}: {err}", path
.display())
153 Entry
::Occupied(e
) => e
.into_mut(),
158 pub struct PanicMessage
{
159 message
: Option
<String
>,
163 pub fn into_string(self) -> Option
<String
> {
169 vars
: HashMap
<OsString
, OsString
>,
173 fn new() -> EnvSnapshot
{
174 EnvSnapshot { vars: env::vars_os().collect() }
180 impl Drop
for EnvSnapshot
{
182 for (name
, value
) in env
::vars_os() {
183 let old_value
= self.vars
.remove(&name
);
184 if old_value
!= Some(value
) {
186 None
=> env
::remove_var(name
),
187 Some(old_value
) => env
::set_var(name
, old_value
),
191 for (name
, old_value
) in self.vars
.drain() {
192 env
::set_var(name
, old_value
)
201 pub fn proc_macro_test_dylib_path() -> std
::path
::PathBuf
{
202 proc_macro_test
::PROC_MACRO_TEST_LOCATION
.into()