Skip to content

Commit 54d4392

Browse files
committed
support multiple stability attributes (a mix of stable and unstable)
An item is unstable if it has any unstable attributes, to make it easier to track which features library items depended on as they stabilize. This changes the text for E0544. Unfortunately, the example doesn't make much sense anymore. The way this merges stability levels together is made to work for stability-checking and rustdoc; as far as I can tell, only `rustc_passes::lib_features` needs them separate, and it extracts them itself.
1 parent 40aa900 commit 54d4392

File tree

8 files changed

+86
-80
lines changed

8 files changed

+86
-80
lines changed

compiler/rustc_attr/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ attr_multiple_item =
8383
multiple '{$item}' items
8484
8585
attr_multiple_stability_levels =
86-
multiple stability levels
86+
multiple stability levels for feature `{$feature}`
8787
8888
attr_non_ident_feature =
8989
'feature' is not an identifier

compiler/rustc_attr/src/builtin.rs

+55-59
Original file line numberDiff line numberDiff line change
@@ -336,13 +336,18 @@ pub fn find_stability(
336336
) -> Option<(Stability, StabilitySpans)> {
337337
let mut level: Option<StabilityLevel> = None;
338338
let mut stab_spans = StabilitySpans(smallvec![]);
339+
let mut features = smallvec![];
339340
let mut allowed_through_unstable_modules = false;
340341

341342
for attr in attrs {
342343
match attr.name_or_empty() {
343344
sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true,
344-
sym::unstable => try_add_unstability(sess, attr, &mut level, &mut stab_spans),
345-
sym::stable => try_add_stability(sess, attr, &mut level, &mut stab_spans),
345+
sym::unstable => {
346+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability)
347+
}
348+
sym::stable => {
349+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability)
350+
}
346351
_ => {}
347352
}
348353
}
@@ -375,6 +380,7 @@ pub fn find_const_stability(
375380
) -> Option<(ConstStability, ConstStabilitySpans)> {
376381
let mut level: Option<StabilityLevel> = None;
377382
let mut stab_spans = StabilitySpans(smallvec![]);
383+
let mut features = smallvec![];
378384
let mut promotable = false;
379385
let mut const_stable_indirect = None;
380386

@@ -383,9 +389,11 @@ pub fn find_const_stability(
383389
sym::rustc_promotable => promotable = true,
384390
sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
385391
sym::rustc_const_unstable => {
386-
try_add_unstability(sess, attr, &mut level, &mut stab_spans)
392+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability)
393+
}
394+
sym::rustc_const_stable => {
395+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability)
387396
}
388-
sym::rustc_const_stable => try_add_stability(sess, attr, &mut level, &mut stab_spans),
389397
_ => {}
390398
}
391399
}
@@ -441,76 +449,63 @@ pub fn find_body_stability(
441449
) -> Option<(DefaultBodyStability, StabilitySpans)> {
442450
let mut level: Option<StabilityLevel> = None;
443451
let mut stab_spans = StabilitySpans(smallvec![]);
452+
let mut features = smallvec![];
444453

445454
for attr in attrs {
446455
if attr.has_name(sym::rustc_default_body_unstable) {
447-
try_add_unstability(sess, attr, &mut level, &mut stab_spans);
456+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability);
448457
}
449458
}
450459

451460
Some((DefaultBodyStability { level: level? }, stab_spans))
452461
}
453462

