//! Parsing and validation of builtin attributes use rustc_ast as ast; use rustc_ast::{Attribute, Lit, LitKind, MetaItem, MetaItemKind, NestedMetaItem, NodeId}; use rustc_ast_pretty::pprust; use rustc_errors::{struct_span_err, Applicability}; use rustc_feature::{find_gated_cfg, is_builtin_attr_name, Features, GatedCfg}; use rustc_macros::HashStable_Generic; use rustc_session::lint::builtin::UNEXPECTED_CFGS; use rustc_session::lint::BuiltinLintDiagnostics; use rustc_session::parse::{feature_err, ParseSess}; use rustc_session::Session; use rustc_span::hygiene::Transparency; use rustc_span::{symbol::sym, symbol::Symbol, Span}; use std::num::NonZeroU32; pub fn is_builtin_attr(attr: &Attribute) -> bool { attr.is_doc_comment() || attr.ident().filter(|ident| is_builtin_attr_name(ident.name)).is_some() } enum AttrError { MultipleItem(String), UnknownMetaItem(String, &'static [&'static str]), MissingSince, NonIdentFeature, MissingFeature, MultipleStabilityLevels, UnsupportedLiteral(&'static str, /* is_bytestr */ bool), } fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) { let diag = &sess.span_diagnostic; match error { AttrError::MultipleItem(item) => { struct_span_err!(diag, span, E0538, "multiple '{}' items", item).emit(); } AttrError::UnknownMetaItem(item, expected) => { let expected = expected.iter().map(|name| format!("`{}`", name)).collect::>(); struct_span_err!(diag, span, E0541, "unknown meta item '{}'", item) .span_label(span, format!("expected one of {}", expected.join(", "))) .emit(); } AttrError::MissingSince => { struct_span_err!(diag, span, E0542, "missing 'since'").emit(); } AttrError::NonIdentFeature => { struct_span_err!(diag, span, E0546, "'feature' is not an identifier").emit(); } AttrError::MissingFeature => { struct_span_err!(diag, span, E0546, "missing 'feature'").emit(); } AttrError::MultipleStabilityLevels => { struct_span_err!(diag, span, E0544, "multiple stability levels").emit(); } AttrError::UnsupportedLiteral(msg, is_bytestr) => { let mut err = struct_span_err!(diag, span, E0565, "{}", msg); if is_bytestr { if let Ok(lint_str) = sess.source_map().span_to_snippet(span) { err.span_suggestion( span, "consider removing the prefix", &lint_str[1..], Applicability::MaybeIncorrect, ); } } err.emit(); } } } #[derive(Copy, Clone, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)] pub enum InlineAttr { None, Hint, Always, Never, } #[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, HashStable_Generic)] pub enum InstructionSetAttr { ArmA32, ArmT32, } #[derive(Clone, Encodable, Decodable, Debug, HashStable_Generic)] pub enum OptimizeAttr { None, Speed, Size, } /// Represents the following attributes: /// /// - `#[stable]` /// - `#[unstable]` #[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct Stability { pub level: StabilityLevel, pub feature: Symbol, } impl Stability { pub fn is_unstable(&self) -> bool { self.level.is_unstable() } pub fn is_stable(&self) -> bool { self.level.is_stable() } } /// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes. #[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct ConstStability { pub level: StabilityLevel, pub feature: Symbol, /// whether the function has a `#[rustc_promotable]` attribute pub promotable: bool, } impl ConstStability { pub fn is_const_unstable(&self) -> bool { self.level.is_unstable() } pub fn is_const_stable(&self) -> bool { self.level.is_stable() } } /// The available stability levels. #[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(HashStable_Generic)] pub enum StabilityLevel { /// `#[unstable]` Unstable { /// Reason for the current stability level. reason: UnstableReason, /// Relevant `rust-lang/rust` issue. issue: Option, is_soft: bool, /// If part of a feature is stabilized and a new feature is added for the remaining parts, /// then the `implied_by` attribute is used to indicate which now-stable feature previously /// contained a item. /// /// ```pseudo-Rust /// #[unstable(feature = "foo", issue = "...")] /// fn foo() {} /// #[unstable(feature = "foo", issue = "...")] /// fn foobar() {} /// ``` /// /// ...becomes... /// /// ```pseudo-Rust /// #[stable(feature = "foo", since = "1.XX.X")] /// fn foo() {} /// #[unstable(feature = "foobar", issue = "...", implied_by = "foo")] /// fn foobar() {} /// ``` implied_by: Option, }, /// `#[stable]` Stable { /// Rust release which stabilized this feature. since: Symbol, /// Is this item allowed to be referred to on stable, despite being contained in unstable /// modules? allowed_through_unstable_modules: bool, }, } impl StabilityLevel { pub fn is_unstable(&self) -> bool { matches!(self, StabilityLevel::Unstable { .. }) } pub fn is_stable(&self) -> bool { matches!(self, StabilityLevel::Stable { .. }) } } #[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(HashStable_Generic)] pub enum UnstableReason { None, Default, Some(Symbol), } impl UnstableReason { fn from_opt_reason(reason: Option) -> Self { // UnstableReason::Default constructed manually match reason { Some(r) => Self::Some(r), None => Self::None, } } pub fn to_opt_reason(&self) -> Option { match self { Self::None => None, Self::Default => Some(sym::unstable_location_reason_default), Self::Some(r) => Some(*r), } } } /// Collects stability info from all stability attributes in `attrs`. /// Returns `None` if no stability attributes are found. pub fn find_stability( sess: &Session, attrs: &[Attribute], item_sp: Span, ) -> (Option<(Stability, Span)>, Option<(ConstStability, Span)>) { find_stability_generic(sess, attrs.iter(), item_sp) } fn find_stability_generic<'a, I>( sess: &Session, attrs_iter: I, item_sp: Span, ) -> (Option<(Stability, Span)>, Option<(ConstStability, Span)>) where I: Iterator, { use StabilityLevel::*; let mut stab: Option<(Stability, Span)> = None; let mut const_stab: Option<(ConstStability, Span)> = None; let mut promotable = false; let mut allowed_through_unstable_modules = false; let diagnostic = &sess.parse_sess.span_diagnostic; 'outer: for attr in attrs_iter { if ![ sym::rustc_const_unstable, sym::rustc_const_stable, sym::unstable, sym::stable, sym::rustc_promotable, sym::rustc_allowed_through_unstable_modules, ] .iter() .any(|&s| attr.has_name(s)) { continue; // not a stability level } let meta = attr.meta(); if attr.has_name(sym::rustc_promotable) { promotable = true; } else if attr.has_name(sym::rustc_allowed_through_unstable_modules) { allowed_through_unstable_modules = true; } // attributes with data else if let Some(MetaItem { kind: MetaItemKind::List(ref metas), .. }) = meta { let meta = meta.as_ref().unwrap(); let get = |meta: &MetaItem, item: &mut Option| { if item.is_some() { handle_errors( &sess.parse_sess, meta.span, AttrError::MultipleItem(pprust::path_to_string(&meta.path)), ); return false; } if let Some(v) = meta.value_str() { *item = Some(v); true } else { struct_span_err!(diagnostic, meta.span, E0539, "incorrect meta item").emit(); false } }; let meta_name = meta.name_or_empty(); match meta_name { sym::rustc_const_unstable | sym::unstable => { if meta_name == sym::unstable && stab.is_some() { handle_errors( &sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels, ); break; } else if meta_name == sym::rustc_const_unstable && const_stab.is_some() { handle_errors( &sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels, ); break; } 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 { handle_errors( &sess.parse_sess, meta.span(), AttrError::UnsupportedLiteral("unsupported literal", false), ); continue 'outer; }; match mi.name_or_empty() { sym::feature => { if !get(mi, &mut feature) { continue 'outer; } } sym::reason => { if !get(mi, &mut reason) { continue 'outer; } } sym::issue => { if !get(mi, &mut issue) { continue 'outer; } // These unwraps are safe because `get` ensures the meta item // is a name/value pair string literal. issue_num = match issue.unwrap().as_str() { "none" => None, issue => { let emit_diag = |msg: &str| { struct_span_err!( diagnostic, mi.span, E0545, "`issue` must be a non-zero numeric string \ or \"none\"", ) .span_label(mi.name_value_literal_span().unwrap(), msg) .emit(); }; match issue.parse() { Ok(0) => { emit_diag( "`issue` must not be \"0\", \ use \"none\" instead", ); continue 'outer; } Ok(num) => NonZeroU32::new(num), Err(err) => { emit_diag(&err.to_string()); continue 'outer; } } } }; } sym::soft => { if !mi.is_word() { let msg = "`soft` should not have any arguments"; sess.parse_sess.span_diagnostic.span_err(mi.span, msg); } is_soft = true; } sym::implied_by => { if !get(mi, &mut implied_by) { continue 'outer; } } _ => { handle_errors( &sess.parse_sess, meta.span(), AttrError::UnknownMetaItem( pprust::path_to_string(&mi.path), &["feature", "reason", "issue", "soft"], ), ); continue 'outer; } } } match (feature, reason, issue) { (Some(feature), reason, Some(_)) => { if !rustc_lexer::is_ident(feature.as_str()) { handle_errors( &sess.parse_sess, attr.span, AttrError::NonIdentFeature, ); continue; } let level = Unstable { reason: UnstableReason::from_opt_reason(reason), issue: issue_num, is_soft, implied_by, }; if sym::unstable == meta_name { stab = Some((Stability { level, feature }, attr.span)); } else { const_stab = Some(( ConstStability { level, feature, promotable: false }, attr.span, )); } } (None, _, _) => { handle_errors(&sess.parse_sess, attr.span, AttrError::MissingFeature); continue; } _ => { struct_span_err!(diagnostic, attr.span, E0547, "missing 'issue'") .emit(); continue; } } } sym::rustc_const_stable | sym::stable => { if meta_name == sym::stable && stab.is_some() { handle_errors( &sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels, ); break; } else if meta_name == sym::rustc_const_stable && const_stab.is_some() { handle_errors( &sess.parse_sess, attr.span, AttrError::MultipleStabilityLevels, ); break; } let mut feature = None; let mut since = None; for meta in metas { match meta { NestedMetaItem::MetaItem(mi) => match mi.name_or_empty() { sym::feature => { if !get(mi, &mut feature) { continue 'outer; } } sym::since => { if !get(mi, &mut since) { continue 'outer; } } _ => { handle_errors( &sess.parse_sess, meta.span(), AttrError::UnknownMetaItem( pprust::path_to_string(&mi.path), &["feature", "since"], ), ); continue 'outer; } }, NestedMetaItem::Literal(lit) => { handle_errors( &sess.parse_sess, lit.span, AttrError::UnsupportedLiteral("unsupported literal", false), ); continue 'outer; } } } match (feature, since) { (Some(feature), Some(since)) => { let level = Stable { since, allowed_through_unstable_modules: false }; if sym::stable == meta_name { stab = Some((Stability { level, feature }, attr.span)); } else { const_stab = Some(( ConstStability { level, feature, promotable: false }, attr.span, )); } } (None, _) => { handle_errors(&sess.parse_sess, attr.span, AttrError::MissingFeature); continue; } _ => { handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince); continue; } } } _ => unreachable!(), } } } // Merge the const-unstable info into the stability info if promotable { if let Some((ref mut stab, _)) = const_stab { stab.promotable = promotable; } else { struct_span_err!( diagnostic, item_sp, E0717, "`rustc_promotable` attribute must be paired with either a `rustc_const_unstable` \ or a `rustc_const_stable` attribute" ) .emit(); } } if allowed_through_unstable_modules { if let Some(( Stability { level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. }, .. }, _, )) = stab { *allowed_through_unstable_modules = true; } else { struct_span_err!( diagnostic, item_sp, E0789, "`rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute" ) .emit(); } } (stab, const_stab) } pub fn find_crate_name(sess: &Session, attrs: &[Attribute]) -> Option { sess.first_attr_value_str_by_name(attrs, sym::crate_name) } #[derive(Clone, Debug)] pub struct Condition { pub name: Symbol, pub name_span: Span, pub value: Option, pub value_span: Option, pub span: Span, } /// Tests if a cfg-pattern matches the cfg set pub fn cfg_matches( cfg: &ast::MetaItem, sess: &ParseSess, lint_node_id: NodeId, features: Option<&Features>, ) -> bool { eval_condition(cfg, sess, features, &mut |cfg| { try_gate_cfg(cfg.name, cfg.span, sess, features); if let Some(names_valid) = &sess.check_config.names_valid { if !names_valid.contains(&cfg.name) { sess.buffer_lint_with_diagnostic( UNEXPECTED_CFGS, cfg.span, lint_node_id, "unexpected `cfg` condition name", BuiltinLintDiagnostics::UnexpectedCfg((cfg.name, cfg.name_span), None), ); } } if let Some(value) = cfg.value { if let Some(values) = &sess.check_config.values_valid.get(&cfg.name) { if !values.contains(&value) { sess.buffer_lint_with_diagnostic( UNEXPECTED_CFGS, cfg.span, lint_node_id, "unexpected `cfg` condition value", BuiltinLintDiagnostics::UnexpectedCfg( (cfg.name, cfg.name_span), cfg.value_span.map(|vs| (value, vs)), ), ); } } } sess.config.contains(&(cfg.name, cfg.value)) }) } fn try_gate_cfg(name: Symbol, span: Span, sess: &ParseSess, features: Option<&Features>) { let gate = find_gated_cfg(|sym| sym == name); if let (Some(feats), Some(gated_cfg)) = (features, gate) { gate_cfg(&gated_cfg, span, sess, feats); } } fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &Features) { let (cfg, feature, has_feature) = gated_cfg; if !has_feature(features) && !cfg_span.allows_unstable(*feature) { let explain = format!("`cfg({})` is experimental and subject to change", cfg); feature_err(sess, *feature, cfg_span, &explain).emit(); } } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] struct Version { major: u16, minor: u16, patch: u16, } fn parse_version(s: &str, allow_appendix: bool) -> Option { let mut components = s.split('-'); let d = components.next()?; if !allow_appendix && components.next().is_some() { return None; } let mut digits = d.splitn(3, '.'); let major = digits.next()?.parse().ok()?; let minor = digits.next()?.parse().ok()?; let patch = digits.next().unwrap_or("0").parse().ok()?; Some(Version { major, minor, patch }) } /// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to /// evaluate individual items. pub fn eval_condition( cfg: &ast::MetaItem, sess: &ParseSess, features: Option<&Features>, eval: &mut impl FnMut(Condition) -> bool, ) -> bool { match cfg.kind { ast::MetaItemKind::List(ref mis) if cfg.name_or_empty() == sym::version => { try_gate_cfg(sym::version, cfg.span, sess, features); let (min_version, span) = match &mis[..] { [NestedMetaItem::Literal(Lit { kind: LitKind::Str(sym, ..), span, .. })] => { (sym, span) } [ NestedMetaItem::Literal(Lit { span, .. }) | NestedMetaItem::MetaItem(MetaItem { span, .. }), ] => { sess.span_diagnostic .struct_span_err(*span, "expected a version literal") .emit(); return false; } [..] => { sess.span_diagnostic .struct_span_err(cfg.span, "expected single version literal") .emit(); return false; } }; let Some(min_version) = parse_version(min_version.as_str(), false) else { sess.span_diagnostic .struct_span_warn( *span, "unknown version literal format, assuming it refers to a future version", ) .emit(); return false; }; let rustc_version = parse_version(env!("CFG_RELEASE"), true).unwrap(); // See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details if sess.assume_incomplete_release { rustc_version > min_version } else { rustc_version >= min_version } } ast::MetaItemKind::List(ref mis) => { for mi in mis.iter() { if !mi.is_meta_item() { handle_errors( sess, mi.span(), AttrError::UnsupportedLiteral("unsupported literal", false), ); return false; } } // The unwraps below may look dangerous, but we've already asserted // that they won't fail with the loop above. match cfg.name_or_empty() { sym::any => mis .iter() // We don't use any() here, because we want to evaluate all cfg condition // as eval_condition can (and does) extra checks .fold(false, |res, mi| { res | eval_condition(mi.meta_item().unwrap(), sess, features, eval) }), sym::all => mis .iter() // We don't use all() here, because we want to evaluate all cfg condition // as eval_condition can (and does) extra checks .fold(true, |res, mi| { res & eval_condition(mi.meta_item().unwrap(), sess, features, eval) }), sym::not => { if mis.len() != 1 { struct_span_err!( sess.span_diagnostic, cfg.span, E0536, "expected 1 cfg-pattern" ) .emit(); return false; } !eval_condition(mis[0].meta_item().unwrap(), sess, features, eval) } sym::target => { if let Some(features) = features && !features.cfg_target_compact { feature_err( sess, sym::cfg_target_compact, cfg.span, &"compact `cfg(target(..))` is experimental and subject to change" ).emit(); } mis.iter().fold(true, |res, mi| { let mut mi = mi.meta_item().unwrap().clone(); if let [seg, ..] = &mut mi.path.segments[..] { seg.ident.name = Symbol::intern(&format!("target_{}", seg.ident.name)); } res & eval_condition(&mi, sess, features, eval) }) } _ => { struct_span_err!( sess.span_diagnostic, cfg.span, E0537, "invalid predicate `{}`", pprust::path_to_string(&cfg.path) ) .emit(); false } } } ast::MetaItemKind::Word | MetaItemKind::NameValue(..) if cfg.path.segments.len() != 1 => { sess.span_diagnostic .span_err(cfg.path.span, "`cfg` predicate key must be an identifier"); true } MetaItemKind::NameValue(ref lit) if !lit.kind.is_str() => { handle_errors( sess, lit.span, AttrError::UnsupportedLiteral( "literal in `cfg` predicate value must be a string", lit.kind.is_bytestr(), ), ); true } ast::MetaItemKind::Word | ast::MetaItemKind::NameValue(..) => { let ident = cfg.ident().expect("multi-segment cfg predicate"); eval(Condition { name: ident.name, name_span: ident.span, value: cfg.value_str(), value_span: cfg.name_value_literal_span(), span: cfg.span, }) } } } #[derive(Copy, Debug, Encodable, Decodable, Clone, HashStable_Generic)] pub struct Deprecation { pub since: Option, /// The note to issue a reason. pub note: Option, /// A text snippet used to completely replace any use of the deprecated item in an expression. /// /// This is currently unstable. pub suggestion: Option, /// Whether to treat the since attribute as being a Rust version identifier /// (rather than an opaque string). pub is_since_rustc_version: bool, } /// Finds the deprecation attribute. `None` if none exists. pub fn find_deprecation(sess: &Session, attrs: &[Attribute]) -> Option<(Deprecation, Span)> { find_deprecation_generic(sess, attrs.iter()) } fn find_deprecation_generic<'a, I>(sess: &Session, attrs_iter: I) -> Option<(Deprecation, Span)> where I: Iterator, { let mut depr: Option<(Deprecation, Span)> = None; let diagnostic = &sess.parse_sess.span_diagnostic; let is_rustc = sess.features_untracked().staged_api; 'outer: for attr in attrs_iter { if !attr.has_name(sym::deprecated) { continue; } let Some(meta) = attr.meta() else { continue; }; let mut since = None; let mut note = None; let mut suggestion = None; match &meta.kind { MetaItemKind::Word => {} MetaItemKind::NameValue(..) => note = meta.value_str(), MetaItemKind::List(list) => { let get = |meta: &MetaItem, item: &mut Option| { if item.is_some() { handle_errors( &sess.parse_sess, meta.span, AttrError::MultipleItem(pprust::path_to_string(&meta.path)), ); return false; } if let Some(v) = meta.value_str() { *item = Some(v); true } else { if let Some(lit) = meta.name_value_literal() { handle_errors( &sess.parse_sess, lit.span, AttrError::UnsupportedLiteral( "literal in `deprecated` \ value must be a string", lit.kind.is_bytestr(), ), ); } else { struct_span_err!(diagnostic, meta.span, E0551, "incorrect meta item") .emit(); } false } }; for meta in list { match meta { NestedMetaItem::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 !sess.features_untracked().deprecated_suggestion { let mut diag = sess.struct_span_err( mi.span, "suggestions on deprecated items are unstable", ); if sess.is_nightly_build() { diag.help("add `#![feature(deprecated_suggestion)]` to the crate root"); } diag.note("see #94785 for more details").emit(); } if !get(mi, &mut suggestion) { continue 'outer; } } _ => { handle_errors( &sess.parse_sess, meta.span(), AttrError::UnknownMetaItem( pprust::path_to_string(&mi.path), if sess.features_untracked().deprecated_suggestion { &["since", "note", "suggestion"] } else { &["since", "note"] }, ), ); continue 'outer; } }, NestedMetaItem::Literal(lit) => { handle_errors( &sess.parse_sess, lit.span, AttrError::UnsupportedLiteral( "item in `deprecated` must be a key/value pair", false, ), ); continue 'outer; } } } } } if is_rustc { if since.is_none() { handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince); continue; } if note.is_none() { struct_span_err!(diagnostic, attr.span, E0543, "missing 'note'").emit(); continue; } } depr = Some(( Deprecation { since, note, suggestion, is_since_rustc_version: is_rustc }, attr.span, )); } depr } #[derive(PartialEq, Debug, Encodable, Decodable, Copy, Clone)] pub enum ReprAttr { ReprInt(IntType), ReprC, ReprPacked(u32), ReprSimd, ReprTransparent, ReprAlign(u32), } #[derive(Eq, PartialEq, Debug, Copy, Clone)] #[derive(Encodable, Decodable, HashStable_Generic)] pub enum IntType { SignedInt(ast::IntTy), UnsignedInt(ast::UintTy), } impl IntType { #[inline] pub fn is_signed(self) -> bool { use IntType::*; match self { SignedInt(..) => true, UnsignedInt(..) => false, } } } /// Parse #[repr(...)] forms. /// /// Valid repr contents: any of the primitive integral type names (see /// `int_type_of_word`, below) to specify enum discriminant type; `C`, to use /// 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: &Attribute) -> Vec { if attr.has_name(sym::repr) { parse_repr_attr(sess, attr) } else { Vec::new() } } pub fn parse_repr_attr(sess: &Session, attr: &Attribute) -> Vec { assert!(attr.has_name(sym::repr), "expected `#[repr(..)]`, found: {:?}", attr); use ReprAttr::*; let mut acc = Vec::new(); let diagnostic = &sess.parse_sess.span_diagnostic; 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::C => Some(ReprC), sym::packed => Some(ReprPacked(1)), sym::simd => Some(ReprSimd), sym::transparent => Some(ReprTransparent), sym::align => { let mut err = struct_span_err!( diagnostic, item.span(), E0589, "invalid `repr(align)` attribute: `align` needs an argument" ); err.span_suggestion( item.span(), "supply an argument here", "align(...)", Applicability::HasPlaceholders, ); err.emit(); 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.name_value_literal() { let mut literal_error = None; if name == sym::align { recognised = true; match parse_alignment(&value.kind) { Ok(literal) => acc.push(ReprAlign(literal)), Err(message) => literal_error = Some(message), }; } else if name == sym::packed { recognised = true; match parse_alignment(&value.kind) { Ok(literal) => acc.push(ReprPacked(literal)), Err(message) => literal_error = Some(message), }; } else if matches!(name, sym::C | sym::simd | sym::transparent) || int_type_of_word(name).is_some() { recognised = true; struct_span_err!( diagnostic, item.span(), E0552, "invalid representation hint: `{}` does not take a parenthesized argument list", name.to_ident_string(), ).emit(); } if let Some(literal_error) = literal_error { struct_span_err!( diagnostic, item.span(), E0589, "invalid `repr({})` attribute: {}", name.to_ident_string(), literal_error ) .emit(); } } else if let Some(meta_item) = item.meta_item() { if let MetaItemKind::NameValue(ref value) = meta_item.kind { 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; let mut err = struct_span_err!( diagnostic, item.span(), E0693, "incorrect `repr({})` attribute format", name, ); match value.kind { ast::LitKind::Int(int, ast::LitIntType::Unsuffixed) => { err.span_suggestion( item.span(), "use parentheses instead", format!("{}({})", name, int), Applicability::MachineApplicable, ); } ast::LitKind::Str(s, _) => { err.span_suggestion( item.span(), "use parentheses instead", format!("{}({})", name, s), Applicability::MachineApplicable, ); } _ => {} } err.emit(); } else { if matches!( meta_item.name_or_empty(), sym::C | sym::simd | sym::transparent ) || int_type_of_word(meta_item.name_or_empty()).is_some() { recognised = true; struct_span_err!( diagnostic, meta_item.span, E0552, "invalid representation hint: `{}` does not take a value", meta_item.name_or_empty().to_ident_string(), ) .emit(); } } } else if let MetaItemKind::List(_) = meta_item.kind { if meta_item.has_name(sym::align) { recognised = true; struct_span_err!( diagnostic, meta_item.span, E0693, "incorrect `repr(align)` attribute format: \ `align` takes exactly one argument in parentheses" ) .emit(); } else if meta_item.has_name(sym::packed) { recognised = true; struct_span_err!( diagnostic, meta_item.span, E0552, "incorrect `repr(packed)` attribute format: \ `packed` takes exactly one parenthesized argument, \ or no parentheses at all" ) .emit(); } else if matches!( meta_item.name_or_empty(), sym::C | sym::simd | sym::transparent ) || int_type_of_word(meta_item.name_or_empty()).is_some() { recognised = true; struct_span_err!( diagnostic, meta_item.span, E0552, "invalid representation hint: `{}` does not take a parenthesized argument list", meta_item.name_or_empty().to_ident_string(), ).emit(); } } } 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 `delay_span_bug` call as follows: if sess.opts.pretty.map_or(true, |pp| pp.needs_analysis()) { diagnostic.delay_span_bug(item.span(), "unrecognized representation hint"); } } } } acc } fn int_type_of_word(s: Symbol) -> Option { 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)), _ => None, } } pub enum TransparencyError { UnknownTransparency(Symbol, Span), MultipleTransparencyAttrs(Span, Span), } pub fn find_transparency( attrs: &[Attribute], macro_rules: bool, ) -> (Transparency, Option) { 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, )); } } } let fallback = if macro_rules { Transparency::SemiTransparent } else { Transparency::Opaque }; (transparency.map_or(fallback, |t| t.0), error) } pub fn allow_internal_unstable<'a>( sess: &'a Session, attrs: &'a [Attribute], ) -> impl Iterator + 'a { allow_unstable(sess, attrs, sym::allow_internal_unstable) } pub fn rustc_allow_const_fn_unstable<'a>( sess: &'a Session, attrs: &'a [Attribute], ) -> impl Iterator + 'a { allow_unstable(sess, attrs, sym::rustc_allow_const_fn_unstable) } fn allow_unstable<'a>( sess: &'a Session, attrs: &'a [Attribute], symbol: Symbol, ) -> impl Iterator + 'a { let attrs = sess.filter_by_name(attrs, symbol); let list = attrs .filter_map(move |attr| { attr.meta_item_list().or_else(|| { sess.diagnostic().span_err( attr.span, &format!("`{}` expects a list of feature names", 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.diagnostic().span_err( it.span(), &format!("`{}` expects feature names", symbol.to_ident_string()), ); } name }) } pub fn parse_alignment(node: &ast::LitKind) -> Result { if let ast::LitKind::Int(literal, ast::LitIntType::Unsuffixed) = node { if literal.is_power_of_two() { // rustc_middle::ty::layout::Align restricts align to <= 2^29 if *literal <= 1 << 29 { Ok(*literal as u32) } else { Err("larger than 2^29") } } else { Err("not a power of two") } } else { Err("not an unsuffixed integer") } }