hir_expand/
cfg_process.rs

1//! Processes out #[cfg] and #[cfg_attr] attributes from the input for the derive macro
2use 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    /// If `Some`, this is an inner attribute.
45    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                    // Attributes of the form `key = value` have `ast::Expr` in them, which returns `Some` for
100                    // `AnyHasAttrs::cast()`, so we also need to check `in_attr`.
101
102                    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                        // Derives need all attributes handled, but attribute macros need only the top attributes handled.
109                        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                            // Find the attr.
151                            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                            // If there is a comma after this node, eat it too.
205                            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                    // No expanded attributes in this `ast::Attr`, or we finished them all already, either way
246                    // the remaining tokens should be discarded.
247                    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                            // We started the next attribute.
253                            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                            // Before any attribute or between the attributes.
277                            return (false, Vec::new());
278                        }
279                    }
280                    NextExpandedAttrState::InTheMiddle => {
281                        if token_range.start() >= expanded_attr.range.end() {
282                            // Finished the current attribute.
283                            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                            // It's safe to ignore the current token because between attributes
293                            // there is always at least one token we skip - either the closing bracket
294                            // in `#[]` or the comma in case of multiple attrs in `cfg_attr` expansion.
295                            return (false, insert_tokens);
296                        } else {
297                            // Still in the middle.
298                            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}