diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index 866b53e4c0d9..0559469bc369 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -2,6 +2,7 @@ use rustc_errors::ErrorGuaranteed; use rustc_feature::ACCEPTED_LANG_FEATURES; +use rustc_hir::attrs::UnstableRemovedFeature; use rustc_hir::target::GenericParamKind; use rustc_hir::{ DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel, @@ -476,3 +477,89 @@ pub(crate) fn parse_unstability( (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 CONVERT: ConvertFn = |items, _| AttributeKind::UnstableRemoved(items); + + fn extend( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, + ) -> impl IntoIterator { + let mut feature = None; + let mut reason = None; + let mut link = None; + let mut since = None; + + if !cx.features().staged_api() { + cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span }); + return None; + } + + let ArgParser::List(list) = args else { + let attr_span = cx.attr_span; + cx.adcx().expected_list(attr_span, args); + return None; + }; + + 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 }) + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index cfc5be3f0b9b..51345162ee07 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -179,6 +179,7 @@ mod late { Combine, Combine, Combine, + Combine, // tidy-alphabetical-end // tidy-alphabetical-start @@ -776,6 +777,11 @@ pub(crate) fn expected_name_value( self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNameValue(name)) } + /// Emit an error that a `name = value` argument is missing in a list of name-value pairs. + pub(crate) fn missing_name_value(&mut self, span: Span, name: Symbol) -> ErrorGuaranteed { + self.emit_parse_error(span, AttributeParseErrorReason::MissingNameValue(name)) + } + /// Emit an error that a `name = value` pair was found where that name was already seen. pub(crate) fn duplicate_key(&mut self, span: Span, key: Symbol) -> ErrorGuaranteed { self.emit_parse_error(span, AttributeParseErrorReason::DuplicateKey(key)) diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index ace233acbd50..203c7f8ebff1 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -568,6 +568,7 @@ pub(crate) enum AttributeParseErrorReason<'a> { ExpectedNonEmptyStringLiteral, ExpectedNotLiteral, ExpectedNameValue(Option), + MissingNameValue(Symbol), DuplicateKey(Symbol), ExpectedSpecificArgument { possibilities: &'a [Symbol], @@ -823,6 +824,9 @@ fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { format!("expected this to be of the form `{name} = \"...\"`"), ); } + AttributeParseErrorReason::MissingNameValue(name) => { + diag.span_label(self.span, format!("missing argument `{name} = \"...\"`")); + } AttributeParseErrorReason::ExpectedSpecificArgument { possibilities, strings, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 60223cc83504..6db23aadcac3 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -928,6 +928,11 @@ pub struct BuiltinAttribute { unstable_feature_bound, Normal, template!(Word, List: &["feat1, feat2, ..."]), DuplicatesOk, EncodeCrossCrate::No, ), + ungated!( + unstable_removed, CrateLevel, + template!(List: &[r#"feature = "name", reason = "...", link = "...", since = "version""#]), + DuplicatesOk, EncodeCrossCrate::Yes + ), ungated!( rustc_const_unstable, Normal, template!(List: &[r#"feature = "name""#]), DuplicatesOk, EncodeCrossCrate::Yes diff --git a/compiler/rustc_feature/src/removed.rs b/compiler/rustc_feature/src/removed.rs index 1e08ad1384cc..7508fb7c250c 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -310,18 +310,4 @@ macro_rules! declare_features { // ------------------------------------------------------------------------- // feature-group-end: removed features // ------------------------------------------------------------------------- - - - // ------------------------------------------------------------------------- - // feature-group-start: removed library features - // ------------------------------------------------------------------------- - // - // FIXME(#141617): we should have a better way to track removed library features, but we reuse - // the infrastructure here so users still get hints. The symbols used here can be remove from - // `symbol.rs` when that happens. - (removed, concat_idents, "1.90.0", Some(29599), - Some("use the `${concat(..)}` metavariable expression instead"), 142704), - // ------------------------------------------------------------------------- - // feature-group-end: removed library features - // ------------------------------------------------------------------------- ); diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 48b03bc94659..67bf1c9b91d0 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -894,6 +894,14 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] +pub struct UnstableRemovedFeature { + pub feature: Symbol, + pub reason: Symbol, + pub link: Symbol, + pub since: RustcVersion, +} + /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -1648,6 +1656,9 @@ pub enum AttributeKind { /// Represents `#[unstable_feature_bound]`. UnstableFeatureBound(ThinVec<(Symbol, Span)>), + /// Represents all `#![unstable_removed(...)]` features + UnstableRemoved(ThinVec), + /// Represents `#[used]` Used { used_by: UsedBy, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index eea6549c02f4..dace9756dc39 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -199,6 +199,7 @@ pub fn encode_cross_crate(&self) -> EncodeCrossCrate { TrackCaller(..) => Yes, TypeLengthLimit { .. } => No, UnstableFeatureBound(..) => No, + UnstableRemoved(..) => Yes, Used { .. } => No, WindowsSubsystem(..) => No, // tidy-alphabetical-end diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 54d34ef26a02..17f12b81751c 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -385,6 +385,7 @@ fn check_attributes( | AttributeKind::ThreadLocal | AttributeKind::TypeLengthLimit { .. } | AttributeKind::UnstableFeatureBound(..) + | AttributeKind::UnstableRemoved(..) | AttributeKind::Used { .. } | AttributeKind::WindowsSubsystem(..) // tidy-alphabetical-end diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 5de43f24b2dc..b32bb70e3fb6 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -895,6 +895,20 @@ pub(crate) struct ImpliedFeatureNotExist { pub implied_by: Symbol, } +#[derive(Diagnostic)] +#[diag("feature `{$feature}` has been removed", code = E0557)] +#[note("removed in {$since}; see <{$link}> for more information")] +#[note("{$reason}")] +pub(crate) struct FeatureRemoved { + #[primary_span] + #[label("feature has been removed")] + pub span: Span, + pub feature: Symbol, + pub reason: Symbol, + pub since: String, + pub link: Symbol, +} + #[derive(Diagnostic)] #[diag( "attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`" diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 220cb57ddd39..e41df43e34bd 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -1097,7 +1097,7 @@ fn check_features<'tcx>( let lang_features = UNSTABLE_LANG_FEATURES.iter().map(|feature| feature.name).collect::>(); let lib_features = crates - .into_iter() + .iter() .flat_map(|&cnum| { tcx.lib_features(cnum).stability.keys().copied().into_sorted_stable_ord() }) @@ -1105,11 +1105,33 @@ fn check_features<'tcx>( let valid_feature_names = [lang_features, lib_features].concat(); + // Collect all of the marked as "removed" features + let unstable_removed_features = crates + .iter() + .flat_map(|&cnum| { + find_attr!(tcx, cnum.as_def_id(), UnstableRemoved(rem_features) => rem_features) + .into_iter() + .flatten() + }) + .collect::>(); + for (feature, span) in remaining_lib_features { - let suggestion = feature - .find_similar(&valid_feature_names) - .map(|(actual_name, _)| errors::MisspelledFeature { span, actual_name }); - tcx.dcx().emit_err(errors::UnknownFeature { span, feature, suggestion }); + if let Some(removed) = + unstable_removed_features.iter().find(|removed| removed.feature == feature) + { + tcx.dcx().emit_err(errors::FeatureRemoved { + span, + feature, + reason: removed.reason, + link: removed.link, + since: removed.since.to_string(), + }); + } else { + let suggestion = feature + .find_similar(&valid_feature_names) + .map(|(actual_name, _)| errors::MisspelledFeature { span, actual_name }); + tcx.dcx().emit_err(errors::UnknownFeature { span, feature, suggestion }); + } } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e7126cf70b57..80d1c91c81dd 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -641,7 +641,6 @@ compiler_move, concat, concat_bytes, - concat_idents, conservative_impl_trait, console, const_allocate, @@ -2185,6 +2184,7 @@ unstable_location_reason_default: "this crate is being loaded from the sysroot, an \ unstable location; did you mean to load this crate \ from crates.io via `Cargo.toml` instead?", + unstable_removed, untagged_unions, unused_imports, unwind, diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index e6467dd0546a..6b5d21e1046f 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -418,6 +418,13 @@ // tidy-alphabetical-end // #![default_lib_allocator] +// Removed features +#![unstable_removed( + feature = "concat_idents", + reason = "Replaced by the macro_metavar_expr_concat feature", + link = "https://github.com/rust-lang/rust/issues/29599#issuecomment-2986866250", + since = "1.90.0" +)] // The Rust prelude // The compiler expects the prelude definition to be defined before its use statement. diff --git a/src/doc/rustc-dev-guide/src/stability.md b/src/doc/rustc-dev-guide/src/stability.md index f2f2dd909fae..93c59675d893 100644 --- a/src/doc/rustc-dev-guide/src/stability.md +++ b/src/doc/rustc-dev-guide/src/stability.md @@ -23,6 +23,9 @@ The `unstable` attribute infects all sub-items, where the attribute doesn't have to be reapplied. So if you apply this to a module, all items in the module will be unstable. +If you rename a feature, you can add `old_name = "old_name"` to produce a +useful error message. + You can make specific sub-items stable by using the `#[stable]` attribute on them. The stability scheme works similarly to how `pub` works. You can have public functions of nonpublic modules and you can have stable functions in @@ -189,4 +192,11 @@ Currently, the items that can be annotated with `#[unstable_feature_bound]` are: - free function - trait +## renamed and removed features +Unstable features can get renamed and removed. If you rename a feature, you can add `old_name = "old_name"` to the `#[unstable]` attribute. +If you remove a feature, the `#!unstable_removed(feature = "foo", reason = "brief description", link = "link", since = "1.90.0")` +attribute should be used to produce a good error message for users of the removed feature. + +The `link` field can be used to link to the most relevant information on the removal of the feature such as a GitHub issue, comment or PR. + [blog]: https://www.ralfj.de/blog/2018/07/19/const.html diff --git a/tests/ui/attributes/auxiliary/unstable_removed_feature.rs b/tests/ui/attributes/auxiliary/unstable_removed_feature.rs new file mode 100644 index 000000000000..3944ef35f8fe --- /dev/null +++ b/tests/ui/attributes/auxiliary/unstable_removed_feature.rs @@ -0,0 +1,9 @@ +#![feature(staged_api)] +#![stable(feature = "unstable_removed_test", since = "1.0.0")] + +#![unstable_removed( + feature="old_feature", + reason="deprecated", + link="https://github.com/rust-lang/rust/issues/141617", + since="1.92.0" +)] diff --git a/tests/ui/attributes/malformed-unstable-removed.rs b/tests/ui/attributes/malformed-unstable-removed.rs new file mode 100644 index 000000000000..d2ba10154bc9 --- /dev/null +++ b/tests/ui/attributes/malformed-unstable-removed.rs @@ -0,0 +1,16 @@ +#![feature(staged_api)] + +#![unstable_removed(feature = "old_feature")] +//~^ ERROR: malformed `unstable_removed` attribute + +#![unstable_removed(invalid = "old_feature")] +//~^ ERROR: malformed `unstable_removed` attribute + +#![unstable_removed("invalid literal")] +//~^ ERROR: malformed `unstable_removed` attribute + +#![unstable_removed = "invalid literal"] +//~^ ERROR: malformed `unstable_removed` attribute + +#![stable(feature="main", since="1.0.0")] +fn main() {} diff --git a/tests/ui/attributes/malformed-unstable-removed.stderr b/tests/ui/attributes/malformed-unstable-removed.stderr new file mode 100644 index 000000000000..02cf3e543c88 --- /dev/null +++ b/tests/ui/attributes/malformed-unstable-removed.stderr @@ -0,0 +1,40 @@ +error[E0539]: malformed `unstable_removed` attribute input + --> $DIR/malformed-unstable-removed.rs:3:1 + | +LL | #![unstable_removed(feature = "old_feature")] + | ^^^^^^^^^^^^^^^^^^^-------------------------^ + | | | + | | missing argument `reason = "..."` + | help: must be of the form: `#![unstable_removed(feature = "name", reason = "...", link = "...", since = "version")]` + +error[E0539]: malformed `unstable_removed` attribute input + --> $DIR/malformed-unstable-removed.rs:6:1 + | +LL | #![unstable_removed(invalid = "old_feature")] + | ^^^^^^^^^^^^^^^^^^^^-----------------------^^ + | | | + | | valid arguments are `feature`, `reason`, `link` or `since` + | help: must be of the form: `#![unstable_removed(feature = "name", reason = "...", link = "...", since = "version")]` + +error[E0565]: malformed `unstable_removed` attribute input + --> $DIR/malformed-unstable-removed.rs:9:1 + | +LL | #![unstable_removed("invalid literal")] + | ^^^^^^^^^^^^^^^^^^^^-----------------^^ + | | | + | | didn't expect a literal here + | help: must be of the form: `#![unstable_removed(feature = "name", reason = "...", link = "...", since = "version")]` + +error[E0539]: malformed `unstable_removed` attribute input + --> $DIR/malformed-unstable-removed.rs:12:1 + | +LL | #![unstable_removed = "invalid literal"] + | ^^^^^^^^^^^^^^^^^^^^-------------------^ + | | | + | | expected this to be a list + | help: must be of the form: `#![unstable_removed(feature = "name", reason = "...", link = "...", since = "version")]` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0539, E0565. +For more information about an error, try `rustc --explain E0539`. diff --git a/tests/ui/attributes/unstable_removed.rs b/tests/ui/attributes/unstable_removed.rs new file mode 100644 index 000000000000..7a9330a1a93e --- /dev/null +++ b/tests/ui/attributes/unstable_removed.rs @@ -0,0 +1,19 @@ +//@ aux-build:unstable_removed_feature.rs + +#![feature(old_feature)] +//~^ ERROR: feature `old_feature` has been removed + +#![feature(concat_idents)] +//~^ ERROR: feature `concat_idents` has been removed + +#![unstable_removed( +//~^ ERROR: stability attributes may not be used outside of the standard library + feature = "old_feature", + reason = "a good one", + link = "https://github.com/rust-lang/rust/issues/141617", + since="1.92.0" +)] + +extern crate unstable_removed_feature; + +fn main() {} diff --git a/tests/ui/attributes/unstable_removed.stderr b/tests/ui/attributes/unstable_removed.stderr new file mode 100644 index 000000000000..e9c81b833f40 --- /dev/null +++ b/tests/ui/attributes/unstable_removed.stderr @@ -0,0 +1,34 @@ +error[E0734]: stability attributes may not be used outside of the standard library + --> $DIR/unstable_removed.rs:9:1 + | +LL | / #![unstable_removed( +LL | | +LL | | feature = "old_feature", +LL | | reason = "a good one", +LL | | link = "https://github.com/rust-lang/rust/issues/141617", +LL | | since="1.92.0" +LL | | )] + | |__^ + +error[E0557]: feature `old_feature` has been removed + --> $DIR/unstable_removed.rs:3:12 + | +LL | #![feature(old_feature)] + | ^^^^^^^^^^^ feature has been removed + | + = note: removed in 1.92.0; see for more information + = note: deprecated + +error[E0557]: feature `concat_idents` has been removed + --> $DIR/unstable_removed.rs:6:12 + | +LL | #![feature(concat_idents)] + | ^^^^^^^^^^^^^ feature has been removed + | + = note: removed in 1.90.0; see for more information + = note: Replaced by the macro_metavar_expr_concat feature + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0557, E0734. +For more information about an error, try `rustc --explain E0557`.