about summary refs log tree commit diff
path: root/compiler/rustc_attr_parsing
diff options
context:
space:
mode:
authorJonathan Brouwer <jonathantbrouwer@gmail.com>2025-07-07 09:09:54 +0200
committerJonathan Brouwer <jonathantbrouwer@gmail.com>2025-07-15 09:21:26 +0200
commit6133c676d7b5e025121c858c4e7942cce70adf27 (patch)
treea20df055417dfa4ea84da76ca7f57c90c4ead70e /compiler/rustc_attr_parsing
parent30f4a9cd539a9bffda21e056c8ab291d02e85541 (diff)
downloadrust-6133c676d7b5e025121c858c4e7942cce70adf27.tar.gz
rust-6133c676d7b5e025121c858c4e7942cce70adf27.zip
Define attribute parser & config evaluator
Diffstat (limited to 'compiler/rustc_attr_parsing')
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/cfg.rs464
1 files changed, 254 insertions, 210 deletions
diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
index a8d9229cbc3..a56855b3bd3 100644
--- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs
@@ -1,247 +1,291 @@
-use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId};
-use rustc_ast_pretty::pprust;
-use rustc_attr_data_structures::RustcVersion;
-use rustc_feature::{Features, GatedCfg, find_gated_cfg};
+use rustc_ast::{LitKind, NodeId};
+use rustc_attr_data_structures::{CfgEntry, RustcVersion};
+use rustc_feature::{AttributeTemplate, Features, template};
 use rustc_session::Session;
 use rustc_session::config::ExpectedValues;
+use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::lint::builtin::UNEXPECTED_CFGS;
-use rustc_session::lint::{BuiltinLintDiag, Lint};
 use rustc_session::parse::feature_err;
 use rustc_span::{Span, Symbol, sym};
+use thin_vec::ThinVec;
 
-use crate::session_diagnostics::{self, UnsupportedLiteralReason};
-use crate::{fluent_generated, parse_version};
+use crate::context::{AcceptContext, Stage};
+use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser};
+use crate::{
+    CfgMatchesLintEmitter, fluent_generated, parse_version, session_diagnostics, try_gate_cfg,
+};
 
-/// Emitter of a builtin lint from `cfg_matches`.
-///
-/// Used to support emitting a lint (currently on check-cfg), either:
-///  - as an early buffered lint (in `rustc`)
-///  - or has a "normal" lint from HIR (in `rustdoc`)
-pub trait CfgMatchesLintEmitter {
-    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag);
-}
+pub const CFG_TEMPLATE: AttributeTemplate = template!(List: "predicate");
 
