diff options
Diffstat (limited to 'compiler/rustc_attr')
-rw-r--r-- | compiler/rustc_attr/Cargo.toml | 19 | ||||
-rw-r--r-- | compiler/rustc_attr/src/builtin.rs | 1274 | ||||
-rw-r--r-- | compiler/rustc_attr/src/lib.rs | 22 |
3 files changed, 1315 insertions, 0 deletions
diff --git a/compiler/rustc_attr/Cargo.toml b/compiler/rustc_attr/Cargo.toml new file mode 100644 index 000000000..ba310a686 --- /dev/null +++ b/compiler/rustc_attr/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rustc_attr" +version = "0.0.0" +edition = "2021" + +[lib] +doctest = false + +[dependencies] +rustc_ast_pretty = { path = "../rustc_ast_pretty" } +rustc_serialize = { path = "../rustc_serialize" } +rustc_errors = { path = "../rustc_errors" } +rustc_span = { path = "../rustc_span" } +rustc_data_structures = { path = "../rustc_data_structures" } +rustc_feature = { path = "../rustc_feature" } +rustc_lexer = { path = "../rustc_lexer" } +rustc_macros = { path = "../rustc_macros" } +rustc_session = { path = "../rustc_session" } +rustc_ast = { path = "../rustc_ast" } diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs new file mode 100644 index 000000000..10a9cfb62 --- /dev/null +++ b/compiler/rustc_attr/src/builtin.rs @@ -0,0 +1,1274 @@ +//! 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::<Vec<_>>(); + 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<NonZeroU32>, + 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<Symbol>, + }, + /// `#[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<Symbol>) -> Self { + // UnstableReason::Default constructed manually + match reason { + Some(r) => Self::Some(r), + None => Self::None, + } + } + + pub fn to_opt_reason(&self) -> Option<Symbol> { + 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<Item = &'a Attribute>, +{ + 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<Symbol>| { + 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<Symbol> { + 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<Symbol>, + pub value_span: Option<Span>, + 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<Version> { + 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<Symbol>, + /// The note to issue a reason. + pub note: Option<Symbol>, + /// A text snippet used to completely replace any use of the deprecated item in an expression. + /// + /// This is currently unstable. + pub suggestion: Option<Symbol>, + + /// 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<Item = &'a Attribute>, +{ + 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<Symbol>| { + 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<ReprAttr> { + if attr.has_name(sym::repr) { parse_repr_attr(sess, attr) } else { Vec::new() } +} + +pub fn parse_repr_attr(sess: &Session, attr: &Attribute) -> Vec<ReprAttr> { + 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<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)), + _ => None, + } +} + +pub enum TransparencyError { + UnknownTransparency(Symbol, Span), + MultipleTransparencyAttrs(Span, Span), +} + +pub fn find_transparency( + attrs: &[Attribute], + 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, + )); + } + } + } + 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<Item = Symbol> + '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<Item = Symbol> + '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<Item = Symbol> + '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<u32, &'static str> { + 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") + } +} diff --git a/compiler/rustc_attr/src/lib.rs b/compiler/rustc_attr/src/lib.rs new file mode 100644 index 000000000..c3f9f0cf3 --- /dev/null +++ b/compiler/rustc_attr/src/lib.rs @@ -0,0 +1,22 @@ +//! Functions and types dealing with attributes and meta items. +//! +//! 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. + +#![feature(let_chains)] +#![feature(let_else)] + +#[macro_use] +extern crate rustc_macros; + +mod builtin; + +pub use builtin::*; +pub use IntType::*; +pub use ReprAttr::*; +pub use StabilityLevel::*; + +pub use rustc_ast::attr::*; + +pub(crate) use rustc_ast::HashStableContext; |