Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/stability.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -477,3 +478,88 @@ 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 {
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, &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.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 })
}
}
5 changes: 5 additions & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ attribute_parsers!(
Combine<RustcThenThisWouldNeedParser>,
Combine<TargetFeatureParser>,
Combine<UnstableFeatureBoundParser>,
Combine<UnstableRemovedParser>,
// tidy-alphabetical-end

// tidy-alphabetical-start
Expand Down Expand Up @@ -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))
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ pub(crate) enum AttributeParseErrorReason<'a> {
ExpectedNonEmptyStringLiteral,
UnexpectedLiteral,
ExpectedNameValue(Option<Symbol>),
MissingNameValue(Symbol),
DuplicateKey(Symbol),
ExpectedSpecificArgument {
possibilities: &'a [Symbol],
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 0 additions & 14 deletions compiler/rustc_feature/src/removed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -------------------------------------------------------------------------
);
11 changes: 11 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1632,6 +1640,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,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ impl AttributeKind {
TrackCaller(..) => Yes,
TypeLengthLimit { .. } => No,
UnstableFeatureBound(..) => No,
UnstableRemoved(..) => Yes,
Used { .. } => No,
WindowsSubsystem(..) => No,
// tidy-alphabetical-end
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::ThreadLocal
| AttributeKind::TypeLengthLimit { .. }
| AttributeKind::UnstableFeatureBound(..)
| AttributeKind::UnstableRemoved(..)
| AttributeKind::Used { .. }
| AttributeKind::WindowsSubsystem(..)
// tidy-alphabetical-end
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`"
Expand Down
32 changes: 27 additions & 5 deletions compiler/rustc_passes/src/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,19 +1098,41 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
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()
})
.collect::<Vec<_>>();

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 });
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,6 @@ symbols! {
compiler_move,
concat,
concat_bytes,
concat_idents,
conservative_impl_trait,
console,
const_allocate,
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions src/doc/rustc-dev-guide/src/stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
9 changes: 9 additions & 0 deletions tests/ui/attributes/auxiliary/unstable_removed_feature.rs
Original file line number Diff line number Diff line change
@@ -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"
)]
16 changes: 16 additions & 0 deletions tests/ui/attributes/malformed-unstable-removed.rs
Original file line number Diff line number Diff line change
@@ -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() {}
40 changes: 40 additions & 0 deletions tests/ui/attributes/malformed-unstable-removed.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading
Loading