454-
/// Collects stability info from one `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
455-
/// attribute, `attr`. Emits an error if the info it collects is inconsistent.
456-
fn try_add_unstability(
463+
/// Collects stability info from one stability attribute, `attr`.
464+
/// Emits an error if multiple stability levels are found for the same feature.
465+
fn add_level(
457466
sess: &Session,
458467
attr: &Attribute,
459-
level: &mut Option<StabilityLevel>,
468+
total_level: &mut Option<StabilityLevel>,
460469
stab_spans: &mut StabilitySpans,
470+
features: &mut SmallVec<[Symbol; 1]>,
471+
parse_level: impl FnOnce(&Session, &Attribute) -> Option<(Symbol, StabilityLevel)>,
461472
) {
462473
use StabilityLevel::*;
463474

464-
match level {
465-
// adding #[unstable] to an item with #[stable] is not permitted
466-
Some(Stable { .. }) => {
467-
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
468-
}
469-
// if other unstable attributes have been found, attempt to merge them
470-
Some(Unstable { unstables, is_soft })
471-
if let Some(Unstable { unstables: new_unstable, is_soft: new_soft }) =
472-
parse_unstability(sess, attr) =>
473-
{
474-
// sanity check: is this the only unstable attr of its kind for its feature?
475-
// FIXME(dianne): should this have a new error associated with it or is "multiple
476-
// stability levels" clear enough, given an update to E0544.md?
477-
// should MultipleStabilityLevels have more fields for diagnostics?
478-
if unstables.iter().any(|u| new_unstable.iter().any(|v| u.feature == v.feature)) {
479-
sess.dcx()
480-
.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
481-
return;
482-
}
483-
unstables.extend(new_unstable.clone());
475+
let Some((feature, feature_level)) = parse_level(sess, attr) else { return };
476+
477+
// sanity check: is this the only stability level of its kind for its feature?
478+
if features.contains(&feature) {
479+
sess.dcx()
480+
.emit_err(session_diagnostics::MultipleStabilityLevels { feature, span: attr.span });
481+
}
482+
features.push(feature);
483+
stab_spans.0.push((feature_level.clone(), attr.span));
484+
485+
match (total_level, feature_level) {
486+
(level @ None, new_level) => *level = Some(new_level),
487+
// if multiple unstable attributes have been found, merge them
488+
(
489+
Some(Unstable { unstables, is_soft }),
490+
Unstable { unstables: new_unstable, is_soft: new_soft },
491+
) => {
492+
unstables.extend(new_unstable);
484493
// Make the unstability soft if any unstable attributes are marked 'soft'; if an
485494
// unstable item is allowed in stable rust, another attribute shouldn't break that.
486495
// FIXME(dianne): should there be a check that all unstables are soft if any are?
487496
*is_soft |= new_soft;
488-
stab_spans.0.push((Unstable { unstables: new_unstable, is_soft: new_soft }, attr.span));
489497
}
490-
// if this is the first unstability of its kind on an item, collect it
491-
None if let Some(new_level) = parse_unstability(sess, attr) => {
492-
*level = Some(new_level.clone());
493-
stab_spans.0.push((new_level, attr.span));
498+
// an item with some stable and some unstable features is unstable
499+
(Some(Unstable { .. }), Stable { .. }) => {}
500+
(Some(level @ Stable { .. }), new_level @ Unstable { .. }) => *level = new_level,
501+
// if multiple stable attributes have been found, use the most recent stabilization date
502+
(
503+
Some(Stable { since, allowed_through_unstable_modules }),
504+
Stable { since: new_since, allowed_through_unstable_modules: new_allowed },
505+
) => {
506+
*since = StableSince::max(*since, new_since);
507+
*allowed_through_unstable_modules |= new_allowed;
494508
}
495-
// if there was an error in `parse_unstability`, it's already been emitted; do nothing
496-
_ => {}
497-
}
498-
}
499-
500-
/// Collects stability info from a single `stable`/`rustc_const_stable` attribute, `attr`.
501-
/// Emits an error if the info it collects is inconsistent.
502-
fn try_add_stability(
503-
sess: &Session,
504-
attr: &Attribute,
505-
level: &mut Option<StabilityLevel>,
506-
stab_spans: &mut StabilitySpans,
507-
) {
508-
// at most one #[stable] attribute is permitted, and not when #[unstable] is present
509-
if level.is_some() {
510-
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
511-
} else if let Some(new_level) = parse_stability(sess, attr) {
512-
*level = Some(new_level.clone());
513-
stab_spans.0.push((new_level, attr.span));
514509
}
515510
}
516511

@@ -532,7 +527,7 @@ fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option<Symbol>) -
532527

533528
/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
534529
/// its stability information.
535-
fn parse_stability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
530+
fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> {
536531
let meta = attr.meta()?;
537532
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
538533

@@ -586,16 +581,17 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
586581
};
587582

588583
match feature {
589-
Ok(_feature) => {
590-
Some(StabilityLevel::Stable { since, allowed_through_unstable_modules: false })
591-
}
584+
Ok(feature) => Some((feature, StabilityLevel::Stable {
585+
since,
586+
allowed_through_unstable_modules: false,
587+
})),
592588
Err(ErrorGuaranteed { .. }) => None,
593589
}
594590
}
595591

596592
/// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
597593
/// attribute, and return the feature name and its stability information.
598-
fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
594+
fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> {
599595
let meta = attr.meta()?;
600596
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
601597

@@ -680,7 +676,7 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel>
680676
issue: issue_num,
681677
implied_by,
682678
};
683-
Some(StabilityLevel::Unstable { unstables: smallvec![unstability], is_soft })
679+
Some((feature, StabilityLevel::Unstable { unstables: smallvec![unstability], is_soft }))
684680
}
685681
(Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
686682
}

compiler/rustc_attr/src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// tidy-alphabetical-start
88
#![allow(internal_features)]
99
#![doc(rust_logo)]
10-
#![feature(if_let_guard)]
1110
#![feature(let_chains)]
1211
#![feature(rustdoc_internals)]
1312
#![warn(unreachable_pub)]

compiler/rustc_attr/src/session_diagnostics.rs

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ pub(crate) struct MissingNote {
7979
pub(crate) struct MultipleStabilityLevels {
8080
#[primary_span]
8181
pub span: Span,
82+
83+
pub feature: Symbol,
8284
}
8385

8486
#[derive(Diagnostic)]

compiler/rustc_error_codes/src/error_codes/E0544.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Multiple stability attributes were declared on the same item.
1+
Multiple stability attributes were declared for the same feature on one item.
22

33
Erroneous code example:
44

@@ -8,18 +8,19 @@ Erroneous code example:
88
#![stable(since = "1.0.0", feature = "rust1")]
99
1010
#[stable(feature = "rust1", since = "1.0.0")]
11-
#[stable(feature = "test", since = "2.0.0")] // invalid
11+
#[stable(feature = "rust1", since = "1.0.0")] // invalid
1212
fn foo() {}
1313
```
1414

15-
To fix this issue, ensure that each item has at most one stability attribute.
15+
To fix this issue, ensure that each item has at most one stability attribute per
16+
feature.
1617

1718
```
1819
#![feature(staged_api)]
1920
#![allow(internal_features)]
2021
#![stable(since = "1.0.0", feature = "rust1")]
2122
22-
#[stable(feature = "test", since = "2.0.0")] // ok!
23+
#[stable(feature = "rust1", since = "1.0.0")] // ok!
2324
fn foo() {}
2425
```
2526

compiler/rustc_passes/src/stability.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
263263
&depr.as_ref().map(|(d, _)| d.since)
264264
&& let Some(stab_since) = stab.stable_since()
265265
{
266-
let &(_, span) =
267-
stab_spans.iter().nth(0).expect("expected one span with a stable attribute");
266+
let &(_, span) = stab_spans
267+
.iter()
268+
.find(|(level, _)| level.stable_since() == Some(stab_since))
269+
.expect("stabilization version should have an associated span");
268270

269271
match stab_since {
270272
StableSince::Current => {

tests/ui/stability-attribute/stability-attribute-sanity.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,23 @@ mod missing_version {
4545
fn f3() { }
4646
}
4747

48-
#[unstable(feature = "b", issue = "none")]
49-
#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels [E0544]
48+
#[stable(feature = "a", since = "4.4.4")]
49+
#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels for feature `a` [E0544]
5050
fn multiple1() { }
5151

5252
#[unstable(feature = "b", issue = "none")]
53-
#[unstable(feature = "b", issue = "none")] //~ ERROR multiple stability levels [E0544]
53+
#[unstable(feature = "b", issue = "none")] //~ ERROR multiple stability levels for feature `b` [E0544]
5454
fn multiple2() { }
5555

56-
#[stable(feature = "a", since = "4.4.4")]
57-
#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels [E0544]
58-
fn multiple3() { }
56+
#[unstable(feature = "c", issue = "none")]
57+
#[stable(feature = "c", since = "4.4.4")] //~ ERROR multiple stability levels for feature `c` [E0544]
58+
fn multiple3() { } //~| ERROR feature `c` is declared stable, but was previously declared unstable
5959

6060
#[stable(feature = "e", since = "b")] //~ ERROR 'since' must be a Rust version number, such as "1.31.0"
6161
#[deprecated(since = "5.5.5", note = "text")]
6262
#[deprecated(since = "5.5.5", note = "text")] //~ ERROR multiple `deprecated` attributes
6363
#[rustc_const_unstable(feature = "d", issue = "none")]
64-
#[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels
64+
#[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels for feature `d`
6565
pub const fn multiple4() { }
6666

6767
#[stable(feature = "a", since = "1.0.0")] //~ ERROR feature `a` is declared stable since 1.0.0

tests/ui/stability-attribute/stability-attribute-sanity.stderr

+12-6
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,22 @@ error[E0543]: missing 'note'
7676
LL | #[deprecated(since = "5.5.5")]
7777
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7878

79-
error[E0544]: multiple stability levels
79+
error[E0544]: multiple stability levels for feature `a`
8080
--> $DIR/stability-attribute-sanity.rs:49:1
8181
|
8282
LL | #[stable(feature = "a", since = "4.4.4")]
8383
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8484

85-
error[E0544]: multiple stability levels
85+
error[E0544]: multiple stability levels for feature `b`
8686
--> $DIR/stability-attribute-sanity.rs:53:1
8787
|
8888
LL | #[unstable(feature = "b", issue = "none")]
8989
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9090

91-
error[E0544]: multiple stability levels
91+
error[E0544]: multiple stability levels for feature `c`
9292
--> $DIR/stability-attribute-sanity.rs:57:1
9393
|
94-
LL | #[stable(feature = "a", since = "4.4.4")]
94+
LL | #[stable(feature = "c", since = "4.4.4")]
9595
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9696

9797
error: 'since' must be a Rust version number, such as "1.31.0"
@@ -100,7 +100,7 @@ error: 'since' must be a Rust version number, such as "1.31.0"
100100
LL | #[stable(feature = "e", since = "b")]
101101
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
102102

103-
error[E0544]: multiple stability levels
103+
error[E0544]: multiple stability levels for feature `d`
104104
--> $DIR/stability-attribute-sanity.rs:64:1
105105
|
106106
LL | #[rustc_const_unstable(feature = "d", issue = "none")]
@@ -118,13 +118,19 @@ error[E0549]: deprecated attribute must be paired with either stable or unstable
118118
LL | #[deprecated(since = "5.5.5", note = "text")]
119119
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
120120

121+
error[E0711]: feature `c` is declared stable, but was previously declared unstable
122+
--> $DIR/stability-attribute-sanity.rs:57:1
123+
|
124+
LL | #[stable(feature = "c", since = "4.4.4")]
125+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
126+
121127
error[E0711]: feature `a` is declared stable since 1.0.0, but was previously declared stable since 4.4.4
122128
--> $DIR/stability-attribute-sanity.rs:67:1
123129
|
124130
LL | #[stable(feature = "a", since = "1.0.0")]
125131
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
126132

127-
error: aborting due to 20 previous errors
133+
error: aborting due to 21 previous errors
128134

129135
Some errors have detailed explanations: E0539, E0541, E0542, E0543, E0544, E0546, E0547, E0549, E0711.
130136
For more information about an error, try `rustc --explain E0539`.

0 commit comments

Comments
 (0)