middle: add implies_by to #[unstable]

If part of a feature is stabilized and a new feature is added for the
remaining parts, then the `implied_by` attribute can be used to indicate
which now-stable feature previously contained a item. If the now-stable
feature is still active (if the user has only just updated rustc, for
example) then there will not be an stability error for uses of the item
from the implied feature.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-07-13 13:10:37 +01:00
parent a1d5af24ec
commit 224aec213d
12 changed files with 150 additions and 4 deletions

View File

@ -142,6 +142,26 @@ pub enum StabilityLevel {
/// Relevant `rust-lang/rust` issue.
issue: Option<NonZeroU32>,
is_soft: bool,
/// If part of a feature is stabilized and a new feature is added for the remaining parts,
/// then the `implied_by` attribute is used to indicate which now-stable feature previously
/// contained a item.
///
/// ```pseudo-Rust
/// #[unstable(feature = "foo", issue = "...")]
/// fn foo() {}
/// #[unstable(feature = "foo", issue = "...")]
/// fn foobar() {}
/// ```
///
/// ...becomes...
///
/// ```pseudo-Rust
/// #[stable(feature = "foo", since = "1.XX.X")]
/// fn foo() {}
/// #[unstable(feature = "foobar", issue = "...", implied_by = "foo")]
/// fn foobar() {}
/// ```
implied_by: Option<Symbol>,
},
/// `#[stable]`
Stable {
@ -256,6 +276,7 @@ fn find_stability_generic<'a, I>(
let mut issue = None;
let mut issue_num = None;
let mut is_soft = false;
let mut implied_by = None;
for meta in metas {
let Some(mi) = meta.meta_item() else {
handle_errors(
@ -321,6 +342,11 @@ fn find_stability_generic<'a, I>(
}
is_soft = true;
}
sym::implied_by => {
if !get(mi, &mut implied_by) {
continue 'outer;
}
}
_ => {
handle_errors(
&sess.parse_sess,
@ -345,7 +371,7 @@ fn find_stability_generic<'a, I>(
);
continue;
}
let level = Unstable { reason, issue: issue_num, is_soft };
let level = Unstable { reason, issue: issue_num, is_soft, implied_by };
if sym::unstable == meta_name {
stab = Some((Stability { level, feature }, attr.span));
} else {

View File

@ -423,7 +423,9 @@ pub fn eval_stability_allow_unstable(
match stability {
Some(Stability {
level: attr::Unstable { reason, issue, is_soft }, feature, ..
level: attr::Unstable { reason, issue, is_soft, implied_by },
feature,
..
}) => {
if span.allows_unstable(feature) {
debug!("stability: skipping span={:?} since it is internal", span);
@ -433,6 +435,13 @@ pub fn eval_stability_allow_unstable(
return EvalResult::Allow;
}
// If this item was previously part of a now-stabilized feature which is still
// active (i.e. the user hasn't removed the attribute for the stabilized feature
// yet) then allow use of this item.
if let Some(implied_by) = implied_by && self.features().active(implied_by) {
return EvalResult::Allow;
}
// When we're compiling the compiler itself we may pull in
// crates from crates.io, but those crates may depend on other
// crates also pulled in from crates.io. We want to ideally be

View File

@ -637,6 +637,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
reason: Some(Symbol::intern(reason)),
issue: NonZeroU32::new(27812),
is_soft: false,
implied_by: None,
},
feature: sym::rustc_private,
};

View File

@ -796,9 +796,16 @@ fn check_stability_and_deprecation(
) {
let span = path.span;
if let Some(stability) = &ext.stability {
if let StabilityLevel::Unstable { reason, issue, is_soft } = stability.level {
if let StabilityLevel::Unstable { reason, issue, is_soft, implied_by } = stability.level
{
let feature = stability.feature;
if !self.active_features.contains(&feature) && !span.allows_unstable(feature) {
let is_allowed = |feature| {
self.active_features.contains(&feature) || span.allows_unstable(feature)
};
let allowed_by_implication =
implied_by.map(|feature| is_allowed(feature)).unwrap_or(false);
if !is_allowed(feature) && !allowed_by_implication {
let lint_buffer = &mut self.lint_buffer;
let soft_handler =
|lint, span, msg: &_| lint_buffer.buffer_lint(lint, node_id, span, msg);

View File

@ -800,6 +800,7 @@
impl_lint_pass,
impl_macros,
impl_trait_in_bindings,
implied_by,
import,
import_shadowing,
imported_main,

View File

@ -0,0 +1,8 @@
#![feature(staged_api)]
#![stable(feature = "stability_attribute_implies", since = "1.0.0")]
#[stable(feature = "foo", since = "1.62.0")]
pub fn foo() {}
#[unstable(feature = "foobar", issue = "1", implied_by = "foo")]
pub fn foobar() {}

View File

@ -0,0 +1,13 @@
// aux-build:stability-attribute-implies.rs
// Tests that despite the `foobar` feature being implied by now-stable feature `foo`, if `foobar`
// isn't allowed in this crate then an error will be emitted.
extern crate stability_attribute_implies;
use stability_attribute_implies::{foo, foobar};
//~^ ERROR use of unstable library feature 'foobar'
fn main() {
foo(); // no error - stable
foobar(); //~ ERROR use of unstable library feature 'foobar'
}

View File

@ -0,0 +1,21 @@
error[E0658]: use of unstable library feature 'foobar'
--> $DIR/stability-attribute-implies-no-feature.rs:7:40
|
LL | use stability_attribute_implies::{foo, foobar};
| ^^^^^^
|
= note: see issue #1 <https://github.com/rust-lang/rust/issues/1> for more information
= help: add `#![feature(foobar)]` to the crate attributes to enable
error[E0658]: use of unstable library feature 'foobar'
--> $DIR/stability-attribute-implies-no-feature.rs:12:5
|
LL | foobar();
| ^^^^^^
|
= note: see issue #1 <https://github.com/rust-lang/rust/issues/1> for more information
= help: add `#![feature(foobar)]` to the crate attributes to enable
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,15 @@
// aux-build:stability-attribute-implies.rs
#![deny(stable_features)]
#![feature(foo)]
//~^ ERROR the feature `foo` has been stable since 1.62.0 and no longer requires an attribute to enable
// Tests that the use of `implied_by` in the `#[unstable]` attribute results in a diagnostic
// mentioning partial stabilization, and that given the implied unstable feature is unused (there
// is no `foobar` call), that the compiler suggests removing the flag.
extern crate stability_attribute_implies;
use stability_attribute_implies::foo;
fn main() {
foo();
}

View File

@ -0,0 +1,14 @@
error: the feature `foo` has been stable since 1.62.0 and no longer requires an attribute to enable
--> $DIR/stability-attribute-implies-using-stable.rs:3:12
|
LL | #![feature(foo)]
| ^^^
|
note: the lint level is defined here
--> $DIR/stability-attribute-implies-using-stable.rs:2:9
|
LL | #![deny(stable_features)]
| ^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,17 @@
// aux-build:stability-attribute-implies.rs
#![deny(stable_features)]
#![feature(foo)]
//~^ ERROR the feature `foo` has been stable since 1.62.0 and no longer requires an attribute to enable
// Tests that the use of `implied_by` in the `#[unstable]` attribute results in a diagnostic
// mentioning partial stabilization and that given the implied unstable feature is used (there is a
// `foobar` call), that the compiler suggests changing to that feature and doesn't error about its
// use.
extern crate stability_attribute_implies;
use stability_attribute_implies::{foo, foobar};
fn main() {
foo();
foobar(); // no error!
}

View File

@ -0,0 +1,14 @@
error: the feature `foo` has been stable since 1.62.0 and no longer requires an attribute to enable
--> $DIR/stability-attribute-implies-using-unstable.rs:3:12
|
LL | #![feature(foo)]
| ^^^
|
note: the lint level is defined here
--> $DIR/stability-attribute-implies-using-unstable.rs:2:9
|
LL | #![deny(stable_features)]
| ^^^^^^^^^^^^^^^
error: aborting due to previous error