1use std::{cell::OnceCell, ops::ControlFlow};
3
4use ::tt::TextRange;
5use base_db::Crate;
6use cfg::CfgExpr;
7use parser::T;
8use smallvec::SmallVec;
9use syntax::{
10 AstNode, PreorderWithTokens, SyntaxElement, SyntaxNode, SyntaxToken, WalkEvent,
11 ast::{self, HasAttrs, TokenTreeChildren},
12};
13use syntax_bridge::DocCommentDesugarMode;
14
15use crate::{
16 attrs::{AttrId, Meta, expand_cfg_attr, is_item_tree_filtered_attr},
17 db::ExpandDatabase,
18 fixup::{self, SyntaxFixupUndoInfo},
19 span_map::SpanMapRef,
20 tt::{self, DelimSpan, Span},
21};
22
23struct ItemIsCfgedOut;
24
25#[derive(Debug)]
26struct ExpandedAttrToProcess {
27 range: TextRange,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31enum NextExpandedAttrState {
32 NotStarted,
33 InTheMiddle,
34}
35
36#[derive(Debug)]
37struct AstAttrToProcess {
38 range: TextRange,
39 expanded_attrs: SmallVec<[ExpandedAttrToProcess; 1]>,
40 expanded_attrs_idx: usize,
41 next_expanded_attr: NextExpandedAttrState,
42 pound_span: Span,
43 brackets_span: DelimSpan,
44 excl_span: Option<Span>,
46}
47
48fn macro_input_callback(
49 db: &dyn ExpandDatabase,
50 is_derive: bool,
51 censor_item_tree_attr_ids: &[AttrId],
52 krate: Crate,
53 default_span: Span,
54 span_map: SpanMapRef<'_>,
55) -> impl FnMut(&mut PreorderWithTokens, &WalkEvent<SyntaxElement>) -> (bool, Vec<tt::Leaf>) {
56 let cfg_options = OnceCell::new();
57 let cfg_options = move || *cfg_options.get_or_init(|| krate.cfg_options(db));
58
59 let mut should_strip_attr = {
60 let mut item_tree_attr_id = 0;
61 let mut censor_item_tree_attr_ids_index = 0;
62 move || {
63 let mut result = false;
64 if let Some(&next_censor_attr_id) =
65 censor_item_tree_attr_ids.get(censor_item_tree_attr_ids_index)
66 && next_censor_attr_id.item_tree_index() == item_tree_attr_id
67 {
68 censor_item_tree_attr_ids_index += 1;
69 result = true;
70 }
71 item_tree_attr_id += 1;
72 result
73 }
74 };
75
76 let mut attrs = Vec::new();
77 let mut attrs_idx = 0;
78 let mut has_inner_attrs_owner = false;
79 let mut in_attr = false;
80 let mut done_with_attrs = false;
81 let mut did_top_attrs = false;
82 move |preorder, event| {
83 match event {
84 WalkEvent::Enter(SyntaxElement::Node(node)) => {
85 if done_with_attrs {
86 return (true, Vec::new());
87 }
88
89 if ast::Attr::can_cast(node.kind()) {
90 in_attr = true;
91 let node_range = node.text_range();
92 while attrs
93 .get(attrs_idx)
94 .is_some_and(|it: &AstAttrToProcess| it.range != node_range)
95 {
96 attrs_idx += 1;
97 }
98 } else if !in_attr && let Some(has_attrs) = ast::AnyHasAttrs::cast(node.clone()) {
99 if has_inner_attrs_owner {
103 has_inner_attrs_owner = false;
104 return (true, Vec::new());
105 }
106
107 if did_top_attrs && !is_derive {
108 done_with_attrs = true;
110 return (true, Vec::new());
111 }
112 did_top_attrs = true;
113
114 if let Some(inner_attrs_node) = has_attrs.inner_attributes_node()
115 && inner_attrs_node != *node
116 {
117 has_inner_attrs_owner = true;
118 }
119
120 let node_attrs = ast::attrs_including_inner(&has_attrs);
121
122 attrs.clear();
123 node_attrs.clone().for_each(|attr| {
124 let span_for = |token: Option<SyntaxToken>| {
125 token
126 .map(|token| span_map.span_for_range(token.text_range()))
127 .unwrap_or(default_span)
128 };
129 attrs.push(AstAttrToProcess {
130 range: attr.syntax().text_range(),
131 pound_span: span_for(attr.pound_token()),
132 brackets_span: DelimSpan {
133 open: span_for(attr.l_brack_token()),
134 close: span_for(attr.r_brack_token()),
135 },
136 excl_span: attr
137 .excl_token()
138 .map(|token| span_map.span_for_range(token.text_range())),
139 expanded_attrs: SmallVec::new(),
140 expanded_attrs_idx: 0,
141 next_expanded_attr: NextExpandedAttrState::NotStarted,
142 });
143 });
144
145 attrs_idx = 0;
146 let strip_current_item = expand_cfg_attr(
147 node_attrs,
148 &cfg_options,
149 |attr, _container, range, top_attr| {
150 while attrs[attrs_idx].range != top_attr.syntax().text_range() {
152 attrs_idx += 1;
153 }
154
155 let mut strip_current_attr = false;
156 match attr {
157 Meta::NamedKeyValue { name, .. } => {
158 if name
159 .is_none_or(|name| !is_item_tree_filtered_attr(name.text()))
160 {
161 strip_current_attr = should_strip_attr();
162 }
163 }
164 Meta::TokenTree { path, tt } => {
165 if path.is1("cfg") {
166 let cfg_expr = CfgExpr::parse_from_ast(
167 &mut TokenTreeChildren::new(&tt).peekable(),
168 );
169 if cfg_options().check(&cfg_expr) == Some(false) {
170 return ControlFlow::Break(ItemIsCfgedOut);
171 }
172 strip_current_attr = true;
173 } else if path.segments.len() != 1
174 || !is_item_tree_filtered_attr(path.segments[0].text())
175 {
176 strip_current_attr = should_strip_attr();
177 }
178 }
179 Meta::Path { path } => {
180 if path.segments.len() != 1
181 || !is_item_tree_filtered_attr(path.segments[0].text())
182 {
183 strip_current_attr = should_strip_attr();
184 }
185 }
186 }
187
188 if !strip_current_attr {
189 attrs[attrs_idx]
190 .expanded_attrs
191 .push(ExpandedAttrToProcess { range });
192 }
193
194 ControlFlow::Continue(())
195 },
196 );
197 attrs_idx = 0;
198
199 if strip_current_item.is_some() {
200 preorder.skip_subtree();
201 attrs.clear();
202
203 'eat_comma: {
204 let mut events_until_comma = 0;
206 for event in preorder.clone() {
207 match event {
208 WalkEvent::Enter(SyntaxElement::Node(_))
209 | WalkEvent::Leave(_) => {}
210 WalkEvent::Enter(SyntaxElement::Token(token)) => {
211 let kind = token.kind();
212 if kind == T![,] {
213 break;
214 } else if !kind.is_trivia() {
215 break 'eat_comma;
216 }
217 }
218 }
219 events_until_comma += 1;
220 }
221 preorder.nth(events_until_comma);
222 }
223
224 return (false, Vec::new());
225 }
226 }
227 }
228 WalkEvent::Leave(SyntaxElement::Node(node)) => {
229 if ast::Attr::can_cast(node.kind()) {
230 in_attr = false;
231 attrs_idx += 1;
232 }
233 }
234 WalkEvent::Enter(SyntaxElement::Token(token)) => {
235 if !in_attr {
236 return (true, Vec::new());
237 }
238
239 let Some(ast_attr) = attrs.get_mut(attrs_idx) else {
240 return (true, Vec::new());
241 };
242 let token_range = token.text_range();
243 let Some(expanded_attr) = ast_attr.expanded_attrs.get(ast_attr.expanded_attrs_idx)
244 else {
245 return (false, Vec::new());
248 };
249 match ast_attr.next_expanded_attr {
250 NextExpandedAttrState::NotStarted => {
251 if token_range.start() >= expanded_attr.range.start() {
252 let mut insert_tokens = Vec::with_capacity(3);
254 insert_tokens.push(tt::Leaf::Punct(tt::Punct {
255 char: '#',
256 spacing: tt::Spacing::Alone,
257 span: ast_attr.pound_span,
258 }));
259 if let Some(span) = ast_attr.excl_span {
260 insert_tokens.push(tt::Leaf::Punct(tt::Punct {
261 char: '!',
262 spacing: tt::Spacing::Alone,
263 span,
264 }));
265 }
266 insert_tokens.push(tt::Leaf::Punct(tt::Punct {
267 char: '[',
268 spacing: tt::Spacing::Alone,
269 span: ast_attr.brackets_span.open,
270 }));
271
272 ast_attr.next_expanded_attr = NextExpandedAttrState::InTheMiddle;
273
274 return (true, insert_tokens);
275 } else {
276 return (false, Vec::new());
278 }
279 }
280 NextExpandedAttrState::InTheMiddle => {
281 if token_range.start() >= expanded_attr.range.end() {
282 let insert_tokens = vec![tt::Leaf::Punct(tt::Punct {
284 char: ']',
285 spacing: tt::Spacing::Alone,
286 span: ast_attr.brackets_span.close,
287 })];
288
289 ast_attr.next_expanded_attr = NextExpandedAttrState::NotStarted;
290 ast_attr.expanded_attrs_idx += 1;
291
292 return (false, insert_tokens);
296 } else {
297 return (true, Vec::new());
299 }
300 }
301 }
302 }
303 WalkEvent::Leave(SyntaxElement::Token(_)) => {}
304 }
305 (true, Vec::new())
306 }
307}
308
309pub(crate) fn attr_macro_input_to_token_tree(
310 db: &dyn ExpandDatabase,
311 node: &SyntaxNode,
312 span_map: SpanMapRef<'_>,
313 span: Span,
314 is_derive: bool,
315 censor_item_tree_attr_ids: &[AttrId],
316 krate: Crate,
317) -> (tt::TopSubtree, SyntaxFixupUndoInfo) {
318 let fixups = fixup::fixup_syntax(span_map, node, span, DocCommentDesugarMode::ProcMacro);
319 (
320 syntax_bridge::syntax_node_to_token_tree_modified(
321 node,
322 span_map,
323 fixups.append,
324 fixups.remove,
325 span,
326 DocCommentDesugarMode::ProcMacro,
327 macro_input_callback(db, is_derive, censor_item_tree_attr_ids, krate, span, span_map),
328 ),
329 fixups.undo_info,
330 )
331}
332
333pub fn check_cfg_attr_value(
334 db: &dyn ExpandDatabase,
335 attr: &ast::TokenTree,
336 krate: Crate,
337) -> Option<bool> {
338 let cfg_expr = CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(attr).peekable());
339 krate.cfg_options(db).check(&cfg_expr)
340}