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