| use std::num::NonZero; |
| |
| use rustc_errors::ErrorGuaranteed; |
| use rustc_feature::{ACCEPTED_LANG_FEATURES, AttributeStability}; |
| use rustc_hir::attrs::UnstableRemovedFeature; |
| use rustc_hir::target::GenericParamKind; |
| use rustc_hir::{ |
| DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel, |
| StableSince, Target, UnstableReason, VERSION_PLACEHOLDER, |
| }; |
| |
| use super::prelude::*; |
| use super::util::parse_version; |
| use crate::session_diagnostics; |
| |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ |
| Allow(Target::Fn), |
| Allow(Target::Struct), |
| Allow(Target::Enum), |
| Allow(Target::Union), |
| Allow(Target::Method(MethodKind::Inherent)), |
| Allow(Target::Method(MethodKind::Trait { body: false })), |
| Allow(Target::Method(MethodKind::Trait { body: true })), |
| Allow(Target::Method(MethodKind::TraitImpl)), |
| Allow(Target::Impl { of_trait: false }), |
| Allow(Target::Impl { of_trait: true }), |
| Allow(Target::MacroDef), |
| Allow(Target::Crate), |
| Allow(Target::Mod), |
| Allow(Target::Use), // FIXME I don't think this does anything? |
| Allow(Target::Const), |
| Allow(Target::AssocConst), |
| Allow(Target::AssocTy), |
| Allow(Target::Trait), |
| Allow(Target::TraitAlias), |
| Allow(Target::TyAlias), |
| Allow(Target::Variant), |
| Allow(Target::Field), |
| Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }), |
| Allow(Target::Static), |
| Allow(Target::ForeignFn), |
| Allow(Target::ForeignStatic), |
| Allow(Target::ExternCrate), |
| ]); |
| |
| #[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 |
| } |
| } |
| } |
| |
| impl AttributeParser for StabilityParser { |
| const ATTRIBUTES: AcceptMapping<Self> = &[ |
| ( |
| &[sym::stable], |
| template!(List: &[r#"feature = "name", since = "version""#]), |
| unstable!(staged_api), |
| |this, cx, args| { |
| if !this.check_duplicate(cx) |
| && let Some((feature, level)) = parse_stability(cx, args) |
| { |
| this.stability = Some((Stability { level, feature }, cx.attr_span)); |
| } |
| }, |
| ), |
| ( |
| &[sym::unstable], |
| template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), |
| unstable!(staged_api), |
| |this, cx, args| { |
| 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], |
| template!(NameValueStr: "deprecation message"), |
| unstable!(staged_api), |
| |this, cx, args| { |
| let Some(nv) = cx.expect_name_value(args, cx.attr_span, None) else { |
| return; |
| }; |
| let Some(value_str) = cx.expect_string_literal(nv) else { |
| return; |
| }; |
| this.allowed_through_unstable_modules = Some(value_str); |
| }, |
| ), |
| ]; |
| const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS; |
| |
| fn finalize(mut self, cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> { |
| if let Some(atum) = self.allowed_through_unstable_modules { |
| if let Some(( |
| Stability { |
| level: StabilityLevel::Stable { ref mut allowed_through_unstable_modules, .. }, |
| .. |
| }, |
| _, |
| )) = self.stability |
| { |
| *allowed_through_unstable_modules = Some(atum); |
| } else { |
| cx.dcx().emit_err(session_diagnostics::RustcAllowedUnstablePairing { |
| span: cx.target_span, |
| }); |
| } |
| } |
| |
| if let Some((Stability { level: StabilityLevel::Stable { .. }, .. }, _)) = self.stability { |
| for other_attr in cx.all_attrs { |
| if other_attr.word_is(sym::unstable_feature_bound) { |
| cx.emit_err(session_diagnostics::UnstableFeatureBoundIncompatibleStability { |
| span: cx.target_span, |
| }); |
| } |
| } |
| } |
| |
| let (stability, span) = self.stability?; |
| |
| Some(AttributeKind::Stability { stability, span }) |
| } |
| } |
| |
| // FIXME(jdonszelmann) change to Single |
| #[derive(Default)] |
| pub(crate) struct BodyStabilityParser { |
| stability: Option<(DefaultBodyStability, Span)>, |
| } |
| |
| impl AttributeParser for BodyStabilityParser { |
| const ATTRIBUTES: AcceptMapping<Self> = &[( |
| &[sym::rustc_default_body_unstable], |
| template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), |
| unstable!(staged_api), |
| |this, cx, args| { |
| 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)); |
| } |
| }, |
| )]; |
| const ALLOWED_TARGETS: AllowedTargets = ALLOWED_TARGETS; |
| |
| fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> { |
| let (stability, span) = self.stability?; |
| |
| Some(AttributeKind::RustcBodyStability { stability, span }) |
| } |
| } |
| |
| pub(crate) struct RustcConstStableIndirectParser; |
| impl NoArgsAttributeParser for RustcConstStableIndirectParser { |
| const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect]; |
| const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ |
| Allow(Target::Fn), |
| Allow(Target::Method(MethodKind::Inherent)), |
| ]); |
| const STABILITY: AttributeStability = unstable!(rustc_attrs); |
| const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConstStableIndirect; |
| } |
| |
| #[derive(Default)] |
| pub(crate) struct ConstStabilityParser { |
| promotable: bool, |
| stability: Option<(PartialConstStability, Span)>, |
| } |
| |
| 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 |
| } |
| } |
| } |
| |
| impl AttributeParser for ConstStabilityParser { |
| const ATTRIBUTES: AcceptMapping<Self> = &[ |
| ( |
| &[sym::rustc_const_stable], |
| template!(List: &[r#"feature = "name""#]), |
| unstable!(staged_api), |
| |this, cx, args| { |
| if !this.check_duplicate(cx) |
| && let Some((feature, level)) = parse_stability(cx, args) |
| { |
| this.stability = Some(( |
| PartialConstStability { level, feature, promotable: false }, |
| cx.attr_span, |
| )); |
| } |
| }, |
| ), |
| ( |
| &[sym::rustc_const_unstable], |
| template!(List: &[r#"feature = "name""#]), |
| unstable!(staged_api), |
| |this, cx, args| { |
| 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], template!(Word), unstable!(staged_api), |this, _cx, _| { |
| this.promotable = true; |
| }), |
| ]; |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ |
| Allow(Target::Fn), |
| Allow(Target::Method(MethodKind::Inherent)), |
| Allow(Target::Method(MethodKind::TraitImpl)), |
| Allow(Target::Method(MethodKind::Trait { body: true })), |
| Allow(Target::Impl { of_trait: false }), |
| Allow(Target::Impl { of_trait: true }), |
| Allow(Target::Use), // FIXME I don't think this does anything? |
| Allow(Target::Const), |
| Allow(Target::AssocConst), |
| Allow(Target::Trait), |
| Allow(Target::Static), |
| Allow(Target::Crate), |
| ]); |
| |
| 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 }); |
| } |
| } |
| |
| let (stability, span) = self.stability?; |
| |
| Some(AttributeKind::RustcConstStability { stability, span }) |
| } |
| } |
| |
| /// 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: &mut AcceptContext<'_, '_>, |
| param: &MetaItemParser, |
| item: &mut Option<Symbol>, |
| name: Ident, |
| ) -> Option<()> { |
| if item.is_some() { |
| cx.adcx().duplicate_key(name.span, name.name); |
| return None; |
| } |
| |
| let (_ident, arg) = cx.expect_name_value(param, param.span(), Some(name.name))?; |
| let s = cx.expect_string_literal(arg)?; |
| |
| *item = Some(s); |
| |
| Some(()) |
| } |
| |
| /// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and |
| /// its stability information. |
| pub(crate) fn parse_stability( |
| cx: &mut AcceptContext<'_, '_>, |
| args: &ArgParser, |
| ) -> Option<(Symbol, StabilityLevel)> { |
| let mut feature = None; |
| let mut since = None; |
| |
| let list = cx.expect_list(args, cx.attr_span)?; |
| |
| for param in list.mixed() { |
| let param_span = param.span(); |
| let Some(param) = param.meta_item() else { |
| cx.adcx().expected_not_literal(param.span()); |
| return None; |
| }; |
| |
| let word = param.path().word(); |
| match word.map(|i| i.name) { |
| Some(sym::feature) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut feature, word.unwrap())? |
| } |
| Some(sym::since) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut since, word.unwrap())? |
| } |
| _ => { |
| cx.adcx().expected_specific_argument(param_span, &[sym::feature, sym::since]); |
| return None; |
| } |
| } |
| } |
| |
| let feature = match feature { |
| Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature), |
| Some(_bad_feature) => { |
| Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span })) |
| } |
| None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })), |
| }; |
| |
| let since = if let Some(since) = since { |
| if since.as_str() == VERSION_PLACEHOLDER { |
| StableSince::Current |
| } else if let Some(version) = parse_version(since) { |
| StableSince::Version(version) |
| } else { |
| let err = cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span }); |
| StableSince::Err(err) |
| } |
| } else { |
| let err = cx.emit_err(session_diagnostics::MissingSince { span: cx.attr_span }); |
| StableSince::Err(err) |
| }; |
| |
| match feature { |
| Ok(feature) => { |
| let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: None }; |
| Some((feature, level)) |
| } |
| Err(ErrorGuaranteed { .. }) => None, |
| } |
| } |
| |
| /// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` |
| /// attribute, and return the feature name and its stability information. |
| pub(crate) fn parse_unstability( |
| cx: &mut 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 implied_by = None; |
| let mut old_name = None; |
| |
| let list = cx.expect_list(args, cx.attr_span)?; |
| |
| for param in list.mixed() { |
| let Some(param) = param.meta_item() else { |
| cx.adcx().expected_not_literal(param.span()); |
| return None; |
| }; |
| |
| let word = param.path().word(); |
| match word.map(|i| i.name) { |
| Some(sym::feature) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut feature, word.unwrap())? |
| } |
| Some(sym::reason) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut reason, word.unwrap())? |
| } |
| Some(sym::issue) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut issue, word.unwrap())?; |
| |
| // 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_str => match issue_str.parse::<NonZero<u32>>() { |
| Ok(num) => Some(num), |
| Err(err) => { |
| cx.emit_err( |
| session_diagnostics::InvalidIssueString { |
| span: param.span(), |
| cause: session_diagnostics::InvalidIssueStringCause::from_int_error_kind( |
| param.args().as_name_value().unwrap().value_span, |
| err.kind(), |
| ), |
| }, |
| ); |
| return None; |
| } |
| }, |
| }; |
| } |
| Some(sym::implied_by) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut implied_by, word.unwrap())? |
| } |
| Some(sym::old_name) => { |
| insert_value_into_option_or_error(cx, ¶m, &mut old_name, word.unwrap())? |
| } |
| _ => { |
| cx.adcx().expected_specific_argument( |
| param.span(), |
| &[sym::feature, sym::reason, sym::issue, sym::implied_by, sym::old_name], |
| ); |
| return None; |
| } |
| } |
| } |
| |
| let feature = match feature { |
| Some(feature) if rustc_lexer::is_ident(feature.as_str()) => Ok(feature), |
| Some(_bad_feature) => { |
| Err(cx.emit_err(session_diagnostics::NonIdentFeature { span: cx.attr_span })) |
| } |
| None => Err(cx.emit_err(session_diagnostics::MissingFeature { span: cx.attr_span })), |
| }; |
| |
| let issue = |
| issue.ok_or_else(|| cx.emit_err(session_diagnostics::MissingIssue { span: cx.attr_span })); |
| |
| match (feature, issue) { |
| (Ok(feature), Ok(_)) => { |
| // Stable *language* features shouldn't be used as unstable library features. |
| // (Not doing this for stable library features is checked by tidy.) |
| if ACCEPTED_LANG_FEATURES.iter().any(|f| f.name == feature) { |
| cx.emit_err(session_diagnostics::UnstableAttrForAlreadyStableFeature { |
| attr_span: cx.attr_span, |
| item_span: cx.target_span, |
| }); |
| return None; |
| } |
| |
| let level = StabilityLevel::Unstable { |
| reason: UnstableReason::from_opt_reason(reason), |
| issue: issue_num, |
| implied_by, |
| old_name, |
| }; |
| Some((feature, level)) |
| } |
| (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None, |
| } |
| } |
| |
| pub(crate) struct UnstableRemovedParser; |
| |
| impl CombineAttributeParser for UnstableRemovedParser { |
| type Item = UnstableRemovedFeature; |
| const PATH: &[Symbol] = &[sym::unstable_removed]; |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Crate)]); |
| const TEMPLATE: AttributeTemplate = |
| template!(List: &[r#"feature = "name", reason = "...", link = "...", since = "version""#]); |
| const STABILITY: AttributeStability = unstable!(staged_api); |
| |
| const CONVERT: ConvertFn<Self::Item> = |items, _| AttributeKind::UnstableRemoved(items); |
| |
| fn extend( |
| cx: &mut AcceptContext<'_, '_>, |
| args: &ArgParser, |
| ) -> impl IntoIterator<Item = Self::Item> { |
| let mut feature = None; |
| let mut reason = None; |
| let mut link = None; |
| let mut since = None; |
| |
| let list = cx.expect_list(args, cx.attr_span)?; |
| |
| for param in list.mixed() { |
| let Some(param) = param.meta_item() else { |
| cx.adcx().expected_not_literal(param.span()); |
| return None; |
| }; |
| |
| let Some(word) = param.path().word() else { |
| cx.adcx().expected_specific_argument( |
| param.span(), |
| &[sym::feature, sym::reason, sym::link, sym::since], |
| ); |
| return None; |
| }; |
| match word.name { |
| sym::feature => insert_value_into_option_or_error(cx, ¶m, &mut feature, word)?, |
| sym::since => insert_value_into_option_or_error(cx, ¶m, &mut since, word)?, |
| sym::reason => insert_value_into_option_or_error(cx, ¶m, &mut reason, word)?, |
| sym::link => insert_value_into_option_or_error(cx, ¶m, &mut link, word)?, |
| _ => { |
| cx.adcx().expected_specific_argument( |
| param.span(), |
| &[sym::feature, sym::reason, sym::link, sym::since], |
| ); |
| return None; |
| } |
| } |
| } |
| |
| // Check all the arguments are present |
| let Some(feature) = feature else { |
| cx.adcx().missing_name_value(list.span, sym::feature); |
| return None; |
| }; |
| let Some(reason) = reason else { |
| cx.adcx().missing_name_value(list.span, sym::reason); |
| return None; |
| }; |
| let Some(link) = link else { |
| cx.adcx().missing_name_value(list.span, sym::link); |
| return None; |
| }; |
| let Some(since) = since else { |
| cx.adcx().missing_name_value(list.span, sym::since); |
| return None; |
| }; |
| |
| let Some(version) = parse_version(since) else { |
| cx.emit_err(session_diagnostics::InvalidSince { span: cx.attr_span }); |
| return None; |
| }; |
| |
| Some(UnstableRemovedFeature { feature, reason, link, since: version }) |
| } |
| } |