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