Skip to main content

hir_expand/
declarative.rs

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