1use 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#[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 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}