diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index e35c10996ceb2..10fde08859a96 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -1,6 +1,7 @@ use std::num::NonZero; use rustc_errors::ErrorGuaranteed; +use rustc_hir::attrs::UnstableRemovedFeature; use rustc_hir::target::GenericParamKind; use rustc_hir::{ DefaultBodyStability, MethodKind, PartialConstStability, Stability, StabilityLevel, @@ -477,3 +478,88 @@ 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 { + cx.expected_list(cx.attr_span, args); + return None; + }; + + for param in list.mixed() { + let Some(param) = param.meta_item() else { + cx.unexpected_literal(param.span()); + return None; + }; + + let Some(word) = param.path().word() else { + cx.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.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.missing_name_value(list.span, sym::feature); + return None; + }; + let Some(reason) = reason else { + cx.missing_name_value(list.span, sym::reason); + return None; + }; + let Some(link) = link else { + cx.missing_name_value(list.span, sym::link); + return None; + }; + let Some(since) = since else { + cx.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 190568bed508d..d6c9c7fcfe82c 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -173,6 +173,7 @@ attribute_parsers!( Combine, Combine, Combine, + Combine, // tidy-alphabetical-end // tidy-alphabetical-start @@ -588,6 +589,10 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> { self.emit_parse_error(span, AttributeParseErrorReason::ExpectedNameValue(name)) } + pub(crate) fn missing_name_value(&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(&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 7c2044ec235a7..dd214ecebe3ec 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -571,6 +571,7 @@ pub(crate) enum AttributeParseErrorReason<'a> { ExpectedNonEmptyStringLiteral, UnexpectedLiteral, ExpectedNameValue(Option), + MissingNameValue(Symbol), DuplicateKey(Symbol), ExpectedSpecificArgument { possibilities: &'a [Symbol], @@ -694,6 +695,9 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { 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 3a2f548902d11..9932a8d8bafcb 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -927,6 +927,11 @@ pub static BUILTIN_ATTRIBUTES: &[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 1e08ad1384ccf..7508fb7c250c7 100644 --- a/compiler/rustc_feature/src/removed.rs +++ b/compiler/rustc_feature/src/removed.rs @@ -310,18 +310,4 @@ 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 e8476c3d8c73b..7b1cee3450a47 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -894,6 +894,14 @@ impl fmt::Display for AutoDiffItem { } } +#[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 @@ -1632,6 +1640,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 27128f6996370..945077ac7abe9 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -196,6 +196,7 @@ impl AttributeKind { 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 bec6ab7e83551..1c33d6f7884b9 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -383,6 +383,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | 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 228f21c81b947..877a0f0485e17 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -987,6 +987,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 3193bf8ac0011..deb7c49f45cc8 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -1098,7 +1098,7 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { 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() }) @@ -1106,11 +1106,33 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { 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 48cdf46b821f9..a6b1b399f446e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -637,7 +637,6 @@ symbols! { compiler_move, concat, concat_bytes, - concat_idents, conservative_impl_trait, console, const_allocate, @@ -2158,6 +2157,7 @@ symbols! { 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 6fcb28edc7d84..2cf95b208807b 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -415,6 +415,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 f2f2dd909fae9..93c59675d8936 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 0000000000000..3944ef35f8fe7 --- /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 0000000000000..d2ba10154bc96 --- /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 0000000000000..02cf3e543c887 --- /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 0000000000000..7a9330a1a93eb --- /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 0000000000000..e9c81b833f409 --- /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`.