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