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