rustc_expand/
config.rs

1//! Conditional compilation stripping.
2
3use rustc_ast::ptr::P;
4use rustc_ast::token::{Delimiter, Token, TokenKind};
5use rustc_ast::tokenstream::{
6    AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree,
7};
8use rustc_ast::{
9    self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner, NodeId,
10};
11use rustc_attr_parsing as attr;
12use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
13use rustc_feature::{
14    ACCEPTED_LANG_FEATURES, AttributeSafety, EnabledLangFeature, EnabledLibFeature, Features,
15    REMOVED_LANG_FEATURES, UNSTABLE_LANG_FEATURES,
16};
17use rustc_lint_defs::BuiltinLintDiag;
18use rustc_parse::validate_attr;
19use rustc_session::Session;
20use rustc_session::parse::feature_err;
21use rustc_span::{STDLIB_STABLE_CRATES, Span, Symbol, sym};
22use thin_vec::ThinVec;
23use tracing::instrument;
24
25use crate::errors::{
26    CrateNameInCfgAttr, CrateTypeInCfgAttr, FeatureNotAllowed, FeatureRemoved,
27    FeatureRemovedReason, InvalidCfg, MalformedFeatureAttribute, MalformedFeatureAttributeHelp,
28    RemoveExprNotSupported,
29};
30
31/// A folder that strips out items that do not belong in the current configuration.
32pub struct StripUnconfigured<'a> {
33    pub sess: &'a Session,
34    pub features: Option<&'a Features>,
35    /// If `true`, perform cfg-stripping on attached tokens.
36    /// This is only used for the input to derive macros,
37    /// which needs eager expansion of `cfg` and `cfg_attr`
38    pub config_tokens: bool,
39    pub lint_node_id: NodeId,
40}
41
42pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -> Features {
43    fn feature_list(attr: &Attribute) -> ThinVec<ast::MetaItemInner> {
44        if attr.has_name(sym::feature)
45            && let Some(list) = attr.meta_item_list()
46        {
47            list
48        } else {
49            ThinVec::new()
50        }
51    }
52
53    let mut features = Features::default();
54
55    // Process all features enabled in the code.
56    for attr in krate_attrs {
57        for mi in feature_list(attr) {
58            let name = match mi.ident() {
59                Some(ident) if mi.is_word() => ident.name,
60                Some(ident) => {
61                    sess.dcx().emit_err(MalformedFeatureAttribute {
62                        span: mi.span(),
63                        help: MalformedFeatureAttributeHelp::Suggestion {
64                            span: mi.span(),
65                            suggestion: ident.name,
66                        },
67                    });
68                    continue;
69                }
70                None => {
71                    sess.dcx().emit_err(MalformedFeatureAttribute {
72                        span: mi.span(),
73                        help: MalformedFeatureAttributeHelp::Label { span: mi.span() },
74                    });
75                    continue;
76                }
77            };
78
79            // If the enabled feature has been removed, issue an error.
80            if let Some(f) = REMOVED_LANG_FEATURES.iter().find(|f| name == f.feature.name) {
81                sess.dcx().emit_err(FeatureRemoved {
82                    span: mi.span(),
83                    reason: f.reason.map(|reason| FeatureRemovedReason { reason }),
84                });
85                continue;
86            }
87
88            // If the enabled feature is stable, record it.
89            if let Some(f) = ACCEPTED_LANG_FEATURES.iter().find(|f| name == f.name) {
90                features.set_enabled_lang_feature(EnabledLangFeature {
91                    gate_name: name,
92                    attr_sp: mi.span(),
93                    stable_since: Some(Symbol::intern(f.since)),
94                });
95                continue;
96            }
97
98            // If `-Z allow-features` is used and the enabled feature is
99            // unstable and not also listed as one of the allowed features,
100            // issue an error.
101            if let Some(allowed) = sess.opts.unstable_opts.allow_features.as_ref() {
102                if allowed.iter().all(|f| name.as_str() != f) {
103                    sess.dcx().emit_err(FeatureNotAllowed { span: mi.span(), name });
104                    continue;
105                }
106            }
107
108            // If the enabled feature is unstable, record it.
109            if UNSTABLE_LANG_FEATURES.iter().find(|f| name == f.name).is_some() {
110                // When the ICE comes a standard library crate, there's a chance that the person
111                // hitting the ICE may be using -Zbuild-std or similar with an untested target.
112                // The bug is probably in the standard library and not the compiler in that case,
113                // but that doesn't really matter - we want a bug report.
114                if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) {
115                    sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed);
116                }
117
118                features.set_enabled_lang_feature(EnabledLangFeature {
119                    gate_name: name,
120                    attr_sp: mi.span(),
121                    stable_since: None,
122                });
123                continue;
124            }
125
126            // Otherwise, the feature is unknown. Enable it as a lib feature.
127            // It will be checked later whether the feature really exists.
128            features
129                .set_enabled_lib_feature(EnabledLibFeature { gate_name: name, attr_sp: mi.span() });
130
131            // Similar to above, detect internal lib features to suppress
132            // the ICE message that asks for a report.
133            if features.internal(name) && !STDLIB_STABLE_CRATES.contains(&crate_name) {
134                sess.using_internal_features.store(true, std::sync::atomic::Ordering::Relaxed);
135            }
136        }
137    }
138
139    features
140}
141
142pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec {
143    let strip_unconfigured = StripUnconfigured {
144        sess,
145        features: None,
146        config_tokens: false,
147        lint_node_id: ast::CRATE_NODE_ID,
148    };
149    attrs
150        .iter()
151        .flat_map(|attr| strip_unconfigured.process_cfg_attr(attr))
152        .take_while(|attr| !is_cfg(attr) || strip_unconfigured.cfg_true(attr).0)
153        .collect()
154}
155
156#[macro_export]
157macro_rules! configure {
158    ($this:ident, $node:ident) => {
159        match $this.configure($node) {
160            Some(node) => node,
161            None => return Default::default(),
162        }
163    };
164}
165
166impl<'a> StripUnconfigured<'a> {
167    pub fn configure<T: HasAttrs + HasTokens>(&self, mut node: T) -> Option<T> {
168        self.process_cfg_attrs(&mut node);
169        self.in_cfg(node.attrs()).then(|| {
170            self.try_configure_tokens(&mut node);
171            node
172        })
173    }
174
175    fn try_configure_tokens<T: HasTokens>(&self, node: &mut T) {
176        if self.config_tokens {
177            if let Some(Some(tokens)) = node.tokens_mut() {
178                let attr_stream = tokens.to_attr_token_stream();
179                *tokens = LazyAttrTokenStream::new(self.configure_tokens(&attr_stream));
180            }
181        }
182    }
183
184    /// Performs cfg-expansion on `stream`, producing a new `AttrTokenStream`.
185    /// This is only used during the invocation of `derive` proc-macros,
186    /// which require that we cfg-expand their entire input.
187    /// Normal cfg-expansion operates on parsed AST nodes via the `configure` method
188    fn configure_tokens(&self, stream: &AttrTokenStream) -> AttrTokenStream {
189        fn can_skip(stream: &AttrTokenStream) -> bool {
190            stream.0.iter().all(|tree| match tree {
191                AttrTokenTree::AttrsTarget(_) => false,
192                AttrTokenTree::Token(..) => true,
193                AttrTokenTree::Delimited(.., inner) => can_skip(inner),
194            })
195        }
196
197        if can_skip(stream) {
198            return stream.clone();
199        }
200
201        let trees: Vec<_> = stream
202            .0
203            .iter()
204            .filter_map(|tree| match tree.clone() {
205                AttrTokenTree::AttrsTarget(mut target) => {
206                    // Expand any `cfg_attr` attributes.
207                    target.attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
208
209                    if self.in_cfg(&target.attrs) {
210                        target.tokens = LazyAttrTokenStream::new(
211                            self.configure_tokens(&target.tokens.to_attr_token_stream()),
212                        );
213                        Some(AttrTokenTree::AttrsTarget(target))
214                    } else {
215                        // Remove the target if there's a `cfg` attribute and
216                        // the condition isn't satisfied.
217                        None
218                    }
219                }
220                AttrTokenTree::Delimited(sp, spacing, delim, mut inner) => {
221                    inner = self.configure_tokens(&inner);
222                    Some(AttrTokenTree::Delimited(sp, spacing, delim, inner))
223                }
224                AttrTokenTree::Token(
225                    Token {
226                        kind:
227                            TokenKind::NtIdent(..)
228                            | TokenKind::NtLifetime(..)
229                            | TokenKind::Interpolated(..),
230                        ..
231                    },
232                    _,
233                ) => {
234                    panic!("Nonterminal should have been flattened: {:?}", tree);
235                }
236                AttrTokenTree::Token(
237                    Token { kind: TokenKind::OpenDelim(_) | TokenKind::CloseDelim(_), .. },
238                    _,
239                ) => {
240                    panic!("Should be `AttrTokenTree::Delimited`, not delim tokens: {:?}", tree);
241                }
242                AttrTokenTree::Token(token, spacing) => Some(AttrTokenTree::Token(token, spacing)),
243            })
244            .collect();
245        AttrTokenStream::new(trees)
246    }
247
248    /// Parse and expand all `cfg_attr` attributes into a list of attributes
249    /// that are within each `cfg_attr` that has a true configuration predicate.
250    ///
251    /// Gives compiler warnings if any `cfg_attr` does not contain any
252    /// attributes and is in the original source code. Gives compiler errors if
253    /// the syntax of any `cfg_attr` is incorrect.
254    fn process_cfg_attrs<T: HasAttrs>(&self, node: &mut T) {
255        node.visit_attrs(|attrs| {
256            attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
257        });
258    }
259
260    fn process_cfg_attr(&self, attr: &Attribute) -> Vec<Attribute> {
261        if attr.has_name(sym::cfg_attr) {
262            self.expand_cfg_attr(attr, true)
263        } else {
264            vec![attr.clone()]
265        }
266    }
267
268    /// Parse and expand a single `cfg_attr` attribute into a list of attributes
269    /// when the configuration predicate is true, or otherwise expand into an
270    /// empty list of attributes.
271    ///
272    /// Gives a compiler warning when the `cfg_attr` contains no attributes and
273    /// is in the original source file. Gives a compiler error if the syntax of
274    /// the attribute is incorrect.
275    pub(crate) fn expand_cfg_attr(&self, cfg_attr: &Attribute, recursive: bool) -> Vec<Attribute> {
276        validate_attr::check_attribute_safety(&self.sess.psess, AttributeSafety::Normal, &cfg_attr);
277
278        let Some((cfg_predicate, expanded_attrs)) =
279            rustc_parse::parse_cfg_attr(cfg_attr, &self.sess.psess)
280        else {
281            return vec![];
282        };
283
284        // Lint on zero attributes in source.
285        if expanded_attrs.is_empty() {
286            self.sess.psess.buffer_lint(
287                rustc_lint_defs::builtin::UNUSED_ATTRIBUTES,
288                cfg_attr.span,
289                ast::CRATE_NODE_ID,
290                BuiltinLintDiag::CfgAttrNoAttributes,
291            );
292        }
293
294        if !attr::cfg_matches(&cfg_predicate, &self.sess, self.lint_node_id, self.features) {
295            return vec![];
296        }
297
298        if recursive {
299            // We call `process_cfg_attr` recursively in case there's a
300            // `cfg_attr` inside of another `cfg_attr`. E.g.
301            //  `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
302            expanded_attrs
303                .into_iter()
304                .flat_map(|item| self.process_cfg_attr(&self.expand_cfg_attr_item(cfg_attr, item)))
305                .collect()
306        } else {
307            expanded_attrs
308                .into_iter()
309                .map(|item| self.expand_cfg_attr_item(cfg_attr, item))
310                .collect()
311        }
312    }
313
314    fn expand_cfg_attr_item(
315        &self,
316        cfg_attr: &Attribute,
317        (item, item_span): (ast::AttrItem, Span),
318    ) -> Attribute {
319        // Convert `#[cfg_attr(pred, attr)]` to `#[attr]`.
320
321        // Use the `#` from `#[cfg_attr(pred, attr)]` in the result `#[attr]`.
322        let mut orig_trees = cfg_attr.token_trees().into_iter();
323        let Some(TokenTree::Token(pound_token @ Token { kind: TokenKind::Pound, .. }, _)) =
324            orig_trees.next()
325        else {
326            panic!("Bad tokens for attribute {cfg_attr:?}");
327        };
328
329        // For inner attributes, we do the same thing for the `!` in `#![attr]`.
330        let mut trees = if cfg_attr.style == AttrStyle::Inner {
331            let Some(TokenTree::Token(bang_token @ Token { kind: TokenKind::Not, .. }, _)) =
332                orig_trees.next()
333            else {
334                panic!("Bad tokens for attribute {cfg_attr:?}");
335            };
336            vec![
337                AttrTokenTree::Token(pound_token, Spacing::Joint),
338                AttrTokenTree::Token(bang_token, Spacing::JointHidden),
339            ]
340        } else {
341            vec![AttrTokenTree::Token(pound_token, Spacing::JointHidden)]
342        };
343
344        // And the same thing for the `[`/`]` delimiters in `#[attr]`.
345        let Some(TokenTree::Delimited(delim_span, delim_spacing, Delimiter::Bracket, _)) =
346            orig_trees.next()
347        else {
348            panic!("Bad tokens for attribute {cfg_attr:?}");
349        };
350        trees.push(AttrTokenTree::Delimited(
351            delim_span,
352            delim_spacing,
353            Delimiter::Bracket,
354            item.tokens
355                .as_ref()
356                .unwrap_or_else(|| panic!("Missing tokens for {item:?}"))
357                .to_attr_token_stream(),
358        ));
359
360        let tokens = Some(LazyAttrTokenStream::new(AttrTokenStream::new(trees)));
361        let attr = ast::attr::mk_attr_from_item(
362            &self.sess.psess.attr_id_generator,
363            item,
364            tokens,
365            cfg_attr.style,
366            item_span,
367        );
368        if attr.has_name(sym::crate_type) {
369            self.sess.dcx().emit_err(CrateTypeInCfgAttr { span: attr.span });
370        }
371        if attr.has_name(sym::crate_name) {
372            self.sess.dcx().emit_err(CrateNameInCfgAttr { span: attr.span });
373        }
374        attr
375    }
376
377    /// Determines if a node with the given attributes should be included in this configuration.
378    fn in_cfg(&self, attrs: &[Attribute]) -> bool {
379        attrs.iter().all(|attr| !is_cfg(attr) || self.cfg_true(attr).0)
380    }
381
382    pub(crate) fn cfg_true(&self, attr: &Attribute) -> (bool, Option<MetaItem>) {
383        let meta_item = match validate_attr::parse_meta(&self.sess.psess, attr) {
384            Ok(meta_item) => meta_item,
385            Err(err) => {
386                err.emit();
387                return (true, None);
388            }
389        };
390
391        validate_attr::deny_builtin_meta_unsafety(&self.sess.psess, &meta_item);
392
393        (
394            parse_cfg(&meta_item, self.sess).is_none_or(|meta_item| {
395                attr::cfg_matches(meta_item, &self.sess, self.lint_node_id, self.features)
396            }),
397            Some(meta_item),
398        )
399    }
400
401    /// If attributes are not allowed on expressions, emit an error for `attr`
402    #[instrument(level = "trace", skip(self))]
403    pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
404        if self.features.is_some_and(|features| !features.stmt_expr_attributes())
405            && !attr.span.allows_unstable(sym::stmt_expr_attributes)
406        {
407            let mut err = feature_err(
408                &self.sess,
409                sym::stmt_expr_attributes,
410                attr.span,
411                crate::fluent_generated::expand_attributes_on_expressions_experimental,
412            );
413
414            if attr.is_doc_comment() {
415                err.help(if attr.style == AttrStyle::Outer {
416                    crate::fluent_generated::expand_help_outer_doc
417                } else {
418                    crate::fluent_generated::expand_help_inner_doc
419                });
420            }
421
422            err.emit();
423        }
424    }
425
426    #[instrument(level = "trace", skip(self))]
427    pub fn configure_expr(&self, expr: &mut P<ast::Expr>, method_receiver: bool) {
428        if !method_receiver {
429            for attr in expr.attrs.iter() {
430                self.maybe_emit_expr_attr_err(attr);
431            }
432        }
433
434        // If an expr is valid to cfg away it will have been removed by the
435        // outer stmt or expression folder before descending in here.
436        // Anything else is always required, and thus has to error out
437        // in case of a cfg attr.
438        //
439        // N.B., this is intentionally not part of the visit_expr() function
440        //     in order for filter_map_expr() to be able to avoid this check
441        if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
442            self.sess.dcx().emit_err(RemoveExprNotSupported { span: attr.span });
443        }
444
445        self.process_cfg_attrs(expr);
446        self.try_configure_tokens(&mut *expr);
447    }
448}
449
450pub fn parse_cfg<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> {
451    let span = meta_item.span;
452    match meta_item.meta_item_list() {
453        None => {
454            sess.dcx().emit_err(InvalidCfg::NotFollowedByParens { span });
455            None
456        }
457        Some([]) => {
458            sess.dcx().emit_err(InvalidCfg::NoPredicate { span });
459            None
460        }
461        Some([_, .., l]) => {
462            sess.dcx().emit_err(InvalidCfg::MultiplePredicates { span: l.span() });
463            None
464        }
465        Some([single]) => match single.meta_item_or_bool() {
466            Some(meta_item) => Some(meta_item),
467            None => {
468                sess.dcx().emit_err(InvalidCfg::PredicateLiteral { span: single.span() });
469                None
470            }
471        },
472    }
473}
474
475fn is_cfg(attr: &Attribute) -> bool {
476    attr.has_name(sym::cfg)
477}