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