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