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