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