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