Skip to main content

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, SourceDatabase};
6use cfg::CfgExpr;
7use parser::T;
8use smallvec::SmallVec;
9use syntax::{
10    AstNode, PreorderWithTokens, SyntaxElement, SyntaxNode, SyntaxToken, WalkEvent,
11    ast::{self, HasAttrs},
12};
13use syntax_bridge::DocCommentDesugarMode;
14
15use crate::{
16    attrs::{AstPathExt, AttrId, expand_cfg_attr, is_item_tree_filtered_attr},
17    fixup::{self, SyntaxFixupUndoInfo},
18    span_map::SpanMap,
19    tt::{self, DelimSpan, Span},
20};
21
22struct ItemIsCfgedOut;
23
24#[derive(Debug)]
25struct ExpandedAttrToProcess {
26    attr: ast::Meta,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30enum NextExpandedAttrState {
31    NotStarted,
32    InTheMiddle,
33}
34
35#[derive(Debug)]
36struct AstAttrToProcess {
37    range: TextRange,
38    expanded_attrs: SmallVec<[ExpandedAttrToProcess; 1]>,
39    expanded_attrs_idx: usize,
40    next_expanded_attr: NextExpandedAttrState,
41    pound_span: Span,
42    brackets_span: DelimSpan,
43    /// If `Some`, this is an inner attribute.
44    excl_span: Option<Span>,
45}
46
47fn macro_input_callback(
48    db: &dyn SourceDatabase,
49    is_derive: bool,
50    censor_item_tree_attr_ids: &[AttrId],
51    krate: Crate,
52    default_span: Span,
53    span_map: SpanMap<'_>,
54) -> impl FnMut(&mut PreorderWithTokens, &WalkEvent<SyntaxElement>) -> (bool, Vec<tt::Leaf>) {
55    let cfg_options = OnceCell::new();
56    let cfg_options = move || *cfg_options.get_or_init(|| krate.cfg_options(db));
57
58    let mut should_strip_attr = {
59        let mut item_tree_attr_id = 0;
60        let mut censor_item_tree_attr_ids_index = 0;
61        move || {
62            let mut result = false;
63            if let Some(&next_censor_attr_id) =
64                censor_item_tree_attr_ids.get(censor_item_tree_attr_ids_index)
65                && next_censor_attr_id.item_tree_index() == item_tree_attr_id
66            {
67                censor_item_tree_attr_ids_index += 1;
68                result = true;
69            }
70            item_tree_attr_id += 1;
71            result
72        }
73    };
74
75    let mut attrs = Vec::new();
76    let mut attrs_idx = 0;
77    let mut has_inner_attrs_owner = false;
78    let mut in_attr = false;
79    let mut done_with_attrs = false;
80    let mut did_top_attrs = false;
81    move |preorder, event| {
82        match event {
83            WalkEvent::Enter(SyntaxElement::Node(node)) => {
84                if done_with_attrs {
85                    return (true, Vec::new());
86                }
87
88                if ast::Attr::can_cast(node.kind()) {
89                    in_attr = true;
90                    let node_range = node.text_range();
91                    while attrs
92                        .get(attrs_idx)
93                        .is_some_and(|it: &AstAttrToProcess| it.range != node_range)
94                    {
95                        attrs_idx += 1;
96                    }
97                } else if !in_attr && let Some(has_attrs) = ast::AnyHasAttrs::cast(node.clone()) {
98                    // Attributes of the form `key = value` have `ast::Expr` in them, which returns `Some` for
99                    // `AnyHasAttrs::cast()`, so we also need to check `in_attr`.
100
101                    if has_inner_attrs_owner {
102                        has_inner_attrs_owner = false;
103                        return (true, Vec::new());
104                    }
105
106                    if did_top_attrs && !is_derive {
107                        // Derives need all attributes handled, but attribute macros need only the top attributes handled.
108                        done_with_attrs = true;
109                        return (true, Vec::new());
110                    }
111                    did_top_attrs = true;
112
113                    if let Some(inner_attrs_node) = has_attrs.inner_attributes_node()
114                        && inner_attrs_node != *node
115                    {
116                        has_inner_attrs_owner = true;
117                    }
118
119                    let node_attrs = ast::attrs_including_inner(&has_attrs);
120
121                    attrs.clear();
122                    node_attrs.clone().for_each(|attr| {
123                        let span_for = |token: Option<SyntaxToken>| {
124                            token
125                                .map(|token| span_map.span_for_range(token.text_range()))
126                                .unwrap_or(default_span)
127                        };
128                        attrs.push(AstAttrToProcess {
129                            range: attr.syntax().text_range(),
130                            pound_span: span_for(attr.pound_token()),
131                            brackets_span: DelimSpan {
132                                open: span_for(attr.l_brack_token()),
133                                close: span_for(attr.r_brack_token()),
134                            },
135                            excl_span: attr
136                                .excl_token()
137                                .map(|token| span_map.span_for_range(token.text_range())),
138                            expanded_attrs: SmallVec::new(),
139                            expanded_attrs_idx: 0,
140                            next_expanded_attr: NextExpandedAttrState::NotStarted,
141                        });
142                    });
143
144                    attrs_idx = 0;
145                    let strip_current_item =
146                        expand_cfg_attr(node_attrs, &cfg_options, |attr, top_attr| {
147                            // Find the attr.
148                            while attrs[attrs_idx].range != top_attr.syntax().text_range() {
149                                attrs_idx += 1;
150                            }
151
152                            let mut strip_current_attr = false;
153                            match &attr {
154                                ast::Meta::CfgMeta(attr) => {
155                                    if let Some(cfg_predicate) = attr.cfg_predicate() {
156                                        let cfg_expr = CfgExpr::parse_from_ast(cfg_predicate);
157                                        if cfg_options().check(&cfg_expr) == Some(false) {
158                                            return ControlFlow::Break(ItemIsCfgedOut);
159                                        }
160                                        strip_current_attr = true;
161                                    }
162                                }
163                                _ => {
164                                    if attr
165                                        .path()
166                                        .as_one_segment()
167                                        .is_none_or(|name| !is_item_tree_filtered_attr(&name))
168                                    {
169                                        strip_current_attr = should_strip_attr();
170                                    }
171                                }
172                            }
173
174                            if !strip_current_attr {
175                                attrs[attrs_idx]
176                                    .expanded_attrs
177                                    .push(ExpandedAttrToProcess { attr });
178                            }
179
180                            ControlFlow::Continue(())
181                        });
182                    attrs_idx = 0;
183
184                    if strip_current_item.is_some() {
185                        preorder.skip_subtree();
186                        attrs.clear();
187
188                        'eat_comma: {
189                            // If there is a comma after this node, eat it too.
190                            let mut events_until_comma = 0;
191                            for event in preorder.clone() {
192                                match event {
193                                    WalkEvent::Enter(SyntaxElement::Node(_))
194                                    | WalkEvent::Leave(_) => {}
195                                    WalkEvent::Enter(SyntaxElement::Token(token)) => {
196                                        let kind = token.kind();
197                                        if kind == T![,] {
198                                            break;
199                                        } else if !kind.is_trivia() {
200                                            break 'eat_comma;
201                                        }
202                                    }
203                                }
204                                events_until_comma += 1;
205                            }
206                            preorder.nth(events_until_comma);
207                        }
208
209                        return (false, Vec::new());
210                    }
211                }
212            }
213            WalkEvent::Leave(SyntaxElement::Node(node)) => {
214                if ast::Attr::can_cast(node.kind()) {
215                    in_attr = false;
216                    attrs_idx += 1;
217                }
218            }
219            WalkEvent::Enter(SyntaxElement::Token(token)) => {
220                if !in_attr {
221                    return (true, Vec::new());
222                }
223
224                let Some(ast_attr) = attrs.get_mut(attrs_idx) else {
225                    return (true, Vec::new());
226                };
227                let token_range = token.text_range();
228                let Some(expanded_attr) = ast_attr.expanded_attrs.get(ast_attr.expanded_attrs_idx)
229                else {
230                    // No expanded attributes in this `ast::Attr`, or we finished them all already, either way
231                    // the remaining tokens should be discarded.
232                    return (false, Vec::new());
233                };
234                match ast_attr.next_expanded_attr {
235                    NextExpandedAttrState::NotStarted => {
236                        if token_range.start() >= expanded_attr.attr.syntax().text_range().start() {
237                            // We started the next attribute.
238                            let mut insert_tokens = Vec::with_capacity(3);
239                            insert_tokens.push(tt::Leaf::Punct(tt::Punct {
240                                char: '#',
241                                spacing: tt::Spacing::Alone,
242                                span: ast_attr.pound_span,
243                            }));
244                            if let Some(span) = ast_attr.excl_span {
245                                insert_tokens.push(tt::Leaf::Punct(tt::Punct {
246                                    char: '!',
247                                    spacing: tt::Spacing::Alone,
248                                    span,
249                                }));
250                            }
251                            insert_tokens.push(tt::Leaf::Punct(tt::Punct {
252                                char: '[',
253                                spacing: tt::Spacing::Alone,
254                                span: ast_attr.brackets_span.open,
255                            }));
256
257                            ast_attr.next_expanded_attr = NextExpandedAttrState::InTheMiddle;
258
259                            return (true, insert_tokens);
260                        } else {
261                            // Before any attribute or between the attributes.
262                            return (false, Vec::new());
263                        }
264                    }
265                    NextExpandedAttrState::InTheMiddle => {
266                        if token_range.start() >= expanded_attr.attr.syntax().text_range().end() {
267                            // Finished the current attribute.
268                            let insert_tokens = vec![tt::Leaf::Punct(tt::Punct {
269                                char: ']',
270                                spacing: tt::Spacing::Alone,
271                                span: ast_attr.brackets_span.close,
272                            })];
273
274                            ast_attr.next_expanded_attr = NextExpandedAttrState::NotStarted;
275                            ast_attr.expanded_attrs_idx += 1;
276
277                            // It's safe to ignore the current token because between attributes
278                            // there is always at least one token we skip - either the closing bracket
279                            // in `#[]` or the comma in case of multiple attrs in `cfg_attr` expansion.
280                            return (false, insert_tokens);
281                        } else {
282                            // Still in the middle.
283                            return (true, Vec::new());
284                        }
285                    }
286                }
287            }
288            WalkEvent::Leave(SyntaxElement::Token(_)) => {}
289        }
290        (true, Vec::new())
291    }
292}
293
294pub(crate) fn attr_macro_input_to_token_tree(
295    db: &dyn SourceDatabase,
296    node: &SyntaxNode,
297    span_map: SpanMap<'_>,
298    span: Span,
299    is_derive: bool,
300    censor_item_tree_attr_ids: &[AttrId],
301    krate: Crate,
302) -> (tt::TopSubtree, SyntaxFixupUndoInfo) {
303    let fixups = fixup::fixup_syntax(span_map, node, span, DocCommentDesugarMode::ProcMacro);
304    (
305        syntax_bridge::syntax_node_to_token_tree_modified(
306            node,
307            span_map,
308            fixups.append,
309            fixups.remove,
310            span,
311            DocCommentDesugarMode::ProcMacro,
312            macro_input_callback(db, is_derive, censor_item_tree_attr_ids, krate, span, span_map),
313        ),
314        fixups.undo_info,
315    )
316}