hir_expand/
declarative.rs

1//! Compiled declarative macro expanders (`macro_rules!` and `macro`)
2
3use std::{cell::OnceCell, ops::ControlFlow};
4
5use base_db::Crate;
6use span::{Edition, Span, SyntaxContext};
7use stdx::TupleExt;
8use syntax::{
9    AstNode, AstToken,
10    ast::{self, HasAttrs},
11};
12use syntax_bridge::DocCommentDesugarMode;
13use triomphe::Arc;
14
15use crate::{
16    AstId, ExpandError, ExpandErrorKind, ExpandResult, HirFileId, Lookup, MacroCallId,
17    MacroCallStyle,
18    attrs::{Meta, expand_cfg_attr},
19    db::ExpandDatabase,
20    hygiene::{Transparency, apply_mark},
21    tt,
22};
23
24/// Old-style `macro_rules` or the new macros 2.0
25#[derive(Debug, Clone, Eq, PartialEq)]
26pub struct DeclarativeMacroExpander {
27    pub mac: mbe::DeclarativeMacro,
28    pub transparency: Transparency,
29    edition: Edition,
30}
31
32impl DeclarativeMacroExpander {
33    pub fn expand(
34        &self,
35        db: &dyn ExpandDatabase,
36        tt: tt::TopSubtree,
37        call_id: MacroCallId,
38        span: Span,
39    ) -> ExpandResult<(tt::TopSubtree, Option<u32>)> {
40        let loc = db.lookup_intern_macro_call(call_id);
41        match self.mac.err() {
42            Some(_) => ExpandResult::new(
43                (tt::TopSubtree::empty(tt::DelimSpan { open: span, close: span }), None),
44                ExpandError::new(span, ExpandErrorKind::MacroDefinition),
45            ),
46            None => self
47                .mac
48                .expand(
49                    db,
50                    &tt,
51                    |s| {
52                        s.ctx =
53                            apply_mark(db, s.ctx, call_id.into(), self.transparency, self.edition)
54                    },
55                    loc.kind.call_style(),
56                    span,
57                )
58                .map_err(Into::into),
59        }
60    }
61
62    pub fn expand_unhygienic(
63        &self,
64        db: &dyn ExpandDatabase,
65        tt: tt::TopSubtree,
66        call_style: MacroCallStyle,
67        call_site: Span,
68    ) -> ExpandResult<tt::TopSubtree> {
69        match self.mac.err() {
70            Some(_) => ExpandResult::new(
71                tt::TopSubtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
72                ExpandError::new(call_site, ExpandErrorKind::MacroDefinition),
73            ),
74            None => self
75                .mac
76                .expand(db, &tt, |_| (), call_style, call_site)
77                .map(TupleExt::head)
78                .map_err(Into::into),
79        }
80    }
81
82    pub(crate) fn expander(
83        db: &dyn ExpandDatabase,
84        def_crate: Crate,
85        id: AstId<ast::Macro>,
86    ) -> Arc<DeclarativeMacroExpander> {
87        let (root, map) = crate::db::parse_with_map(db, id.file_id);
88        let root = root.syntax_node();
89
90        let transparency = |node: ast::AnyHasAttrs| {
91            let cfg_options = OnceCell::new();
92            expand_cfg_attr(
93                node.attrs(),
94                || cfg_options.get_or_init(|| def_crate.cfg_options(db)),
95                |attr, _, _, _| {
96                    if let Meta::NamedKeyValue { name: Some(name), value, .. } = attr
97                        && name.text() == "rustc_macro_transparency"
98                        && let Some(value) = value.and_then(ast::String::cast)
99                        && let Ok(value) = value.value()
100                    {
101                        match &*value {
102                            "transparent" => ControlFlow::Break(Transparency::Transparent),
103                            "semitransparent" => ControlFlow::Break(Transparency::SemiTransparent),
104                            "opaque" => ControlFlow::Break(Transparency::Opaque),
105                            _ => ControlFlow::Continue(()),
106                        }
107                    } else {
108                        ControlFlow::Continue(())
109                    }
110                },
111            )
112        };
113        let ctx_edition = |ctx: SyntaxContext| {
114            if ctx.is_root() {
115                def_crate.data(db).edition
116            } else {
117                // UNWRAP-SAFETY: Only the root context has no outer expansion
118                let krate =
119                    db.lookup_intern_macro_call(ctx.outer_expn(db).unwrap().into()).def.krate;
120                krate.data(db).edition
121            }
122        };
123        let (mac, transparency) = match id.to_ptr(db).to_node(&root) {
124            ast::Macro::MacroRules(macro_rules) => (
125                match macro_rules.token_tree() {
126                    Some(arg) => {
127                        let tt = syntax_bridge::syntax_node_to_token_tree(
128                            arg.syntax(),
129                            map.as_ref(),
130                            map.span_for_range(
131                                macro_rules.macro_rules_token().unwrap().text_range(),
132                            ),
133                            DocCommentDesugarMode::Mbe,
134                        );
135
136                        mbe::DeclarativeMacro::parse_macro_rules(&tt, ctx_edition)
137                    }
138                    None => mbe::DeclarativeMacro::from_err(mbe::ParseError::Expected(
139                        "expected a token tree".into(),
140                    )),
141                },
142                transparency(ast::AnyHasAttrs::from(macro_rules))
143                    .unwrap_or(Transparency::SemiTransparent),
144            ),
145            ast::Macro::MacroDef(macro_def) => (
146                match macro_def.body() {
147                    Some(body) => {
148                        let span =
149                            map.span_for_range(macro_def.macro_token().unwrap().text_range());
150                        let args = macro_def.args().map(|args| {
151                            syntax_bridge::syntax_node_to_token_tree(
152                                args.syntax(),
153                                map.as_ref(),
154                                span,
155                                DocCommentDesugarMode::Mbe,
156                            )
157                        });
158                        let body = syntax_bridge::syntax_node_to_token_tree(
159                            body.syntax(),
160                            map.as_ref(),
161                            span,
162                            DocCommentDesugarMode::Mbe,
163                        );
164
165                        mbe::DeclarativeMacro::parse_macro2(args.as_ref(), &body, ctx_edition)
166                    }
167                    None => mbe::DeclarativeMacro::from_err(mbe::ParseError::Expected(
168                        "expected a token tree".into(),
169                    )),
170                },
171                transparency(macro_def.into()).unwrap_or(Transparency::Opaque),
172            ),
173        };
174        let edition = ctx_edition(match id.file_id {
175            HirFileId::MacroFile(macro_file) => macro_file.lookup(db).ctxt,
176            HirFileId::FileId(file) => SyntaxContext::root(file.edition(db)),
177        });
178        Arc::new(DeclarativeMacroExpander { mac, transparency, edition })
179    }
180}