rustc_expand/
config.rs

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