diff options
Diffstat (limited to 'compiler/rustc_attr_parsing')
| -rw-r--r-- | compiler/rustc_attr_parsing/Cargo.toml | 4 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/messages.ftl | 23 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs | 92 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/cfg.rs | 34 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/confusables.rs | 65 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/deprecation.rs | 214 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/mod.rs | 169 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/repr.rs | 381 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/stability.rs | 471 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/transparency.rs | 55 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/attributes/util.rs | 24 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/context.rs | 348 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/lib.rs | 131 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/parser.rs | 624 | ||||
| -rw-r--r-- | compiler/rustc_attr_parsing/src/session_diagnostics.rs | 72 |
15 files changed, 2033 insertions, 674 deletions
diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 7ccedf40c3f..c335eeb5f71 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustc_attr_parsing" version = "0.0.0" -edition = "2021" +edition = "2024" [dependencies] # tidy-alphabetical-start @@ -13,9 +13,11 @@ rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_feature = { path = "../rustc_feature" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } +rustc_hir = { path = "../rustc_hir" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +thin-vec = "0.2.12" # tidy-alphabetical-end diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index faa2865cb91..45174c9582d 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -6,6 +6,8 @@ attr_parsing_deprecated_item_suggestion = .help = add `#![feature(deprecated_suggestion)]` to the crate root .note = see #94785 for more details +attr_parsing_empty_confusables = + expected at least one confusable name attr_parsing_expected_one_cfg_pattern = expected 1 cfg-pattern @@ -21,8 +23,8 @@ attr_parsing_expects_feature_list = attr_parsing_expects_features = `{$name}` expects feature names -attr_parsing_incorrect_meta_item = - incorrect meta item +attr_parsing_incorrect_meta_item = expected a quoted string literal +attr_parsing_incorrect_meta_item_suggestion = consider surrounding this with quotes attr_parsing_incorrect_repr_format_align_one_arg = incorrect `repr(align)` attribute format: `align` takes exactly one argument in parentheses @@ -88,18 +90,20 @@ attr_parsing_multiple_stability_levels = attr_parsing_non_ident_feature = 'feature' is not an identifier +attr_parsing_repr_ident = + meta item in `repr` must be an identifier + attr_parsing_rustc_allowed_unstable_pairing = `rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute -attr_parsing_rustc_const_stable_indirect_pairing = - `const_stable_indirect` attribute does not make sense on `rustc_const_stable` function, its behavior is already implied - attr_parsing_rustc_promotable_pairing = `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute attr_parsing_soft_no_args = `soft` should not have any arguments +attr_parsing_stability_outside_std = stability attributes may not be used outside of the standard library + attr_parsing_unknown_meta_item = unknown meta item '{$item}' .label = expected one of {$expected} @@ -107,6 +111,10 @@ attr_parsing_unknown_meta_item = attr_parsing_unknown_version_literal = unknown version literal format, assuming it refers to a future version +attr_parsing_unrecognized_repr_hint = + unrecognized representation hint + .help = valid reprs are `Rust` (default), `C`, `align`, `packed`, `transparent`, `simd`, `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `isize`, `usize` + attr_parsing_unstable_cfg_target_compact = compact `cfg(target(..))` is experimental and subject to change @@ -122,3 +130,8 @@ attr_parsing_unsupported_literal_generic = unsupported literal attr_parsing_unsupported_literal_suggestion = consider removing the prefix + +attr_parsing_unused_multiple = + multiple `{$name}` attributes + .suggestion = remove this attribute + .note = attribute also specified here diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index 471168ed4f5..d37ede86cfd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -1,49 +1,67 @@ -use rustc_ast::attr::{AttributeExt, filter_by_name}; -use rustc_session::Session; -use rustc_span::{Symbol, sym}; +use std::iter; +use rustc_attr_data_structures::AttributeKind; +use rustc_span::{Span, Symbol, sym}; + +use super::{CombineAttributeParser, ConvertFn}; +use crate::context::AcceptContext; +use crate::parser::ArgParser; use crate::session_diagnostics; -pub fn allow_internal_unstable<'a>( - sess: &'a Session, - attrs: &'a [impl AttributeExt], -) -> impl Iterator<Item = Symbol> + 'a { - allow_unstable(sess, attrs, sym::allow_internal_unstable) +pub(crate) struct AllowInternalUnstableParser; +impl CombineAttributeParser for AllowInternalUnstableParser { + const PATH: &'static [rustc_span::Symbol] = &[sym::allow_internal_unstable]; + type Item = (Symbol, Span); + const CONVERT: ConvertFn<Self::Item> = AttributeKind::AllowInternalUnstable; + + fn extend<'a>( + cx: &'a AcceptContext<'a>, + args: &'a ArgParser<'a>, + ) -> impl IntoIterator<Item = Self::Item> + 'a { + parse_unstable(cx, args, Self::PATH[0]).into_iter().zip(iter::repeat(cx.attr_span)) + } } -pub fn rustc_allow_const_fn_unstable<'a>( - sess: &'a Session, - attrs: &'a [impl AttributeExt], -) -> impl Iterator<Item = Symbol> + 'a { - allow_unstable(sess, attrs, sym::rustc_allow_const_fn_unstable) +pub(crate) struct AllowConstFnUnstableParser; +impl CombineAttributeParser for AllowConstFnUnstableParser { + const PATH: &'static [rustc_span::Symbol] = &[sym::rustc_allow_const_fn_unstable]; + type Item = Symbol; + const CONVERT: ConvertFn<Self::Item> = AttributeKind::AllowConstFnUnstable; + + fn extend<'a>( + cx: &'a AcceptContext<'a>, + args: &'a ArgParser<'a>, + ) -> impl IntoIterator<Item = Self::Item> + 'a { + parse_unstable(cx, args, Self::PATH[0]) + } } -fn allow_unstable<'a>( - sess: &'a Session, - attrs: &'a [impl AttributeExt], +fn parse_unstable<'a>( + cx: &AcceptContext<'_>, + args: &'a ArgParser<'a>, symbol: Symbol, -) -> impl Iterator<Item = Symbol> + 'a { - let attrs = filter_by_name(attrs, symbol); - let list = attrs - .filter_map(move |attr| { - attr.meta_item_list().or_else(|| { - sess.dcx().emit_err(session_diagnostics::ExpectsFeatureList { - span: attr.span(), - name: symbol.to_ident_string(), - }); - None - }) - }) - .flatten(); - - list.into_iter().filter_map(move |it| { - let name = it.ident().map(|ident| ident.name); - if name.is_none() { - sess.dcx().emit_err(session_diagnostics::ExpectsFeatures { - span: it.span(), +) -> impl IntoIterator<Item = Symbol> { + let mut res = Vec::new(); + + let Some(list) = args.list() else { + cx.emit_err(session_diagnostics::ExpectsFeatureList { + span: cx.attr_span, + name: symbol.to_ident_string(), + }); + return res; + }; + + for param in list.mixed() { + let param_span = param.span(); + if let Some(ident) = param.meta_item().and_then(|i| i.word_without_args()) { + res.push(ident.name); + } else { + cx.emit_err(session_diagnostics::ExpectsFeatures { + span: param_span, name: symbol.to_ident_string(), }); } - name - }) + } + + res } diff --git a/compiler/rustc_attr_parsing/src/attributes/cfg.rs b/compiler/rustc_attr_parsing/src/attributes/cfg.rs index bb9aaaa2fea..0d6d521b40c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/cfg.rs +++ b/compiler/rustc_attr_parsing/src/attributes/cfg.rs @@ -1,6 +1,4 @@ -//! Parsing and validation of builtin attributes - -use rustc_ast::{self as ast, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NodeId}; +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}; @@ -9,10 +7,11 @@ use rustc_session::config::ExpectedValues; use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::parse::feature_err; -use rustc_span::{Span, Symbol, kw, sym}; +use rustc_span::symbol::kw; +use rustc_span::{Span, Symbol, sym}; -use crate::util::UnsupportedLiteralReason; -use crate::{fluent_generated, parse_version, session_diagnostics}; +use crate::session_diagnostics::{self, UnsupportedLiteralReason}; +use crate::{fluent_generated, parse_version}; #[derive(Clone, Debug)] pub struct Condition { @@ -25,7 +24,7 @@ pub struct Condition { /// Tests if a cfg-pattern matches the cfg set pub fn cfg_matches( - cfg: &ast::MetaItemInner, + cfg: &MetaItemInner, sess: &Session, lint_node_id: NodeId, features: Option<&Features>, @@ -80,7 +79,7 @@ fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &Session, features: &Fea /// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to /// evaluate individual items. pub fn eval_condition( - cfg: &ast::MetaItemInner, + cfg: &MetaItemInner, sess: &Session, features: Option<&Features>, eval: &mut impl FnMut(Condition) -> bool, @@ -88,8 +87,8 @@ pub fn eval_condition( let dcx = sess.dcx(); let cfg = match cfg { - ast::MetaItemInner::MetaItem(meta_item) => meta_item, - ast::MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => { + MetaItemInner::MetaItem(meta_item) => meta_item, + MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => { if let Some(features) = features { // we can't use `try_gate_cfg` as symbols don't differentiate between `r#true` // and `true`, and we want to keep the former working without feature gate @@ -118,7 +117,7 @@ pub fn eval_condition( }; match &cfg.kind { - ast::MetaItemKind::List(mis) if cfg.name_or_empty() == sym::version => { + MetaItemKind::List(mis) if cfg.name_or_empty() == 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, .. })] => { @@ -150,7 +149,7 @@ pub fn eval_condition( RustcVersion::CURRENT >= min_version } } - ast::MetaItemKind::List(mis) => { + MetaItemKind::List(mis) => { for mi in mis.iter() { if mi.meta_item_or_bool().is_none() { dcx.emit_err(session_diagnostics::UnsupportedLiteral { @@ -209,12 +208,7 @@ pub fn eval_condition( seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name)); } - res & eval_condition( - &ast::MetaItemInner::MetaItem(mi), - sess, - features, - eval, - ) + res & eval_condition(&MetaItemInner::MetaItem(mi), sess, features, eval) }) } _ => { @@ -226,7 +220,7 @@ pub fn eval_condition( } } } - ast::MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => { + MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => { dcx.emit_err(session_diagnostics::CfgPredicateIdentifier { span: cfg.path.span }); true } @@ -239,7 +233,7 @@ pub fn eval_condition( }); true } - ast::MetaItemKind::Word | ast::MetaItemKind::NameValue(..) => { + MetaItemKind::Word | MetaItemKind::NameValue(..) => { let ident = cfg.ident().expect("multi-segment cfg predicate"); eval(Condition { name: ident.name, diff --git a/compiler/rustc_attr_parsing/src/attributes/confusables.rs b/compiler/rustc_attr_parsing/src/attributes/confusables.rs index 2ced759fd88..6cff952fcf2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/confusables.rs +++ b/compiler/rustc_attr_parsing/src/attributes/confusables.rs @@ -1,21 +1,58 @@ -//! Parsing and validation of builtin attributes +use rustc_attr_data_structures::AttributeKind; +use rustc_span::{Span, Symbol, sym}; +use thin_vec::ThinVec; -use rustc_ast::MetaItemInner; -use rustc_ast::attr::AttributeExt; -use rustc_span::Symbol; +use super::{AcceptMapping, AttributeParser}; +use crate::context::FinalizeContext; +use crate::session_diagnostics; -/// Read the content of a `rustc_confusables` attribute, and return the list of candidate names. -pub fn parse_confusables(attr: &impl AttributeExt) -> Option<Vec<Symbol>> { - let metas = attr.meta_item_list()?; +#[derive(Default)] +pub(crate) struct ConfusablesParser { + confusables: ThinVec<Symbol>, + first_span: Option<Span>, +} + +impl AttributeParser for ConfusablesParser { + const ATTRIBUTES: AcceptMapping<Self> = &[(&[sym::rustc_confusables], |this, cx, args| { + let Some(list) = args.list() else { + // FIXME(jdonszelmann): error when not a list? Bring validation code here. + // NOTE: currently subsequent attributes are silently ignored using + // tcx.get_attr(). + return; + }; + + if list.is_empty() { + cx.emit_err(session_diagnostics::EmptyConfusables { span: cx.attr_span }); + } + + for param in list.mixed() { + let span = param.span(); - let mut candidates = Vec::new(); + let Some(lit) = param.lit() else { + cx.emit_err(session_diagnostics::IncorrectMetaItem { + span, + suggestion: Some(session_diagnostics::IncorrectMetaItemSuggestion { + lo: span.shrink_to_lo(), + hi: span.shrink_to_hi(), + }), + }); + continue; + }; - for meta in metas { - let MetaItemInner::Lit(meta_lit) = meta else { + this.confusables.push(lit.symbol); + } + + this.first_span.get_or_insert(cx.attr_span); + })]; + + fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + if self.confusables.is_empty() { return None; - }; - candidates.push(meta_lit.symbol); - } + } - Some(candidates) + Some(AttributeKind::Confusables { + symbols: self.confusables, + first_span: self.first_span.unwrap(), + }) + } } diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index d7415a7198f..7d1417446b2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -1,121 +1,122 @@ -//! Parsing and validation of builtin attributes - -use rustc_ast::attr::AttributeExt; -use rustc_ast::{MetaItem, MetaItemInner}; -use rustc_ast_pretty::pprust; -use rustc_attr_data_structures::{DeprecatedSince, Deprecation}; -use rustc_feature::Features; -use rustc_session::Session; +use rustc_attr_data_structures::{AttributeKind, DeprecatedSince, Deprecation}; +use rustc_span::symbol::Ident; use rustc_span::{Span, Symbol, sym}; -use super::util::UnsupportedLiteralReason; -use crate::{parse_version, session_diagnostics}; +use super::SingleAttributeParser; +use super::util::parse_version; +use crate::context::AcceptContext; +use crate::parser::ArgParser; +use crate::session_diagnostics; +use crate::session_diagnostics::UnsupportedLiteralReason; -/// Finds the deprecation attribute. `None` if none exists. -pub fn find_deprecation( - sess: &Session, - features: &Features, - attrs: &[impl AttributeExt], -) -> Option<(Deprecation, Span)> { - let mut depr: Option<(Deprecation, Span)> = None; - let is_rustc = features.staged_api(); +pub(crate) struct DeprecationParser; - 'outer: for attr in attrs { - if !attr.has_name(sym::deprecated) { - continue; +fn get( + cx: &AcceptContext<'_>, + ident: Ident, + param_span: Span, + arg: &ArgParser<'_>, + item: &Option<Symbol>, +) -> Option<Symbol> { + if item.is_some() { + cx.emit_err(session_diagnostics::MultipleItem { + span: param_span, + item: ident.to_string(), + }); + return None; + } + if let Some(v) = arg.name_value() { + if let Some(value_str) = v.value_as_str() { + Some(value_str) + } else { + let lit = v.value_as_lit(); + cx.emit_err(session_diagnostics::UnsupportedLiteral { + span: v.value_span, + reason: UnsupportedLiteralReason::DeprecatedString, + is_bytestr: lit.kind.is_bytestr(), + start_point_span: cx.sess().source_map().start_point(lit.span), + }); + None } + } else { + // FIXME(jdonszelmann): suggestion? + cx.emit_err(session_diagnostics::IncorrectMetaItem { span: param_span, suggestion: None }); + None + } +} + +impl SingleAttributeParser for DeprecationParser { + const PATH: &'static [rustc_span::Symbol] = &[sym::deprecated]; + + fn on_duplicate(cx: &AcceptContext<'_>, first_span: rustc_span::Span) { + // FIXME(jdonszelmann): merge with errors from check_attrs.rs + cx.emit_err(session_diagnostics::UnusedMultiple { + this: cx.attr_span, + other: first_span, + name: sym::deprecated, + }); + } + + fn convert(cx: &AcceptContext<'_>, args: &ArgParser<'_>) -> Option<AttributeKind> { + let features = cx.features(); let mut since = None; let mut note = None; let mut suggestion = None; - if attr.is_doc_comment() { - continue; - } else if attr.is_word() { - } else if let Some(value) = attr.value_str() { - note = Some(value) - } else if let Some(list) = attr.meta_item_list() { - let get = |meta: &MetaItem, item: &mut Option<Symbol>| { - if item.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleItem { - span: meta.span, - item: pprust::path_to_string(&meta.path), + let is_rustc = features.staged_api(); + + if let Some(value) = args.name_value() + && let Some(value_str) = value.value_as_str() + { + note = Some(value_str) + } else if let Some(list) = args.list() { + for param in list.mixed() { + let param_span = param.span(); + let Some(param) = param.meta_item() else { + cx.emit_err(session_diagnostics::UnsupportedLiteral { + span: param_span, + reason: UnsupportedLiteralReason::DeprecatedKvPair, + is_bytestr: false, + start_point_span: cx.sess().source_map().start_point(param_span), }); - return false; - } - if let Some(v) = meta.value_str() { - *item = Some(v); - true - } else { - if let Some(lit) = meta.name_value_literal() { - sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral { - span: lit.span, - reason: UnsupportedLiteralReason::DeprecatedString, - is_bytestr: lit.kind.is_bytestr(), - start_point_span: sess.source_map().start_point(lit.span), - }); - } else { - sess.dcx() - .emit_err(session_diagnostics::IncorrectMetaItem { span: meta.span }); - } - false - } - }; + return None; + }; - for meta in &list { - match meta { - MetaItemInner::MetaItem(mi) => match mi.name_or_empty() { - sym::since => { - if !get(mi, &mut since) { - continue 'outer; - } - } - sym::note => { - if !get(mi, &mut note) { - continue 'outer; - } - } - sym::suggestion => { - if !features.deprecated_suggestion() { - sess.dcx().emit_err( - session_diagnostics::DeprecatedItemSuggestion { - span: mi.span, - is_nightly: sess.is_nightly_build(), - details: (), - }, - ); - } + let (ident, arg) = param.word_or_empty(); - if !get(mi, &mut suggestion) { - continue 'outer; - } - } - _ => { - sess.dcx().emit_err(session_diagnostics::UnknownMetaItem { - span: meta.span(), - item: pprust::path_to_string(&mi.path), - expected: if features.deprecated_suggestion() { - &["since", "note", "suggestion"] - } else { - &["since", "note"] - }, + match ident.name { + sym::since => { + since = Some(get(cx, ident, param_span, arg, &since)?); + } + sym::note => { + note = Some(get(cx, ident, param_span, arg, ¬e)?); + } + sym::suggestion => { + if !features.deprecated_suggestion() { + cx.emit_err(session_diagnostics::DeprecatedItemSuggestion { + span: param_span, + is_nightly: cx.sess().is_nightly_build(), + details: (), }); - continue 'outer; } - }, - MetaItemInner::Lit(lit) => { - sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral { - span: lit.span, - reason: UnsupportedLiteralReason::DeprecatedKvPair, - is_bytestr: false, - start_point_span: sess.source_map().start_point(lit.span), + + suggestion = Some(get(cx, ident, param_span, arg, &suggestion)?); + } + _ => { + cx.emit_err(session_diagnostics::UnknownMetaItem { + span: param_span, + item: ident.to_string(), + expected: if features.deprecated_suggestion() { + &["since", "note", "suggestion"] + } else { + &["since", "note"] + }, }); - continue 'outer; + return None; } } } - } else { - continue; } let since = if let Some(since) = since { @@ -126,23 +127,24 @@ pub fn find_deprecation( } else if let Some(version) = parse_version(since) { DeprecatedSince::RustcVersion(version) } else { - sess.dcx().emit_err(session_diagnostics::InvalidSince { span: attr.span() }); + cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span }); DeprecatedSince::Err } } else if is_rustc { - sess.dcx().emit_err(session_diagnostics::MissingSince { span: attr.span() }); + cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span }); DeprecatedSince::Err } else { DeprecatedSince::Unspecified }; if is_rustc && note.is_none() { - sess.dcx().emit_err(session_diagnostics::MissingNote { span: attr.span() }); - continue; + cx.emit_err(session_diagnostics::MissingNote { span: cx.attr_span }); + return None; } - depr = Some((Deprecation { since, note, suggestion }, attr.span())); + Some(AttributeKind::Deprecation { + deprecation: Deprecation { since, note, suggestion }, + span: cx.attr_span, + }) } - - depr } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index a78e0b54b64..6ecd6b4d7db 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -1,17 +1,152 @@ -mod allow_unstable; -mod cfg; -mod confusables; -mod deprecation; -mod repr; -mod stability; -mod transparency; - -pub mod util; - -pub use allow_unstable::*; -pub use cfg::*; -pub use confusables::*; -pub use deprecation::*; -pub use repr::*; -pub use stability::*; -pub use transparency::*; +//! This module defines traits for attribute parsers, little state machines that recognize and parse +//! attributes out of a longer list of attributes. The main trait is called [`AttributeParser`]. +//! You can find more docs about [`AttributeParser`]s on the trait itself. +//! However, for many types of attributes, implementing [`AttributeParser`] is not necessary. +//! It allows for a lot of flexibility you might not want. +//! +//! Specifically, you might not care about managing the state of your [`AttributeParser`] +//! state machine yourself. In this case you can choose to implement: +//! +//! - [`SingleAttributeParser`]: makes it easy to implement an attribute which should error if it +//! appears more than once in a list of attributes +//! - [`CombineAttributeParser`]: makes it easy to implement an attribute which should combine the +//! contents of attributes, if an attribute appear multiple times in a list +//! +//! Attributes should be added to [`ATTRIBUTE_MAPPING`](crate::context::ATTRIBUTE_MAPPING) to be parsed. + +use std::marker::PhantomData; + +use rustc_attr_data_structures::AttributeKind; +use rustc_span::Span; +use thin_vec::ThinVec; + +use crate::context::{AcceptContext, FinalizeContext}; +use crate::parser::ArgParser; + +pub(crate) mod allow_unstable; +pub(crate) mod cfg; +pub(crate) mod confusables; +pub(crate) mod deprecation; +pub(crate) mod repr; +pub(crate) mod stability; +pub(crate) mod transparency; +pub(crate) mod util; + +type AcceptFn<T> = fn(&mut T, &AcceptContext<'_>, &ArgParser<'_>); +type AcceptMapping<T> = &'static [(&'static [rustc_span::Symbol], AcceptFn<T>)]; + +/// An [`AttributeParser`] is a type which searches for syntactic attributes. +/// +/// Parsers are often tiny state machines that gets to see all syntactical attributes on an item. +/// [`Default::default`] creates a fresh instance that sits in some kind of initial state, usually that the +/// attribute it is looking for was not yet seen. +/// +/// Then, it defines what paths this group will accept in [`AttributeParser::ATTRIBUTES`]. +/// These are listed as pairs, of symbols and function pointers. The function pointer will +/// be called when that attribute is found on an item, which can influence the state of the little +/// state machine. +/// +/// Finally, after all attributes on an item have been seen, and possibly been accepted, +/// the [`finalize`](AttributeParser::finalize) functions for all attribute parsers are called. Each can then report +/// whether it has seen the attribute it has been looking for. +/// +/// The state machine is automatically reset to parse attributes on the next item. +pub(crate) trait AttributeParser: Default + 'static { + /// The symbols for the attributes that this parser is interested in. + /// + /// If an attribute has this symbol, the `accept` function will be called on it. + const ATTRIBUTES: AcceptMapping<Self>; + + /// The parser has gotten a chance to accept the attributes on an item, + /// here it can produce an attribute. + fn finalize(self, cx: &FinalizeContext<'_>) -> Option<AttributeKind>; +} + +/// Alternative to [`AttributeParser`] that automatically handles state management. +/// A slightly simpler and more restricted way to convert attributes. +/// Assumes that an attribute can only appear a single time on an item, +/// and errors when it sees more. +/// +/// [`Single<T> where T: SingleAttributeParser`](Single) implements [`AttributeParser`]. +/// +/// [`SingleAttributeParser`] can only convert attributes one-to-one, and cannot combine multiple +/// attributes together like is necessary for `#[stable()]` and `#[unstable()]` for example. +pub(crate) trait SingleAttributeParser: 'static { + const PATH: &'static [rustc_span::Symbol]; + + /// Caled when a duplicate attribute is found. + /// + /// `first_span` is the span of the first occurrence of this attribute. + // FIXME(jdonszelmann): default error + fn on_duplicate(cx: &AcceptContext<'_>, first_span: Span); + + /// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`] + fn convert(cx: &AcceptContext<'_>, args: &ArgParser<'_>) -> Option<AttributeKind>; +} + +pub(crate) struct Single<T: SingleAttributeParser>(PhantomData<T>, Option<(AttributeKind, Span)>); + +impl<T: SingleAttributeParser> Default for Single<T> { + fn default() -> Self { + Self(Default::default(), Default::default()) + } +} + +impl<T: SingleAttributeParser> AttributeParser for Single<T> { + const ATTRIBUTES: AcceptMapping<Self> = &[(T::PATH, |group: &mut Single<T>, cx, args| { + if let Some((_, s)) = group.1 { + T::on_duplicate(cx, s); + return; + } + + if let Some(pa) = T::convert(cx, args) { + group.1 = Some((pa, cx.attr_span)); + } + })]; + + fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + Some(self.1?.0) + } +} + +type ConvertFn<E> = fn(ThinVec<E>) -> AttributeKind; + +/// Alternative to [`AttributeParser`] that automatically handles state management. +/// If multiple attributes appear on an element, combines the values of each into a +/// [`ThinVec`]. +/// [`Combine<T> where T: CombineAttributeParser`](Combine) implements [`AttributeParser`]. +/// +/// [`CombineAttributeParser`] can only convert a single kind of attribute, and cannot combine multiple +/// attributes together like is necessary for `#[stable()]` and `#[unstable()]` for example. +pub(crate) trait CombineAttributeParser: 'static { + const PATH: &'static [rustc_span::Symbol]; + + type Item; + const CONVERT: ConvertFn<Self::Item>; + + /// Converts a single syntactical attribute to a number of elements of the semantic attribute, or [`AttributeKind`] + fn extend<'a>( + cx: &'a AcceptContext<'a>, + args: &'a ArgParser<'a>, + ) -> impl IntoIterator<Item = Self::Item> + 'a; +} + +pub(crate) struct Combine<T: CombineAttributeParser>( + PhantomData<T>, + ThinVec<<T as CombineAttributeParser>::Item>, +); + +impl<T: CombineAttributeParser> Default for Combine<T> { + fn default() -> Self { + Self(Default::default(), Default::default()) + } +} + +impl<T: CombineAttributeParser> AttributeParser for Combine<T> { + const ATTRIBUTES: AcceptMapping<Self> = + &[(T::PATH, |group: &mut Combine<T>, cx, args| group.1.extend(T::extend(cx, args)))]; + + fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + if self.1.is_empty() { None } else { Some(T::CONVERT(self.1)) } + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index 28c381160b8..26ca637faec 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -1,15 +1,13 @@ -//! Parsing and validation of builtin attributes - use rustc_abi::Align; -use rustc_ast::attr::AttributeExt; -use rustc_ast::{self as ast, MetaItemKind}; -use rustc_attr_data_structures::IntType; -use rustc_attr_data_structures::ReprAttr::*; -use rustc_session::Session; -use rustc_span::{Symbol, sym}; +use rustc_ast::{IntTy, LitIntType, LitKind, UintTy}; +use rustc_attr_data_structures::{AttributeKind, IntType, ReprAttr}; +use rustc_span::{Span, Symbol, sym}; -use crate::ReprAttr; -use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause}; +use super::{CombineAttributeParser, ConvertFn}; +use crate::context::AcceptContext; +use crate::parser::{ArgParser, MetaItemListParser, MetaItemParser}; +use crate::session_diagnostics; +use crate::session_diagnostics::IncorrectReprFormatGenericCause; /// Parse #[repr(...)] forms. /// @@ -18,185 +16,216 @@ use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause}; /// the same discriminant size that the corresponding C enum would or C /// structure layout, `packed` to remove padding, and `transparent` to delegate representation /// concerns to the only non-ZST field. -pub fn find_repr_attrs(sess: &Session, attr: &impl AttributeExt) -> Vec<ReprAttr> { - if attr.has_name(sym::repr) { parse_repr_attr(sess, attr) } else { Vec::new() } -} +// FIXME(jdonszelmann): is a vec the right representation here even? isn't it just a struct? +pub(crate) struct ReprParser; -pub fn parse_repr_attr(sess: &Session, attr: &impl AttributeExt) -> Vec<ReprAttr> { - assert!(attr.has_name(sym::repr), "expected `#[repr(..)]`, found: {attr:?}"); - let mut acc = Vec::new(); - let dcx = sess.dcx(); - - if let Some(items) = attr.meta_item_list() { - for item in items { - let mut recognised = false; - if item.is_word() { - let hint = match item.name_or_empty() { - sym::Rust => Some(ReprRust), - sym::C => Some(ReprC), - sym::packed => Some(ReprPacked(Align::ONE)), - sym::simd => Some(ReprSimd), - sym::transparent => Some(ReprTransparent), - sym::align => { - sess.dcx().emit_err(session_diagnostics::InvalidReprAlignNeedArg { - span: item.span(), - }); - recognised = true; - None - } - name => int_type_of_word(name).map(ReprInt), - }; - - if let Some(h) = hint { - recognised = true; - acc.push(h); - } - } else if let Some((name, value)) = item.singleton_lit_list() { - let mut literal_error = None; - let mut err_span = item.span(); - if name == sym::align { - recognised = true; - match parse_alignment(&value.kind) { - Ok(literal) => acc.push(ReprAlign(literal)), - Err(message) => { - err_span = value.span; - literal_error = Some(message) - } - }; - } else if name == sym::packed { - recognised = true; - match parse_alignment(&value.kind) { - Ok(literal) => acc.push(ReprPacked(literal)), - Err(message) => { - err_span = value.span; - literal_error = Some(message) - } - }; - } else if matches!(name, sym::Rust | sym::C | sym::simd | sym::transparent) - || int_type_of_word(name).is_some() - { - recognised = true; - sess.dcx().emit_err(session_diagnostics::InvalidReprHintNoParen { - span: item.span(), - name: name.to_ident_string(), - }); - } - if let Some(literal_error) = literal_error { - sess.dcx().emit_err(session_diagnostics::InvalidReprGeneric { - span: err_span, - repr_arg: name.to_ident_string(), - error_part: literal_error, - }); - } - } else if let Some(meta_item) = item.meta_item() { - match &meta_item.kind { - MetaItemKind::NameValue(value) => { - if meta_item.has_name(sym::align) || meta_item.has_name(sym::packed) { - let name = meta_item.name_or_empty().to_ident_string(); - recognised = true; - sess.dcx().emit_err(session_diagnostics::IncorrectReprFormatGeneric { - span: item.span(), - repr_arg: &name, - cause: IncorrectReprFormatGenericCause::from_lit_kind( - item.span(), - &value.kind, - &name, - ), - }); - } else if matches!( - meta_item.name_or_empty(), - sym::Rust | sym::C | sym::simd | sym::transparent - ) || int_type_of_word(meta_item.name_or_empty()).is_some() - { - recognised = true; - sess.dcx().emit_err(session_diagnostics::InvalidReprHintNoValue { - span: meta_item.span, - name: meta_item.name_or_empty().to_ident_string(), - }); - } - } - MetaItemKind::List(nested_items) => { - if meta_item.has_name(sym::align) { - recognised = true; - if let [nested_item] = nested_items.as_slice() { - sess.dcx().emit_err( - session_diagnostics::IncorrectReprFormatExpectInteger { - span: nested_item.span(), - }, - ); - } else { - sess.dcx().emit_err( - session_diagnostics::IncorrectReprFormatAlignOneArg { - span: meta_item.span, - }, - ); - } - } else if meta_item.has_name(sym::packed) { - recognised = true; - if let [nested_item] = nested_items.as_slice() { - sess.dcx().emit_err( - session_diagnostics::IncorrectReprFormatPackedExpectInteger { - span: nested_item.span(), - }, - ); - } else { - sess.dcx().emit_err( - session_diagnostics::IncorrectReprFormatPackedOneOrZeroArg { - span: meta_item.span, - }, - ); - } - } else if matches!( - meta_item.name_or_empty(), - sym::Rust | sym::C | sym::simd | sym::transparent - ) || int_type_of_word(meta_item.name_or_empty()).is_some() - { - recognised = true; - sess.dcx().emit_err(session_diagnostics::InvalidReprHintNoParen { - span: meta_item.span, - name: meta_item.name_or_empty().to_ident_string(), - }); - } - } - _ => (), - } - } - if !recognised { - // Not a word we recognize. This will be caught and reported by - // the `check_mod_attrs` pass, but this pass doesn't always run - // (e.g. if we only pretty-print the source), so we have to gate - // the `span_delayed_bug` call as follows: - if sess.opts.pretty.is_none_or(|pp| pp.needs_analysis()) { - dcx.span_delayed_bug(item.span(), "unrecognized representation hint"); - } +impl CombineAttributeParser for ReprParser { + type Item = (ReprAttr, Span); + const PATH: &'static [rustc_span::Symbol] = &[sym::repr]; + const CONVERT: ConvertFn<Self::Item> = AttributeKind::Repr; + + fn extend<'a>( + cx: &'a AcceptContext<'a>, + args: &'a ArgParser<'a>, + ) -> impl IntoIterator<Item = Self::Item> + 'a { + let mut reprs = Vec::new(); + + let Some(list) = args.list() else { + return reprs; + }; + + if list.is_empty() { + // this is so validation can emit a lint + reprs.push((ReprAttr::ReprEmpty, cx.attr_span)); + } + + for param in list.mixed() { + if let Some(_) = param.lit() { + cx.emit_err(session_diagnostics::ReprIdent { span: cx.attr_span }); + continue; } + + reprs.extend( + param.meta_item().and_then(|mi| parse_repr(cx, &mi)).map(|r| (r, param.span())), + ); } + + reprs } - acc +} + +macro_rules! int_pat { + () => { + sym::i8 + | sym::u8 + | sym::i16 + | sym::u16 + | sym::i32 + | sym::u32 + | sym::i64 + | sym::u64 + | sym::i128 + | sym::u128 + | sym::isize + | sym::usize + }; } fn int_type_of_word(s: Symbol) -> Option<IntType> { - use rustc_attr_data_structures::IntType::*; + use IntType::*; match s { - sym::i8 => Some(SignedInt(ast::IntTy::I8)), - sym::u8 => Some(UnsignedInt(ast::UintTy::U8)), - sym::i16 => Some(SignedInt(ast::IntTy::I16)), - sym::u16 => Some(UnsignedInt(ast::UintTy::U16)), - sym::i32 => Some(SignedInt(ast::IntTy::I32)), - sym::u32 => Some(UnsignedInt(ast::UintTy::U32)), - sym::i64 => Some(SignedInt(ast::IntTy::I64)), - sym::u64 => Some(UnsignedInt(ast::UintTy::U64)), - sym::i128 => Some(SignedInt(ast::IntTy::I128)), - sym::u128 => Some(UnsignedInt(ast::UintTy::U128)), - sym::isize => Some(SignedInt(ast::IntTy::Isize)), - sym::usize => Some(UnsignedInt(ast::UintTy::Usize)), + sym::i8 => Some(SignedInt(IntTy::I8)), + sym::u8 => Some(UnsignedInt(UintTy::U8)), + sym::i16 => Some(SignedInt(IntTy::I16)), + sym::u16 => Some(UnsignedInt(UintTy::U16)), + sym::i32 => Some(SignedInt(IntTy::I32)), + sym::u32 => Some(UnsignedInt(UintTy::U32)), + sym::i64 => Some(SignedInt(IntTy::I64)), + sym::u64 => Some(UnsignedInt(UintTy::U64)), + sym::i128 => Some(SignedInt(IntTy::I128)), + sym::u128 => Some(UnsignedInt(UintTy::U128)), + sym::isize => Some(SignedInt(IntTy::Isize)), + sym::usize => Some(UnsignedInt(UintTy::Usize)), _ => None, } } -pub fn parse_alignment(node: &ast::LitKind) -> Result<Align, &'static str> { - if let ast::LitKind::Int(literal, ast::LitIntType::Unsuffixed) = node { +fn parse_repr(cx: &AcceptContext<'_>, param: &MetaItemParser<'_>) -> Option<ReprAttr> { + use ReprAttr::*; + + // FIXME(jdonszelmann): invert the parsing here to match on the word first and then the + // structure. + let (ident, args) = param.word_or_empty(); + + match (ident.name, args) { + (sym::align, ArgParser::NoArgs) => { + cx.emit_err(session_diagnostics::InvalidReprAlignNeedArg { span: ident.span }); + None + } + (sym::align, ArgParser::List(l)) => parse_repr_align(cx, l, param.span(), AlignKind::Align), + + (sym::packed, ArgParser::NoArgs) => Some(ReprPacked(Align::ONE)), + (sym::packed, ArgParser::List(l)) => { + parse_repr_align(cx, l, param.span(), AlignKind::Packed) + } + + (sym::align | sym::packed, ArgParser::NameValue(l)) => { + cx.emit_err(session_diagnostics::IncorrectReprFormatGeneric { + span: param.span(), + // FIXME(jdonszelmann) can just be a string in the diag type + repr_arg: &ident.to_string(), + cause: IncorrectReprFormatGenericCause::from_lit_kind( + param.span(), + &l.value_as_lit().kind, + ident.name.as_str(), + ), + }); + None + } + + (sym::Rust, ArgParser::NoArgs) => Some(ReprRust), + (sym::C, ArgParser::NoArgs) => Some(ReprC), + (sym::simd, ArgParser::NoArgs) => Some(ReprSimd), + (sym::transparent, ArgParser::NoArgs) => Some(ReprTransparent), + (i @ int_pat!(), ArgParser::NoArgs) => { + // int_pat!() should make sure it always parses + Some(ReprInt(int_type_of_word(i).unwrap())) + } + + ( + sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!(), + ArgParser::NameValue(_), + ) => { + cx.emit_err(session_diagnostics::InvalidReprHintNoValue { + span: param.span(), + name: ident.to_string(), + }); + None + } + (sym::Rust | sym::C | sym::simd | sym::transparent | int_pat!(), ArgParser::List(_)) => { + cx.emit_err(session_diagnostics::InvalidReprHintNoParen { + span: param.span(), + name: ident.to_string(), + }); + None + } + + _ => { + cx.emit_err(session_diagnostics::UnrecognizedReprHint { span: param.span() }); + None + } + } +} + +enum AlignKind { + Packed, + Align, +} + +fn parse_repr_align( + cx: &AcceptContext<'_>, + list: &MetaItemListParser<'_>, + param_span: Span, + align_kind: AlignKind, +) -> Option<ReprAttr> { + use AlignKind::*; + + let Some(align) = list.single() else { + match align_kind { + Packed => { + cx.emit_err(session_diagnostics::IncorrectReprFormatPackedOneOrZeroArg { + span: param_span, + }); + } + Align => { + cx.dcx().emit_err(session_diagnostics::IncorrectReprFormatAlignOneArg { + span: param_span, + }); + } + } + + return None; + }; + + let Some(lit) = align.lit() else { + match align_kind { + Packed => { + cx.emit_err(session_diagnostics::IncorrectReprFormatPackedExpectInteger { + span: align.span(), + }); + } + Align => { + cx.emit_err(session_diagnostics::IncorrectReprFormatExpectInteger { + span: align.span(), + }); + } + } + + return None; + }; + + match parse_alignment(&lit.kind) { + Ok(literal) => Some(match align_kind { + AlignKind::Packed => ReprAttr::ReprPacked(literal), + AlignKind::Align => ReprAttr::ReprAlign(literal), + }), + Err(message) => { + cx.emit_err(session_diagnostics::InvalidReprGeneric { + span: lit.span, + repr_arg: match align_kind { + Packed => "packed".to_string(), + Align => "align".to_string(), + }, + error_part: message, + }); + None + } + } +} + +fn parse_alignment(node: &LitKind) -> Result<Align, &'static str> { + if let LitKind::Int(literal, LitIntType::Unsuffixed) = node { // `Align::from_bytes` accepts 0 as an input, check is_power_of_two() first if literal.get().is_power_of_two() { // Only possible error is larger than 2^29 diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index 454b8b5de82..6d76456e83c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -1,266 +1,258 @@ -//! Parsing and validation of builtin attributes - use std::num::NonZero; -use rustc_ast::MetaItem; -use rustc_ast::attr::AttributeExt; -use rustc_ast_pretty::pprust; use rustc_attr_data_structures::{ - ConstStability, DefaultBodyStability, Stability, StabilityLevel, StableSince, UnstableReason, - VERSION_PLACEHOLDER, + AttributeKind, DefaultBodyStability, PartialConstStability, Stability, StabilityLevel, + StableSince, UnstableReason, VERSION_PLACEHOLDER, }; use rustc_errors::ErrorGuaranteed; -use rustc_session::Session; use rustc_span::{Span, Symbol, kw, sym}; -use crate::attributes::util::UnsupportedLiteralReason; -use crate::{parse_version, session_diagnostics}; - -/// Collects stability info from `stable`/`unstable`/`rustc_allowed_through_unstable_modules` -/// attributes in `attrs`. Returns `None` if no stability attributes are found. -pub fn find_stability( - sess: &Session, - attrs: &[impl AttributeExt], - item_sp: Span, -) -> Option<(Stability, Span)> { - let mut stab: Option<(Stability, Span)> = None; - let mut allowed_through_unstable_modules = None; - - for attr in attrs { - match attr.name_or_empty() { - sym::rustc_allowed_through_unstable_modules => { - // The value is mandatory, but avoid ICEs in case such code reaches this function. - allowed_through_unstable_modules = Some(attr.value_str().unwrap_or_else(|| { - sess.dcx().span_delayed_bug( - item_sp, - "`#[rustc_allowed_through_unstable_modules]` without deprecation message", - ); - kw::Empty - })) - } - sym::unstable => { - if stab.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { - span: attr.span(), - }); - break; - } +use super::util::parse_version; +use super::{AcceptMapping, AttributeParser, SingleAttributeParser}; +use crate::context::{AcceptContext, FinalizeContext}; +use crate::parser::{ArgParser, MetaItemParser}; +use crate::session_diagnostics::{self, UnsupportedLiteralReason}; + +macro_rules! reject_outside_std { + ($cx: ident) => { + // Emit errors for non-staged-api crates. + if !$cx.features().staged_api() { + $cx.emit_err(session_diagnostics::StabilityOutsideStd { span: $cx.attr_span }); + return; + } + }; +} - if let Some((feature, level)) = parse_unstability(sess, attr) { - stab = Some((Stability { level, feature }, attr.span())); - } - } - sym::stable => { - if stab.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { - span: attr.span(), - }); - break; - } - if let Some((feature, level)) = parse_stability(sess, attr) { - stab = Some((Stability { level, feature }, attr.span())); - } - } - _ => {} +#[derive(Default)] +pub(crate) struct StabilityParser { + allowed_through_unstable_modules: Option<Symbol>, + stability: Option<(Stability, Span)>, +} + +impl StabilityParser { + /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate. + fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool { + if let Some((_, _)) = self.stability { + cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span }); + true + } else { + false } } +} - if let Some(allowed_through_unstable_modules) = allowed_through_unstable_modules { - match &mut stab { - Some(( +impl AttributeParser for StabilityParser { + const ATTRIBUTES: AcceptMapping<Self> = &[ + (&[sym::stable], |this, cx, args| { + reject_outside_std!(cx); + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_stability(cx, args) + { + this.stability = Some((Stability { level, feature }, cx.attr_span)); + } + }), + (&[sym::unstable], |this, cx, args| { + reject_outside_std!(cx); + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_unstability(cx, args) + { + this.stability = Some((Stability { level, feature }, cx.attr_span)); + } + }), + (&[sym::rustc_allowed_through_unstable_modules], |this, cx, args| { + reject_outside_std!(cx); + this.allowed_through_unstable_modules = + Some(match args.name_value().and_then(|i| i.value_as_str()) { + Some(msg) => msg, + None => kw::Empty, + }); + }), + ]; + + fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + if let Some(atum) = self.allowed_through_unstable_modules { + if let Some(( Stability { - level: StabilityLevel::Stable { allowed_through_unstable_modules: in_stab, .. }, + level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. }, .. }, _, - )) => *in_stab = Some(allowed_through_unstable_modules), - _ => { - sess.dcx() - .emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp }); + )) = self.stability + { + *allowed_through_unstable_modules = Some(atum); + } else { + cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing { + span: cx.target_span, + }); } } - } - stab + let (stability, span) = self.stability?; + + Some(AttributeKind::Stability { stability, span }) + } } -/// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable` -/// attributes in `attrs`. Returns `None` if no stability attributes are found. -pub fn find_const_stability( - sess: &Session, - attrs: &[impl AttributeExt], - item_sp: Span, -) -> Option<(ConstStability, Span)> { - let mut const_stab: Option<(ConstStability, Span)> = None; - let mut promotable = false; - let mut const_stable_indirect = false; - - for attr in attrs { - match attr.name_or_empty() { - sym::rustc_promotable => promotable = true, - sym::rustc_const_stable_indirect => const_stable_indirect = true, - sym::rustc_const_unstable => { - if const_stab.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { - span: attr.span(), - }); - break; - } +// FIXME(jdonszelmann) change to Single +#[derive(Default)] +pub(crate) struct BodyStabilityParser { + stability: Option<(DefaultBodyStability, Span)>, +} - if let Some((feature, level)) = parse_unstability(sess, attr) { - const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, - attr.span(), - )); - } +impl AttributeParser for BodyStabilityParser { + const ATTRIBUTES: AcceptMapping<Self> = + &[(&[sym::rustc_default_body_unstable], |this, cx, args| { + reject_outside_std!(cx); + if this.stability.is_some() { + cx.dcx() + .emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span }); + } else if let Some((feature, level)) = parse_unstability(cx, args) { + this.stability = Some((DefaultBodyStability { level, feature }, cx.attr_span)); } - sym::rustc_const_stable => { - if const_stab.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { - span: attr.span(), - }); - break; - } - if let Some((feature, level)) = parse_stability(sess, attr) { - const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, - attr.span(), - )); - } - } - _ => {} - } - } + })]; - // Merge promotable and const_stable_indirect into stability info - if promotable { - match &mut const_stab { - Some((stab, _)) => stab.promotable = promotable, - _ => { - _ = sess - .dcx() - .emit_err(session_diagnostics::RustcPromotablePairing { span: item_sp }) - } - } + fn finalize(self, _cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + let (stability, span) = self.stability?; + + Some(AttributeKind::BodyStability { stability, span }) } - if const_stable_indirect { - match &mut const_stab { - Some((stab, _)) => { - if stab.is_const_unstable() { - stab.const_stable_indirect = true; - } else { - _ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing { - span: item_sp, - }) - } - } - _ => { - // This function has no const stability attribute, but has `const_stable_indirect`. - // We ignore that; unmarked functions are subject to recursive const stability - // checks by default so we do carry out the user's intent. - } - } +} + +pub(crate) struct ConstStabilityIndirectParser; +// FIXME(jdonszelmann): single word attribute group when we have these +impl SingleAttributeParser for ConstStabilityIndirectParser { + const PATH: &'static [rustc_span::Symbol] = &[sym::rustc_const_stable_indirect]; + + // ignore + fn on_duplicate(_cx: &AcceptContext<'_>, _first_span: Span) {} + + fn convert(_cx: &AcceptContext<'_>, _args: &ArgParser<'_>) -> Option<AttributeKind> { + Some(AttributeKind::ConstStabilityIndirect) } +} - const_stab +#[derive(Default)] +pub(crate) struct ConstStabilityParser { + promotable: bool, + stability: Option<(PartialConstStability, Span)>, } -/// Calculates the const stability for a const function in a `-Zforce-unstable-if-unmarked` crate -/// without the `staged_api` feature. -pub fn unmarked_crate_const_stab( - _sess: &Session, - attrs: &[impl AttributeExt], - regular_stab: Stability, -) -> ConstStability { - assert!(regular_stab.level.is_unstable()); - // The only attribute that matters here is `rustc_const_stable_indirect`. - // We enforce recursive const stability rules for those functions. - let const_stable_indirect = - attrs.iter().any(|a| a.name_or_empty() == sym::rustc_const_stable_indirect); - ConstStability { - feature: regular_stab.feature, - const_stable_indirect, - promotable: false, - level: regular_stab.level, +impl ConstStabilityParser { + /// Checks, and emits an error when a stability (or unstability) was already set, which would be a duplicate. + fn check_duplicate(&self, cx: &AcceptContext<'_>) -> bool { + if let Some((_, _)) = self.stability { + cx.emit_err(session_diagnostics::MultipleStabilityLevels { span: cx.attr_span }); + true + } else { + false + } } } -/// Collects stability info from `rustc_default_body_unstable` attributes in `attrs`. -/// Returns `None` if no stability attributes are found. -pub fn find_body_stability( - sess: &Session, - attrs: &[impl AttributeExt], -) -> Option<(DefaultBodyStability, Span)> { - let mut body_stab: Option<(DefaultBodyStability, Span)> = None; - - for attr in attrs { - if attr.has_name(sym::rustc_default_body_unstable) { - if body_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span() }); - break; +impl AttributeParser for ConstStabilityParser { + const ATTRIBUTES: AcceptMapping<Self> = &[ + (&[sym::rustc_const_stable], |this, cx, args| { + reject_outside_std!(cx); + + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_stability(cx, args) + { + this.stability = Some(( + PartialConstStability { level, feature, promotable: false }, + cx.attr_span, + )); } - - if let Some((feature, level)) = parse_unstability(sess, attr) { - body_stab = Some((DefaultBodyStability { level, feature }, attr.span())); + }), + (&[sym::rustc_const_unstable], |this, cx, args| { + reject_outside_std!(cx); + if !this.check_duplicate(cx) + && let Some((feature, level)) = parse_unstability(cx, args) + { + this.stability = Some(( + PartialConstStability { level, feature, promotable: false }, + cx.attr_span, + )); + } + }), + (&[sym::rustc_promotable], |this, cx, _| { + reject_outside_std!(cx); + this.promotable = true; + }), + ]; + + fn finalize(mut self, cx: &FinalizeContext<'_>) -> Option<AttributeKind> { + if self.promotable { + if let Some((ref mut stab, _)) = self.stability { + stab.promotable = true; + } else { + cx.dcx() + .emit_err(session_diagnostics::RustcPromotablePairing { span: cx.target_span }); } } - } - body_stab + let (stability, span) = self.stability?; + + Some(AttributeKind::ConstStability { stability, span }) + } } -fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option<Symbol>) -> Option<()> { +/// Tries to insert the value of a `key = value` meta item into an option. +/// +/// Emits an error when either the option was already Some, or the arguments weren't of form +/// `name = value` +fn insert_value_into_option_or_error( + cx: &AcceptContext<'_>, + param: &MetaItemParser<'_>, + item: &mut Option<Symbol>, +) -> Option<()> { if item.is_some() { - sess.dcx().emit_err(session_diagnostics::MultipleItem { - span: meta.span, - item: pprust::path_to_string(&meta.path), + cx.emit_err(session_diagnostics::MultipleItem { + span: param.span(), + item: param.path_without_args().to_string(), }); None - } else if let Some(v) = meta.value_str() { - *item = Some(v); + } else if let Some(v) = param.args().name_value() + && let Some(s) = v.value_as_str() + { + *item = Some(s); Some(()) } else { - sess.dcx().emit_err(session_diagnostics::IncorrectMetaItem { span: meta.span }); + cx.emit_err(session_diagnostics::IncorrectMetaItem { + span: param.span(), + suggestion: None, + }); None } } /// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and /// its stability information. -fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, StabilityLevel)> { - let metas = attr.meta_item_list()?; - +pub(crate) fn parse_stability( + cx: &AcceptContext<'_>, + args: &ArgParser<'_>, +) -> Option<(Symbol, StabilityLevel)> { let mut feature = None; let mut since = None; - for meta in metas { - let Some(mi) = meta.meta_item() else { - sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral { - span: meta.span(), + + for param in args.list()?.mixed() { + let param_span = param.span(); + let Some(param) = param.meta_item() else { + cx.emit_err(session_diagnostics::UnsupportedLiteral { + span: param_span, reason: UnsupportedLiteralReason::Generic, is_bytestr: false, - start_point_span: sess.source_map().start_point(meta.span()), + start_point_span: cx.sess().source_map().start_point(param_span), }); return None; }; - match mi.name_or_empty() { - sym::feature => insert_or_error(sess, mi, &mut feature)?, - sym::since => insert_or_error(sess, mi, &mut since)?, + match param.word_or_empty_without_args().name { + sym::feature => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, + sym::since => insert_value_into_option_or_error(cx, ¶m, &mut since)?, _ => { - sess.dcx().emit_err(session_diagnostics::UnknownMetaItem { - span: meta.span(), - item: pprust::path_to_string(&mi.path), + cx.emit_err(session_diagnostics::UnknownMetaItem { + span: param_span, + item: param.path_without_args().to_string(), expected: &["feature", "since"], }); return None; @@ -271,9 +263,9 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, let feature = match feature { Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature), Some(_bad_feature) => { - Err(sess.dcx().emit_err(session_diagnostics::NonIdentFeature { span: attr.span() })) + Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span })) } - None => Err(sess.dcx().emit_err(session_diagnostics::MissingFeature { span: attr.span() })), + None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })), }; let since = if let Some(since) = since { @@ -282,11 +274,11 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, } else if let Some(version) = parse_version(since) { StableSince::Version(version) } else { - sess.dcx().emit_err(session_diagnostics::InvalidSince { span: attr.span() }); + cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span }); StableSince::Err } } else { - sess.dcx().emit_err(session_diagnostics::MissingSince { span: attr.span() }); + cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span }); StableSince::Err }; @@ -299,46 +291,48 @@ fn parse_stability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, } } -/// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` +// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` /// attribute, and return the feature name and its stability information. -fn parse_unstability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol, StabilityLevel)> { - let metas = attr.meta_item_list()?; - +pub(crate) fn parse_unstability( + cx: &AcceptContext<'_>, + args: &ArgParser<'_>, +) -> Option<(Symbol, StabilityLevel)> { let mut feature = None; let mut reason = None; let mut issue = None; let mut issue_num = None; let mut is_soft = false; let mut implied_by = None; - for meta in metas { - let Some(mi) = meta.meta_item() else { - sess.dcx().emit_err(session_diagnostics::UnsupportedLiteral { - span: meta.span(), + for param in args.list()?.mixed() { + let Some(param) = param.meta_item() else { + cx.emit_err(session_diagnostics::UnsupportedLiteral { + span: param.span(), reason: UnsupportedLiteralReason::Generic, is_bytestr: false, - start_point_span: sess.source_map().start_point(meta.span()), + start_point_span: cx.sess().source_map().start_point(param.span()), }); return None; }; - match mi.name_or_empty() { - sym::feature => insert_or_error(sess, mi, &mut feature)?, - sym::reason => insert_or_error(sess, mi, &mut reason)?, + let (word, args) = param.word_or_empty(); + match word.name { + sym::feature => insert_value_into_option_or_error(cx, ¶m, &mut feature)?, + sym::reason => insert_value_into_option_or_error(cx, ¶m, &mut reason)?, sym::issue => { - insert_or_error(sess, mi, &mut issue)?; + insert_value_into_option_or_error(cx, ¶m, &mut issue)?; - // These unwraps are safe because `insert_or_error` ensures the meta item + // These unwraps are safe because `insert_value_into_option_or_error` ensures the meta item // is a name/value pair string literal. issue_num = match issue.unwrap().as_str() { "none" => None, - issue => match issue.parse::<NonZero<u32>>() { + issue_str => match issue_str.parse::<NonZero<u32>>() { Ok(num) => Some(num), Err(err) => { - sess.dcx().emit_err( + cx.emit_err( session_diagnostics::InvalidIssueString { - span: mi.span, + span: param.span(), cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind( - mi.name_value_literal_span().unwrap(), + args.name_value().unwrap().value_span, err.kind(), ), }, @@ -349,16 +343,16 @@ fn parse_unstability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol }; } sym::soft => { - if !mi.is_word() { - sess.dcx().emit_err(session_diagnostics::SoftNoArgs { span: mi.span }); + if !args.no_args() { + cx.emit_err(session_diagnostics::SoftNoArgs { span: param.span() }); } is_soft = true; } - sym::implied_by => insert_or_error(sess, mi, &mut implied_by)?, + sym::implied_by => insert_value_into_option_or_error(cx, ¶m, &mut implied_by)?, _ => { - sess.dcx().emit_err(session_diagnostics::UnknownMetaItem { - span: meta.span(), - item: pprust::path_to_string(&mi.path), + cx.emit_err(session_diagnostics::UnknownMetaItem { + span: param.span(), + item: param.path_without_args().to_string(), expected: &["feature", "reason", "issue", "soft", "implied_by"], }); return None; @@ -369,14 +363,13 @@ fn parse_unstability(sess: &Session, attr: &impl AttributeExt) -> Option<(Symbol let feature = match feature { Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature), Some(_bad_feature) => { - Err(sess.dcx().emit_err(session_diagnostics::NonIdentFeature { span: attr.span() })) + Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span })) } - None => Err(sess.dcx().emit_err(session_diagnostics::MissingFeature { span: attr.span() })), + None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })), }; - let issue = issue.ok_or_else(|| { - sess.dcx().emit_err(session_diagnostics::MissingIssue { span: attr.span() }) - }); + let issue = + issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span })); match (feature, issue) { (Ok(feature), Ok(_)) => { diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs index f4065a77048..ad83a1f7af8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs +++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs @@ -1,36 +1,33 @@ -use rustc_ast::attr::AttributeExt; -use rustc_attr_data_structures::TransparencyError; +use rustc_attr_data_structures::AttributeKind; use rustc_span::hygiene::Transparency; use rustc_span::sym; -pub fn find_transparency( - attrs: &[impl AttributeExt], - macro_rules: bool, -) -> (Transparency, Option<TransparencyError>) { - let mut transparency = None; - let mut error = None; - for attr in attrs { - if attr.has_name(sym::rustc_macro_transparency) { - if let Some((_, old_span)) = transparency { - error = Some(TransparencyError::MultipleTransparencyAttrs(old_span, attr.span())); - break; - } else if let Some(value) = attr.value_str() { - transparency = Some(( - match value { - sym::transparent => Transparency::Transparent, - sym::semitransparent => Transparency::SemiTransparent, - sym::opaque => Transparency::Opaque, - _ => { - error = - Some(TransparencyError::UnknownTransparency(value, attr.span())); - continue; - } - }, - attr.span(), - )); +use super::{AcceptContext, SingleAttributeParser}; +use crate::parser::ArgParser; + +pub(crate) struct TransparencyParser; + +// FIXME(jdonszelmann): make these proper diagnostics +#[allow(rustc::untranslatable_diagnostic)] +#[allow(rustc::diagnostic_outside_of_impl)] +impl SingleAttributeParser for TransparencyParser { + const PATH: &'static [rustc_span::Symbol] = &[sym::rustc_macro_transparency]; + + fn on_duplicate(cx: &crate::context::AcceptContext<'_>, first_span: rustc_span::Span) { + cx.dcx().span_err(vec![first_span, cx.attr_span], "multiple macro transparency attributes"); + } + + fn convert(cx: &AcceptContext<'_>, args: &ArgParser<'_>) -> Option<AttributeKind> { + match args.name_value().and_then(|nv| nv.value_as_str()) { + Some(sym::transparent) => Some(Transparency::Transparent), + Some(sym::semitransparent) => Some(Transparency::SemiTransparent), + Some(sym::opaque) => Some(Transparency::Opaque), + Some(other) => { + cx.dcx().span_err(cx.attr_span, format!("unknown macro transparency: `{other}`")); + None } + None => None, } + .map(AttributeKind::MacroTransparency) } - let fallback = if macro_rules { Transparency::SemiTransparent } else { Transparency::Opaque }; - (transparency.map_or(fallback, |t| t.0), error) } diff --git a/compiler/rustc_attr_parsing/src/attributes/util.rs b/compiler/rustc_attr_parsing/src/attributes/util.rs index e36f7dfff5a..05a9029c59a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/util.rs +++ b/compiler/rustc_attr_parsing/src/attributes/util.rs @@ -3,22 +3,6 @@ use rustc_attr_data_structures::RustcVersion; use rustc_feature::is_builtin_attr_name; use rustc_span::{Symbol, sym}; -pub(crate) enum UnsupportedLiteralReason { - Generic, - CfgString, - CfgBoolean, - DeprecatedString, - DeprecatedKvPair, -} - -pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool { - attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name)) -} - -pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> { - first_attr_value_str_by_name(attrs, sym::crate_name) -} - /// Parse a rustc version number written inside string literal in an attribute, /// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are /// not accepted in this position, unlike when parsing CFG_RELEASE. @@ -34,3 +18,11 @@ pub fn parse_version(s: Symbol) -> Option<RustcVersion> { let patch = digits.next().unwrap_or("0").parse().ok()?; Some(RustcVersion { major, minor, patch }) } + +pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool { + attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name)) +} + +pub fn find_crate_name(attrs: &[impl AttributeExt]) -> Option<Symbol> { + first_attr_value_str_by_name(attrs, sym::crate_name) +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs new file mode 100644 index 00000000000..99eee0d3c4a --- /dev/null +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -0,0 +1,348 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::ops::Deref; +use std::sync::LazyLock; + +use rustc_ast::{self as ast, DelimArgs}; +use rustc_attr_data_structures::AttributeKind; +use rustc_errors::{DiagCtxtHandle, Diagnostic}; +use rustc_feature::Features; +use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId}; +use rustc_session::Session; +use rustc_span::symbol::kw; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym}; + +use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser}; +use crate::attributes::confusables::ConfusablesParser; +use crate::attributes::deprecation::DeprecationParser; +use crate::attributes::repr::ReprParser; +use crate::attributes::stability::{ + BodyStabilityParser, ConstStabilityIndirectParser, ConstStabilityParser, StabilityParser, +}; +use crate::attributes::transparency::TransparencyParser; +use crate::attributes::{AttributeParser as _, Combine, Single}; +use crate::parser::{ArgParser, MetaItemParser}; + +macro_rules! attribute_groups { + ( + pub(crate) static $name: ident = [$($names: ty),* $(,)?]; + ) => { + pub(crate) static $name: LazyLock<( + BTreeMap<&'static [Symbol], Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>, + Vec<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>> + )> = LazyLock::new(|| { + let mut accepts = BTreeMap::<_, Vec<Box<dyn Fn(&AcceptContext<'_>, &ArgParser<'_>) + Send + Sync>>>::new(); + let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&FinalizeContext<'_>) -> Option<AttributeKind>>>::new(); + $( + { + thread_local! { + static STATE_OBJECT: RefCell<$names> = RefCell::new(<$names>::default()); + }; + + for (k, v) in <$names>::ATTRIBUTES { + accepts.entry(*k).or_default().push(Box::new(|cx, args| { + STATE_OBJECT.with_borrow_mut(|s| { + v(s, cx, args) + }) + })); + } + + finalizes.push(Box::new(|cx| { + let state = STATE_OBJECT.take(); + state.finalize(cx) + })); + } + )* + + (accepts, finalizes) + }); + }; +} + +attribute_groups!( + pub(crate) static ATTRIBUTE_MAPPING = [ + // tidy-alphabetical-start + BodyStabilityParser, + ConfusablesParser, + ConstStabilityParser, + StabilityParser, + // tidy-alphabetical-end + + // tidy-alphabetical-start + Combine<AllowConstFnUnstableParser>, + Combine<AllowInternalUnstableParser>, + Combine<ReprParser>, + // tidy-alphabetical-end + + // tidy-alphabetical-start + Single<ConstStabilityIndirectParser>, + Single<DeprecationParser>, + Single<TransparencyParser>, + // tidy-alphabetical-end + ]; +); + +/// Context given to every attribute parser when accepting +/// +/// Gives [`AttributeParser`]s enough information to create errors, for example. +pub(crate) struct AcceptContext<'a> { + pub(crate) group_cx: &'a FinalizeContext<'a>, + /// The span of the attribute currently being parsed + pub(crate) attr_span: Span, +} + +impl<'a> AcceptContext<'a> { + pub(crate) fn emit_err(&self, diag: impl Diagnostic<'a>) -> ErrorGuaranteed { + if self.limit_diagnostics { + self.dcx().create_err(diag).delay_as_bug() + } else { + self.dcx().emit_err(diag) + } + } +} + +impl<'a> Deref for AcceptContext<'a> { + type Target = FinalizeContext<'a>; + + fn deref(&self) -> &Self::Target { + &self.group_cx + } +} + +/// Context given to every attribute parser during finalization. +/// +/// Gives [`AttributeParser`](crate::attributes::AttributeParser)s enough information to create errors, for example. +pub(crate) struct FinalizeContext<'a> { + /// The parse context, gives access to the session and the + /// diagnostics context. + pub(crate) cx: &'a AttributeParser<'a>, + /// The span of the syntactical component this attribute was applied to + pub(crate) target_span: Span, +} + +impl<'a> Deref for FinalizeContext<'a> { + type Target = AttributeParser<'a>; + + fn deref(&self) -> &Self::Target { + &self.cx + } +} + +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum OmitDoc { + Lower, + Skip, +} + +/// Context created once, for example as part of the ast lowering +/// context, through which all attributes can be lowered. +pub struct AttributeParser<'sess> { + #[expect(dead_code)] // FIXME(jdonszelmann): needed later to verify we parsed all attributes + tools: Vec<Symbol>, + sess: &'sess Session, + features: Option<&'sess Features>, + + /// *only* parse attributes with this symbol. + /// + /// Used in cases where we want the lowering infrastructure for + /// parse just a single attribute. + parse_only: Option<Symbol>, + + /// Can be used to instruct parsers to reduce the number of diagnostics it emits. + /// Useful when using `parse_limited` and you know the attr will be reparsed later. + pub(crate) limit_diagnostics: bool, +} + +impl<'sess> AttributeParser<'sess> { + /// This method allows you to parse attributes *before* you have access to features or tools. + /// One example where this is necessary, is to parse `feature` attributes themselves for + /// example. + /// + /// Try to use this as little as possible. Attributes *should* be lowered during `rustc_ast_lowering`. + /// Some attributes require access to features to parse, which would crash if you tried to do so + /// through [`parse_limited`](Self::parse_limited). + /// + /// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with + /// that symbol are picked out of the list of instructions and parsed. Those are returned. + pub fn parse_limited( + sess: &'sess Session, + attrs: &[ast::Attribute], + sym: Symbol, + target_span: Span, + limit_diagnostics: bool, + ) -> Option<Attribute> { + let mut parsed = Self { + sess, + features: None, + tools: Vec::new(), + parse_only: Some(sym), + limit_diagnostics, + } + .parse_attribute_list(attrs, target_span, OmitDoc::Skip, std::convert::identity); + + assert!(parsed.len() <= 1); + + parsed.pop() + } + + pub fn new(sess: &'sess Session, features: &'sess Features, tools: Vec<Symbol>) -> Self { + Self { sess, features: Some(features), tools, parse_only: None, limit_diagnostics: false } + } + + pub(crate) fn sess(&self) -> &'sess Session { + self.sess + } + + pub(crate) fn features(&self) -> &'sess Features { + self.features.expect("features not available at this point in the compiler") + } + + pub(crate) fn dcx(&self) -> DiagCtxtHandle<'sess> { + self.sess.dcx() + } + + /// Parse a list of attributes. + /// + /// `target_span` is the span of the thing this list of attributes is applied to, + /// and when `omit_doc` is set, doc attributes are filtered out. + pub fn parse_attribute_list<'a>( + &'a self, + attrs: &'a [ast::Attribute], + target_span: Span, + omit_doc: OmitDoc, + + lower_span: impl Copy + Fn(Span) -> Span, + ) -> Vec<Attribute> { + let mut attributes = Vec::new(); + + let group_cx = FinalizeContext { cx: self, target_span }; + + for attr in attrs { + // if we're only looking for a single attribute, + // skip all the ones we don't care about + if let Some(expected) = self.parse_only { + if attr.name_or_empty() != expected { + continue; + } + } + + // sometimes, for example for `#![doc = include_str!("readme.md")]`, + // doc still contains a non-literal. You might say, when we're lowering attributes + // that's expanded right? But no, sometimes, when parsing attributes on macros, + // we already use the lowering logic and these are still there. So, when `omit_doc` + // is set we *also* want to ignore these + if omit_doc == OmitDoc::Skip && attr.name_or_empty() == sym::doc { + continue; + } + + match &attr.kind { + ast::AttrKind::DocComment(comment_kind, symbol) => { + if omit_doc == OmitDoc::Skip { + continue; + } + + attributes.push(Attribute::Parsed(AttributeKind::DocComment { + style: attr.style, + kind: *comment_kind, + span: lower_span(attr.span), + comment: *symbol, + })) + } + // // FIXME: make doc attributes go through a proper attribute parser + // ast::AttrKind::Normal(n) if n.name_or_empty() == sym::doc => { + // let p = GenericMetaItemParser::from_attr(&n, self.dcx()); + // + // attributes.push(Attribute::Parsed(AttributeKind::DocComment { + // style: attr.style, + // kind: CommentKind::Line, + // span: attr.span, + // comment: p.args().name_value(), + // })) + // } + ast::AttrKind::Normal(n) => { + let parser = MetaItemParser::from_attr(n, self.dcx()); + let (path, args) = parser.deconstruct(); + let parts = path.segments().map(|i| i.name).collect::<Vec<_>>(); + + if let Some(accepts) = ATTRIBUTE_MAPPING.0.get(parts.as_slice()) { + for f in accepts { + let cx = AcceptContext { + group_cx: &group_cx, + attr_span: lower_span(attr.span), + }; + + f(&cx, &args) + } + } else { + // if we're here, we must be compiling a tool attribute... Or someone forgot to + // parse their fancy new attribute. Let's warn them in any case. If you are that + // person, and you really your attribute should remain unparsed, carefully read the + // documentation in this module and if you still think so you can add an exception + // to this assertion. + + // FIXME(jdonszelmann): convert other attributes, and check with this that + // we caught em all + // const FIXME_TEMPORARY_ATTR_ALLOWLIST: &[Symbol] = &[sym::cfg]; + // assert!( + // self.tools.contains(&parts[0]) || true, + // // || FIXME_TEMPORARY_ATTR_ALLOWLIST.contains(&parts[0]), + // "attribute {path} wasn't parsed and isn't a know tool attribute", + // ); + + attributes.push(Attribute::Unparsed(Box::new(AttrItem { + path: AttrPath::from_ast(&n.item.path), + args: self.lower_attr_args(&n.item.args, lower_span), + id: HashIgnoredAttrId { attr_id: attr.id }, + style: attr.style, + span: lower_span(attr.span), + }))); + } + } + } + } + + let mut parsed_attributes = Vec::new(); + for f in &ATTRIBUTE_MAPPING.1 { + if let Some(attr) = f(&group_cx) { + parsed_attributes.push(Attribute::Parsed(attr)); + } + } + + attributes.extend(parsed_attributes); + + attributes + } + + fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs { + match args { + ast::AttrArgs::Empty => AttrArgs::Empty, + ast::AttrArgs::Delimited(args) => AttrArgs::Delimited(DelimArgs { + dspan: args.dspan, + delim: args.delim, + tokens: args.tokens.flattened(), + }), + // This is an inert key-value attribute - it will never be visible to macros + // after it gets lowered to HIR. Therefore, we can extract literals to handle + // nonterminals in `#[doc]` (e.g. `#[doc = $e]`). + ast::AttrArgs::Eq { eq_span, expr } => { + // In valid code the value always ends up as a single literal. Otherwise, a dummy + // literal suffices because the error is handled elsewhere. + let lit = if let ast::ExprKind::Lit(token_lit) = expr.kind + && let Ok(lit) = + ast::MetaItemLit::from_token_lit(token_lit, lower_span(expr.span)) + { + lit + } else { + let guar = self.dcx().has_errors().unwrap(); + ast::MetaItemLit { + symbol: kw::Empty, + suffix: None, + kind: ast::LitKind::Err(guar), + span: DUMMY_SP, + } + }; + AttrArgs::Eq { eq_span: lower_span(*eq_span), expr: lit } + } + } + } +} diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index a1264a6875f..9841166b37d 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -1,8 +1,79 @@ -//! Functions and types dealing with attributes and meta items. +//! Centralized logic for parsing and attributes. //! -//! FIXME(Centril): For now being, much of the logic is still in `rustc_ast::attr`. -//! The goal is to move the definition of `MetaItem` and things that don't need to be in `syntax` -//! to this crate. +//! Part of a series of crates: +//! - rustc_attr_data_structures: contains types that the parsers parse into +//! - rustc_attr_parsing: this crate +//! - (in the future): rustc_attr_validation +//! +//! History: Check out [#131229](https://github.com/rust-lang/rust/issues/131229). +//! There used to be only one definition of attributes in the compiler: `ast::Attribute`. +//! These were then parsed or validated or both in places distributed all over the compiler. +//! This was a mess... +//! +//! Attributes are markers on items. +//! Many of them are actually attribute-like proc-macros, and are expanded to some other rust syntax. +//! This could either be a user provided proc macro, or something compiler provided. +//! `derive` is an example of one that the compiler provides. +//! These are built-in, but they have a valid expansion to Rust tokens and are thus called "active". +//! I personally like calling these *active* compiler-provided attributes, built-in *macros*, +//! because they still expand, and this helps to differentiate them from built-in *attributes*. +//! However, I'll be the first to admit that the naming here can be confusing. +//! +//! The alternative to active attributes, are inert attributes. +//! These can occur in user code (proc-macro helper attributes). +//! But what's important is, many built-in attributes are inert like this. +//! There is nothing they expand to during the macro expansion process, +//! sometimes because they literally cannot expand to something that is valid Rust. +//! They are really just markers to guide the compilation process. +//! An example is `#[inline(...)]` which changes how code for functions is generated. +//! +//! ```text +//! Active Inert +//! ┌──────────────────────┬──────────────────────┐ +//! │ (mostly in) │ these are parsed │ +//! │ rustc_builtin_macros │ here! │ +//! │ │ │ +//! │ │ │ +//! │ #[derive(...)] │ #[stable()] │ +//! Built-in │ #[cfg()] │ #[inline()] │ +//! │ #[cfg_attr()] │ #[repr()] │ +//! │ │ │ +//! │ │ │ +//! │ │ │ +//! ├──────────────────────┼──────────────────────┤ +//! │ │ │ +//! │ │ │ +//! │ │ `b` in │ +//! │ │ #[proc_macro_derive( │ +//! User created │ #[proc_macro_attr()] │ a, │ +//! │ │ attributes(b) │ +//! │ │ ] │ +//! │ │ │ +//! │ │ │ +//! │ │ │ +//! └──────────────────────┴──────────────────────┘ +//! ``` +//! +//! In this crate, syntactical attributes (sequences of tokens that look like +//! `#[something(something else)]`) are parsed into more semantic attributes, markers on items. +//! Multiple syntactic attributes might influence a single semantic attribute. For example, +//! `#[stable(...)]` and `#[unstable()]` cannot occur together, and both semantically define +//! a "stability" of an item. So, the stability attribute has an +//! [`AttributeParser`](attributes::AttributeParser) that recognizes both the `#[stable()]` +//! and `#[unstable()]` syntactic attributes, and at the end produce a single [`AttributeKind::Stability`]. +//! +//! As a rule of thumb, when a syntactical attribute can be applied more than once, they should be +//! combined into a single semantic attribute. For example: +//! +//! ``` +//! #[repr(C)] +//! #[repr(packed)] +//! struct Meow {} +//! ``` +//! +//! should result in a single `AttributeKind::Repr` containing a list of repr annotations, in this +//! case `C` and `packed`. This is equivalent to writing `#[repr(C, packed)]` in a single +//! syntactical annotation. // tidy-alphabetical-start #![allow(internal_features)] @@ -12,11 +83,59 @@ #![warn(unreachable_pub)] // tidy-alphabetical-end +#[macro_use] mod attributes; +mod context; +pub mod parser; mod session_diagnostics; -pub use attributes::*; +pub use attributes::cfg::*; +pub use attributes::util::{find_crate_name, is_builtin_attr, parse_version}; +pub use context::{AttributeParser, OmitDoc}; pub use rustc_attr_data_structures::*; -pub use util::{find_crate_name, is_builtin_attr, parse_version}; rustc_fluent_macro::fluent_messages! { "../messages.ftl" } + +/// Finds attributes in sequences of attributes by pattern matching. +/// +/// A little like `matches` but for attributes. +/// +/// ```rust,ignore (illustrative) +/// // finds the repr attribute +/// if let Some(r) = find_attr!(attrs, AttributeKind::Repr(r) => r) { +/// +/// } +/// +/// // checks if one has matched +/// if find_attr!(attrs, AttributeKind::Repr(_)) { +/// +/// } +/// ``` +/// +/// Often this requires you to first end up with a list of attributes. +/// A common way to get those is through `tcx.get_all_attrs(did)` +#[macro_export] +macro_rules! find_attr { + ($attributes_list: expr, $pattern: pat $(if $guard: expr)?) => {{ + $crate::find_attr!($attributes_list, $pattern $(if $guard)? => ()).is_some() + }}; + + ($attributes_list: expr, $pattern: pat $(if $guard: expr)? => $e: expr) => {{ + fn check_attribute_iterator<'a>(_: &'_ impl IntoIterator<Item = &'a rustc_hir::Attribute>) {} + check_attribute_iterator(&$attributes_list); + + let find_attribute = |iter| { + for i in $attributes_list { + match i { + rustc_hir::Attribute::Parsed($pattern) $(if $guard)? => { + return Some($e); + } + _ => {} + } + } + + None + }; + find_attribute($attributes_list) + }}; +} diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs new file mode 100644 index 00000000000..0ee0ea4ea59 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -0,0 +1,624 @@ +//! This is in essence an (improved) duplicate of `rustc_ast/attr/mod.rs`. +//! That module is intended to be deleted in its entirety. +//! +//! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs` + +use std::fmt::{Debug, Display}; +use std::iter::Peekable; + +use rustc_ast::token::{self, Delimiter, Token}; +use rustc_ast::tokenstream::{TokenStreamIter, TokenTree}; +use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path}; +use rustc_ast_pretty::pprust; +use rustc_errors::DiagCtxtHandle; +use rustc_hir::{self as hir, AttrPath}; +use rustc_span::symbol::{Ident, kw}; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol}; + +pub struct SegmentIterator<'a> { + offset: usize, + path: &'a PathParser<'a>, +} + +impl<'a> Iterator for SegmentIterator<'a> { + type Item = &'a Ident; + + fn next(&mut self) -> Option<Self::Item> { + if self.offset >= self.path.len() { + return None; + } + + let res = match self.path { + PathParser::Ast(ast_path) => &ast_path.segments[self.offset].ident, + PathParser::Attr(attr_path) => &attr_path.segments[self.offset], + }; + + self.offset += 1; + Some(res) + } +} + +#[derive(Clone, Debug)] +pub enum PathParser<'a> { + Ast(&'a Path), + Attr(AttrPath), +} + +impl<'a> PathParser<'a> { + pub fn get_attribute_path(&self) -> hir::AttrPath { + AttrPath { + segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(), + span: self.span(), + } + } + + pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> { + SegmentIterator { offset: 0, path: self } + } + + pub fn span(&self) -> Span { + match self { + PathParser::Ast(path) => path.span, + PathParser::Attr(attr_path) => attr_path.span, + } + } + + pub fn len(&self) -> usize { + match self { + PathParser::Ast(path) => path.segments.len(), + PathParser::Attr(attr_path) => attr_path.segments.len(), + } + } + + pub fn segments_is(&self, segments: &[Symbol]) -> bool { + self.len() == segments.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b) + } + + pub fn word(&self) -> Option<Ident> { + (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap()) + } + + pub fn word_or_empty(&self) -> Ident { + self.word().unwrap_or_else(Ident::empty) + } + + /// Asserts that this MetaItem is some specific word. + /// + /// See [`word`](Self::word) for examples of what a word is. + pub fn word_is(&self, sym: Symbol) -> bool { + self.word().map(|i| i.name == sym).unwrap_or(false) + } +} + +impl Display for PathParser<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PathParser::Ast(path) => write!(f, "{}", pprust::path_to_string(path)), + PathParser::Attr(attr_path) => write!(f, "{attr_path}"), + } + } +} + +#[derive(Clone, Debug)] +#[must_use] +pub enum ArgParser<'a> { + NoArgs, + List(MetaItemListParser<'a>), + NameValue(NameValueParser), +} + +impl<'a> ArgParser<'a> { + pub fn span(&self) -> Option<Span> { + match self { + Self::NoArgs => None, + Self::List(l) => Some(l.span), + Self::NameValue(n) => Some(n.value_span.with_lo(n.eq_span.lo())), + } + } + + pub fn from_attr_args(value: &'a AttrArgs, dcx: DiagCtxtHandle<'a>) -> Self { + match value { + AttrArgs::Empty => Self::NoArgs, + AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => { + Self::List(MetaItemListParser::new(args, dcx)) + } + AttrArgs::Delimited(args) => { + Self::List(MetaItemListParser { sub_parsers: vec![], span: args.dspan.entire() }) + } + AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser { + eq_span: *eq_span, + value: expr_to_lit(dcx, &expr), + value_span: expr.span, + }), + } + } + + /// Asserts that this MetaItem is a list + /// + /// Some examples: + /// + /// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list + /// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list + pub fn list(&self) -> Option<&MetaItemListParser<'a>> { + match self { + Self::List(l) => Some(l), + Self::NameValue(_) | Self::NoArgs => None, + } + } + + /// Asserts that this MetaItem is a name-value pair. + /// + /// Some examples: + /// + /// - `#[clippy::cyclomatic_complexity = "100"]`: `clippy::cyclomatic_complexity = "100"` is a name value pair, + /// where the name is a path (`clippy::cyclomatic_complexity`). You already checked the path + /// to get an `ArgParser`, so this method will effectively only assert that the `= "100"` is + /// there + /// - `#[doc = "hello"]`: `doc = "hello` is also a name value pair + pub fn name_value(&self) -> Option<&NameValueParser> { + match self { + Self::NameValue(n) => Some(n), + Self::List(_) | Self::NoArgs => None, + } + } + + /// Asserts that there are no arguments + pub fn no_args(&self) -> bool { + matches!(self, Self::NoArgs) + } +} + +/// Inside lists, values could be either literals, or more deeply nested meta items. +/// This enum represents that. +/// +/// Choose which one you want using the provided methods. +#[derive(Debug, Clone)] +pub enum MetaItemOrLitParser<'a> { + MetaItemParser(MetaItemParser<'a>), + Lit(MetaItemLit), + Err(Span, ErrorGuaranteed), +} + +impl<'a> MetaItemOrLitParser<'a> { + pub fn span(&self) -> Span { + match self { + MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => { + generic_meta_item_parser.span() + } + MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span, + MetaItemOrLitParser::Err(span, _) => *span, + } + } + + pub fn lit(&self) -> Option<&MetaItemLit> { + match self { + MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit), + _ => None, + } + } + + pub fn meta_item(&self) -> Option<&MetaItemParser<'a>> { + match self { + MetaItemOrLitParser::MetaItemParser(parser) => Some(parser), + _ => None, + } + } +} + +/// Utility that deconstructs a MetaItem into usable parts. +/// +/// MetaItems are syntactically extremely flexible, but specific attributes want to parse +/// them in custom, more restricted ways. This can be done using this struct. +/// +/// MetaItems consist of some path, and some args. The args could be empty. In other words: +/// +/// - `name` -> args are empty +/// - `name(...)` -> args are a [`list`](ArgParser::list), which is the bit between the parentheses +/// - `name = value`-> arg is [`name_value`](ArgParser::name_value), where the argument is the +/// `= value` part +/// +/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html> +#[derive(Clone)] +pub struct MetaItemParser<'a> { + path: PathParser<'a>, + args: ArgParser<'a>, +} + +impl<'a> Debug for MetaItemParser<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MetaItemParser") + .field("path", &self.path) + .field("args", &self.args) + .finish() + } +} + +impl<'a> MetaItemParser<'a> { + /// Create a new parser from a [`NormalAttr`], which is stored inside of any + /// [`ast::Attribute`](rustc_ast::Attribute) + pub fn from_attr(attr: &'a NormalAttr, dcx: DiagCtxtHandle<'a>) -> Self { + Self { + path: PathParser::Ast(&attr.item.path), + args: ArgParser::from_attr_args(&attr.item.args, dcx), + } + } +} + +impl<'a> MetaItemParser<'a> { + pub fn span(&self) -> Span { + if let Some(other) = self.args.span() { + self.path.span().with_hi(other.hi()) + } else { + self.path.span() + } + } + + /// Gets just the path, without the args. + pub fn path_without_args(&self) -> PathParser<'a> { + self.path.clone() + } + + /// Gets just the args parser, without caring about the path. + pub fn args(&self) -> &ArgParser<'a> { + &self.args + } + + pub fn deconstruct(&self) -> (PathParser<'a>, &ArgParser<'a>) { + (self.path_without_args(), self.args()) + } + + /// Asserts that this MetaItem starts with a path. Some examples: + /// + /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path + /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path + /// - `#[inline]`: `inline` is a single segment path + pub fn path(&self) -> (PathParser<'a>, &ArgParser<'a>) { + self.deconstruct() + } + + /// Asserts that this MetaItem starts with a word, or single segment path. + /// Doesn't return the args parser. + /// + /// For examples. see [`Self::word`] + pub fn word_without_args(&self) -> Option<Ident> { + Some(self.word()?.0) + } + + /// Like [`word`](Self::word), but returns an empty symbol instead of None + pub fn word_or_empty_without_args(&self) -> Ident { + self.word_or_empty().0 + } + + /// Asserts that this MetaItem starts with a word, or single segment path. + /// + /// Some examples: + /// - `#[inline]`: `inline` is a word + /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path, + /// and not a word and should instead be parsed using [`path`](Self::path) + pub fn word(&self) -> Option<(Ident, &ArgParser<'a>)> { + let (path, args) = self.deconstruct(); + Some((path.word()?, args)) + } + + /// Like [`word`](Self::word), but returns an empty symbol instead of None + pub fn word_or_empty(&self) -> (Ident, &ArgParser<'a>) { + let (path, args) = self.deconstruct(); + (path.word().unwrap_or(Ident::empty()), args) + } + + /// Asserts that this MetaItem starts with some specific word. + /// + /// See [`word`](Self::word) for examples of what a word is. + pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> { + self.path_without_args().word_is(sym).then(|| self.args()) + } + + /// Asserts that this MetaItem starts with some specific path. + /// + /// See [`word`](Self::path) for examples of what a word is. + pub fn path_is(&self, segments: &[Symbol]) -> Option<&ArgParser<'a>> { + self.path_without_args().segments_is(segments).then(|| self.args()) + } +} + +#[derive(Clone)] +pub struct NameValueParser { + pub eq_span: Span, + value: MetaItemLit, + pub value_span: Span, +} + +impl Debug for NameValueParser { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NameValueParser") + .field("eq_span", &self.eq_span) + .field("value", &self.value) + .field("value_span", &self.value_span) + .finish() + } +} + +impl NameValueParser { + pub fn value_as_lit(&self) -> &MetaItemLit { + &self.value + } + + pub fn value_as_str(&self) -> Option<Symbol> { + self.value_as_lit().kind.str() + } +} + +fn expr_to_lit(dcx: DiagCtxtHandle<'_>, expr: &Expr) -> MetaItemLit { + // In valid code the value always ends up as a single literal. Otherwise, a dummy + // literal suffices because the error is handled elsewhere. + if let ExprKind::Lit(token_lit) = expr.kind + && let Ok(lit) = MetaItemLit::from_token_lit(token_lit, expr.span) + { + lit + } else { + let guar = dcx.has_errors().unwrap(); + MetaItemLit { symbol: kw::Empty, suffix: None, kind: LitKind::Err(guar), span: DUMMY_SP } + } +} + +struct MetaItemListParserContext<'a> { + // the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside + inside_delimiters: Peekable<TokenStreamIter<'a>>, + dcx: DiagCtxtHandle<'a>, +} + +impl<'a> MetaItemListParserContext<'a> { + fn done(&mut self) -> bool { + self.inside_delimiters.peek().is_none() + } + + fn next_path(&mut self) -> Option<AttrPath> { + // FIXME: Share code with `parse_path`. + let tt = self.inside_delimiters.next().map(|tt| TokenTree::uninterpolate(tt)); + + match tt.as_deref()? { + &TokenTree::Token( + Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span }, + _, + ) => { + // here we have either an ident or pathsep `::`. + + let mut segments = if let &token::Ident(name, _) = kind { + // when we lookahead another pathsep, more path's coming + if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = + self.inside_delimiters.peek() + { + self.inside_delimiters.next(); + vec![Ident::new(name, span)] + } else { + // else we have a single identifier path, that's all + return Some(AttrPath { + segments: vec![Ident::new(name, span)].into_boxed_slice(), + span, + }); + } + } else { + // if `::` is all we get, we just got a path root + vec![Ident::new(kw::PathRoot, span)] + }; + + // one segment accepted. accept n more + loop { + // another ident? + if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) = + self.inside_delimiters + .next() + .map(|tt| TokenTree::uninterpolate(tt)) + .as_deref() + { + segments.push(Ident::new(name, span)); + } else { + return None; + } + // stop unless we see another `::` + if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) = + self.inside_delimiters.peek() + { + self.inside_delimiters.next(); + } else { + break; + } + } + let span = span.with_hi(segments.last().unwrap().span.hi()); + Some(AttrPath { segments: segments.into_boxed_slice(), span }) + } + TokenTree::Token(Token { kind: token::OpenDelim(_) | token::CloseDelim(_), .. }, _) => { + None + } + _ => { + // malformed attributes can get here. We can't crash, but somewhere else should've + // already warned for this. + None + } + } + } + + fn value(&mut self) -> Option<MetaItemLit> { + match self.inside_delimiters.next() { + Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => { + MetaItemListParserContext { + inside_delimiters: inner_tokens.iter().peekable(), + dcx: self.dcx, + } + .value() + } + Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token), + _ => None, + } + } + + /// parses one element on the inside of a list attribute like `#[my_attr( <insides> )]` + /// + /// parses a path followed be either: + /// 1. nothing (a word attr) + /// 2. a parenthesized list + /// 3. an equals sign and a literal (name-value) + /// + /// Can also parse *just* a literal. This is for cases like as `#[my_attr("literal")]` + /// where no path is given before the literal + /// + /// Some exceptions too for interpolated attributes which are already pre-processed + fn next(&mut self) -> Option<MetaItemOrLitParser<'a>> { + // a list element is either a literal + if let Some(TokenTree::Token(token, _)) = self.inside_delimiters.peek() + && let Some(lit) = MetaItemLit::from_token(token) + { + self.inside_delimiters.next(); + return Some(MetaItemOrLitParser::Lit(lit)); + } + + // or a path. + let path = + if let Some(TokenTree::Token(Token { kind: token::Interpolated(nt), span, .. }, _)) = + self.inside_delimiters.peek() + { + match &**nt { + // or maybe a full nt meta including the path but we return immediately + token::Nonterminal::NtMeta(item) => { + self.inside_delimiters.next(); + + return Some(MetaItemOrLitParser::MetaItemParser(MetaItemParser { + path: PathParser::Ast(&item.path), + args: ArgParser::from_attr_args(&item.args, self.dcx), + })); + } + // an already interpolated path from a macro expansion is a path, no need to parse + // one from tokens + token::Nonterminal::NtPath(path) => { + self.inside_delimiters.next(); + + AttrPath::from_ast(path) + } + _ => { + self.inside_delimiters.next(); + // we go into this path if an expr ended up in an attribute that + // expansion did not turn into a literal. Say, `#[repr(align(macro!()))]` + // where the macro didn't expand to a literal. An error is already given + // for this at this point, and then we do continue. This makes this path + // reachable... + let e = self.dcx.span_delayed_bug( + *span, + "expr in place where literal is expected (builtin attr parsing)", + ); + + return Some(MetaItemOrLitParser::Err(*span, e)); + } + } + } else { + self.next_path()? + }; + + // Paths can be followed by: + // - `(more meta items)` (another list) + // - `= lit` (a name-value) + // - nothing + Some(MetaItemOrLitParser::MetaItemParser(match self.inside_delimiters.peek() { + Some(TokenTree::Delimited(dspan, _, Delimiter::Parenthesis, inner_tokens)) => { + self.inside_delimiters.next(); + + MetaItemParser { + path: PathParser::Attr(path), + args: ArgParser::List(MetaItemListParser::new_tts( + inner_tokens.iter(), + dspan.entire(), + self.dcx, + )), + } + } + Some(TokenTree::Delimited(_, ..)) => { + self.inside_delimiters.next(); + // self.dcx.span_delayed_bug(span.entire(), "wrong delimiters"); + return None; + } + Some(TokenTree::Token(Token { kind: token::Eq, span }, _)) => { + self.inside_delimiters.next(); + let value = self.value()?; + MetaItemParser { + path: PathParser::Attr(path), + args: ArgParser::NameValue(NameValueParser { + eq_span: *span, + value_span: value.span, + value, + }), + } + } + _ => MetaItemParser { path: PathParser::Attr(path), args: ArgParser::NoArgs }, + })) + } + + fn parse(mut self, span: Span) -> MetaItemListParser<'a> { + let mut sub_parsers = Vec::new(); + + while !self.done() { + let Some(n) = self.next() else { + continue; + }; + sub_parsers.push(n); + + match self.inside_delimiters.peek() { + None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => { + self.inside_delimiters.next(); + } + Some(_) => {} + } + } + + MetaItemListParser { sub_parsers, span } + } +} + +#[derive(Debug, Clone)] +pub struct MetaItemListParser<'a> { + sub_parsers: Vec<MetaItemOrLitParser<'a>>, + pub span: Span, +} + +impl<'a> MetaItemListParser<'a> { + fn new(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'a>) -> MetaItemListParser<'a> { + MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx) + } + + fn new_tts(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'a>) -> Self { + MetaItemListParserContext { inside_delimiters: tts.peekable(), dcx }.parse(span) + } + + /// Lets you pick and choose as what you want to parse each element in the list + pub fn mixed<'s>(&'s self) -> impl Iterator<Item = &'s MetaItemOrLitParser<'a>> + 's { + self.sub_parsers.iter() + } + + pub fn len(&self) -> usize { + self.sub_parsers.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Asserts that every item in the list is another list starting with a word. + /// + /// See [`MetaItemParser::word`] for examples of words. + pub fn all_word_list<'s>(&'s self) -> Option<Vec<(Ident, &'s ArgParser<'a>)>> { + self.mixed().map(|i| i.meta_item()?.word()).collect() + } + + /// Asserts that every item in the list is another list starting with a full path. + /// + /// See [`MetaItemParser::path`] for examples of paths. + pub fn all_path_list<'s>(&'s self) -> Option<Vec<(PathParser<'a>, &'s ArgParser<'a>)>> { + self.mixed().map(|i| Some(i.meta_item()?.path())).collect() + } + + /// Returns Some if the list contains only a single element. + /// + /// Inside the Some is the parser to parse this single element. + pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> { + let mut iter = self.mixed(); + iter.next().filter(|_| iter.next().is_none()) + } +} diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 92bc2a8aeb0..9d34b807ac2 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -6,9 +6,16 @@ use rustc_errors::{Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuar use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; -use crate::attributes::util::UnsupportedLiteralReason; use crate::fluent_generated as fluent; +pub(crate) enum UnsupportedLiteralReason { + Generic, + CfgString, + CfgBoolean, + DeprecatedString, + DeprecatedKvPair, +} + #[derive(Diagnostic)] #[diag(attr_parsing_expected_one_cfg_pattern, code = E0536)] pub(crate) struct ExpectedOneCfgPattern { @@ -39,6 +46,21 @@ pub(crate) struct MultipleItem { pub(crate) struct IncorrectMetaItem { #[primary_span] pub span: Span, + + #[subdiagnostic] + pub suggestion: Option<IncorrectMetaItemSuggestion>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + attr_parsing_incorrect_meta_item_suggestion, + applicability = "maybe-incorrect" +)] +pub(crate) struct IncorrectMetaItemSuggestion { + #[suggestion_part(code = "\"")] + pub lo: Span, + #[suggestion_part(code = "\"")] + pub hi: Span, } /// Error code: E0541 @@ -338,13 +360,6 @@ pub(crate) struct RustcPromotablePairing { } #[derive(Diagnostic)] -#[diag(attr_parsing_rustc_const_stable_indirect_pairing)] -pub(crate) struct RustcConstStableIndirectPairing { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] #[diag(attr_parsing_rustc_allowed_unstable_pairing, code = E0789)] pub(crate) struct RustcAllowedUnstablePairing { #[primary_span] @@ -423,3 +438,44 @@ pub(crate) struct UnknownVersionLiteral { #[primary_span] pub span: Span, } + +// FIXME(jdonszelmann) duplicated from `rustc_passes`, remove once `check_attr` is integrated. +#[derive(Diagnostic)] +#[diag(attr_parsing_unused_multiple)] +pub(crate) struct UnusedMultiple { + #[primary_span] + #[suggestion(code = "", applicability = "machine-applicable")] + pub this: Span, + #[note] + pub other: Span, + pub name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_stability_outside_std, code = E0734)] +pub(crate) struct StabilityOutsideStd { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_empty_confusables)] +pub(crate) struct EmptyConfusables { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_repr_ident, code = E0565)] +pub(crate) struct ReprIdent { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(attr_parsing_unrecognized_repr_hint, code = E0552)] +#[help] +pub(crate) struct UnrecognizedReprHint { + #[primary_span] + pub span: Span, +} |
