rustc_attr_parsing/attributes/
cfg.rs

1use rustc_ast::token::Delimiter;
2use rustc_ast::tokenstream::DelimSpan;
3use rustc_ast::{AttrItem, Attribute, CRATE_NODE_ID, LitKind, NodeId, ast, token};
4use rustc_errors::{Applicability, PResult};
5use rustc_feature::{AttrSuggestionStyle, AttributeTemplate, Features, template};
6use rustc_hir::attrs::CfgEntry;
7use rustc_hir::{AttrPath, RustcVersion};
8use rustc_parse::parser::{ForceCollect, Parser};
9use rustc_parse::{exp, parse_in};
10use rustc_session::Session;
11use rustc_session::config::ExpectedValues;
12use rustc_session::lint::BuiltinLintDiag;
13use rustc_session::lint::builtin::UNEXPECTED_CFGS;
14use rustc_session::parse::{ParseSess, feature_err};
15use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
16use thin_vec::ThinVec;
17
18use crate::context::{AcceptContext, ShouldEmit, Stage};
19use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
20use crate::session_diagnostics::{
21    AttributeParseError, AttributeParseErrorReason, CfgAttrBadDelim, MetaBadDelimSugg,
22    ParsedDescription,
23};
24use crate::{
25    AttributeParser, CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics,
26    try_gate_cfg,
27};
28
29pub const CFG_TEMPLATE: AttributeTemplate = template!(
30    List: &["predicate"],
31    "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute"
32);
33
34const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
35    List: &["predicate, attr1, attr2, ..."],
36    "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
37);
38
39pub fn parse_cfg<'c, S: Stage>(
40    cx: &'c mut AcceptContext<'_, '_, S>,
41    args: &'c ArgParser<'_>,
42) -> Option<CfgEntry> {
43    let ArgParser::List(list) = args else {
44        cx.expected_list(cx.attr_span);
45        return None;
46    };
47    let Some(single) = list.single() else {
48        cx.expected_single_argument(list.span);
49        return None;
50    };
51    parse_cfg_entry(cx, single).ok()
52}
53
54pub fn parse_cfg_entry<S: Stage>(
55    cx: &mut AcceptContext<'_, '_, S>,
56    item: &MetaItemOrLitParser<'_>,
57) -> Result<CfgEntry, ErrorGuaranteed> {
58    Ok(match item {
59        MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
60            ArgParser::List(list) => match meta.path().word_sym() {
61                Some(sym::not) => {
62                    let Some(single) = list.single() else {
63                        return Err(cx.expected_single_argument(list.span));
64                    };
65                    CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
66                }
67                Some(sym::any) => CfgEntry::Any(
68                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
69                    list.span,
70                ),
71                Some(sym::all) => CfgEntry::All(
72                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
73                    list.span,
74                ),
75                Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
76                Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
77                _ => {
78                    return Err(cx.emit_err(session_diagnostics::InvalidPredicate {
79                        span: meta.span(),
80                        predicate: meta.path().to_string(),
81                    }));
82                }
83            },
84            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
85                let Some(name) = meta.path().word_sym().filter(|s| !s.is_path_segment_keyword())
86                else {
87                    return Err(cx.expected_identifier(meta.path().span()));
88                };
89                parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
90            }
91        },
92        MetaItemOrLitParser::Lit(lit) => match lit.kind {
93            LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
94            _ => return Err(cx.expected_identifier(lit.span)),
95        },
96        MetaItemOrLitParser::Err(_, err) => return Err(*err),
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(&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
175fn 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    Ok(CfgEntry::NameValue { name, name_span, value, span })
197}
198
199pub fn eval_config_entry(
200    sess: &Session,
201    cfg_entry: &CfgEntry,
202    id: NodeId,
203    emit_lints: ShouldEmit,
204) -> EvalConfigResult {
205    match cfg_entry {
206        CfgEntry::All(subs, ..) => {
207            let mut all = None;
208            for sub in subs {
209                let res = eval_config_entry(sess, sub, id, emit_lints);
210                // We cannot short-circuit because `eval_config_entry` emits some lints
211                if !res.as_bool() {
212                    all.get_or_insert(res);
213                }
214            }
215            all.unwrap_or_else(|| EvalConfigResult::True)
216        }
217        CfgEntry::Any(subs, span) => {
218            let mut any = None;
219            for sub in subs {
220                let res = eval_config_entry(sess, sub, id, emit_lints);
221                // We cannot short-circuit because `eval_config_entry` emits some lints
222                if res.as_bool() {
223                    any.get_or_insert(res);
224                }
225            }
226            any.unwrap_or_else(|| EvalConfigResult::False {
227                reason: cfg_entry.clone(),
228                reason_span: *span,
229            })
230        }
231        CfgEntry::Not(sub, span) => {
232            if eval_config_entry(sess, sub, id, emit_lints).as_bool() {
233                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
234            } else {
235                EvalConfigResult::True
236            }
237        }
238        CfgEntry::Bool(b, span) => {
239            if *b {
240                EvalConfigResult::True
241            } else {
242                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
243            }
244        }
245        CfgEntry::NameValue { name, name_span, value, span } => {
246            if let ShouldEmit::ErrorsAndLints = emit_lints {
247                match sess.psess.check_config.expecteds.get(name) {
248                    Some(ExpectedValues::Some(values))
249                        if !values.contains(&value.map(|(v, _)| v)) =>
250                    {
251                        id.emit_span_lint(
252                            sess,
253                            UNEXPECTED_CFGS,
254                            *span,
255                            BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
256                        );
257                    }
258                    None if sess.psess.check_config.exhaustive_names => {
259                        id.emit_span_lint(
260                            sess,
261                            UNEXPECTED_CFGS,
262                            *span,
263                            BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
264                        );
265                    }
266                    _ => { /* not unexpected */ }
267                }
268            }
269
270            if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
271                EvalConfigResult::True
272            } else {
273                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
274            }
275        }
276        CfgEntry::Version(min_version, version_span) => {
277            let Some(min_version) = min_version else {
278                return EvalConfigResult::False {
279                    reason: cfg_entry.clone(),
280                    reason_span: *version_span,
281                };
282            };
283            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
284            let min_version_ok = if sess.psess.assume_incomplete_release {
285                RustcVersion::current_overridable() > *min_version
286            } else {
287                RustcVersion::current_overridable() >= *min_version
288            };
289            if min_version_ok {
290                EvalConfigResult::True
291            } else {
292                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
293            }
294        }
295    }
296}
297
298pub enum EvalConfigResult {
299    True,
300    False { reason: CfgEntry, reason_span: Span },
301}
302
303impl EvalConfigResult {
304    pub fn as_bool(&self) -> bool {
305        match self {
306            EvalConfigResult::True => true,
307            EvalConfigResult::False { .. } => false,
308        }
309    }
310}
311
312pub fn parse_cfg_attr(
313    cfg_attr: &Attribute,
314    sess: &Session,
315    features: Option<&Features>,
316) -> Option<(CfgEntry, Vec<(AttrItem, Span)>)> {
317    match cfg_attr.get_normal_item().args {
318        ast::AttrArgs::Delimited(ast::DelimArgs { dspan, delim, ref tokens })
319            if !tokens.is_empty() =>
320        {
321            check_cfg_attr_bad_delim(&sess.psess, dspan, delim);
322            match parse_in(&sess.psess, tokens.clone(), "`cfg_attr` input", |p| {
323                parse_cfg_attr_internal(p, sess, features, cfg_attr)
324            }) {
325                Ok(r) => return Some(r),
326                Err(e) => {
327                    let suggestions = CFG_ATTR_TEMPLATE
328                        .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr);
329                    e.with_span_suggestions(
330                        cfg_attr.span,
331                        "must be of the form",
332                        suggestions,
333                        Applicability::HasPlaceholders,
334                    )
335                    .with_note(format!(
336                        "for more information, visit <{}>",
337                        CFG_ATTR_TEMPLATE.docs.expect("cfg_attr has docs")
338                    ))
339                    .emit();
340                }
341            }
342        }
343        _ => {
344            let (span, reason) = if let ast::AttrArgs::Delimited(ast::DelimArgs { dspan, .. }) =
345                cfg_attr.get_normal_item().args
346            {
347                (dspan.entire(), AttributeParseErrorReason::ExpectedAtLeastOneArgument)
348            } else {
349                (cfg_attr.span, AttributeParseErrorReason::ExpectedList)
350            };
351
352            sess.dcx().emit_err(AttributeParseError {
353                span,
354                attr_span: cfg_attr.span,
355                template: CFG_ATTR_TEMPLATE,
356                path: AttrPath::from_ast(&cfg_attr.get_normal_item().path),
357                description: ParsedDescription::Attribute,
358                reason,
359                suggestions: CFG_ATTR_TEMPLATE
360                    .suggestions(AttrSuggestionStyle::Attribute(cfg_attr.style), sym::cfg_attr),
361            });
362        }
363    }
364    None
365}
366
367fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
368    if let Delimiter::Parenthesis = delim {
369        return;
370    }
371    psess.dcx().emit_err(CfgAttrBadDelim {
372        span: span.entire(),
373        sugg: MetaBadDelimSugg { open: span.open, close: span.close },
374    });
375}
376
377/// Parses `cfg_attr(pred, attr_item_list)` where `attr_item_list` is comma-delimited.
378fn parse_cfg_attr_internal<'a>(
379    parser: &mut Parser<'a>,
380    sess: &'a Session,
381    features: Option<&Features>,
382    attribute: &Attribute,
383) -> PResult<'a, (CfgEntry, Vec<(ast::AttrItem, Span)>)> {
384    // Parse cfg predicate
385    let pred_start = parser.token.span;
386    let meta = MetaItemOrLitParser::parse_single(parser, ShouldEmit::ErrorsAndLints)?;
387    let pred_span = pred_start.with_hi(parser.token.span.hi());
388
389    let cfg_predicate = AttributeParser::parse_single_args(
390        sess,
391        attribute.span,
392        attribute.get_normal_item().span(),
393        attribute.style,
394        AttrPath {
395            segments: attribute
396                .ident_path()
397                .expect("cfg_attr is not a doc comment")
398                .into_boxed_slice(),
399            span: attribute.span,
400        },
401        ParsedDescription::Attribute,
402        pred_span,
403        CRATE_NODE_ID,
404        features,
405        ShouldEmit::ErrorsAndLints,
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(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(exp!(Comma)) {
428            break;
429        }
430    }
431
432    Ok((cfg_predicate, expanded_attrs))
433}