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