-impl CfgMatchesLintEmitter for NodeId {
-    fn emit_span_lint(&self, sess: &Session, lint: &'static Lint, sp: Span, diag: BuiltinLintDiag) {
-        sess.psess.buffer_lint(lint, sp, *self, diag);
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct Condition {
-    pub name: Symbol,
-    pub name_span: Span,
-    pub value: Option<Symbol>,
-    pub value_span: Option<Span>,
-    pub span: Span,
+pub fn parse_cfg_attr<'c, S: Stage>(
+    cx: &'c mut AcceptContext<'_, '_, S>,
+    args: &'c ArgParser<'_>,
+) -> Option<CfgEntry> {
+    let ArgParser::List(list) = args else {
+        cx.expected_list(cx.attr_span);
+        return None;
+    };
+    let Some(single) = list.single() else {
+        cx.expected_single_argument(list.span);
+        return None;
+    };
+    parse_cfg_entry(cx, single)
 }
 
-/// Tests if a cfg-pattern matches the cfg set
-pub fn cfg_matches(
-    cfg: &MetaItemInner,
-    sess: &Session,
-    lint_emitter: impl CfgMatchesLintEmitter,
-    features: Option<&Features>,
-) -> bool {
-    eval_condition(cfg, sess, features, &mut |cfg| {
-        try_gate_cfg(cfg.name, cfg.span, sess, features);
-        match sess.psess.check_config.expecteds.get(&cfg.name) {
-            Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
-                lint_emitter.emit_span_lint(
-                    sess,
-                    UNEXPECTED_CFGS,
-                    cfg.span,
-                    BuiltinLintDiag::UnexpectedCfgValue(
-                        (cfg.name, cfg.name_span),
-                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
-                    ),
-                );
+fn parse_cfg_entry<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    item: &MetaItemOrLitParser<'_>,
+) -> Option<CfgEntry> {
+    Some(match item {
+        MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
+            ArgParser::List(list) => match meta.path().word_sym() {
+                Some(sym::not) => {
+                    let Some(single) = list.single() else {
+                        cx.expected_single_argument(list.span);
+                        return None;
+                    };
+                    CfgEntry::Not(Box::new(parse_cfg_entry(cx, single)?), list.span)
+                }
+                Some(sym::any) => CfgEntry::Any(
+                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
+                    list.span,
+                ),
+                Some(sym::all) => CfgEntry::All(
+                    list.mixed().flat_map(|sub_item| parse_cfg_entry(cx, sub_item)).collect(),
+                    list.span,
+                ),
+                Some(sym::target) => parse_cfg_entry_target(cx, list, meta.span())?,
+                Some(sym::version) => parse_cfg_entry_version(cx, list, meta.span())?,
+                _ => {
+                    cx.emit_err(session_diagnostics::InvalidPredicate {
+                        span: meta.span(),
+                        predicate: meta.path().to_string(),
+                    });
+                    return None;
+                }
+            },
+            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
+                let Some(name) = meta.path().word_sym() else {
+                    cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
+                        span: meta.path().span(),
+                    });
+                    return None;
+                };
+                parse_name_value(name, meta.path().span(), a.name_value(), meta.span(), cx)?
             }
-            None if sess.psess.check_config.exhaustive_names => {
-                lint_emitter.emit_span_lint(
-                    sess,
-                    UNEXPECTED_CFGS,
-                    cfg.span,
-                    BuiltinLintDiag::UnexpectedCfgName(
-                        (cfg.name, cfg.name_span),
-                        cfg.value.map(|v| (v, cfg.value_span.unwrap())),
-                    ),
-                );
+        },
+        MetaItemOrLitParser::Lit(lit) => match lit.kind {
+            LitKind::Bool(b) => CfgEntry::Bool(b, lit.span),
+            _ => {
+                cx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: lit.span });
+                return None;
             }
-            _ => { /* not unexpected */ }
-        }
-        sess.psess.config.contains(&(cfg.name, cfg.value))
+        },
+        MetaItemOrLitParser::Err(_, _) => return None,
     })
 }
 
-fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
-    let gate = find_gated_cfg(|sym| sym == name);
-    if let (Some(feats), Some(gated_cfg)) = (features, gate) {
-        gate_cfg(gated_cfg, span, sess, feats);
-    }
+fn parse_cfg_entry_version<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    list: &MetaItemListParser<'_>,
+    meta_span: Span,
+) -> Option<CfgEntry> {
+    try_gate_cfg(sym::version, meta_span, cx.sess(), Some(cx.features()));
+    let Some(version) = list.single() else {
+        cx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral { span: list.span });
+        return None;
+    };
+    let Some(version_lit) = version.lit() else {
+        cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version.span() });
+        return None;
+    };
+    let Some(version_str) = version_lit.value_str() else {
+        cx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: version_lit.span });
+        return None;
+    };
+
+    let min_version = parse_version(version_str).or_else(|| {
+        cx.sess()
+            .dcx()
+            .emit_warn(session_diagnostics::UnknownVersionLiteral { span: version_lit.span });
+        None
+    });
+
+    Some(CfgEntry::Version(min_version, list.span))
 }
 
