Skip to main content

rustc_attr_parsing/attributes/
cfg.rs

1use std::convert::identity;
2
3use rustc_ast::token::Delimiter;
4use rustc_ast::tokenstream::DelimSpan;
5use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, ast, token};
6use rustc_errors::{Applicability, PResult, msg};
7use rustc_feature::{
8    AttrSuggestionStyle, AttributeTemplate, Features, GatedCfg, find_gated_cfg, template,
9};
10use rustc_hir::attrs::CfgEntry;
11use rustc_hir::lints::AttributeLintKind;
12use rustc_hir::{AttrPath, RustcVersion, Target};
13use rustc_parse::parser::{ForceCollect, Parser, Recovery};
14use rustc_parse::{exp, parse_in};
15use rustc_session::Session;
16use rustc_session::config::ExpectedValues;
17use rustc_session::lint::builtin::UNEXPECTED_CFGS;
18use rustc_session::parse::{ParseSess, feature_err};
19use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
20use thin_vec::ThinVec;
21
22use crate::context::{AcceptContext, ShouldEmit, Stage};
23use crate::parser::{
24    AllowExprMetavar, ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser,
25};
26use crate::session_diagnostics::{
27    AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg,
28    ParsedDescription,
29};
30use crate::{AttributeParser, parse_version, session_diagnostics};
31
32pub const CFG_TEMPLATE: AttributeTemplate = ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&["predicate"]),
    one_of: &[],
    name_value_str: None,
    docs: Some("https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"),
}template!(
33    List: &["predicate"],
34    "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
35);
36
37const CFG_ATTR_TEMPLATE: AttributeTemplate = ::rustc_feature::AttributeTemplate {
    word: false,
    list: Some(&["predicate, attr1, attr2, ..."]),
    one_of: &[],
    name_value_str: None,
    docs: Some("https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"),
}template!(
38    List: &["predicate, attr1, attr2, ..."],
39    "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
40);
41
42pub fn parse_cfg<S: Stage>(
43    cx: &mut AcceptContext<'_, '_, S>,
44    args: &ArgParser,
45) -> Option<CfgEntry> {
46    let ArgParser::List(list) = args else {
47        let attr_span = cx.attr_span;
48        cx.adcx().expected_list(attr_span, args);
49        return None;
50    };
51
52    let Some(single) = list.single() else {
53        let target = cx.target;
54        let mut adcx = cx.adcx();
55        if list.is_empty() {
56            // `#[cfg()]`
57            let message = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if the {0} should be disabled, use `#[cfg(false)]`",
                target))
    })format!("if the {target} should be disabled, use `#[cfg(false)]`");
58            adcx.push_suggestion(message, list.span, "(false)".to_string());
59        } else {
60            // `#[cfg(foo, bar)]`
61            if let Ok(args) = adcx
62                .sess()
63                .source_map()
64                .span_to_source(list.span, |src, start, end| Ok(src[start..end].to_string()))
65            {
66                let all = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("(all{0})", args))
    })format!("(all{args})");
67                let any = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("(any{0})", args))
    })format!("(any{args})");
