ra_ap_hir_expand/
proc_macro.rs

1//! Proc Macro Expander stuff
2
3use core::fmt;
4use std::any::Any;
5use std::{panic::RefUnwindSafe, sync};
6
7use base_db::{Crate, CrateBuilderId, CratesIdMap, Env};
8use intern::Symbol;
9use rustc_hash::FxHashMap;
10use span::Span;
11use triomphe::Arc;
12
13use crate::{ExpandError, ExpandErrorKind, ExpandResult, db::ExpandDatabase, tt};
14
15#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
16pub enum ProcMacroKind {
17    CustomDerive,
18    Bang,
19    Attr,
20}
21
22pub trait AsAny: Any {
23    fn as_any(&self) -> &dyn Any;
24}
25
26impl<T: Any> AsAny for T {
27    fn as_any(&self) -> &dyn Any {
28        self
29    }
30}
31
32/// A proc-macro expander implementation.
33pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe + AsAny {
34    /// Run the expander with the given input subtree, optional attribute input subtree (for
35    /// [`ProcMacroKind::Attr`]), environment variables, and span information.
36    fn expand(
37        &self,
38        subtree: &tt::TopSubtree,
39        attrs: Option<&tt::TopSubtree>,
40        env: &Env,
41        def_site: Span,
42        call_site: Span,
43        mixed_site: Span,
44        current_dir: String,
45    ) -> Result<tt::TopSubtree, ProcMacroExpansionError>;
46
47    fn eq_dyn(&self, other: &dyn ProcMacroExpander) -> bool;
48}
49
50impl PartialEq for dyn ProcMacroExpander {
51    fn eq(&self, other: &Self) -> bool {
52        self.eq_dyn(other)
53    }
54}
55
56impl Eq for dyn ProcMacroExpander {}
57
58#[derive(Debug)]
59pub enum ProcMacroExpansionError {
60    /// The proc-macro panicked.
61    Panic(String),
62    /// The server itself errored out.
63    System(String),
64}
65
66pub type ProcMacroLoadResult = Result<Vec<ProcMacro>, (String, bool)>;
67type StoredProcMacroLoadResult = Result<Box<[ProcMacro]>, (Box<str>, bool)>;
68
69#[derive(Default, Debug)]
70pub struct ProcMacrosBuilder(FxHashMap<CrateBuilderId, Arc<CrateProcMacros>>);
71
72impl ProcMacrosBuilder {
73    pub fn insert(
74        &mut self,
75        proc_macros_crate: CrateBuilderId,
76        mut proc_macro: ProcMacroLoadResult,
77    ) {
78        if let Ok(proc_macros) = &mut proc_macro {
79            // Sort proc macros to improve incrementality when only their order has changed (ideally the build system
80            // will not change their order, but just to be sure).
81            proc_macros.sort_unstable_by(|proc_macro, proc_macro2| {
82                (proc_macro.name.as_str(), proc_macro.kind)
83                    .cmp(&(proc_macro2.name.as_str(), proc_macro2.kind))
84            });
85        }
86        self.0.insert(
87            proc_macros_crate,
88            match proc_macro {
89                Ok(it) => Arc::new(CrateProcMacros(Ok(it.into_boxed_slice()))),
90                Err((e, hard_err)) => {
91                    Arc::new(CrateProcMacros(Err((e.into_boxed_str(), hard_err))))
92                }
93            },
94        );
95    }
96
97    pub(crate) fn build(self, crates_id_map: &CratesIdMap) -> ProcMacros {
98        let mut map = self
99            .0
100            .into_iter()
101            .map(|(krate, proc_macro)| (crates_id_map[&krate], proc_macro))
102            .collect::<FxHashMap<_, _>>();
103        map.shrink_to_fit();
104        ProcMacros(map)
105    }
106}
107
108impl FromIterator<(CrateBuilderId, ProcMacroLoadResult)> for ProcMacrosBuilder {
109    fn from_iter<T: IntoIterator<Item = (CrateBuilderId, ProcMacroLoadResult)>>(iter: T) -> Self {
110        let mut builder = ProcMacrosBuilder::default();
111        for (k, v) in iter {
112            builder.insert(k, v);
113        }
114        builder
115    }
116}
117
118#[derive(Debug, PartialEq, Eq)]
119pub struct CrateProcMacros(StoredProcMacroLoadResult);
120
121#[derive(Default, Debug)]
122pub struct ProcMacros(FxHashMap<Crate, Arc<CrateProcMacros>>);
123impl ProcMacros {
124    fn get(&self, krate: Crate) -> Option<Arc<CrateProcMacros>> {
125        self.0.get(&krate).cloned()
126    }
127}
128
129impl CrateProcMacros {
130    fn get(&self, idx: u32, err_span: Span) -> Result<&ProcMacro, ExpandError> {
131        let proc_macros = match &self.0 {
132            Ok(proc_macros) => proc_macros,
133            Err(_) => {
134                return Err(ExpandError::other(
135                    err_span,
136                    "internal error: no proc macros for crate",
137                ));
138            }
139        };
140        proc_macros.get(idx as usize).ok_or_else(|| {
141                ExpandError::other(err_span,
142                    format!(
143                        "internal error: proc-macro index out of bounds: the length is {} but the index is {}",
144                        proc_macros.len(),
145                        idx
146                    )
147                )
148            }
149        )
150    }
151
152    pub fn get_error(&self) -> Option<(&str, bool)> {
153        self.0.as_ref().err().map(|(e, hard_err)| (&**e, *hard_err))
154    }
155
156    /// Fetch the [`CustomProcMacroExpander`]s and their corresponding names for the given crate.
157    pub fn list(
158        &self,
159        def_site_ctx: span::SyntaxContext,
160    ) -> Option<Box<[(crate::name::Name, CustomProcMacroExpander, bool)]>> {
161        match &self.0 {
162            Ok(proc_macros) => Some(
163                proc_macros
164                    .iter()
165                    .enumerate()
166                    .map(|(idx, it)| {
167                        let name = crate::name::Name::new_symbol(it.name.clone(), def_site_ctx);
168                        (name, CustomProcMacroExpander::new(idx as u32), it.disabled)
169                    })
170                    .collect(),
171            ),
172            _ => None,
173        }
174    }
175}
176
177/// A loaded proc-macro.
178#[derive(Debug, Clone, Eq)]
179pub struct ProcMacro {
180    /// The name of the proc macro.
181    pub name: Symbol,
182    pub kind: ProcMacroKind,
183    /// The expander handle for this proc macro.
184    pub expander: sync::Arc<dyn ProcMacroExpander>,
185    /// Whether this proc-macro is disabled for early name resolution. Notably, the
186    /// [`Self::expander`] is still usable.
187    pub disabled: bool,
188}
189
190// `#[derive(PartialEq)]` generates a strange "cannot move" error.
191impl PartialEq for ProcMacro {
192    fn eq(&self, other: &Self) -> bool {
193        let Self { name, kind, expander, disabled } = self;
194        let Self {
195            name: other_name,
196            kind: other_kind,
197            expander: other_expander,
198            disabled: other_disabled,
199        } = other;
200        name == other_name
201            && kind == other_kind
202            && expander == other_expander
203            && disabled == other_disabled
204    }
205}
206
207/// A custom proc-macro expander handle. This handle together with its crate resolves to a [`ProcMacro`]
208#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
209pub struct CustomProcMacroExpander {
210    proc_macro_id: u32,
211}
212
213impl CustomProcMacroExpander {
214    const MISSING_EXPANDER: u32 = !0;
215    const DISABLED_ID: u32 = !1;
216    const PROC_MACRO_ATTR_DISABLED: u32 = !2;
217
218    pub fn new(proc_macro_id: u32) -> Self {
219        assert_ne!(proc_macro_id, Self::MISSING_EXPANDER);
220        assert_ne!(proc_macro_id, Self::DISABLED_ID);
221        assert_ne!(proc_macro_id, Self::PROC_MACRO_ATTR_DISABLED);
222        Self { proc_macro_id }
223    }
224
225    /// An expander that always errors due to the actual proc-macro expander missing.
226    pub const fn missing_expander() -> Self {
227        Self { proc_macro_id: Self::MISSING_EXPANDER }
228    }
229
230    /// A dummy expander that always errors. This expander is used for macros that have been disabled.
231    pub const fn disabled() -> Self {
232        Self { proc_macro_id: Self::DISABLED_ID }
233    }
234
235    /// A dummy expander that always errors. This expander is used for attribute macros when
236    /// proc-macro attribute expansion is disabled.
237    pub const fn disabled_proc_attr() -> Self {
238        Self { proc_macro_id: Self::PROC_MACRO_ATTR_DISABLED }
239    }
240
241    /// The macro-expander is missing or has yet to be build.
242    pub const fn is_missing(&self) -> bool {
243        self.proc_macro_id == Self::MISSING_EXPANDER
244    }
245
246    /// The macro is explicitly disabled and cannot be expanded.
247    pub const fn is_disabled(&self) -> bool {
248        self.proc_macro_id == Self::DISABLED_ID
249    }
250
251    /// The macro is explicitly disabled due to proc-macro attribute expansion being disabled.
252    pub const fn is_disabled_proc_attr(&self) -> bool {
253        self.proc_macro_id == Self::PROC_MACRO_ATTR_DISABLED
254    }
255
256    pub fn as_expand_error(&self, def_crate: Crate) -> Option<ExpandErrorKind> {
257        match self.proc_macro_id {
258            Self::PROC_MACRO_ATTR_DISABLED => Some(ExpandErrorKind::ProcMacroAttrExpansionDisabled),
259            Self::DISABLED_ID => Some(ExpandErrorKind::MacroDisabled),
260            Self::MISSING_EXPANDER => Some(ExpandErrorKind::MissingProcMacroExpander(def_crate)),
261            _ => None,
262        }
263    }
264
265    pub fn expand(
266        self,
267        db: &dyn ExpandDatabase,
268        def_crate: Crate,
269        calling_crate: Crate,
270        tt: &tt::TopSubtree,
271        attr_arg: Option<&tt::TopSubtree>,
272        def_site: Span,
273        call_site: Span,
274        mixed_site: Span,
275    ) -> ExpandResult<tt::TopSubtree> {
276        match self.proc_macro_id {
277            Self::PROC_MACRO_ATTR_DISABLED => ExpandResult::new(
278                tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
279                ExpandError::new(call_site, ExpandErrorKind::ProcMacroAttrExpansionDisabled),
280            ),
281            Self::MISSING_EXPANDER => ExpandResult::new(
282                tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
283                ExpandError::new(call_site, ExpandErrorKind::MissingProcMacroExpander(def_crate)),
284            ),
285            Self::DISABLED_ID => ExpandResult::new(
286                tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
287                ExpandError::new(call_site, ExpandErrorKind::MacroDisabled),
288            ),
289            id => {
290                let proc_macros = match db.proc_macros_for_crate(def_crate) {
291                    Some(it) => it,
292                    None => {
293                        return ExpandResult::new(
294                            tt::TopSubtree::empty(tt::DelimSpan {
295                                open: call_site,
296                                close: call_site,
297                            }),
298                            ExpandError::other(
299                                call_site,
300                                "internal error: no proc macros for crate",
301                            ),
302                        );
303                    }
304                };
305                let proc_macro = match proc_macros.get(id, call_site) {
306                    Ok(proc_macro) => proc_macro,
307                    Err(e) => {
308                        return ExpandResult::new(
309                            tt::TopSubtree::empty(tt::DelimSpan {
310                                open: call_site,
311                                close: call_site,
312                            }),
313                            e,
314                        );
315                    }
316                };
317
318                // Proc macros have access to the environment variables of the invoking crate.
319                let env = calling_crate.env(db);
320                // FIXME: Can we avoid the string allocation here?
321                let current_dir = calling_crate.data(db).proc_macro_cwd.to_string();
322
323                match proc_macro.expander.expand(
324                    tt,
325                    attr_arg,
326                    env,
327                    def_site,
328                    call_site,
329                    mixed_site,
330                    current_dir,
331                ) {
332                    Ok(t) => ExpandResult::ok(t),
333                    Err(err) => match err {
334                        // Don't discard the item in case something unexpected happened while expanding attributes
335                        ProcMacroExpansionError::System(text)
336                            if proc_macro.kind == ProcMacroKind::Attr =>
337                        {
338                            ExpandResult {
339                                value: tt.clone(),
340                                err: Some(ExpandError::other(call_site, text)),
341                            }
342                        }
343                        ProcMacroExpansionError::System(text)
344                        | ProcMacroExpansionError::Panic(text) => ExpandResult::new(
345                            tt::TopSubtree::empty(tt::DelimSpan {
346                                open: call_site,
347                                close: call_site,
348                            }),
349                            ExpandError::new(
350                                call_site,
351                                ExpandErrorKind::ProcMacroPanic(text.into_boxed_str()),
352                            ),
353                        ),
354                    },
355                }
356            }
357        }
358    }
359}
360
361pub(crate) fn proc_macros_for_crate(
362    db: &dyn ExpandDatabase,
363    krate: Crate,
364) -> Option<Arc<CrateProcMacros>> {
365    db.proc_macros().get(krate)
366}