Skip to content

Commit 35fd749

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 4a8a6cc commit 35fd749

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
@@ -266,13 +266,18 @@ pub fn find_stability(
266266
) -> Option<(Stability, StabilitySpans)> {
267267
let mut level: Option<StabilityLevel> = None;
268268
let mut stab_spans = StabilitySpans(smallvec![]);
269+
let mut features = smallvec![];
269270
let mut allowed_through_unstable_modules = false;
270271

271272
for attr in attrs {
272273
match attr.name_or_empty() {
273274
sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true,
274-
sym::unstable => try_add_unstability(sess, attr, &mut level, &mut stab_spans),
275-
sym::stable => try_add_stability(sess, attr, &mut level, &mut stab_spans),
275+
sym::unstable => {
276+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability)
277+
}
278+
sym::stable => {
279+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability)
280+
}
276281
_ => {}
277282
}
278283
}
@@ -301,15 +306,18 @@ pub fn find_const_stability(
301306
) -> Option<(ConstStability, StabilitySpans)> {
302307
let mut level: Option<StabilityLevel> = None;
303308
let mut stab_spans = StabilitySpans(smallvec![]);
309+
let mut features = smallvec![];
304310
let mut promotable = false;
305311

306312
for attr in attrs {
307313
match attr.name_or_empty() {
308314
sym::rustc_promotable => promotable = true,
309315
sym::rustc_const_unstable => {
310-
try_add_unstability(sess, attr, &mut level, &mut stab_spans)
316+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability)
317+
}
318+
sym::rustc_const_stable => {
319+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability)
311320
}
312-
sym::rustc_const_stable => try_add_stability(sess, attr, &mut level, &mut stab_spans),
313321
_ => {}
314322
}
315323
}
@@ -333,76 +341,63 @@ pub fn find_body_stability(
333341
) -> Option<(DefaultBodyStability, StabilitySpans)> {
334342
let mut level: Option<StabilityLevel> = None;
335343
let mut stab_spans = StabilitySpans(smallvec![]);
344+
let mut features = smallvec![];
336345

337346
for attr in attrs {
338347
if attr.has_name(sym::rustc_default_body_unstable) {
339-
try_add_unstability(sess, attr, &mut level, &mut stab_spans);
348+
add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability);
340349
}
341350
}
342351

343352
Some((DefaultBodyStability { level: level? }, stab_spans))
344353
}
345354

