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