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_lint_defs::builtin::UNEXPECTED_CFGS;
11use rustc_parse::parser::{ForceCollect, Parser, Recovery};
12use rustc_parse::{exp, parse_in};
13use rustc_session::Session;
14use rustc_session::config::ExpectedValues;
15use rustc_session::diagnostics::feature_err;
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, _) => {
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() }
248        }
249        CfgEntry::Not(sub, _) => {
250            if eval_config_entry(sess, sub).as_bool() {
251                EvalConfigResult::False { reason: cfg_entry.clone() }
252            } else {
253                EvalConfigResult::True
254            }
255        }
256        CfgEntry::Bool(b, _) => {
257            if *b {
258                EvalConfigResult::True
259            } else {
260                EvalConfigResult::False { reason: cfg_entry.clone() }
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() }
268            }
269        }
270        CfgEntry::Version(min_version, _) => {
271            let Some(min_version) = min_version else {
272                return EvalConfigResult::False { reason: cfg_entry.clone() };
273            };
274            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
275            let min_version_ok = if sess.opts.unstable_opts.assume_incomplete_release {
276                RustcVersion::current_overridable() > *min_version
277            } else {
278                RustcVersion::current_overridable() >= *min_version
279            };
280            if min_version_ok {
281                EvalConfigResult::True
282            } else {
283                EvalConfigResult::False { reason: cfg_entry.clone() }
284            }
285        }
286    }
287}
288
289pub enum EvalConfigResult {
290    True,
291    False { reason: CfgEntry },
292}
293
294impl EvalConfigResult {
295    pub fn as_bool(&self) -> bool {
296        match self {
297            EvalConfigResult::True => true,
298            EvalConfigResult::False { .. } => false,
299        }
300    }
301}
302
303pub fn parse_cfg_attr(
304    cfg_attr: &Attribute,
305    sess: &Session,
306    features: Option<&Features>,
307    lint_node_id: ast::NodeId,
308) -> Option<(CfgEntry, Vec<(WithTokens<AttrItem>, Span)>)> {
309    match &cfg_attr.get_normal_item().args {
310        ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, tokens }) if !tokens.is_empty() => {
311            check_cfg_attr_bad_delim(&sess.psess, *dspan, *delim);
312            match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
313                parse_cfg_attr_internal(p, sess, features, lint_node_id, cfg_attr)
314            }) {
315                Ok(r) => return Some(r),
316                Err(e) => {
317                    let suggestions = CFG_ATTR_TEMPLATE.suggestions(
318                        ParsedDescription::Attribute,
319                        cfg_attr.get_normal_item().unsafety,
320                        sym::cfg_attr,
321                    );
322                    e.with_span_suggestions(
323                        cfg_attr.get_normal_item().span,
324                        "must be of the form",
325                        suggestions,
326                        Applicability::HasPlaceholders,
327                    )
328                    .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!(
329                        "for more information, visit <{}>",
330                        CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
331                    ))
332                    .emit();
333                }
334            }
335        }
336        _ => {
337            let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
338                cfg_attr.get_normal_item().args
339            {
340                (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
341            } else {
342                (cfg_attr.get_normal_item().span, AttributeParseErrorReason::ExpectedList)
343            };
344
345            sess.dcx().emit_err(AttributeParseError {
346                span,
347                inner_span: cfg_attr.get_normal_item().span,
348                template: CFG_ATTR_TEMPLATE,
349                path: AttrPath::from_ast(&cfg_attr.get_normal_item().path, identity),
350                description: ParsedDescription::Attribute,
351                reason,
352                suggestions: diagnostics::AttributeParseErrorSuggestions::CreatedByTemplate(
353                    CFG_ATTR_TEMPLATE.suggestions(
354                        ParsedDescription::Attribute,
355                        cfg_attr.get_normal_item().unsafety,
356                        sym::cfg_attr,
357                    ),
358                ),
359            });
360        }
361    }
362    None
363}
364
365fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
366    if let Delimiter::Parenthesis = delim {
367        return;
368    }
369    psess.dcx().emit_err(CfgAttrBadDelim {
370        span: span.entire(),
371        sugg: MetaBadDelimSugg { open: span.open, close: span.close },
372    });
373}
374
375/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
376fn parse_cfg_attr_internal<'a>(
377    parser: &mut Parser<'a>,
378    sess: &'a Session,
379    features: Option<&Features>,
380    lint_node_id: ast::NodeId,
381    attribute: &Attribute,
382) -> PResult<'a, (CfgEntry, Vec<(WithTokens<ast::AttrItem>, Span)>)> {
383    // Parse cfg predicate
384    let pred_start = parser.token.span;
385    let meta = MetaItemOrLitParser::parse_single(
386        parser,
387        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
388        AllowExprMetavar::Yes,
389    )?;
390    let pred_span = pred_start.with_hi(parser.token.span.hi());
391
392    let cfg_predicate = AttributeParser::parse_single_args(
393        sess,
394        attribute.span,
395        attribute.get_normal_item().span,
396        attribute.style,
397        AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span },
398        Some(attribute.get_normal_item().unsafety),
399        AttributeSafety::Normal,
400        ParsedDescription::Attribute,
401        pred_span,
402        lint_node_id,
403        Target::Crate,
404        features,
405        ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
406        &meta,
407        parse_cfg_entry,
408        &CFG_ATTR_TEMPLATE,
409    )
410    .map_err(|_err: ErrorGuaranteed| {
411        // We have an `ErrorGuaranteed` so this delayed bug cannot fail, but we need a `Diag` for the `PResult` so we make one anyways
412        let mut diag = sess.dcx().struct_err(
413            "cfg_entry parsing failing with `ShouldEmit::ErrorsAndLints` should emit a error.",
414        );
415        diag.downgrade_to_delayed_bug();
416        diag
417    })?;
418
419    parser.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma))?;
420
421    // Presumably, the majority of the time there will only be one attr.
422    let mut expanded_attrs = Vec::with_capacity(1);
423    while parser.token != token::Eof {
424        let lo = parser.token.span;
425        let item = parser.parse_attr_item(ForceCollect::Yes)?;
426        expanded_attrs.push((item, lo.to(parser.prev_token.span)));
427        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)) {
428            break;
429        }
430    }
431
432    Ok((cfg_predicate, expanded_attrs))
433}
434
435fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
436    let gate = find_gated_cfg(name);
437    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
438        gate_cfg(gated_cfg, span, sess, feats);
439    }
440}
441
442fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
443    let (cfg, feature, has_feature) = gated_cfg;
444    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
445        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");
446        feature_err(sess, *feature, cfg_span, explain).emit();
447    }
448}