-#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
-fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Features) {
-    let (cfg, feature, has_feature) = gated_cfg;
-    if !has_feature(features) && !cfg_span.allows_unstable(*feature) {
-        let explain = format!("`cfg({cfg})` is experimental and subject to change");
-        feature_err(sess, *feature, cfg_span, explain).emit();
+fn parse_cfg_entry_target<S: Stage>(
+    cx: &mut AcceptContext<'_, '_, S>,
+    list: &MetaItemListParser<'_>,
+    meta_span: Span,
+) -> Option<CfgEntry> {
+    if !cx.features().cfg_target_compact() {
+        feature_err(
+            cx.sess(),
+            sym::cfg_target_compact,
+            meta_span,
+            fluent_generated::attr_parsing_unstable_cfg_target_compact,
+        )
+        .emit();
     }
-}
 
-/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
-/// evaluate individual items.
-pub fn eval_condition(
-    cfg: &MetaItemInner,
-    sess: &Session,
-    features: Option<&Features>,
-    eval: &mut impl FnMut(Condition) -> bool,
-) -> bool {
-    let dcx = sess.dcx();
-
-    let cfg = match cfg {
-        MetaItemInner::MetaItem(meta_item) => meta_item,
-        MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
-            return *b;
-        }
-        _ => {
-            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                span: cfg.span(),
-                reason: UnsupportedLiteralReason::CfgBoolean,
-                is_bytestr: false,
-                start_point_span: sess.source_map().start_point(cfg.span()),
+    let mut result = ThinVec::new();
+    for sub_item in list.mixed() {
+        // First, validate that this is a NameValue item
+        let Some(sub_item) = sub_item.meta_item() else {
+            cx.expected_name_value(sub_item.span(), None);
+            continue;
+        };
+        let Some(nv) = sub_item.args().name_value() else {
+            cx.expected_name_value(sub_item.span(), None);
+            continue;
+        };
+
+        // Then, parse it as a name-value item
+        let Some(name) = sub_item.path().word_sym() else {
+            cx.emit_err(session_diagnostics::CfgPredicateIdentifier {
+                span: sub_item.path().span(),
             });
-            return false;
+            return None;
+        };
+        let name = Symbol::intern(&format!("target_{name}"));
+        if let Some(cfg) =
+            parse_name_value(name, sub_item.path().span(), Some(nv), sub_item.span(), cx)
+        {
+            result.push(cfg);
+        }
+    }
+    Some(CfgEntry::All(result, list.span))
+}
+
+fn parse_name_value<S: Stage>(
+    name: Symbol,
+    name_span: Span,
+    value: Option<&NameValueParser>,
+    span: Span,
+    cx: &mut AcceptContext<'_, '_, S>,
+) -> Option<CfgEntry> {
+    try_gate_cfg(name, span, cx.sess(), cx.features_option());
+
+    let value = match value {
+        None => None,
+        Some(value) => {
+            let Some(value_str) = value.value_as_str() else {
+                cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
+                return None;
+            };
+            Some((value_str, value.value_span))
         }
     };
 
-    match &cfg.kind {
-        MetaItemKind::List(mis) if cfg.has_name(sym::version) => {
-            try_gate_cfg(sym::version, cfg.span, sess, features);
-            let (min_version, span) = match &mis[..] {
-                [MetaItemInner::Lit(MetaItemLit { kind: LitKind::Str(sym, ..), span, .. })] => {
-                    (sym, span)
-                }
-                [
-                    MetaItemInner::Lit(MetaItemLit { span, .. })
-                    | MetaItemInner::MetaItem(MetaItem { span, .. }),
-                ] => {
-                    dcx.emit_err(session_diagnostics::ExpectedVersionLiteral { span: *span });
-                    return false;
+    Some(CfgEntry::NameValue { name, name_span, value, span })
+}
+
+pub fn eval_config_entry(
+    sess: &Session,
+    cfg_entry: &CfgEntry,
+    id: NodeId,
+    features: Option<&Features>,
+) -> EvalConfigResult {
+    match cfg_entry {
+        CfgEntry::All(subs, ..) => {
+            let mut all = None;
+            for sub in subs {
+                let res = eval_config_entry(sess, sub, id, features);
+                // We cannot short-circuit because `eval_config_entry` emits some lints
+                if !res.as_bool() {
+                    all.get_or_insert(res);
                 }
-                [..] => {
-                    dcx.emit_err(session_diagnostics::ExpectedSingleVersionLiteral {
-                        span: cfg.span,
-                    });
-                    return false;
+            }
+            all.unwrap_or_else(|| EvalConfigResult::True)
+        }
+        CfgEntry::Any(subs, span) => {
+            let mut any = None;
+            for sub in subs {
+                let res = eval_config_entry(sess, sub, id, features);
+                // We cannot short-circuit because `eval_config_entry` emits some lints
+                if res.as_bool() {
+                    any.get_or_insert(res);
                 }
-            };
-            let Some(min_version) = parse_version(*min_version) else {
-                dcx.emit_warn(session_diagnostics::UnknownVersionLiteral { span: *span });
-                return false;
-            };
-
-            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
-            if sess.psess.assume_incomplete_release {
-                RustcVersion::current_overridable() > min_version
+            }
+            any.unwrap_or_else(|| EvalConfigResult::False {
+                reason: cfg_entry.clone(),
+                reason_span: *span,
+            })
+        }
+        CfgEntry::Not(sub, span) => {
+            if eval_config_entry(sess, sub, id, features).as_bool() {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             } else {
-                RustcVersion::current_overridable() >= min_version
+                EvalConfigResult::True
             }
         }
-        MetaItemKind::List(mis) => {
-            for mi in mis.iter() {
-                if mi.meta_item_or_bool().is_none() {
-                    dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                        span: mi.span(),
-                        reason: UnsupportedLiteralReason::Generic,
-                        is_bytestr: false,
-                        start_point_span: sess.source_map().start_point(mi.span()),
-                    });
-                    return false;
-                }
+        CfgEntry::Bool(b, span) => {
+            if *b {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             }
-
-            // The unwraps below may look dangerous, but we've already asserted
-            // that they won't fail with the loop above.
-            match cfg.name() {
-                Some(sym::any) => mis
-                    .iter()
-                    // We don't use any() here, because we want to evaluate all cfg condition
-                    // as eval_condition can (and does) extra checks
-                    .fold(false, |res, mi| res | eval_condition(mi, sess, features, eval)),
-                Some(sym::all) => mis
-                    .iter()
-                    // We don't use all() here, because we want to evaluate all cfg condition
-                    // as eval_condition can (and does) extra checks
-                    .fold(true, |res, mi| res & eval_condition(mi, sess, features, eval)),
-                Some(sym::not) => {
-                    let [mi] = mis.as_slice() else {
-                        dcx.emit_err(session_diagnostics::ExpectedOneCfgPattern { span: cfg.span });
-                        return false;
-                    };
-
-                    !eval_condition(mi, sess, features, eval)
-                }
-                Some(sym::target) => {
-                    if let Some(features) = features
-                        && !features.cfg_target_compact()
-                    {
-                        feature_err(
-                            sess,
-                            sym::cfg_target_compact,
-                            cfg.span,
-                            fluent_generated::attr_parsing_unstable_cfg_target_compact,
-                        )
-                        .emit();
-                    }
-
-                    mis.iter().fold(true, |res, mi| {
-                        let Some(mut mi) = mi.meta_item().cloned() else {
-                            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier {
-                                span: mi.span(),
-                            });
-                            return false;
-                        };
-
-                        if let [seg, ..] = &mut mi.path.segments[..] {
-                            seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name));
-                        }
-
-                        res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval)
-                    })
+        }
+        CfgEntry::NameValue { name, name_span, value, span } => {
+            match sess.psess.check_config.expecteds.get(name) {
+                Some(ExpectedValues::Some(values)) if !values.contains(&value.map(|(v, _)| v)) => {
+                    id.emit_span_lint(
+                        sess,
+                        UNEXPECTED_CFGS,
+                        *span,
+                        BuiltinLintDiag::UnexpectedCfgValue((*name, *name_span), *value),
+                    );
                 }
-                _ => {
-                    dcx.emit_err(session_diagnostics::InvalidPredicate {
-                        span: cfg.span,
-                        predicate: pprust::path_to_string(&cfg.path),
-                    });
-                    false
+                None if sess.psess.check_config.exhaustive_names => {
+                    id.emit_span_lint(
+                        sess,
+                        UNEXPECTED_CFGS,
+                        *span,
+                        BuiltinLintDiag::UnexpectedCfgName((*name, *name_span), *value),
+                    );
                 }
+                _ => { /* not unexpected */ }
+            }
+
+            if sess.psess.config.contains(&(*name, value.map(|(v, _)| v))) {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *span }
             }
         }
-        MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => {
-            dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span });
-            true
-        }
-        MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
-            dcx.emit_err(session_diagnostics::UnsupportedLiteral {
-                span: lit.span,
-                reason: UnsupportedLiteralReason::CfgString,
-                is_bytestr: lit.kind.is_bytestr(),
-                start_point_span: sess.source_map().start_point(lit.span),
-            });
-            true
+        CfgEntry::Version(min_version, version_span) => {
+            let Some(min_version) = min_version else {
+                return EvalConfigResult::False {
+                    reason: cfg_entry.clone(),
+                    reason_span: *version_span,
+                };
+            };
+            // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
+            let min_version_ok = if sess.psess.assume_incomplete_release {
+                RustcVersion::current_overridable() > *min_version
+            } else {
+                RustcVersion::current_overridable() >= *min_version
+            };
+            if min_version_ok {
+                EvalConfigResult::True
+            } else {
+                EvalConfigResult::False { reason: cfg_entry.clone(), reason_span: *version_span }
+            }
         }
-        MetaItemKind::Word | MetaItemKind::NameValue(..) => {
-            let ident = cfg.ident().expect("multi-segment cfg predicate");
-            eval(Condition {
-                name: ident.name,
-                name_span: ident.span,
-                value: cfg.value_str(),
-                value_span: cfg.name_value_literal_span(),
-                span: cfg.span,
-            })
+    }
+}
+
+pub enum EvalConfigResult {
+    True,
+    False { reason: CfgEntry, reason_span: Span },
+}
+
+impl EvalConfigResult {
+    pub fn as_bool(&self) -> bool {
+        match self {
+            EvalConfigResult::True => true,
+            EvalConfigResult::False { .. } => false,
         }
     }
 }