68
69                let all_msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if the {0} should be enabled when all these predicates are, wrap them in `all`",
                target))
    })format!(
70                    "if the {target} should be enabled when all these predicates are, wrap them in `all`"
71                );
72                let any_msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("alternately, if the {0} should be enabled when any of these predicates are, wrap them in `any`",
                target))
    })format!(
73                    "alternately, if the {target} should be enabled when any of these predicates are, wrap them in `any`"
74                );
75
76                adcx.push_suggestion(all_msg, list.span, all);
77                adcx.push_suggestion(any_msg, list.span, any);
78            }
79        }
80
81        adcx.expected_single_argument(list.span);
82        return None;
83    };
84    parse_cfg_entry(cx, single).ok()
85}
86
87pub fn parse_cfg_entry<S: Stage>(
88    cx: &mut AcceptContext<'_, '_, S>,
89    item: &MetaItemOrLitParser,
90) -> Result<CfgEntry, ErrorGuaranteed> {
91    Ok(match item {
92        MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
93            ArgParser::List(list) => match meta.path().word_sym() {
94                Some(sym::not) => {
95                    let Some(single) = list.single() else {
96                        return Err(cx.adcx().expected_single_argument(list.span));
97                    };
98                    CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
99                }
100                Some(sym::any) => CfgEntry::Any(
101                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
102                    list.span,
103                ),
104                Some(sym::all) => CfgEntry::All(
105                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
106                    list.span,
107                ),
108                Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
109                Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
110                _ => {
111                    return Err(cx.emit_err(session_diagnostics::InvalidPredicate {
112                        span: meta.span(),
113                        predicate: meta.path().to_string(),
114                    }));
115                }
116            },
117            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
118                let Some(name) = meta.path().word_sym().filter(|s| !s.is_path_segment_keyword())
119                else {
120                    return Err(cx.adcx().expected_identifier(meta.path().span()));
121                };
122                parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
123            }
124        },
125        MetaItemOrLitParser::Lit(lit) => match lit.kind {
126            LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
127            _ => return Err(cx.adcx().expected_identifier(lit.span)),
128        },
129    })
130}
131
132fn parse_cfg_entry_version<S: Stage>(
133    cx: &mut AcceptContext<'_, '_, S>,
134    list: &MetaItemListParser,
135    meta_span: Span,
136) -> Result<CfgEntry, ErrorGuaranteed> {
137    try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
138    let Some(version) = list.single() else {
139        return Err(
140            cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span })
141        );
142    };
143    let Some(version_lit) = version.lit() else {
144        return Err(
145            cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() })
146        );
147    };
148    let Some(version_str) = version_lit.value_str() else {
149        return Err(
150            cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span })
151        );
152    };
153
154    let min_version = parse_version(version_str).or_else(|| {
155        cx.sess()
156            .dcx()
157            .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
158        None
159    });
160
161    Ok(CfgEntry::Version(min_version, list.span))
162}
163
164fn parse_cfg_entry_target<S: Stage>(
165    cx: &mut AcceptContext<'_, '_, S>,
166    list: &MetaItemListParser,
167    meta_span: Span,
168) -> Result<CfgEntry, ErrorGuaranteed> {
169    if let Some(features) = cx.features_option()
170        && !features.cfg_target_compact()
171    {
172        feature_err(
173            cx.sess(),
174            sym::cfg_target_compact,
175            meta_span,
176            rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("compact `cfg(target(..))` is experimental and subject to change"))msg!("compact `cfg(target(..))` is experimental and subject to change"),
177        )
178        .emit();
179    }
180
181    let mut result = ThinVec::new();
182    for sub_item in list.mixed() {
183        // First, validate that this is a NameValue item
184        let Some(sub_item) = sub_item.meta_item() else {
185            cx.adcx().expected_name_value(sub_item.span(), None);
186            continue;
187        };
188        let Some(nv) = sub_item.args().name_value() else {
189            cx.adcx().expected_name_value(sub_item.span(), None);
190            continue;
191        };
192
193        // Then, parse it as a name-value item
194        let Some(name) = sub_item.path().word_sym().filter(|s| !s.is_path_segment_keyword()) else {
195            return Err(cx.adcx().expected_identifier(sub_item.path().span()));
196        };
197        let name = Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("target_{0}", name))
    })format!("target_{name}"));
