hir_expand/
declarative.rs

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