mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Rollup merge of #153335 - Ozzy1423:removed-features, r=jdonszelmann
Add #![unstable_removed(..)] attribute to track removed features Adds the #![unstable_removed(..)] attribute to enable tracking removed library features. Produce an error when a removed attribute is used. Add a test that it works. For https://github.com/rust-lang/rust/issues/141617 r? @jyn514
This commit is contained in:
@@ -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<S: Stage>(
|
||||
(Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct UnstableRemovedParser;
|
||||
|
||||
impl<S: Stage> CombineAttributeParser<S> 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<Self::Item> = |items, _| AttributeKind::UnstableRemoved(items);
|
||||
|
||||
fn extend(
|
||||
cx: &mut AcceptContext<'_, '_, S>,
|
||||
args: &ArgParser,
|
||||
) -> impl IntoIterator<Item = Self::Item> {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,7 @@ mod late {
|
||||
Combine<RustcThenThisWouldNeedParser>,
|
||||
Combine<TargetFeatureParser>,
|
||||
Combine<UnstableFeatureBoundParser>,
|
||||
Combine<UnstableRemovedParser>,
|
||||
// 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))
|
||||
|
||||
@@ -568,6 +568,7 @@ pub(crate) enum AttributeParseErrorReason<'a> {
|
||||
ExpectedNonEmptyStringLiteral,
|
||||
ExpectedNotLiteral,
|
||||
ExpectedNameValue(Option<Symbol>),
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
);
|
||||
|
||||
@@ -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<UnstableRemovedFeature>),
|
||||
|
||||
/// Represents `#[used]`
|
||||
Used {
|
||||
used_by: UsedBy,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -385,6 +385,7 @@ fn check_attributes(
|
||||
| AttributeKind::ThreadLocal
|
||||
| AttributeKind::TypeLengthLimit { .. }
|
||||
| AttributeKind::UnstableFeatureBound(..)
|
||||
| AttributeKind::UnstableRemoved(..)
|
||||
| AttributeKind::Used { .. }
|
||||
| AttributeKind::WindowsSubsystem(..)
|
||||
// tidy-alphabetical-end
|
||||
|
||||
@@ -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`"
|
||||
|
||||
@@ -1097,7 +1097,7 @@ fn check_features<'tcx>(
|
||||
let lang_features =
|
||||
UNSTABLE_LANG_FEATURES.iter().map(|feature| feature.name).collect::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)]
|
||||
@@ -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() {}
|
||||
@@ -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`.
|
||||
@@ -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() {}
|
||||
@@ -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 <https://github.com/rust-lang/rust/issues/141617> 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 <https://github.com/rust-lang/rust/issues/29599#issuecomment-2986866250> 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`.
|
||||
Reference in New Issue
Block a user