198        if let Ok(cfg) =
199            parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
200        {
201            result.push(cfg);
202        }
203    }
204    Ok(CfgEntry::All(result, list.span))
205}
206
207pub(crate) fn parse_name_value<S: Stage>(
208    name: Symbol,
209    name_span: Span,
210    value: Option<&NameValueParser>,
211    span: Span,
212    cx: &mut AcceptContext<'_, '_, S>,
213) -> Result<CfgEntry, ErrorGuaranteed> {
214    try_gate_cfg(name, span, cx.sess(), cx.features_option());
215
216    let value = match value {
217        None => None,
218        Some(value) => {
219            let Some(value_str) = value.value_as_str() else {
220                return Err(cx
221                    .adcx()
222                    .expected_string_literal(value.value_span, Some(value.value_as_lit())));
223            };
224            Some((value_str, value.value_span))
225        }
226    };
227
228    match cx.sess.psess.check_config.expecteds.get(&name) {
229        Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => cx
230            .emit_lint(
231                UNEXPECTED_CFGS,
232                AttributeLintKind::UnexpectedCfgValue((name, name_span), value),
233                span,
234            ),
235        None if cx.sess.psess.check_config.exhaustive_names => cx.emit_lint(
236            UNEXPECTED_CFGS,
237            AttributeLintKind::UnexpectedCfgName((name, name_span), value),
238            span,
239        ),
240        _ => { /* not unexpected */ }
241    }
242
243    Ok(CfgEntry::NameValue { name, value: value.map(|(v, _)| v), span })
244}
245
246pub fn eval_config_entry(sess: &Session, cfg_entry: &CfgEntry) -> EvalConfigResult {
247    match cfg_entry {
248        CfgEntry::All(subs, ..) => {
249            for sub in subs {
250                let res = eval_config_entry(sess, sub);
251                if !res.as_bool() {
252                    return res;
253                }
254            }
255            EvalConfigResult::True
256        }
257        CfgEntry::Any(subs, span) => {
258            for sub in subs {
259                let res = eval_config_entry(sess, sub);
260                if res.as_bool() {
261                    return res;
262                }
263            }
264            EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
265        }
266        CfgEntry::Not(sub, span) => {
267            if eval_config_entry(sess, sub).as_bool() {
268                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
269            } else {
270                EvalConfigResult::True
271            }
272        }
273        CfgEntry::Bool(b, span) => {
274            if *b {
275                EvalConfigResult::True
276            } else {
277                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
278            }
279        }
280        CfgEntry::NameValue { name, value, span } => {
281            if sess.psess.config.contains(&(*name, *value)) {
282                EvalConfigResult::True
283            } else {
284                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
285            }
286        }
287        CfgEntry::Version(min_version, version_span) => {
288            let Some(min_version) = min_version else {
289                return EvalConfigResult::False {
290                    reason: cfg_entry.clone(),
291                    reason_span: *version_span,
292                };
293            };
294            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
295            let min_version_ok = if sess.psess.assume_incomplete_release {
296                RustcVersion::current_overridable() > *min_version
297            } else {
298                RustcVersion::current_overridable() >= *min_version
299            };
300            if min_version_ok {
301                EvalConfigResult::True
302            } else {
303                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
304            }
305        }
306    }
307}
308
309pub enum EvalConfigResult {
310    True,
311    False { reason: CfgEntry, reason_span: Span },
312}
313
314impl EvalConfigResult {
315    pub fn as_bool(&self) -> bool {
316        match self {
317            EvalConfigResult::True => true,
318            EvalConfigResult::False { .. } => false,
319        }
320    }
321}
322
323pub fn parse_cfg_attr(
324    cfg_attr: &Attribute,
325    sess: &Session,
326    features: Option<&Features>,
327) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
328    match cfg_attr.get_normal_item().args.unparsed_ref().unwrap() {
329        ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => {
330            check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim);
331            match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
332                parse_cfg_attr_internal(p, sess, features, cfg_attr)
333            }) {
334                Ok(r) => return Some(r),
335                Err(e) => {
336                    let suggestions = CFG_ATTR_TEMPLATE
337                        .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr);
338                    e.with_span_suggestions(
339                        cfg_attr.span,
340                        "must be of the form",
341                        suggestions,
342                        Applicability::HasPlaceholders,
343                    )
344                    .with_note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("for more information, visit <{0}>",
                CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")))
    })format!(
345                        "for more information, visit <{}>",
346                        CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
347                    ))
348                    .emit();
349                }
350            }
351        }
352        _ => {
353            let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
354                cfg_attr.get_normal_item().args.unparsed_ref()?
355            {
356                (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
357            } else {
358                (cfg_attr.span, AttributeParseErrorReason::ExpectedList)
359            };
360
361            sess.dcx().emit_err(AttributeParseError {
362                span,
363                attr_span: cfg_attr.span,
364                template: CFG_ATTR_TEMPLATE,
365                path: AttrPath::from_ast(&cfg_attr.get_normal_item().path, identity),
366                description: ParsedDescription::Attribute,
367                reason,
368                suggestions: session_diagnostics::AttributeParseErrorSuggestions::CreatedByTemplate(
369                    CFG_ATTR_TEMPLATE
370                        .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr),
371                ),
372            });
373        }
374    }
375    None
376}
377
378fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
379    if let Delimiter::Parenthesis = delim {
380        return;
381    }
382    psess.dcx().emit_err(CfgAttrBadDelim {
383        span: span.entire(),
384        sugg: MetaBadDelimSugg { open: span.open, close: span.close },
385    });
386}
387
388/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
389fn parse_cfg_attr_internal<'a>(
390    parser: &mut Parser<'a>,
391    sess: &'a Session,
392    features: Option<&Features>,
393    attribute: &Attribute,
394) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
395    // Parse cfg predicate
396    let pred_start = parser.token.span;
397    let meta = MetaItemOrLitParser::parse_single(
398        parser,
399        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
400        AllowExprMetavar::Yes,
401    )?;
402    let pred_span = pred_start.with_hi(parser.token.span.hi());
403
404    let cfg_predicate = AttributeParser::parse_single_args(
405        sess,
406        attribute.span,
407        attribute.get_normal_item().span(),
408        attribute.style,
409        AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span },
410        Some(attribute.get_normal_item().unsafety),
411        ParsedDescription::Attribute,
412        pred_span,
413        CRATE_NODE_ID,
414        Target::Crate,
415        features,
416        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
417        &meta,
418        parse_cfg_entry,
419        &CFG_ATTR_TEMPLATE,
420    )
421    .map_err(|_err: ErrorGuaranteed| {
422        // We have an `ErrorGuaranteed` so this delayed bug cannot fail, but we need a `Diag` for the `PResult` so we make one anyways
423        let mut diag = sess.dcx().struct_err(
424            "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
425        );
426        diag.downgrade_to_delayed_bug();
427        diag
428    })?;
429
430    parser.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma))?;
431
432    // Presumably, the majority of the time there will only be one attr.
433    let mut expanded_attrs = Vec::with_capacity(1);
434    while parser.token != token::Eof {
435        let lo = parser.token.span;
436        let item = parser.parse_attr_item(ForceCollect::Yes)?;
437        expanded_attrs.push((item, lo.to(parser.prev_token.span)));
438        if !parser.eat(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
439            break;
440        }
441    }
442
443    Ok((cfg_predicate, expanded_attrs))
444}
445
446fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
447    let gate = find_gated_cfg(|sym| sym == name);
448    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
449        gate_cfg(gated_cfg, span, sess, feats);
450    }
451}
452
453fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
454    let (cfg, feature, has_feature) = gated_cfg;
455    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
456        let explain = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`cfg({0})` is experimental and subject to change",
                cfg))
    })format!("`cfg({cfg})` is experimental and subject to change");
457        feature_err(sess, *feature, cfg_span, explain).emit();
458    }
459}