| 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::ForeignTy), | |
| 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_path.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_path.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, param, &mut feature, word.unwrap())? | |
| } | |
| Some(sym::since) => { | |
| insert_value_into_option_or_error(cx, param, &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, param, &mut feature, word.unwrap())? | |
| } | |
| Some(sym::reason) => { | |
| insert_value_into_option_or_error(cx, param, &mut reason, word.unwrap())? | |
| } | |
| Some(sym::issue) => { | |
| insert_value_into_option_or_error(cx, param, &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, param, &mut implied_by, word.unwrap())? | |
| } | |
| Some(sym::old_name) => { | |
| insert_value_into_option_or_error(cx, param, &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, param, &mut feature, word)?, | |
| sym::since => insert_value_into_option_or_error(cx, param, &mut since, word)?, | |
| sym::reason => insert_value_into_option_or_error(cx, param, &mut reason, word)?, | |
| sym::link => insert_value_into_option_or_error(cx, param, &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 }) | |
| } | |
| } |