346-
/// Collects stability info from one `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
347-
/// attribute, `attr`. Emits an error if the info it collects is inconsistent.
348-
fn try_add_unstability(
355+
/// Collects stability info from one stability attribute, `attr`.
356+
/// Emits an error if multiple stability levels are found for the same feature.
357+
fn add_level(
349358
sess: &Session,
350359
attr: &Attribute,
351-
level: &mut Option<StabilityLevel>,
360+
total_level: &mut Option<StabilityLevel>,
352361
stab_spans: &mut StabilitySpans,
362+
features: &mut SmallVec<[Symbol; 1]>,
363+
parse_level: impl FnOnce(&Session, &Attribute) -> Option<(Symbol, StabilityLevel)>,
353364
) {
354365
use StabilityLevel::*;
355366

356-
match level {
357-
// adding #[unstable] to an item with #[stable] is not permitted
358-
Some(Stable { .. }) => {
359-
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
360-
}
361-
// if other unstable attributes have been found, attempt to merge them
362-
Some(Unstable { unstables, is_soft })
363-
if let Some(Unstable { unstables: new_unstable, is_soft: new_soft }) =
364-
parse_unstability(sess, attr) =>
365-
{
366-
// sanity check: is this the only unstable attr of its kind for its feature?
367-
// FIXME(dianne): should this have a new error associated with it or is "multiple
368-
// stability levels" clear enough, given an update to E0544.md?
369-
// should MultipleStabilityLevels have more fields for diagnostics?
370-
if unstables.iter().any(|u| new_unstable.iter().any(|v| u.feature == v.feature)) {
371-
sess.dcx()
372-
.emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
373-
return;
374-
}
375-
unstables.extend(new_unstable.clone());
367+
let Some((feature, feature_level)) = parse_level(sess, attr) else { return };
368+
369+
// sanity check: is this the only stability level of its kind for its feature?
370+
if features.contains(&feature) {
371+
sess.dcx()
372+
.emit_err(session_diagnostics::MultipleStabilityLevels { feature, span: attr.span });
373+
}
374+
features.push(feature);
375+
stab_spans.0.push((feature_level.clone(), attr.span));
376+
377+
match (total_level, feature_level) {
378+
(level @ None, new_level) => *level = Some(new_level),
379+
// if multiple unstable attributes have been found, merge them
380+
(
381+
Some(Unstable { unstables, is_soft }),
382+
Unstable { unstables: new_unstable, is_soft: new_soft },
383+
) => {
384+
unstables.extend(new_unstable);
376385
// Make the unstability soft if any unstable attributes are marked 'soft'; if an
377386
// unstable item is allowed in stable rust, another attribute shouldn't break that.
378387
// FIXME(dianne): should there be a check that all unstables are soft if any are?
379388
*is_soft |= new_soft;
380-
stab_spans.0.push((Unstable { unstables: new_unstable, is_soft: new_soft }, attr.span));
381389
}
382-
// if this is the first unstability of its kind on an item, collect it
383-
None if let Some(new_level) = parse_unstability(sess, attr) => {
384-
*level = Some(new_level.clone());
385-
stab_spans.0.push((new_level, attr.span));
390+
// an item with some stable and some unstable features is unstable
391+
(Some(Unstable { .. }), Stable { .. }) => {}
392+
(Some(level @ Stable { .. }), new_level @ Unstable { .. }) => *level = new_level,
393+
// if multiple stable attributes have been found, use the most recent stabilization date
394+
(
395+
Some(Stable { since, allowed_through_unstable_modules }),
396+
Stable { since: new_since, allowed_through_unstable_modules: new_allowed },
397+
) => {
398+
*since = StableSince::max(*since, new_since);
399+
*allowed_through_unstable_modules |= new_allowed;
386400
}
387-
// if there was an error in `parse_unstability`, it's already been emitted; do nothing
388-
_ => {}
389-
}
390-
}
391-
392-
/// Collects stability info from a single `stable`/`rustc_const_stable` attribute, `attr`.
393-
/// Emits an error if the info it collects is inconsistent.
394-
fn try_add_stability(
395-
sess: &Session,
396-
attr: &Attribute,
397-
level: &mut Option<StabilityLevel>,
398-
stab_spans: &mut StabilitySpans,
399-
) {
400-
// at most one #[stable] attribute is permitted, and not when #[unstable] is present
401-
if level.is_some() {
402-
sess.dcx().emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span });
403-
} else if let Some(new_level) = parse_stability(sess, attr) {
404-
*level = Some(new_level.clone());
405-
stab_spans.0.push((new_level, attr.span));
406401
}
407402
}
408403

@@ -424,7 +419,7 @@ fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option<Symbol>) -
424419

425420
/// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and
426421
/// its stability information.
427-
fn parse_stability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
422+
fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> {
428423
let meta = attr.meta()?;
429424
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
430425

@@ -478,16 +473,17 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
478473
};
479474

480475
match feature {
481-
Ok(_feature) => {
482-
Some(StabilityLevel::Stable { since, allowed_through_unstable_modules: false })
483-
}
476+
Ok(feature) => Some((feature, StabilityLevel::Stable {
477+
since,
478+
allowed_through_unstable_modules: false,
479+
})),
484480
Err(ErrorGuaranteed { .. }) => None,
485481
}
486482
}
487483

488484
/// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable`
489485
/// attribute, and return the feature name and its stability information.
490-
fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel> {
486+
fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> {
491487
let meta = attr.meta()?;
492488
let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None };
493489

@@ -572,7 +568,7 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<StabilityLevel>
572568
issue: issue_num,
573569
implied_by,
574570
};
575-
Some(StabilityLevel::Unstable { unstables: smallvec![unstability], is_soft })
571+
Some((feature, StabilityLevel::Unstable { unstables: smallvec![unstability], is_soft }))
576572
}
577573
(Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
578574
}

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
@@ -230,8 +230,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
230230
&depr.as_ref().map(|(d, _)| d.since)
231231
&& let Some(stab_since) = stab.stable_since()
232232
{
233-
let &(_, span) =
234-
stab_spans.iter().nth(0).expect("expected one span with a stable attribute");
233+
let &(_, span) = stab_spans
234+
.iter()
235+
.find(|(level, _)| level.stable_since() == Some(stab_since))
236+
.expect("stabilization version should have an associated span");
235237

236238
match stab_since {
237239
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)