From 27dadd43a7d2c27ddfc2277487187bdcc1267ba2 Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 16 Sep 2024 03:52:15 -0700 Subject: [PATCH 01/10] Refactor stability structs This moves stability structs' `feature` fields into `StabilityLevel::Unstable` and `ConstStabilityLevel::Unstable`, in preparation to support multiple unstable attributes on items. Seemingly, the `feature` field isn't used with the `StabilityLevel::Stable` variant, so I haven't included it. `rustc_passes::lib_features` uses the 'feature' meta-item for 'stable' attributes, but it extracts them itself, rather than relying on `rustc_attr`. --- compiler/rustc_attr/src/builtin.rs | 108 ++++++++---------- .../src/check_consts/check.rs | 12 +- compiler/rustc_middle/src/middle/stability.rs | 13 +-- compiler/rustc_passes/src/stability.rs | 32 +++--- compiler/rustc_resolve/src/macros.rs | 6 +- 5 files changed, 76 insertions(+), 95 deletions(-) diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 94f9727eb7fbe..deba46f9380e8 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -70,7 +70,6 @@ pub enum OptimizeAttr { #[derive(HashStable_Generic)] pub struct Stability { pub level: StabilityLevel, - pub feature: Symbol, } impl Stability { @@ -88,11 +87,11 @@ impl Stability { } /// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes. +/// For details see [the dev guide](https://rustc-dev-guide.rust-lang.org/stability.html#rustc_const_unstable). #[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct ConstStability { pub level: StabilityLevel, - pub feature: Symbol, /// This is true iff the `const_stable_indirect` attribute is present. pub const_stable_indirect: bool, /// whether the function has a `#[rustc_promotable]` attribute @@ -114,7 +113,6 @@ impl ConstStability { #[derive(HashStable_Generic)] pub struct DefaultBodyStability { pub level: StabilityLevel, - pub feature: Symbol, } /// The available stability levels. @@ -123,31 +121,11 @@ pub struct DefaultBodyStability { pub enum StabilityLevel { /// `#[unstable]` Unstable { + /// The information unique to each `#[unstable]` attribute + unstables: Unstability, /// Reason for the current stability level. reason: UnstableReason, - /// Relevant `rust-lang/rust` issue. - issue: Option>, 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 an 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, }, /// `#[stable]` Stable { @@ -185,6 +163,35 @@ impl StabilityLevel { } } +/// An instance of an `#[unstable]`, `#[rustc_const_unstable]`, or similar attribute +#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] +#[derive(HashStable_Generic)] +pub struct Unstability { + pub feature: Symbol, + /// Relevant `rust-lang/rust` issue. + pub issue: Option>, + /// 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 an 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() {} + /// ``` + pub implied_by: Option, +} + #[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(HashStable_Generic)] pub enum UnstableReason { @@ -231,8 +238,8 @@ pub fn find_stability( break; } - if let Some((feature, level)) = parse_unstability(sess, attr) { - stab = Some((Stability { level, feature }, attr.span)); + if let Some(level) = parse_unstability(sess, attr) { + stab = Some((Stability { level }, attr.span)); } } sym::stable => { @@ -241,8 +248,8 @@ pub fn find_stability( .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); break; } - if let Some((feature, level)) = parse_stability(sess, attr) { - stab = Some((Stability { level, feature }, attr.span)); + if let Some(level) = parse_stability(sess, attr) { + stab = Some((Stability { level }, attr.span)); } } _ => {} @@ -290,14 +297,9 @@ pub fn find_const_stability( break; } - if let Some((feature, level)) = parse_unstability(sess, attr) { + if let Some(level) = parse_unstability(sess, attr) { const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, + ConstStability { level, const_stable_indirect: false, promotable: false }, attr.span, )); } @@ -308,14 +310,9 @@ pub fn find_const_stability( .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); break; } - if let Some((feature, level)) = parse_stability(sess, attr) { + if let Some(level) = parse_stability(sess, attr) { const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, + ConstStability { level, const_stable_indirect: false, promotable: false }, attr.span, )); } @@ -369,12 +366,7 @@ pub fn unmarked_crate_const_stab( // We enforce recursive const stability rules for those functions. let const_stable_indirect = attrs.iter().any(|a| a.name_or_empty() == sym::rustc_const_stable_indirect); - ConstStability { - feature: regular_stab.feature, - const_stable_indirect, - promotable: false, - level: regular_stab.level, - } + ConstStability { const_stable_indirect, promotable: false, level: regular_stab.level } } /// Collects stability info from `rustc_default_body_unstable` attributes in `attrs`. @@ -393,8 +385,8 @@ pub fn find_body_stability( break; } - if let Some((feature, level)) = parse_unstability(sess, attr) { - body_stab = Some((DefaultBodyStability { level, feature }, attr.span)); + if let Some(level) = parse_unstability(sess, attr) { + body_stab = Some((DefaultBodyStability { level }, attr.span)); } } } @@ -420,7 +412,7 @@ fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option) - /// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and /// its stability information. -fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> { +fn parse_stability(sess: &Session, attr: &Attribute) -> Option { let meta = attr.meta()?; let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None }; @@ -474,9 +466,8 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit }; match feature { - Ok(feature) => { - let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false }; - Some((feature, level)) + Ok(_feature) => { + Some(StabilityLevel::Stable { since, allowed_through_unstable_modules: false }) } Err(ErrorGuaranteed { .. }) => None, } @@ -484,7 +475,7 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit /// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` /// attribute, and return the feature name and its stability information. -fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> { +fn parse_unstability(sess: &Session, attr: &Attribute) -> Option { let meta = attr.meta()?; let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None }; @@ -564,12 +555,11 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil match (feature, issue) { (Ok(feature), Ok(_)) => { let level = StabilityLevel::Unstable { + unstables: Unstability { feature, issue: issue_num, implied_by }, reason: UnstableReason::from_opt_reason(reason), - issue: issue_num, is_soft, - implied_by, }; - Some((feature, level)) + Some(level) } (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None, } diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 8e96d365bebec..7189e17256770 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -6,7 +6,7 @@ use std::mem; use std::num::NonZero; use std::ops::Deref; -use rustc_attr::{ConstStability, StabilityLevel}; +use rustc_attr::{ConstStability, StabilityLevel, Unstability}; use rustc_errors::{Diag, ErrorGuaranteed}; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem}; @@ -744,13 +744,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } Some(ConstStability { - level: StabilityLevel::Unstable { .. }, - feature, + level: StabilityLevel::Unstable { unstables, .. }, .. }) => { self.check_op(ops::IntrinsicUnstable { name: intrinsic.name, - feature, + feature: unstables.feature, const_stable_indirect: is_const_stable, }); } @@ -796,10 +795,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } Some(ConstStability { - level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. }, - feature, + level: StabilityLevel::Unstable { unstables, .. }, .. }) => { + let Unstability { feature, implied_by: implied_feature, issue, .. } = + unstables; // An unstable const fn with a feature gate. let callee_safe_to_expose_on_stable = is_safe_to_expose_on_stable_const_fn(tcx, callee); diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 94d13021612be..1b5fe661b18da 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -391,11 +391,8 @@ impl<'tcx> TyCtxt<'tcx> { ); match stability { - Some(Stability { - level: attr::Unstable { reason, issue, is_soft, implied_by }, - feature, - .. - }) => { + Some(Stability { level: attr::Unstable { unstables, reason, is_soft } }) => { + let attr::Unstability { feature, issue, implied_by } = unstables; if span.allows_unstable(feature) { debug!("stability: skipping span={:?} since it is internal", span); return EvalResult::Allow; @@ -474,10 +471,8 @@ impl<'tcx> TyCtxt<'tcx> { ); match stability { - Some(DefaultBodyStability { - level: attr::Unstable { reason, issue, is_soft, .. }, - feature, - }) => { + Some(DefaultBodyStability { level: attr::Unstable { unstables, reason, is_soft } }) => { + let attr::Unstability { feature, issue, .. } = unstables; if span.allows_unstable(feature) { debug!("body stability: skipping span={:?} since it is internal", span); return EvalResult::Allow; diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 4a793f1875ec8..a85e580b660a4 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -6,7 +6,7 @@ use std::num::NonZero; use rustc_attr::{ self as attr, ConstStability, DeprecatedSince, Stability, StabilityLevel, StableSince, - Unstable, UnstableReason, VERSION_PLACEHOLDER, + Unstability, Unstable, UnstableReason, VERSION_PLACEHOLDER, }; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::unord::{ExtendUnord, UnordMap, UnordSet}; @@ -224,15 +224,15 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // Stable *language* features shouldn't be used as unstable library features. // (Not doing this for stable library features is checked by tidy.) - if let Stability { level: Unstable { .. }, feature } = stab { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() { + if let Unstable { unstables, .. } = stab.level { + if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == unstables.feature).is_some() { self.tcx .dcx() .emit_err(errors::UnstableAttrForAlreadyStableFeature { span, item_sp }); } } - if let Stability { level: Unstable { implied_by: Some(implied_by), .. }, feature } = - stab + if let Unstable { unstables, .. } = stab.level + && let Unstability { feature, implied_by: Some(implied_by), .. } = unstables { self.index.implications.insert(implied_by, feature); } @@ -278,10 +278,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // Stable *language* features shouldn't be used as unstable library features. // (Not doing this for stable library features is checked by tidy.) - if let Some((ConstStability { level: Unstable { .. }, feature, .. }, const_span)) = + if let Some((ConstStability { level: Unstable { unstables, .. }, .. }, const_span)) = const_stab { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() { + if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == unstables.feature).is_some() { self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature { span: const_span, item_sp, @@ -304,7 +304,6 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { const_stable_indirect: true, promotable: false, level: inherit_regular_stab.level, - feature: inherit_regular_stab.feature, }); } @@ -313,13 +312,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { self.index.const_stab_map.insert(def_id, *const_stab); }); - if let Some(ConstStability { - level: Unstable { implied_by: Some(implied_by), .. }, - feature, - .. - }) = const_stab + if let Some(ConstStability { level: Unstable { unstables, .. }, .. }) = const_stab + && let Some(implied_by) = unstables.implied_by { - self.index.implications.insert(implied_by, feature); + self.index.implications.insert(implied_by, unstables.feature); } // `impl const Trait for Type` items forward their const stability to their @@ -702,12 +698,14 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { if tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { let stability = Stability { level: attr::StabilityLevel::Unstable { + unstables: attr::Unstability { + feature: sym::rustc_private, + issue: NonZero::new(27812), + implied_by: None, + }, reason: UnstableReason::Default, - issue: NonZero::new(27812), is_soft: false, - implied_by: None, }, - feature: sym::rustc_private, }; annotator.parent_stab = Some(stability); } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 0b4d0e04c295c..f2dd079ad70c4 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1003,10 +1003,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) { let span = path.span; if let Some(stability) = &ext.stability { - if let StabilityLevel::Unstable { reason, issue, is_soft, implied_by } = stability.level - { - let feature = stability.feature; - + if let StabilityLevel::Unstable { unstables, reason, is_soft } = stability.level { + let rustc_attr::Unstability { feature, issue, implied_by } = unstables; let is_allowed = |feature| self.tcx.features().enabled(feature) || span.allows_unstable(feature); let allowed_by_implication = implied_by.is_some_and(|feature| is_allowed(feature)); From 459ff1b51b1ed230c17b855d7df572c4f1d5a1f6 Mon Sep 17 00:00:00 2001 From: dianne Date: Mon, 16 Sep 2024 06:07:59 -0700 Subject: [PATCH 02/10] factor out some commonalities in the `find_stability` family of functions the logic for adding unstable attrs gets a bit messier when supporting multiple instances thereof. this keeps that from being duplicated in 3 places. --- compiler/rustc_attr/src/builtin.rs | 146 +++++++++++++---------------- 1 file changed, 67 insertions(+), 79 deletions(-) diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index deba46f9380e8..7c4b368891913 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -225,46 +225,31 @@ pub fn find_stability( attrs: &[Attribute], item_sp: Span, ) -> Option<(Stability, Span)> { - let mut stab: Option<(Stability, Span)> = None; + let mut level: Option<(StabilityLevel, Span)> = None; let mut allowed_through_unstable_modules = false; for attr in attrs { match attr.name_or_empty() { sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true, sym::unstable => { - if stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); + if try_add_unstability(sess, attr, &mut level).is_err() { break; } - - if let Some(level) = parse_unstability(sess, attr) { - stab = Some((Stability { level }, attr.span)); - } } sym::stable => { - if stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); + if try_add_stability(sess, attr, &mut level).is_err() { break; } - if let Some(level) = parse_stability(sess, attr) { - stab = Some((Stability { level }, attr.span)); - } } _ => {} } } if allowed_through_unstable_modules { - match &mut stab { - Some(( - Stability { - level: StabilityLevel::Stable { allowed_through_unstable_modules, .. }, - .. - }, - _, - )) => *allowed_through_unstable_modules = true, + match &mut level { + Some((StabilityLevel::Stable { allowed_through_unstable_modules, .. }, _)) => { + *allowed_through_unstable_modules = true + } _ => { sess.dcx() .emit_err(session_diagnostics::RustcAllowedUnstablePairing { span: item_sp }); @@ -272,7 +257,8 @@ pub fn find_stability( } } - stab + let (level, stab_sp) = level?; + Some((Stability { level }, stab_sp)) } /// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable` @@ -282,7 +268,7 @@ pub fn find_const_stability( attrs: &[Attribute], item_sp: Span, ) -> Option<(ConstStability, Span)> { - let mut const_stab: Option<(ConstStability, Span)> = None; + let mut level: Option<(StabilityLevel, Span)> = None; let mut promotable = false; let mut const_stable_indirect = false; @@ -291,67 +277,38 @@ pub fn find_const_stability( sym::rustc_promotable => promotable = true, sym::rustc_const_stable_indirect => const_stable_indirect = true, sym::rustc_const_unstable => { - if const_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); + if try_add_unstability(sess, attr, &mut level).is_err() { break; } - - if let Some(level) = parse_unstability(sess, attr) { - const_stab = Some(( - ConstStability { level, const_stable_indirect: false, promotable: false }, - attr.span, - )); - } } sym::rustc_const_stable => { - if const_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); + if try_add_stability(sess, attr, &mut level).is_err() { break; } - if let Some(level) = parse_stability(sess, attr) { - const_stab = Some(( - ConstStability { level, const_stable_indirect: false, promotable: false }, - attr.span, - )); - } } _ => {} } } // Merge promotable and const_stable_indirect into stability info - if promotable { - match &mut const_stab { - Some((stab, _)) => stab.promotable = promotable, - _ => { - _ = sess - .dcx() - .emit_err(session_diagnostics::RustcPromotablePairing { span: item_sp }) - } + if let Some((level, stab_sp)) = level { + if level.is_stable() && const_stable_indirect { + sess.dcx() + .emit_err(session_diagnostics::RustcConstStableIndirectPairing { span: item_sp }); + const_stable_indirect = false; } - } - if const_stable_indirect { - match &mut const_stab { - Some((stab, _)) => { - if stab.is_const_unstable() { - stab.const_stable_indirect = true; - } else { - _ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing { - span: item_sp, - }) - } - } - _ => { - // This function has no const stability attribute, but has `const_stable_indirect`. - // We ignore that; unmarked functions are subject to recursive const stability - // checks by default so we do carry out the user's intent. - } + let const_stab = ConstStability { level, const_stable_indirect, promotable }; + + Some((const_stab, stab_sp)) + } else { + if promotable { + sess.dcx().emit_err(session_diagnostics::RustcPromotablePairing { span: item_sp }); } + // This function has no const stability attribute, but may have `const_stable_indirect`. + // We ignore that; unmarked functions are subject to recursive const stability + // checks by default so we do carry out the user's intent. + None } - - const_stab } /// Calculates the const stability for a const function in a `-Zforce-unstable-if-unmarked` crate @@ -375,23 +332,54 @@ pub fn find_body_stability( sess: &Session, attrs: &[Attribute], ) -> Option<(DefaultBodyStability, Span)> { - let mut body_stab: Option<(DefaultBodyStability, Span)> = None; + let mut level: Option<(StabilityLevel, Span)> = None; for attr in attrs { if attr.has_name(sym::rustc_default_body_unstable) { - if body_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); + if try_add_unstability(sess, attr, &mut level).is_err() { break; } - - if let Some(level) = parse_unstability(sess, attr) { - body_stab = Some((DefaultBodyStability { level }, attr.span)); - } } } - body_stab + let (level, stab_sp) = level?; + Some((DefaultBodyStability { level }, stab_sp)) +} + +/// Collects stability info from one `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` +/// attribute, `attr`. Emits an error if the info it collects is inconsistent. +fn try_add_unstability( + sess: &Session, + attr: &Attribute, + level: &mut Option<(StabilityLevel, Span)>, +) -> Result<(), ErrorGuaranteed> { + if level.is_some() { + return Err(sess + .dcx() + .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span })); + } + if let Some(new_level) = parse_unstability(sess, attr) { + *level = Some((new_level, attr.span)); + } + Ok(()) +} + +/// Collects stability info from a single `stable`/`rustc_const_stable` attribute, `attr`. +/// Emits an error if the info it collects is inconsistent. +fn try_add_stability( + sess: &Session, + attr: &Attribute, + level: &mut Option<(StabilityLevel, Span)>, +) -> Result<(), ErrorGuaranteed> { + if level.is_some() { + return Err(sess + .dcx() + .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span })); + } + if let Some(new_level) = parse_stability(sess, attr) { + *level = Some((new_level, attr.span)); + } + Ok(()) } fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option) -> Option<()> { From 729ced5a926aa3785a05710a338c592f7c1914ae Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 10 Nov 2024 13:00:30 -0800 Subject: [PATCH 03/10] Allow multiple unstable features in diagnostics --- Cargo.lock | 1 + compiler/rustc_ast_passes/src/feature_gate.rs | 6 +-- compiler/rustc_feature/Cargo.toml | 1 + compiler/rustc_feature/src/lib.rs | 20 ++++++---- compiler/rustc_hir_analysis/src/check/mod.rs | 6 +-- compiler/rustc_lint/src/builtin.rs | 4 +- compiler/rustc_lint/src/levels.rs | 8 ++-- compiler/rustc_middle/src/middle/stability.rs | 7 ++-- compiler/rustc_session/src/errors.rs | 6 +-- compiler/rustc_session/src/parse.rs | 37 ++++++++++--------- 10 files changed, 53 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5025a1ba4f9c..a77d48106b4a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3683,6 +3683,7 @@ dependencies = [ name = "rustc_feature" version = "0.0.0" dependencies = [ + "either", "rustc_data_structures", "rustc_span", ] diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index be6f2c152a4d2..d43ecf40ad62f 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -1,9 +1,9 @@ use rustc_ast as ast; use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor}; use rustc_ast::{NodeId, PatKind, attr, token}; -use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features, GateIssue}; +use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features}; use rustc_session::Session; -use rustc_session::parse::{feature_err, feature_err_issue, feature_warn}; +use rustc_session::parse::{feature_err, feature_warn}; use rustc_span::source_map::Spanned; use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; @@ -81,7 +81,7 @@ impl<'a> PostExpansionVisitor<'a> { match abi::is_enabled(self.features, span, symbol_unescaped.as_str()) { Ok(()) => (), Err(abi::AbiDisabled::Unstable { feature, explain }) => { - feature_err_issue(&self.sess, feature, span, GateIssue::Language, explain).emit(); + feature_err(&self.sess, feature, span, explain).emit(); } Err(abi::AbiDisabled::Unrecognized) => { if self.sess.opts.pretty.map_or(true, |ppm| ppm.needs_hir()) { diff --git a/compiler/rustc_feature/Cargo.toml b/compiler/rustc_feature/Cargo.toml index 9df320e1279ed..5766b4a43dbf4 100644 --- a/compiler/rustc_feature/Cargo.toml +++ b/compiler/rustc_feature/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] # tidy-alphabetical-start +either = "1.5.0" rustc_data_structures = { path = "../rustc_data_structures" } rustc_span = { path = "../rustc_span" } # tidy-alphabetical-end diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 5d27b8f542cbb..1eb08c6e15496 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -97,7 +97,7 @@ impl UnstableFeatures { } } -fn find_lang_feature_issue(feature: Symbol) -> Option> { +pub fn find_lang_feature_issue(feature: Symbol) -> Option> { // Search in all the feature lists. if let Some(f) = UNSTABLE_LANG_FEATURES.iter().find(|f| f.name == feature) { return f.issue; @@ -120,15 +120,21 @@ const fn to_nonzero(n: Option) -> Option> { } } -pub enum GateIssue { +pub enum GateIssues { Language, - Library(Option>), + Library(Vec>), } -pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option> { - match issue { - GateIssue::Language => find_lang_feature_issue(feature), - GateIssue::Library(lib) => lib, +pub fn find_feature_issues( + features: &[Symbol], + issues: GateIssues, +) -> impl Iterator> + use<'_> { + use either::{Left, Right}; + match issues { + GateIssues::Language => { + Left(features.iter().flat_map(|&feature| find_lang_feature_issue(feature))) + } + GateIssues::Library(lib) => Right(lib.into_iter()), } } diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 375cbfd1c4fb8..60cdb3e9fcf03 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -320,11 +320,11 @@ fn default_body_is_unstable( let inject_span = item_did .as_local() .and_then(|id| tcx.crate_level_attribute_injection_span(tcx.local_def_id_to_hir_id(id))); - rustc_session::parse::add_feature_diagnostics_for_issue( + rustc_session::parse::add_feature_diagnostics_for_issues( &mut err, &tcx.sess, - feature, - rustc_feature::GateIssue::Library(issue), + &[feature], + rustc_feature::GateIssues::Library(Vec::from_iter(issue)), false, inject_span, ); diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index f6366ec3b8012..b4f632fdd2b80 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -22,7 +22,7 @@ use rustc_ast::visit::{FnCtxt, FnKind}; use rustc_ast::{self as ast, *}; use rustc_ast_pretty::pprust::{self, expr_to_string}; use rustc_errors::{Applicability, LintDiagnostic}; -use rustc_feature::{AttributeGate, BuiltinAttribute, GateIssue, Stability, deprecated_attributes}; +use rustc_feature::{AttributeGate, BuiltinAttribute, Stability, deprecated_attributes}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId}; @@ -2313,7 +2313,7 @@ impl EarlyLintPass for IncompleteInternalFeatures { .filter(|(name, _)| features.incomplete(*name) || features.internal(*name)) .for_each(|(name, span)| { if features.incomplete(name) { - let note = rustc_feature::find_feature_issue(name, GateIssue::Language) + let note = rustc_feature::find_lang_feature_issue(name) .map(|n| BuiltinFeatureIssueNote { n }); let help = HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 97a957874225d..9d4e3e45f66a6 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,7 +1,7 @@ use rustc_ast_pretty::pprust; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_errors::{Diag, LintDiagnostic, MultiSpan}; -use rustc_feature::{Features, GateIssue}; +use rustc_feature::{Features, GateIssues}; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{CRATE_HIR_ID, HirId}; use rustc_index::IndexVec; @@ -985,11 +985,11 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { lint.primary_message(fluent::lint_unknown_gated_lint); lint.arg("name", lint_id.lint.name_lower()); lint.note(fluent::lint_note); - rustc_session::parse::add_feature_diagnostics_for_issue( + rustc_session::parse::add_feature_diagnostics_for_issues( lint, &self.sess, - feature, - GateIssue::Language, + &[feature], + GateIssues::Language, lint_from_cli, None, ); diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 1b5fe661b18da..df7309d90a1e3 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -9,7 +9,7 @@ use rustc_attr::{ }; use rustc_data_structures::unord::UnordMap; use rustc_errors::{Applicability, Diag, EmissionGuarantee}; -use rustc_feature::GateIssue; +use rustc_feature::GateIssues; use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap}; use rustc_hir::{self as hir, HirId}; use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic}; @@ -17,7 +17,7 @@ use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_session::Session; use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE}; use rustc_session::lint::{BuiltinLintDiag, DeprecatedSinceKind, Level, Lint, LintBuffer}; -use rustc_session::parse::feature_err_issue; +use rustc_session::parse::feature_err_issues; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; use tracing::debug; @@ -118,7 +118,8 @@ pub fn report_unstable( if is_soft { soft_handler(SOFT_UNSTABLE, span, msg) } else { - let mut err = feature_err_issue(sess, feature, span, GateIssue::Library(issue), msg); + let issues = Vec::from_iter(issue); + let mut err = feature_err_issues(sess, &[feature], span, GateIssues::Library(issues), msg); if let Some((inner_types, msg, sugg, applicability)) = suggestion { err.span_suggestion(inner_types, msg, sugg, applicability); } diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 33f84f104474d..6521d22632f4e 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -53,7 +53,7 @@ impl SuggestUpgradeCompiler { #[derive(Subdiagnostic)] #[help(session_feature_diagnostic_help)] pub(crate) struct FeatureDiagnosticHelp { - pub(crate) feature: Symbol, + pub(crate) feature: String, } #[derive(Subdiagnostic)] @@ -63,7 +63,7 @@ pub(crate) struct FeatureDiagnosticHelp { code = "#![feature({feature})]\n" )] pub struct FeatureDiagnosticSuggestion { - pub feature: Symbol, + pub feature: String, #[primary_span] pub span: Span, } @@ -71,7 +71,7 @@ pub struct FeatureDiagnosticSuggestion { #[derive(Subdiagnostic)] #[help(session_cli_feature_diagnostic_help)] pub(crate) struct CliFeatureDiagnosticHelp { - pub(crate) feature: Symbol, + pub(crate) feature: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 21c1165511099..c65cb890963b1 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -12,7 +12,7 @@ use rustc_errors::{ ColorConfig, Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, EmissionGuarantee, MultiSpan, StashKey, fallback_fluent_bundle, }; -use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue}; +use rustc_feature::{GateIssues, UnstableFeatures, find_feature_issues}; use rustc_span::edition::Edition; use rustc_span::hygiene::ExpnId; use rustc_span::source_map::{FilePathMapping, SourceMap}; @@ -87,7 +87,7 @@ pub fn feature_err( span: impl Into, explain: impl Into, ) -> Diag<'_> { - feature_err_issue(sess, feature, span, GateIssue::Language, explain) + feature_err_issues(sess, &[feature], span, GateIssues::Language, explain) } /// Construct a diagnostic for a feature gate error. @@ -95,13 +95,13 @@ pub fn feature_err( /// This variant allows you to control whether it is a library or language feature. /// Almost always, you want to use this for a language feature. If so, prefer `feature_err`. #[track_caller] -pub fn feature_err_issue( - sess: &Session, - feature: Symbol, +pub fn feature_err_issues<'a>( + sess: &'a Session, + features: &[Symbol], span: impl Into, - issue: GateIssue, + issues: GateIssues, explain: impl Into, -) -> Diag<'_> { +) -> Diag<'a> { let span = span.into(); // Cancel an earlier warning for this same error, if it exists. @@ -112,7 +112,7 @@ pub fn feature_err_issue( } let mut err = sess.dcx().create_err(FeatureGateError { span, explain: explain.into() }); - add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None); + add_feature_diagnostics_for_issues(&mut err, sess, features, issues, false, None); err } @@ -121,7 +121,7 @@ pub fn feature_err_issue( /// This diagnostic is only a warning and *does not cause compilation to fail*. #[track_caller] pub fn feature_warn(sess: &Session, feature: Symbol, span: Span, explain: &'static str) { - feature_warn_issue(sess, feature, span, GateIssue::Language, explain); + feature_warn_issues(sess, &[feature], span, GateIssues::Language, explain); } /// Construct a future incompatibility diagnostic for a feature gate. @@ -133,15 +133,15 @@ pub fn feature_warn(sess: &Session, feature: Symbol, span: Span, explain: &'stat #[allow(rustc::diagnostic_outside_of_impl)] #[allow(rustc::untranslatable_diagnostic)] #[track_caller] -pub fn feature_warn_issue( +pub fn feature_warn_issues( sess: &Session, - feature: Symbol, + features: &[Symbol], span: Span, - issue: GateIssue, + issues: GateIssues, explain: &'static str, ) { let mut err = sess.dcx().struct_span_warn(span, explain); - add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None); + add_feature_diagnostics_for_issues(&mut err, sess, features, issues, false, None); // Decorate this as a future-incompatibility lint as in rustc_middle::lint::lint_level let lint = UNSTABLE_SYNTAX_PRE_EXPANSION; @@ -161,7 +161,7 @@ pub fn add_feature_diagnostics( sess: &Session, feature: Symbol, ) { - add_feature_diagnostics_for_issue(err, sess, feature, GateIssue::Language, false, None); + add_feature_diagnostics_for_issues(err, sess, &[feature], GateIssues::Language, false, None); } /// Adds the diagnostics for a feature to an existing error. @@ -170,20 +170,21 @@ pub fn add_feature_diagnostics( /// Almost always, you want to use this for a language feature. If so, prefer /// `add_feature_diagnostics`. #[allow(rustc::diagnostic_outside_of_impl)] // FIXME -pub fn add_feature_diagnostics_for_issue( +pub fn add_feature_diagnostics_for_issues( err: &mut Diag<'_, G>, sess: &Session, - feature: Symbol, - issue: GateIssue, + features: &[Symbol], + issues: GateIssues, feature_from_cli: bool, inject_span: Option, ) { - if let Some(n) = find_feature_issue(feature, issue) { + for n in find_feature_issues(features, issues) { err.subdiagnostic(FeatureDiagnosticForIssue { n }); } // #23973: do not suggest `#![feature(...)]` if we are in beta/stable if sess.psess.unstable_features.is_nightly_build() { + let feature: String = features.iter().map(|s| s.as_str()).intersperse(", ").collect(); if feature_from_cli { err.subdiagnostic(CliFeatureDiagnosticHelp { feature }); } else if let Some(span) = inject_span { From 039e6de0fedd3bf6a56f50c140ac7e35c9de1034 Mon Sep 17 00:00:00 2001 From: dianne Date: Wed, 16 Oct 2024 15:30:45 -0700 Subject: [PATCH 04/10] Make unstable library feature errors translatable Translation is in an awkward position, but this provides a similar interface for both errors and soft-unstable lints, and enables the use of `DiagSymbolList` for simplifying error message construction. --- .../rustc_lint/src/context/diagnostics.rs | 5 +- compiler/rustc_lint/src/lints.rs | 10 ---- compiler/rustc_lint_defs/src/lib.rs | 7 ++- compiler/rustc_middle/messages.ftl | 9 +++ compiler/rustc_middle/src/error.rs | 41 ++++++++++++- compiler/rustc_middle/src/lib.rs | 1 + compiler/rustc_middle/src/middle/stability.rs | 60 +++++++++---------- compiler/rustc_middle/src/ty/context.rs | 8 +-- compiler/rustc_resolve/src/macros.rs | 38 ++++++------ 9 files changed, 107 insertions(+), 72 deletions(-) diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs index 565c3c0425256..25a8c2c57ebca 100644 --- a/compiler/rustc_lint/src/context/diagnostics.rs +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -382,8 +382,9 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: & BuiltinLintDiag::MacroRuleNeverUsed(n, name) => { lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag); } - BuiltinLintDiag::UnstableFeature(msg) => { - lints::UnstableFeature { msg }.decorate_lint(diag); + BuiltinLintDiag::SoftUnstableMacro { feature, reason } => { + rustc_middle::error::SoftUnstableLibraryFeature::new(feature, reason) + .decorate_lint(diag); } BuiltinLintDiag::AvoidUsingIntelSyntax => { lints::AvoidIntelSyntax.decorate_lint(diag); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 352155729e51c..f41172d4bfb4d 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2408,16 +2408,6 @@ pub(crate) struct MacroRuleNeverUsed { pub name: Symbol, } -pub(crate) struct UnstableFeature { - pub msg: DiagMessage, -} - -impl<'a> LintDiagnostic<'a, ()> for UnstableFeature { - fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { - diag.primary_message(self.msg); - } -} - #[derive(LintDiagnostic)] #[diag(lint_avoid_intel_syntax)] pub(crate) struct AvoidIntelSyntax; diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index eac4afee05009..726d48102a860 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::stable_hasher::{ HashStable, StableCompare, StableHasher, ToStableHashKey, }; -use rustc_error_messages::{DiagMessage, MultiSpan}; +use rustc_error_messages::MultiSpan; use rustc_hir::def::Namespace; use rustc_hir::{HashStableContext, HirId, MissingLifetimeKind}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; @@ -750,7 +750,10 @@ pub enum BuiltinLintDiag { MacroIsPrivate(Ident), UnusedMacroDefinition(Symbol), MacroRuleNeverUsed(usize, Symbol), - UnstableFeature(DiagMessage), + SoftUnstableMacro { + feature: Symbol, + reason: Option, + }, AvoidUsingIntelSyntax, AvoidUsingAttSyntax, IncompleteInclude, diff --git a/compiler/rustc_middle/messages.ftl b/compiler/rustc_middle/messages.ftl index 52c3212ab803a..d7a3e47b3ae86 100644 --- a/compiler/rustc_middle/messages.ftl +++ b/compiler/rustc_middle/messages.ftl @@ -102,6 +102,15 @@ middle_type_length_limit = reached the type-length limit while instantiating `{$ middle_unknown_layout = the type `{$ty}` has an unknown layout +middle_unstable_library_feature = + use of unstable library {$count -> + [one] feature + *[other] features + } {$features}{STREQ($reason, "") -> + [true] {""} + *[false] : {$reason} + } + middle_values_too_big = values of the type `{$ty}` are too big for the target architecture middle_written_to_path = the full type name has been written to '{$path}' diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 5c2aa0005d405..4904d8c1d9254 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -2,8 +2,8 @@ use std::fmt; use std::path::PathBuf; use rustc_errors::codes::*; -use rustc_errors::{DiagArgName, DiagArgValue, DiagMessage}; -use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_errors::{DiagArgName, DiagArgValue, DiagMessage, DiagSymbolList}; +use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_span::{Span, Symbol}; use crate::ty::Ty; @@ -164,3 +164,40 @@ pub struct TypeLengthLimit { pub path: PathBuf, pub type_length: usize, } + +#[derive(Diagnostic)] +#[diag(middle_unstable_library_feature, code = E0658)] +pub struct UnstableLibraryFeatureError { + #[primary_span] + pub span: Span, + pub features: DiagSymbolList, + pub count: usize, + pub reason: String, +} + +impl UnstableLibraryFeatureError { + pub fn new(feature: Symbol, reason: Option, span: Span) -> Self { + let SoftUnstableLibraryFeature { features, count, reason } = + SoftUnstableLibraryFeature::new(feature, reason); + UnstableLibraryFeatureError { span, features, count, reason } + } +} + +/// Lint diagnostic for soft_unstable +#[derive(LintDiagnostic)] +#[diag(middle_unstable_library_feature)] +pub struct SoftUnstableLibraryFeature { + pub features: DiagSymbolList, + pub count: usize, + pub reason: String, +} + +impl SoftUnstableLibraryFeature { + pub fn new(feature: Symbol, reason: Option) -> Self { + SoftUnstableLibraryFeature { + features: vec![feature].into(), + count: 1, + reason: reason.map_or(String::new(), |r| r.to_string()), + } + } +} diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 04a06ba7464cb..accb081069d4a 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -48,6 +48,7 @@ #![feature(if_let_guard)] #![feature(intra_doc_pointers)] #![feature(iter_from_coroutine)] +#![feature(iter_intersperse)] #![feature(let_chains)] #![feature(macro_metavar_expr)] #![feature(min_specialization)] diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index df7309d90a1e3..f73008c308322 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -17,12 +17,13 @@ use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_session::Session; use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE}; use rustc_session::lint::{BuiltinLintDiag, DeprecatedSinceKind, Level, Lint, LintBuffer}; -use rustc_session::parse::feature_err_issues; +use rustc_session::parse::add_feature_diagnostics_for_issues; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; use tracing::debug; pub use self::StabilityLevel::*; +use crate::error::{SoftUnstableLibraryFeature, UnstableLibraryFeatureError}; use crate::ty::TyCtxt; #[derive(PartialEq, Clone, Copy, Debug)] @@ -106,25 +107,23 @@ pub fn report_unstable( reason: Option, issue: Option>, suggestion: Option<(Span, String, String, Applicability)>, - is_soft: bool, span: Span, - soft_handler: impl FnOnce(&'static Lint, Span, String), ) { - let msg = match reason { - Some(r) => format!("use of unstable library feature `{feature}`: {r}"), - None => format!("use of unstable library feature `{feature}`"), - }; - - if is_soft { - soft_handler(SOFT_UNSTABLE, span, msg) - } else { - let issues = Vec::from_iter(issue); - let mut err = feature_err_issues(sess, &[feature], span, GateIssues::Library(issues), msg); - if let Some((inner_types, msg, sugg, applicability)) = suggestion { - err.span_suggestion(inner_types, msg, sugg, applicability); - } - err.emit(); + let features = vec![feature]; + + let mut err = sess.dcx().create_err(UnstableLibraryFeatureError::new(feature, reason, span)); + add_feature_diagnostics_for_issues( + &mut err, + sess, + &features, + GateIssues::Library(Vec::from_iter(issue)), + false, + None, + ); + if let Some((inner_types, msg, sugg, applicability)) = suggestion { + err.span_suggestion(inner_types, msg, sugg, applicability); } + err.emit(); } fn deprecation_lint(is_in_effect: bool) -> &'static Lint { @@ -565,26 +564,23 @@ impl<'tcx> TyCtxt<'tcx> { allow_unstable: AllowUnstable, unmarked: impl FnOnce(Span, DefId), ) -> bool { - let soft_handler = |lint, span, msg: String| { - self.node_span_lint(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| { - lint.primary_message(msg); - }) - }; let eval_result = self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable); let is_allowed = matches!(eval_result, EvalResult::Allow); match eval_result { EvalResult::Allow => {} - EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable( - self.sess, - feature, - reason, - issue, - suggestion, - is_soft, - span, - soft_handler, - ), + EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => { + if is_soft { + self.emit_node_span_lint( + SOFT_UNSTABLE, + id.unwrap_or(hir::CRATE_HIR_ID), + span, + SoftUnstableLibraryFeature::new(feature, reason), + ); + } else { + report_unstable(self.sess, feature, reason, issue, suggestion, span); + } + } EvalResult::Unmarked => unmarked(span, def_id), } diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 2ba1bf2822fbb..1b5fed83eccd2 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2969,22 +2969,22 @@ impl<'tcx> TyCtxt<'tcx> { self, diag: &mut Diag<'_, E>, hir_id: Option, - features: impl IntoIterator, + featuresets: impl IntoIterator, ) { if !self.sess.is_nightly_build() { return; } let span = hir_id.and_then(|id| self.crate_level_attribute_injection_span(id)); - for (desc, feature) in features { + for (desc, features) in featuresets { // FIXME: make this string translatable let msg = - format!("add `#![feature({feature})]` to the crate attributes to enable{desc}"); + format!("add `#![feature({features})]` to the crate attributes to enable{desc}"); if let Some(span) = span { diag.span_suggestion_verbose( span, msg, - format!("#![feature({feature})]\n"), + format!("#![feature({features})]\n"), Applicability::MaybeIncorrect, ); } else { diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index f2dd079ad70c4..f8dcb8051c5ed 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1009,28 +1009,26 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |feature| self.tcx.features().enabled(feature) || span.allows_unstable(feature); let allowed_by_implication = implied_by.is_some_and(|feature| is_allowed(feature)); if !is_allowed(feature) && !allowed_by_implication { - let lint_buffer = &mut self.lint_buffer; - let soft_handler = |lint, span, msg: String| { - lint_buffer.buffer_lint( - lint, + if is_soft { + self.lint_buffer.buffer_lint( + SOFT_UNSTABLE, node_id, span, - BuiltinLintDiag::UnstableFeature( - // FIXME make this translatable - msg.into(), - ), - ) - }; - stability::report_unstable( - self.tcx.sess, - feature, - reason.to_opt_reason(), - issue, - None, - is_soft, - span, - soft_handler, - ); + BuiltinLintDiag::SoftUnstableMacro { + feature, + reason: reason.to_opt_reason(), + }, + ); + } else { + stability::report_unstable( + self.tcx.sess, + feature, + reason.to_opt_reason(), + issue, + None, + span, + ); + } } } } From 67f9341a796fbedefa60b1025ae2be4cd4b18b78 Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 21 Sep 2024 02:18:14 -0700 Subject: [PATCH 05/10] compiler: support multiple stability attributes on items 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. This includes the clarified/fixed const-unstable semantics that were previously in a different commit. --- Cargo.lock | 3 + compiler/rustc_attr/Cargo.toml | 1 + compiler/rustc_attr/messages.ftl | 2 +- compiler/rustc_attr/src/builtin.rs | 197 +++++++++++------- .../rustc_attr/src/session_diagnostics.rs | 2 + compiler/rustc_const_eval/Cargo.toml | 1 + .../src/check_consts/check.rs | 70 ++++--- .../rustc_const_eval/src/check_consts/ops.rs | 70 ++++--- compiler/rustc_const_eval/src/errors.rs | 2 +- compiler/rustc_const_eval/src/lib.rs | 2 + .../src/error_codes/E0544.md | 9 +- compiler/rustc_expand/src/base.rs | 4 +- compiler/rustc_expand/src/errors.rs | 4 +- compiler/rustc_feature/src/unstable.rs | 5 + compiler/rustc_hir_analysis/messages.ftl | 10 +- .../rustc_hir_analysis/src/check/check.rs | 18 +- compiler/rustc_hir_analysis/src/check/mod.rs | 22 +- compiler/rustc_hir_analysis/src/errors.rs | 11 +- compiler/rustc_hir_typeck/src/method/probe.rs | 29 +-- .../rustc_lint/src/context/diagnostics.rs | 4 +- compiler/rustc_lint_defs/src/lib.rs | 2 +- .../src/rmeta/decoder/cstore_impl.rs | 7 + compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_middle/src/arena.rs | 4 + compiler/rustc_middle/src/error.rs | 10 +- compiler/rustc_middle/src/middle/stability.rs | 188 ++++++++++------- compiler/rustc_middle/src/query/erase.rs | 6 - compiler/rustc_middle/src/query/mod.rs | 8 +- .../rustc_middle/src/query/on_disk_cache.rs | 21 ++ compiler/rustc_middle/src/ty/context.rs | 2 +- compiler/rustc_passes/Cargo.toml | 1 + compiler/rustc_passes/src/errors.rs | 2 +- compiler/rustc_passes/src/lib_features.rs | 4 - compiler/rustc_passes/src/stability.rs | 112 +++++----- compiler/rustc_resolve/src/macros.rs | 64 +++--- compiler/rustc_session/src/session.rs | 22 +- .../auxiliary/soft-unstable.rs | 14 ++ .../auxiliary/two-unstables.rs | 27 +++ .../soft-unstable.none.stderr | 43 ++++ tests/ui/stability-attribute/soft-unstable.rs | 17 ++ .../stability-attribute-sanity.rs | 16 +- .../stability-attribute-sanity.stderr | 18 +- .../two-unstables.none.stderr | 47 +++++ tests/ui/stability-attribute/two-unstables.rs | 27 +++ .../two-unstables.some.stderr | 44 ++++ 45 files changed, 798 insertions(+), 376 deletions(-) create mode 100644 tests/ui/stability-attribute/auxiliary/soft-unstable.rs create mode 100644 tests/ui/stability-attribute/auxiliary/two-unstables.rs create mode 100644 tests/ui/stability-attribute/soft-unstable.none.stderr create mode 100644 tests/ui/stability-attribute/soft-unstable.rs create mode 100644 tests/ui/stability-attribute/two-unstables.none.stderr create mode 100644 tests/ui/stability-attribute/two-unstables.rs create mode 100644 tests/ui/stability-attribute/two-unstables.some.stderr diff --git a/Cargo.lock b/Cargo.lock index a77d48106b4a4..05ace1c99f9ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3334,6 +3334,7 @@ dependencies = [ "rustc_serialize", "rustc_session", "rustc_span", + "smallvec", ] [[package]] @@ -3506,6 +3507,7 @@ dependencies = [ "rustc_target", "rustc_trait_selection", "rustc_type_ir", + "smallvec", "tracing", ] @@ -4218,6 +4220,7 @@ dependencies = [ "rustc_span", "rustc_target", "rustc_trait_selection", + "smallvec", "tracing", ] diff --git a/compiler/rustc_attr/Cargo.toml b/compiler/rustc_attr/Cargo.toml index 3b24452450abe..320d09f08d88d 100644 --- a/compiler/rustc_attr/Cargo.toml +++ b/compiler/rustc_attr/Cargo.toml @@ -17,4 +17,5 @@ rustc_macros = { path = "../rustc_macros" } rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } # tidy-alphabetical-end diff --git a/compiler/rustc_attr/messages.ftl b/compiler/rustc_attr/messages.ftl index 235ab7572c419..d8a1d18b5e7d0 100644 --- a/compiler/rustc_attr/messages.ftl +++ b/compiler/rustc_attr/messages.ftl @@ -83,7 +83,7 @@ attr_multiple_item = multiple '{$item}' items attr_multiple_stability_levels = - multiple stability levels + multiple stability levels for feature `{$feature}` attr_non_ident_feature = 'feature' is not an identifier diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 7c4b368891913..487ddd854ccfb 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -19,6 +19,7 @@ use rustc_session::{RustcVersion, Session}; use rustc_span::Span; use rustc_span::hygiene::Transparency; use rustc_span::symbol::{Symbol, kw, sym}; +use smallvec::{SmallVec, smallvec}; use crate::fluent_generated; use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause}; @@ -66,7 +67,7 @@ pub enum OptimizeAttr { /// /// - `#[stable]` /// - `#[unstable]` -#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct Stability { pub level: StabilityLevel, @@ -84,11 +85,15 @@ impl Stability { pub fn stable_since(&self) -> Option { self.level.stable_since() } + + pub fn unstable_features(&self) -> impl Iterator + use<'_> { + self.level.unstable_features() + } } /// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes. /// For details see [the dev guide](https://rustc-dev-guide.rust-lang.org/stability.html#rustc_const_unstable). -#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct ConstStability { pub level: StabilityLevel, @@ -106,23 +111,27 @@ impl ConstStability { pub fn is_const_stable(&self) -> bool { self.level.is_stable() } + + pub fn unstable_features(&self) -> impl Iterator + use<'_> { + self.level.unstable_features() + } } /// Represents the `#[rustc_default_body_unstable]` attribute. -#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Encodable, Decodable, Clone, Debug, PartialEq, Eq, Hash)] #[derive(HashStable_Generic)] pub struct DefaultBodyStability { pub level: StabilityLevel, } /// The available stability levels. -#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)] +#[derive(Encodable, Decodable, PartialEq, Clone, Debug, Eq, Hash)] #[derive(HashStable_Generic)] pub enum StabilityLevel { /// `#[unstable]` Unstable { /// The information unique to each `#[unstable]` attribute - unstables: Unstability, + unstables: SmallVec<[Unstability; 1]>, /// Reason for the current stability level. reason: UnstableReason, is_soft: bool, @@ -152,15 +161,30 @@ impl StabilityLevel { pub fn is_unstable(&self) -> bool { matches!(self, StabilityLevel::Unstable { .. }) } + pub fn is_stable(&self) -> bool { matches!(self, StabilityLevel::Stable { .. }) } + pub fn stable_since(&self) -> Option { match *self { StabilityLevel::Stable { since, .. } => Some(since), StabilityLevel::Unstable { .. } => None, } } + + pub fn unstable_features(&self) -> impl Iterator + use<'_> { + let features = if let StabilityLevel::Unstable { unstables, .. } = self { + Some(unstables.iter().map(|u| u.feature)) + } else { + None + }; + features.into_iter().flatten() + } + + pub fn has_unstable_feature(&self, feature: Symbol) -> bool { + self.unstable_features().any(|f| f == feature) + } } /// An instance of an `#[unstable]`, `#[rustc_const_unstable]`, or similar attribute @@ -218,28 +242,40 @@ impl UnstableReason { } } +/// The spans of each individual parsed stability attribute for an item. +/// This is reported separately from the overall stability level for more precise diagnostics. +pub struct StabilitySpans(SmallVec<[(StabilityLevel, Span); 1]>); + +impl StabilitySpans { + pub fn spans(&self) -> Vec { + self.0.iter().map(|&(_, span)| span).collect() + } + + pub fn iter(&self) -> impl Iterator + use<'_> { + self.0.iter() + } +} + /// Collects stability info from `stable`/`unstable`/`rustc_allowed_through_unstable_modules` /// attributes in `attrs`. Returns `None` if no stability attributes are found. pub fn find_stability( sess: &Session, attrs: &[Attribute], item_sp: Span, -) -> Option<(Stability, Span)> { - let mut level: Option<(StabilityLevel, Span)> = None; +) -> Option<(Stability, StabilitySpans)> { + let mut level: Option = None; + let mut stab_spans = StabilitySpans(smallvec![]); + let mut features = smallvec![]; let mut allowed_through_unstable_modules = false; for attr in attrs { match attr.name_or_empty() { sym::rustc_allowed_through_unstable_modules => allowed_through_unstable_modules = true, sym::unstable => { - if try_add_unstability(sess, attr, &mut level).is_err() { - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability) } sym::stable => { - if try_add_stability(sess, attr, &mut level).is_err() { - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability) } _ => {} } @@ -247,8 +283,8 @@ pub fn find_stability( if allowed_through_unstable_modules { match &mut level { - Some((StabilityLevel::Stable { allowed_through_unstable_modules, .. }, _)) => { - *allowed_through_unstable_modules = true + Some(StabilityLevel::Stable { allowed_through_unstable_modules, .. }) => { + *allowed_through_unstable_modules = true; } _ => { sess.dcx() @@ -257,8 +293,7 @@ pub fn find_stability( } } - let (level, stab_sp) = level?; - Some((Stability { level }, stab_sp)) + Some((Stability { level: level? }, stab_spans)) } /// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable` @@ -267,8 +302,10 @@ pub fn find_const_stability( sess: &Session, attrs: &[Attribute], item_sp: Span, -) -> Option<(ConstStability, Span)> { - let mut level: Option<(StabilityLevel, Span)> = None; +) -> Option<(ConstStability, StabilitySpans)> { + let mut level: Option = None; + let mut stab_spans = StabilitySpans(smallvec![]); + let mut features = smallvec![]; let mut promotable = false; let mut const_stable_indirect = false; @@ -277,21 +314,17 @@ pub fn find_const_stability( sym::rustc_promotable => promotable = true, sym::rustc_const_stable_indirect => const_stable_indirect = true, sym::rustc_const_unstable => { - if try_add_unstability(sess, attr, &mut level).is_err() { - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability) } sym::rustc_const_stable => { - if try_add_stability(sess, attr, &mut level).is_err() { - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability) } _ => {} } } // Merge promotable and const_stable_indirect into stability info - if let Some((level, stab_sp)) = level { + if let Some(level) = level { if level.is_stable() && const_stable_indirect { sess.dcx() .emit_err(session_diagnostics::RustcConstStableIndirectPairing { span: item_sp }); @@ -299,7 +332,7 @@ pub fn find_const_stability( } let const_stab = ConstStability { level, const_stable_indirect, promotable }; - Some((const_stab, stab_sp)) + Some((const_stab, stab_spans)) } else { if promotable { sess.dcx().emit_err(session_diagnostics::RustcPromotablePairing { span: item_sp }); @@ -331,55 +364,72 @@ pub fn unmarked_crate_const_stab( pub fn find_body_stability( sess: &Session, attrs: &[Attribute], -) -> Option<(DefaultBodyStability, Span)> { - let mut level: Option<(StabilityLevel, Span)> = None; +) -> Option<(DefaultBodyStability, StabilitySpans)> { + let mut level: Option = None; + let mut stab_spans = StabilitySpans(smallvec![]); + let mut features = smallvec![]; for attr in attrs { if attr.has_name(sym::rustc_default_body_unstable) { - if try_add_unstability(sess, attr, &mut level).is_err() { - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability); } } - let (level, stab_sp) = level?; - Some((DefaultBodyStability { level }, stab_sp)) + Some((DefaultBodyStability { level: level? }, stab_spans)) } -/// Collects stability info from one `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` -/// attribute, `attr`. Emits an error if the info it collects is inconsistent. -fn try_add_unstability( +/// Collects stability info from one stability attribute, `attr`. +/// Emits an error if multiple stability levels are found for the same feature. +fn add_level( sess: &Session, attr: &Attribute, - level: &mut Option<(StabilityLevel, Span)>, -) -> Result<(), ErrorGuaranteed> { - if level.is_some() { - return Err(sess - .dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span })); - } - if let Some(new_level) = parse_unstability(sess, attr) { - *level = Some((new_level, attr.span)); + total_level: &mut Option, + stab_spans: &mut StabilitySpans, + features: &mut SmallVec<[Symbol; 1]>, + parse_level: impl FnOnce(&Session, &Attribute) -> Option<(Symbol, StabilityLevel)>, +) { + use StabilityLevel::*; + + let Some((feature, feature_level)) = parse_level(sess, attr) else { return }; + + // sanity check: is this the only stability level of its kind for its feature? + if features.contains(&feature) { + sess.dcx() + .emit_err(session_diagnostics::MultipleStabilityLevels { feature, span: attr.span }); } - Ok(()) -} - -/// Collects stability info from a single `stable`/`rustc_const_stable` attribute, `attr`. -/// Emits an error if the info it collects is inconsistent. -fn try_add_stability( - sess: &Session, - attr: &Attribute, - level: &mut Option<(StabilityLevel, Span)>, -) -> Result<(), ErrorGuaranteed> { - if level.is_some() { - return Err(sess - .dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span })); - } - if let Some(new_level) = parse_stability(sess, attr) { - *level = Some((new_level, attr.span)); + features.push(feature); + stab_spans.0.push((feature_level.clone(), attr.span)); + + match (total_level, feature_level) { + (level @ None, new_level) => *level = Some(new_level), + // if multiple unstable attributes have been found, merge them + ( + Some(Unstable { unstables, reason, is_soft }), + Unstable { unstables: new_unstable, reason: new_reason, is_soft: new_soft }, + ) => { + unstables.extend(new_unstable); + match (reason, new_reason) { + (_, UnstableReason::None) => {} + (reason @ UnstableReason::None, _) => *reason = new_reason, + _ => { + // TODO: sanity check for only one reason + } + } + // TODO: sanity check for is_soft consistency + *is_soft |= new_soft; + } + // an item with some stable and some unstable features is unstable + (Some(Unstable { .. }), Stable { .. }) => {} + (Some(level @ Stable { .. }), new_level @ Unstable { .. }) => *level = new_level, + // if multiple stable attributes have been found, use the most recent stabilization date + ( + Some(Stable { since, allowed_through_unstable_modules }), + Stable { since: new_since, allowed_through_unstable_modules: new_allowed }, + ) => { + *since = StableSince::max(*since, new_since); + *allowed_through_unstable_modules |= new_allowed; + } } - Ok(()) } fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option) -> Option<()> { @@ -400,7 +450,7 @@ fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option) - /// Read the content of a `stable`/`rustc_const_stable` attribute, and return the feature name and /// its stability information. -fn parse_stability(sess: &Session, attr: &Attribute) -> Option { +fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> { let meta = attr.meta()?; let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None }; @@ -454,16 +504,17 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option { }; match feature { - Ok(_feature) => { - Some(StabilityLevel::Stable { since, allowed_through_unstable_modules: false }) - } + Ok(feature) => Some((feature, StabilityLevel::Stable { + since, + allowed_through_unstable_modules: false, + })), Err(ErrorGuaranteed { .. }) => None, } } /// Read the content of a `unstable`/`rustc_const_unstable`/`rustc_default_body_unstable` /// attribute, and return the feature name and its stability information. -fn parse_unstability(sess: &Session, attr: &Attribute) -> Option { +fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, StabilityLevel)> { let meta = attr.meta()?; let MetaItem { kind: MetaItemKind::List(ref metas), .. } = meta else { return None }; @@ -542,12 +593,12 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option match (feature, issue) { (Ok(feature), Ok(_)) => { - let level = StabilityLevel::Unstable { - unstables: Unstability { feature, issue: issue_num, implied_by }, + let unstability = Unstability { feature, issue: issue_num, implied_by }; + Some((feature, StabilityLevel::Unstable { + unstables: smallvec![unstability], reason: UnstableReason::from_opt_reason(reason), is_soft, - }; - Some(level) + })) } (Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None, } diff --git a/compiler/rustc_attr/src/session_diagnostics.rs b/compiler/rustc_attr/src/session_diagnostics.rs index 9d08a9f575425..1bf11e7196b4b 100644 --- a/compiler/rustc_attr/src/session_diagnostics.rs +++ b/compiler/rustc_attr/src/session_diagnostics.rs @@ -79,6 +79,8 @@ pub(crate) struct MissingNote { pub(crate) struct MultipleStabilityLevels { #[primary_span] pub span: Span, + + pub feature: Symbol, } #[derive(Diagnostic)] diff --git a/compiler/rustc_const_eval/Cargo.toml b/compiler/rustc_const_eval/Cargo.toml index 41136019a88df..0fba089ec0d09 100644 --- a/compiler/rustc_const_eval/Cargo.toml +++ b/compiler/rustc_const_eval/Cargo.toml @@ -24,5 +24,6 @@ rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_type_ir = { path = "../rustc_type_ir" } +smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } tracing = "0.1" # tidy-alphabetical-end diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 7189e17256770..57d4560849069 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -271,14 +271,16 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { /// Emits an error at the given `span` if an expression cannot be evaluated in the current /// context. pub fn check_op_spanned>(&mut self, op: O, span: Span) { - let gate = match op.status_in_item(self.ccx) { + let gates = match op.status_in_item(self.ccx) { Status::Unstable { - gate, + gates, safe_to_expose_on_stable, is_function_call, - gate_already_checked, - } if gate_already_checked || self.tcx.features().enabled(gate) => { - if gate_already_checked { + gates_already_checked, + } if gates_already_checked + || self.tcx.features().all_enabled(gates.iter().copied()) => + { + if gates_already_checked { assert!( !safe_to_expose_on_stable, "setting `gate_already_checked` without `safe_to_expose_on_stable` makes no sense" @@ -288,20 +290,30 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { // if this function wants to be safe-to-expose-on-stable. if !safe_to_expose_on_stable && self.enforce_recursive_const_stability() - && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate) + && !gates.iter().all(|&gate| { + super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate) + }) { - emit_unstable_in_stable_exposed_error(self.ccx, span, gate, is_function_call); + emit_unstable_in_stable_exposed_error(self.ccx, span, &gates, is_function_call); } return; } - Status::Unstable { gate, .. } => Some(gate), + Status::Unstable { gates, .. } => Some(gates), Status::Forbidden => None, }; if self.tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you { - self.tcx.sess.miri_unleashed_feature(span, gate); + if let Some(gates) = gates { + for gate in gates { + if !self.tcx.features().enabled(gate) { + self.tcx.sess.miri_unleashed_feature(span, Some(gate)); + } + } + } else { + self.tcx.sess.miri_unleashed_feature(span, None); + }; return; } @@ -749,7 +761,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }) => { self.check_op(ops::IntrinsicUnstable { name: intrinsic.name, - feature: unstables.feature, + features: unstables.iter().map(|u| u.feature).collect(), const_stable_indirect: is_const_stable, }); } @@ -798,9 +810,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { level: StabilityLevel::Unstable { unstables, .. }, .. }) => { - let Unstability { feature, implied_by: implied_feature, issue, .. } = - unstables; - // An unstable const fn with a feature gate. + // An unstable const fn with feature gates. let callee_safe_to_expose_on_stable = is_safe_to_expose_on_stable_const_fn(tcx, callee); @@ -812,9 +822,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // integrated in the check below since we want to enforce // `callee_safe_to_expose_on_stable` even if // `!self.enforce_recursive_const_stability()`. - if (self.span.allows_unstable(feature) - || implied_feature.is_some_and(|f| self.span.allows_unstable(f))) - && callee_safe_to_expose_on_stable + if callee_safe_to_expose_on_stable + && unstables.iter().all(|u| { + self.span.allows_unstable(u.feature) + || u.implied_by.is_some_and(|f| self.span.allows_unstable(f)) + }) { return; } @@ -823,9 +835,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // the logic is a bit different than elsewhere: local functions don't need // the feature gate, and there might be an "implied" gate that also suffices // to allow this. - let feature_enabled = callee.is_local() - || tcx.features().enabled(feature) - || implied_feature.is_some_and(|f| tcx.features().enabled(f)) + let features_enabled = callee.is_local() + || unstables.iter().all(|u| { + tcx.features().enabled(u.feature) + || u.implied_by.is_some_and(|f| tcx.features().enabled(f)) + }) || { // When we're compiling the compiler itself we may pull in // crates from crates.io, but those crates may depend on other @@ -838,17 +852,19 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // annotation slide. // This matches what we do in `eval_stability_allow_unstable` for // regular stability. - feature == sym::rustc_private - && issue == NonZero::new(27812) - && self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked + matches!(unstables.as_slice(), [Unstability { + feature: sym::rustc_private, + issue: const { NonZero::new(27812) }, + .. + }]) && self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked }; // Even if the feature is enabled, we still need check_op to double-check // this if the callee is not safe to expose on stable. - if !feature_enabled || !callee_safe_to_expose_on_stable { + if !features_enabled || !callee_safe_to_expose_on_stable { self.check_op(ops::FnCallUnstable { def_id: callee, - feature, - feature_enabled, + features: unstables.iter().map(|u| u.feature).collect(), + features_enabled, safe_to_expose_on_stable: callee_safe_to_expose_on_stable, }); } @@ -937,13 +953,13 @@ fn is_int_bool_float_or_char(ty: Ty<'_>) -> bool { fn emit_unstable_in_stable_exposed_error( ccx: &ConstCx<'_, '_>, span: Span, - gate: Symbol, + gates: &[Symbol], is_function_call: bool, ) -> ErrorGuaranteed { let attr_span = ccx.tcx.def_span(ccx.def_id()).shrink_to_lo(); ccx.dcx().emit_err(errors::UnstableInStableExposed { - gate: gate.to_string(), + gate: gates.iter().map(|s| s.as_str()).intersperse(", ").collect(), span, attr_span, is_function_call, diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 8ba6b89aad4d5..241b9434b6e16 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -18,24 +18,25 @@ use rustc_middle::util::{CallDesugaringKind, CallKind, call_kind}; use rustc_span::symbol::sym; use rustc_span::{BytePos, Pos, Span, Symbol}; use rustc_trait_selection::traits::SelectionContext; +use smallvec::{SmallVec, smallvec}; use tracing::debug; use super::ConstCx; use crate::{errors, fluent_generated}; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Status { Unstable { - /// The feature that must be enabled to use this operation. - gate: Symbol, - /// Whether the feature gate was already checked (because the logic is a bit more + /// The features that must be enabled to use this operation. + gates: SmallVec<[Symbol; 1]>, + /// Whether the features gate were already checked (because the logic is a bit more /// complicated than just checking a single gate). - gate_already_checked: bool, + gates_already_checked: bool, /// Whether it is allowed to use this operation from stable `const fn`. /// This will usually be `false`. safe_to_expose_on_stable: bool, /// We indicate whether this is a function call, since we can use targeted - /// diagnostics for "callee is not safe to expose om stable". + /// diagnostics for "callee is not safe to expose on stable". is_function_call: bool, }, Forbidden, @@ -84,8 +85,8 @@ impl<'tcx> NonConstOp<'tcx> for ConditionallyConstCall<'tcx> { fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { // We use the `const_trait_impl` gate for all conditionally-const calls. Status::Unstable { - gate: sym::const_trait_impl, - gate_already_checked: false, + gates: smallvec![sym::const_trait_impl], + gates_already_checked: false, safe_to_expose_on_stable: false, // We don't want the "mark the callee as `#[rustc_const_stable_indirect]`" hint is_function_call: false, @@ -327,36 +328,47 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> { /// A call to an `#[unstable]` const fn or `#[rustc_const_unstable]` function. /// -/// Contains the name of the feature that would allow the use of this function. +/// Contains the names of the features that would allow the use of this function. #[derive(Debug)] pub(crate) struct FnCallUnstable { pub def_id: DefId, - pub feature: Symbol, - /// If this is true, then the feature is enabled, but we need to still check if it is safe to + pub features: SmallVec<[Symbol; 1]>, + /// If this is true, then the features are enabled, but we need to still check if it is safe to /// expose on stable. - pub feature_enabled: bool, + pub features_enabled: bool, pub safe_to_expose_on_stable: bool, } impl<'tcx> NonConstOp<'tcx> for FnCallUnstable { fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { Status::Unstable { - gate: self.feature, - gate_already_checked: self.feature_enabled, + gates: self.features.clone(), + gates_already_checked: self.features_enabled, safe_to_expose_on_stable: self.safe_to_expose_on_stable, is_function_call: true, } } fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { - assert!(!self.feature_enabled); + assert!(!self.features_enabled); let mut err = ccx.dcx().create_err(errors::UnstableConstFn { span, def_path: ccx.tcx.def_path_str(self.def_id), }); // FIXME: make this translatable #[allow(rustc::untranslatable_diagnostic)] - err.help(format!("add `#![feature({})]` to the crate attributes to enable", self.feature)); + if ccx.tcx.sess.is_nightly_build() { + let missing_features = self + .features + .iter() + .filter(|&&feature| !ccx.tcx.features().enabled(feature)) + .map(|feature| feature.as_str()) + .intersperse(", ") + .collect::(); + err.help(format!( + "add `#![feature({missing_features})]` to the crate attributes to enable", + )); + } err } @@ -382,15 +394,15 @@ impl<'tcx> NonConstOp<'tcx> for IntrinsicNonConst { #[derive(Debug)] pub(crate) struct IntrinsicUnstable { pub name: Symbol, - pub feature: Symbol, + pub features: SmallVec<[Symbol; 1]>, pub const_stable_indirect: bool, } impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable { fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { Status::Unstable { - gate: self.feature, - gate_already_checked: false, + gates: self.features.clone(), + gates_already_checked: false, safe_to_expose_on_stable: self.const_stable_indirect, // We do *not* want to suggest to mark the intrinsic as `const_stable_indirect`, // that's not a trivial change! @@ -399,10 +411,18 @@ impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable { } fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { + // Only report the features that aren't already enabled. + let missing_features = self + .features + .iter() + .filter(|&&feature| !ccx.tcx.features().enabled(feature)) + .map(|feature| feature.as_str()) + .intersperse(", ") + .collect(); ccx.dcx().create_err(errors::UnstableIntrinsic { span, name: self.name, - feature: self.feature, + feature: missing_features, }) } } @@ -417,8 +437,8 @@ impl<'tcx> NonConstOp<'tcx> for Coroutine { ) = self.0 { Status::Unstable { - gate: sym::const_async_blocks, - gate_already_checked: false, + gates: smallvec![sym::const_async_blocks], + gates_already_checked: false, safe_to_expose_on_stable: false, is_function_call: false, } @@ -429,8 +449,10 @@ impl<'tcx> NonConstOp<'tcx> for Coroutine { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { let msg = format!("{:#}s are not allowed in {}s", self.0, ccx.const_kind()); - if let Status::Unstable { gate, .. } = self.status_in_item(ccx) { - ccx.tcx.sess.create_feature_err(errors::UnallowedOpInConstContext { span, msg }, gate) + if let Status::Unstable { gates, .. } = self.status_in_item(ccx) { + ccx.tcx + .sess + .create_features_err(errors::UnallowedOpInConstContext { span, msg }, &gates) } else { ccx.dcx().create_err(errors::UnallowedOpInConstContext { span, msg }) } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 604e5ed61a35b..272edadc52844 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -128,7 +128,7 @@ pub(crate) struct UnstableIntrinsic { #[primary_span] pub span: Span, pub name: Symbol, - pub feature: Symbol, + pub feature: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index 527236b2c22e3..bd1a7136e53d7 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -6,6 +6,8 @@ #![feature(box_patterns)] #![feature(decl_macro)] #![feature(if_let_guard)] +#![feature(inline_const_pat)] +#![feature(iter_intersperse)] #![feature(let_chains)] #![feature(never_type)] #![feature(rustdoc_internals)] diff --git a/compiler/rustc_error_codes/src/error_codes/E0544.md b/compiler/rustc_error_codes/src/error_codes/E0544.md index 202401f9d45f9..94e6e36a70d0c 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0544.md +++ b/compiler/rustc_error_codes/src/error_codes/E0544.md @@ -1,4 +1,4 @@ -Multiple stability attributes were declared on the same item. +Multiple stability attributes were declared for the same feature on one item. Erroneous code example: @@ -8,18 +8,19 @@ Erroneous code example: #![stable(since = "1.0.0", feature = "rust1")] #[stable(feature = "rust1", since = "1.0.0")] -#[stable(feature = "test", since = "2.0.0")] // invalid +#[stable(feature = "rust1", since = "1.0.0")] // invalid fn foo() {} ``` -To fix this issue, ensure that each item has at most one stability attribute. +To fix this issue, ensure that each item has at most one stability attribute per +feature. ``` #![feature(staged_api)] #![allow(internal_features)] #![stable(since = "1.0.0", feature = "rust1")] -#[stable(feature = "test", since = "2.0.0")] // ok! +#[stable(feature = "rust1", since = "1.0.0")] // ok! fn foo() {} ``` diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index bed500c303242..25262fd441de0 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -870,13 +870,13 @@ impl SyntaxExtension { let body_stability = attr::find_body_stability(sess, attrs); if let Some((_, sp)) = const_stability { sess.dcx().emit_err(errors::MacroConstStability { - span: sp, + spans: sp.spans(), head_span: sess.source_map().guess_head_span(span), }); } if let Some((_, sp)) = body_stability { sess.dcx().emit_err(errors::MacroBodyStability { - span: sp, + spans: sp.spans(), head_span: sess.source_map().guess_head_span(span), }); } diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index 7bd7c30553913..739f34eb469b7 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -72,7 +72,7 @@ pub(crate) struct CollapseMacroDebuginfoIllegal { pub(crate) struct MacroConstStability { #[primary_span] #[label] - pub span: Span, + pub spans: Vec, #[label(expand_label2)] pub head_span: Span, } @@ -82,7 +82,7 @@ pub(crate) struct MacroConstStability { pub(crate) struct MacroBodyStability { #[primary_span] #[label] - pub span: Span, + pub spans: Vec, #[label(expand_label2)] pub head_span: Span, } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a67a5776449d7..7c2d676bad4c4 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -95,6 +95,11 @@ impl Features { pub fn enabled(&self, feature: Symbol) -> bool { self.enabled_features.contains(&feature) } + + /// Are all the given features enabled (via `#[feature(...)]`)? + pub fn all_enabled(&self, features: impl IntoIterator) -> bool { + features.into_iter().all(|feature| self.enabled(feature)) + } } macro_rules! declare_features { diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index 64a30e633cf9d..629340369a390 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -311,8 +311,14 @@ hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snip hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}` .note = default implementation of `{$missing_item_name}` is unstable - .some_note = use of unstable library feature `{$feature}`: {$reason} - .none_note = use of unstable library feature `{$feature}` + .unstable_features_note = + use of unstable library {$feature_count -> + [one] feature + *[other] features + } {$features}{STREQ($reason, "") -> + [true] {""} + *[false] : {$reason} + } hir_analysis_missing_type_params = the type {$parameterCount -> diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index cf8c81c0b0890..35aed1c5d322d 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -1044,14 +1044,16 @@ fn check_impl_items_against_trait<'tcx>( if !is_implemented_here { let full_impl_span = tcx.hir().span_with_body(tcx.local_def_id_to_hir_id(impl_id)); match tcx.eval_default_body_stability(trait_item_id, full_impl_span) { - EvalResult::Deny { feature, reason, issue, .. } => default_body_is_unstable( - tcx, - full_impl_span, - trait_item_id, - feature, - reason, - issue, - ), + EvalResult::Deny { features, reason, issues, .. } => { + default_body_is_unstable( + tcx, + full_impl_span, + trait_item_id, + features, + reason, + issues, + ); + } // Unmarked default bodies are considered stable (at least for now). EvalResult::Allow | EvalResult::Unmarked => {} diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 60cdb3e9fcf03..8b293c018d87d 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -294,26 +294,18 @@ fn default_body_is_unstable( tcx: TyCtxt<'_>, impl_span: Span, item_did: DefId, - feature: Symbol, + features: Vec, reason: Option, - issue: Option>, + issues: Vec>, ) { let missing_item_name = tcx.associated_item(item_did).name; - let (mut some_note, mut none_note, mut reason_str) = (false, false, String::new()); - match reason { - Some(r) => { - some_note = true; - reason_str = r.to_string(); - } - None => none_note = true, - }; + let reason_str = reason.map_or(String::new(), |r| r.to_string()); let mut err = tcx.dcx().create_err(errors::MissingTraitItemUnstable { span: impl_span, - some_note, - none_note, missing_item_name, - feature, + features: features.clone().into(), + feature_count: features.len(), reason: reason_str, }); @@ -323,8 +315,8 @@ fn default_body_is_unstable( rustc_session::parse::add_feature_diagnostics_for_issues( &mut err, &tcx.sess, - &[feature], - rustc_feature::GateIssues::Library(Vec::from_iter(issue)), + &features, + rustc_feature::GateIssues::Library(issues), false, inject_span, ); diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index 07d3273b09c77..73eb2fafdd4cc 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -2,7 +2,8 @@ use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, MultiSpan, + Applicability, Diag, DiagCtxtHandle, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, + MultiSpan, }; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::Ty; @@ -974,15 +975,13 @@ pub(crate) struct MissingOneOfTraitItem { #[derive(Diagnostic)] #[diag(hir_analysis_missing_trait_item_unstable, code = E0046)] #[note] +#[note(hir_analysis_unstable_features_note)] pub(crate) struct MissingTraitItemUnstable { #[primary_span] pub span: Span, - #[note(hir_analysis_some_note)] - pub some_note: bool, - #[note(hir_analysis_none_note)] - pub none_note: bool, pub missing_item_name: Symbol, - pub feature: Symbol, + pub features: DiagSymbolList, + pub feature_count: usize, pub reason: String, } diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 640729576fcc6..9eedc949c2234 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -180,7 +180,7 @@ pub(crate) struct Pick<'tcx> { pub self_ty: Ty<'tcx>, /// Unstable candidates alongside the stable ones. - unstable_candidates: Vec<(Candidate<'tcx>, Symbol)>, + unstable_candidates: Vec<(Candidate<'tcx>, Vec)>, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -1061,7 +1061,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { fn pick_all_method( &self, - mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { self.steps .iter() @@ -1126,7 +1126,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { &self, step: &CandidateStep<'tcx>, self_ty: Ty<'tcx>, - unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { if step.unsize { return None; @@ -1170,7 +1170,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { step: &CandidateStep<'tcx>, self_ty: Ty<'tcx>, mutbl: hir::Mutability, - unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { let tcx = self.tcx; @@ -1194,7 +1194,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { &self, step: &CandidateStep<'tcx>, self_ty: Ty<'tcx>, - unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { if !self.tcx.features().pin_ergonomics() { return None; @@ -1232,7 +1232,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { &self, step: &CandidateStep<'tcx>, self_ty: Ty<'tcx>, - unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { // Don't convert an unsized reference to ptr if step.unsize { @@ -1256,7 +1256,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { fn pick_method( &self, self_ty: Ty<'tcx>, - mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { debug!("pick_method(self_ty={})", self.ty_to_string(self_ty)); @@ -1302,7 +1302,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { Option>, Option>, )>, - mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Symbol)>>, + mut unstable_candidates: Option<&mut Vec<(Candidate<'tcx>, Vec)>>, ) -> Option> { let mut applicable_candidates: Vec<_> = candidates .iter() @@ -1324,10 +1324,10 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { if let Some(uc) = &mut unstable_candidates { applicable_candidates.retain(|&(candidate, _)| { - if let stability::EvalResult::Deny { feature, .. } = + if let stability::EvalResult::Deny { features, .. } = self.tcx.eval_stability(candidate.item.def_id, None, self.span, None) { - uc.push((candidate.clone(), feature)); + uc.push((candidate.clone(), features.clone())); return false; } true @@ -1425,8 +1425,11 @@ impl<'tcx> Pick<'tcx> { tcx.disabled_nightly_features( lint, Some(scope_expr_id), - self.unstable_candidates.iter().map(|(candidate, feature)| { - (format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature) + self.unstable_candidates.iter().map(|(candidate, features)| { + ( + format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), + features.iter().map(Symbol::as_str).intersperse(", ").collect::(), + ) }), ); }); @@ -2014,7 +2017,7 @@ impl<'tcx> Candidate<'tcx> { fn to_unadjusted_pick( &self, self_ty: Ty<'tcx>, - unstable_candidates: Vec<(Candidate<'tcx>, Symbol)>, + unstable_candidates: Vec<(Candidate<'tcx>, Vec)>, ) -> Pick<'tcx> { Pick { item: self.item, diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs index 25a8c2c57ebca..550b6da5f669c 100644 --- a/compiler/rustc_lint/src/context/diagnostics.rs +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -382,8 +382,8 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: & BuiltinLintDiag::MacroRuleNeverUsed(n, name) => { lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag); } - BuiltinLintDiag::SoftUnstableMacro { feature, reason } => { - rustc_middle::error::SoftUnstableLibraryFeature::new(feature, reason) + BuiltinLintDiag::SoftUnstableMacro { features, reason } => { + rustc_middle::error::SoftUnstableLibraryFeature::new(features, reason) .decorate_lint(diag); } BuiltinLintDiag::AvoidUsingIntelSyntax => { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 726d48102a860..cdebd337a7257 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -751,7 +751,7 @@ pub enum BuiltinLintDiag { UnusedMacroDefinition(Symbol), MacroRuleNeverUsed(usize, Symbol), SoftUnstableMacro { - feature: Symbol, + features: Vec, reason: Option, }, AvoidUsingIntelSyntax, diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 045fd0565ba0d..6c4345b4e8451 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -59,6 +59,13 @@ impl<'tcx, T: ArenaAllocatable<'tcx>> ProcessQueryValue<'tcx, &'tcx T> for Optio } } +impl<'tcx, T: ArenaAllocatable<'tcx>> ProcessQueryValue<'tcx, Option<&'tcx T>> for Option { + #[inline(always)] + fn process_decoded(self, tcx: TyCtxt<'tcx>, _err: impl Fn() -> !) -> Option<&'tcx T> { + self.map(|value| tcx.arena.alloc(value) as &_) + } +} + impl ProcessQueryValue<'_, Result, E>> for Option { #[inline(always)] fn process_decoded(self, _tcx: TyCtxt<'_>, _err: impl Fn() -> !) -> Result, E> { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index b5391247cea54..deab2dd4a0318 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1924,7 +1924,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { } } - Some(ProcMacroData { proc_macro_decls_static, stability, macros }) + Some(ProcMacroData { proc_macro_decls_static, stability: stability.cloned(), macros }) } else { None } diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index b664230d10bce..91ef2cccb0b61 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -114,6 +114,10 @@ macro_rules! arena_types { [decode] specialization_graph: rustc_middle::traits::specialization_graph::Graph, [] crate_inherent_impls: rustc_middle::ty::CrateInherentImpls, [] hir_owner_nodes: rustc_hir::OwnerNodes<'tcx>, + + [] stability: rustc_attr::Stability, + [] const_stability: rustc_attr::ConstStability, + [] default_body_stability: rustc_attr::DefaultBodyStability, ]); ) } diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 4904d8c1d9254..d8cb32c43205e 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -176,9 +176,9 @@ pub struct UnstableLibraryFeatureError { } impl UnstableLibraryFeatureError { - pub fn new(feature: Symbol, reason: Option, span: Span) -> Self { + pub fn new(features: Vec, reason: Option, span: Span) -> Self { let SoftUnstableLibraryFeature { features, count, reason } = - SoftUnstableLibraryFeature::new(feature, reason); + SoftUnstableLibraryFeature::new(features, reason); UnstableLibraryFeatureError { span, features, count, reason } } } @@ -193,10 +193,10 @@ pub struct SoftUnstableLibraryFeature { } impl SoftUnstableLibraryFeature { - pub fn new(feature: Symbol, reason: Option) -> Self { + pub fn new(features: Vec, reason: Option) -> Self { SoftUnstableLibraryFeature { - features: vec![feature].into(), - count: 1, + count: features.len(), + features: features.into(), reason: reason.map_or(String::new(), |r| r.to_string()), } } diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index f73008c308322..12d9f8eadc2d6 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -61,12 +61,12 @@ impl DeprecationEntry { /// A stability index, giving the stability level for items and methods. #[derive(HashStable, Debug)] -pub struct Index { +pub struct Index<'tcx> { /// This is mostly a cache, except the stabilities of local items /// are filled by the annotator. - pub stab_map: LocalDefIdMap, - pub const_stab_map: LocalDefIdMap, - pub default_body_stab_map: LocalDefIdMap, + pub stab_map: LocalDefIdMap<&'tcx Stability>, + pub const_stab_map: LocalDefIdMap<&'tcx ConstStability>, + pub default_body_stab_map: LocalDefIdMap<&'tcx DefaultBodyStability>, pub depr_map: LocalDefIdMap, /// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]` /// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute @@ -83,16 +83,19 @@ pub struct Index { pub implications: UnordMap, } -impl Index { - pub fn local_stability(&self, def_id: LocalDefId) -> Option { +impl<'tcx> Index<'tcx> { + pub fn local_stability(&self, def_id: LocalDefId) -> Option<&'tcx Stability> { self.stab_map.get(&def_id).copied() } - pub fn local_const_stability(&self, def_id: LocalDefId) -> Option { + pub fn local_const_stability(&self, def_id: LocalDefId) -> Option<&'tcx ConstStability> { self.const_stab_map.get(&def_id).copied() } - pub fn local_default_body_stability(&self, def_id: LocalDefId) -> Option { + pub fn local_default_body_stability( + &self, + def_id: LocalDefId, + ) -> Option<&'tcx DefaultBodyStability> { self.default_body_stab_map.get(&def_id).copied() } @@ -103,24 +106,23 @@ impl Index { pub fn report_unstable( sess: &Session, - feature: Symbol, + features: Vec, reason: Option, - issue: Option>, - suggestion: Option<(Span, String, String, Applicability)>, + issues: Vec>, + suggestions: Vec<(Span, String, String, Applicability)>, span: Span, ) { - let features = vec![feature]; - - let mut err = sess.dcx().create_err(UnstableLibraryFeatureError::new(feature, reason, span)); + let mut err = + sess.dcx().create_err(UnstableLibraryFeatureError::new(features.clone(), reason, span)); add_feature_diagnostics_for_issues( &mut err, sess, &features, - GateIssues::Library(Vec::from_iter(issue)), + GateIssues::Library(issues), false, None, ); - if let Some((inner_types, msg, sugg, applicability)) = suggestion { + for (inner_types, msg, sugg, applicability) in suggestions { err.span_suggestion(inner_types, msg, sugg, applicability); } err.emit(); @@ -256,16 +258,16 @@ fn late_report_deprecation( /// Result of `TyCtxt::eval_stability`. pub enum EvalResult { - /// We can use the item because it is stable or we provided the - /// corresponding feature gate. + /// We can use the item because it is stable or we enabled the + /// corresponding feature gates. Allow, - /// We cannot use the item because it is unstable and we did not provide the - /// corresponding feature gate. + /// We cannot use the item because it is unstable and we did not enable the + /// corresponding feature gates. Deny { - feature: Symbol, + features: Vec, reason: Option, - issue: Option>, - suggestion: Option<(Span, String, String, Applicability)>, + issues: Vec>, + suggestions: Vec<(Span, String, String, Applicability)>, is_soft: bool, }, /// The item does not have the `#[stable]` or `#[unstable]` marker assigned. @@ -329,8 +331,8 @@ impl<'tcx> TyCtxt<'tcx> { /// Evaluates the stability of an item. /// /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding - /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending - /// unstable feature otherwise. + /// `#![feature]`s have been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable features otherwise. /// /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to @@ -392,51 +394,64 @@ impl<'tcx> TyCtxt<'tcx> { match stability { Some(Stability { level: attr::Unstable { unstables, reason, is_soft } }) => { - let attr::Unstability { feature, issue, implied_by } = unstables; - if span.allows_unstable(feature) { - debug!("stability: skipping span={:?} since it is internal", span); - return EvalResult::Allow; - } - if self.features().enabled(feature) { + if matches!(allow_unstable, AllowUnstable::Yes) { return EvalResult::Allow; } - // If this item was previously part of a now-stabilized feature which is still - // enabled (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().enabled(implied_by) - { - return EvalResult::Allow; - } + let mut missing_features = vec![]; + let mut issues = vec![]; + let mut suggestions = vec![]; - // 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 - // able to compile everything without requiring upstream - // modifications, so in the case that this looks like a - // `rustc_private` crate (e.g., a compiler crate) and we also have - // the `-Z force-unstable-if-unmarked` flag present (we're - // compiling a compiler crate), then let this missing feature - // annotation slide. - if feature == sym::rustc_private - && issue == NonZero::new(27812) - && self.sess.opts.unstable_opts.force_unstable_if_unmarked - { - return EvalResult::Allow; - } + for unstability in unstables { + let &attr::Unstability { feature, issue, .. } = unstability; + if span.allows_unstable(feature) { + debug!("stability: skipping span={:?} since it is internal", span); + continue; + } + if self.features().enabled(feature) { + continue; + } - if matches!(allow_unstable, AllowUnstable::Yes) { - 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) = unstability.implied_by + && self.features().enabled(implied_by) + { + continue; + } + + // 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 + // able to compile everything without requiring upstream + // modifications, so in the case that this looks like a + // `rustc_private` crate (e.g., a compiler crate) and we also have + // the `-Z force-unstable-if-unmarked` flag present (we're + // compiling a compiler crate), then let this missing feature + // annotation slide. + if feature == sym::rustc_private + && issue == NonZero::new(27812) + && self.sess.opts.unstable_opts.force_unstable_if_unmarked + { + continue; + } + + missing_features.push(feature); + issues.extend(issue); + suggestions.extend(suggestion_for_allocator_api(self, def_id, span, feature)); } - let suggestion = suggestion_for_allocator_api(self, def_id, span, feature); - EvalResult::Deny { - feature, - reason: reason.to_opt_reason(), - issue, - suggestion, - is_soft, + if missing_features.is_empty() { + EvalResult::Allow + } else { + EvalResult::Deny { + features: missing_features, + reason: reason.to_opt_reason(), + issues, + suggestions, + is_soft: *is_soft, + } } } Some(_) => { @@ -451,8 +466,8 @@ impl<'tcx> TyCtxt<'tcx> { /// Evaluates the default-impl stability of an item. /// /// Returns `EvalResult::Allow` if the item's default implementation is stable, or unstable but the corresponding - /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending - /// unstable feature otherwise. + /// `#![feature]`s have been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable features otherwise. pub fn eval_default_body_stability(self, def_id: DefId, span: Span) -> EvalResult { let is_staged_api = self.lookup_stability(def_id.krate.as_def_id()).is_some(); if !is_staged_api { @@ -472,21 +487,32 @@ impl<'tcx> TyCtxt<'tcx> { match stability { Some(DefaultBodyStability { level: attr::Unstable { unstables, reason, is_soft } }) => { - let attr::Unstability { feature, issue, .. } = unstables; - if span.allows_unstable(feature) { - debug!("body stability: skipping span={:?} since it is internal", span); - return EvalResult::Allow; - } - if self.features().enabled(feature) { - return EvalResult::Allow; + let mut missing_features = vec![]; + let mut issues = vec![]; + for unstability in unstables { + let feature = unstability.feature; + if span.allows_unstable(feature) { + debug!("body stability: skipping span={:?} since it is internal", span); + continue; + } + if self.features().enabled(feature) { + continue; + } + + missing_features.push(feature); + issues.extend(unstability.issue); } - EvalResult::Deny { - feature, - reason: reason.to_opt_reason(), - issue, - suggestion: None, - is_soft, + if missing_features.is_empty() { + EvalResult::Allow + } else { + EvalResult::Deny { + features: missing_features, + reason: reason.to_opt_reason(), + issues, + suggestions: vec![], + is_soft: *is_soft, + } } } Some(_) => { @@ -569,16 +595,16 @@ impl<'tcx> TyCtxt<'tcx> { let is_allowed = matches!(eval_result, EvalResult::Allow); match eval_result { EvalResult::Allow => {} - EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => { + EvalResult::Deny { features, reason, issues, suggestions, is_soft } => { if is_soft { self.emit_node_span_lint( SOFT_UNSTABLE, id.unwrap_or(hir::CRATE_HIR_ID), span, - SoftUnstableLibraryFeature::new(feature, reason), + SoftUnstableLibraryFeature::new(features, reason), ); } else { - report_unstable(self.sess, feature, reason, issue, suggestion, span); + report_unstable(self.sess, features, reason, issues, suggestions, span); } } EvalResult::Unmarked => unmarked(span, def_id), diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 013847f0b2d53..e65b03649d1b5 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -239,9 +239,6 @@ trivial! { bool, Option<(rustc_span::def_id::DefId, rustc_session::config::EntryFnType)>, Option, - Option, - Option, - Option, Option, Option, Option, @@ -264,10 +261,7 @@ trivial! { Result, rustc_abi::ReprOptions, rustc_ast::expand::allocator::AllocatorKind, - rustc_attr::ConstStability, - rustc_attr::DefaultBodyStability, rustc_attr::Deprecation, - rustc_attr::Stability, rustc_data_structures::svh::Svh, rustc_errors::ErrorGuaranteed, rustc_hir::Constness, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 684d5b6c2a778..2d7774ef1a04e 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1220,19 +1220,19 @@ rustc_queries! { feedable } - query lookup_stability(def_id: DefId) -> Option { + query lookup_stability(def_id: DefId) -> Option<&'tcx attr::Stability> { desc { |tcx| "looking up stability of `{}`", tcx.def_path_str(def_id) } cache_on_disk_if { def_id.is_local() } separate_provide_extern } - query lookup_const_stability(def_id: DefId) -> Option { + query lookup_const_stability(def_id: DefId) -> Option<&'tcx attr::ConstStability> { desc { |tcx| "looking up const stability of `{}`", tcx.def_path_str(def_id) } cache_on_disk_if { def_id.is_local() } separate_provide_extern } - query lookup_default_body_stability(def_id: DefId) -> Option { + query lookup_default_body_stability(def_id: DefId) -> Option<&'tcx attr::DefaultBodyStability> { desc { |tcx| "looking up default body stability of `{}`", tcx.def_path_str(def_id) } separate_provide_extern } @@ -1969,7 +1969,7 @@ rustc_queries! { desc { |tcx| "finding names imported by glob use for `{}`", tcx.def_path_str(def_id) } } - query stability_index(_: ()) -> &'tcx stability::Index { + query stability_index(_: ()) -> &'tcx stability::Index<'tcx> { arena_cache eval_always desc { "calculating the stability index for the local crate" } diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 3849cb72668f6..48863a8ddd0b4 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -790,6 +790,27 @@ impl<'a, 'tcx> Decodable> } } +impl<'a, 'tcx> Decodable> for &'tcx rustc_attr::Stability { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + d.tcx.arena.alloc(Decodable::decode(d)) + } +} + +impl<'a, 'tcx> Decodable> for &'tcx rustc_attr::ConstStability { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + d.tcx.arena.alloc(Decodable::decode(d)) + } +} + +impl<'a, 'tcx> Decodable> for &'tcx rustc_attr::DefaultBodyStability { + #[inline] + fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self { + d.tcx.arena.alloc(Decodable::decode(d)) + } +} + macro_rules! impl_ref_decoder { (<$tcx:tt> $($ty:ty,)*) => { $(impl<'a, $tcx> Decodable> for &$tcx [$ty] { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 1b5fed83eccd2..ae8ff331d1b5e 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1644,7 +1644,7 @@ impl<'tcx> TyCtxt<'tcx> { ) } - pub fn stability(self) -> &'tcx stability::Index { + pub fn stability(self) -> &'tcx stability::Index<'tcx> { self.stability_index(()) } diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml index ed5991459ac1b..3e44cc469521c 100644 --- a/compiler/rustc_passes/Cargo.toml +++ b/compiler/rustc_passes/Cargo.toml @@ -24,5 +24,6 @@ rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } +smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } tracing = "0.1" # tidy-alphabetical-end diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 2d1734c031433..e9cbe87838975 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1507,7 +1507,7 @@ pub(crate) struct DeprecatedAttribute { pub(crate) struct UselessStability { #[primary_span] #[label] - pub span: Span, + pub spans: Vec, #[label(passes_item)] pub item_sp: Span, } diff --git a/compiler/rustc_passes/src/lib_features.rs b/compiler/rustc_passes/src/lib_features.rs index 8a360c017adbf..41a5a928de1da 100644 --- a/compiler/rustc_passes/src/lib_features.rs +++ b/compiler/rustc_passes/src/lib_features.rs @@ -75,10 +75,6 @@ impl<'tcx> LibFeatureCollector<'tcx> { return Some((feature, FeatureStability::AcceptedSince(since), attr.span)); } } - // We need to iterate over the other attributes, because - // `rustc_const_unstable` is not mutually exclusive with - // the other stability attributes, so we can't just `break` - // here. } } diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index a85e580b660a4..22181bcf2e035 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -27,6 +27,7 @@ use rustc_session::lint; use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED}; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; +use smallvec::smallvec; use tracing::{debug, info}; use crate::errors; @@ -90,9 +91,9 @@ impl InheritStability { /// A private tree-walker for producing an `Index`. struct Annotator<'a, 'tcx> { tcx: TyCtxt<'tcx>, - index: &'a mut Index, - parent_stab: Option, - parent_const_stab: Option, + index: &'a mut Index<'tcx>, + parent_stab: Option<&'tcx Stability>, + parent_const_stab: Option<&'tcx ConstStability>, parent_depr: Option, in_trait_impl: bool, } @@ -148,11 +149,11 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // -Zforce-unstable-if-unmarked is set. if let Some(stab) = self.parent_stab { if inherit_deprecation.yes() && stab.is_unstable() { - self.index.stab_map.insert(def_id, stab); + self.index.stab_map.insert(def_id, &stab); if fn_sig.is_some_and(|s| s.header.is_const()) { let const_stab = - attr::unmarked_crate_const_stab(self.tcx.sess, attrs, stab); - self.index.const_stab_map.insert(def_id, const_stab); + attr::unmarked_crate_const_stab(self.tcx.sess, attrs, stab.clone()); + self.index.const_stab_map.insert(def_id, self.tcx.arena.alloc(const_stab)); } } } @@ -178,30 +179,35 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { self.tcx.dcx().emit_err(errors::DeprecatedAttribute { span: *span }); } - if let Some((body_stab, _span)) = body_stab { + if let Some((body_stab, _spans)) = body_stab { // FIXME: check that this item can have body stability - self.index.default_body_stab_map.insert(def_id, body_stab); + self.index.default_body_stab_map.insert(def_id, self.tcx.arena.alloc(body_stab)); debug!(?self.index.default_body_stab_map); } - let stab = stab.map(|(stab, span)| { + let stab = stab.map(|(stab, stab_spans)| { // Error if prohibited, or can't inherit anything from a container. if kind == AnnotationKind::Prohibited || (kind == AnnotationKind::Container && stab.level.is_stable() && is_deprecated) { - self.tcx.dcx().emit_err(errors::UselessStability { span, item_sp }); + let spans = stab_spans.spans(); + self.tcx.dcx().emit_err(errors::UselessStability { spans, item_sp }); } debug!("annotate: found {:?}", stab); // Check if deprecated_since < stable_since. If it is, // this is *almost surely* an accident. - if let ( - &Some(DeprecatedSince::RustcVersion(dep_since)), - &attr::Stable { since: stab_since, .. }, - ) = (&depr.as_ref().map(|(d, _)| d.since), &stab.level) + if let &Some(DeprecatedSince::RustcVersion(dep_since)) = + &depr.as_ref().map(|(d, _)| d.since) + && let Some(stab_since) = stab.stable_since() { + let &(_, span) = stab_spans + .iter() + .find(|(level, _)| level.stable_since() == Some(stab_since)) + .expect("stabilization version should have an associated span"); + match stab_since { StableSince::Current => { self.tcx @@ -224,19 +230,24 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // Stable *language* features shouldn't be used as unstable library features. // (Not doing this for stable library features is checked by tidy.) - if let Unstable { unstables, .. } = stab.level { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == unstables.feature).is_some() { - self.tcx - .dcx() - .emit_err(errors::UnstableAttrForAlreadyStableFeature { span, item_sp }); + for (level, span) in stab_spans.iter() { + if ACCEPTED_LANG_FEATURES.iter().any(|f| level.has_unstable_feature(f.name)) { + self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature { + span: *span, + item_sp, + }); } } - if let Unstable { unstables, .. } = stab.level - && let Unstability { feature, implied_by: Some(implied_by), .. } = unstables - { - self.index.implications.insert(implied_by, feature); + + if let Unstable { unstables, .. } = &stab.level { + for &Unstability { feature, implied_by, .. } in unstables { + if let Some(implied_by) = implied_by { + self.index.implications.insert(implied_by, feature); + } + } } + let stab: &_ = self.tcx.arena.alloc(stab); self.index.stab_map.insert(def_id, stab); stab }); @@ -266,9 +277,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { } // If this is marked const *stable*, it must also be regular-stable. - if let Some((const_stab, const_span)) = const_stab + if let Some((const_stab, const_spans)) = &const_stab && let Some(fn_sig) = fn_sig && const_stab.is_const_stable() + && let Some(&(_, const_span)) = const_spans.iter().find(|(level, _)| level.is_stable()) && !stab.is_some_and(|s| s.is_stable()) { self.tcx @@ -278,14 +290,14 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // Stable *language* features shouldn't be used as unstable library features. // (Not doing this for stable library features is checked by tidy.) - if let Some((ConstStability { level: Unstable { unstables, .. }, .. }, const_span)) = - const_stab - { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == unstables.feature).is_some() { - self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature { - span: const_span, - item_sp, - }); + if let Some((_, const_spans)) = &const_stab { + for (level, const_span) in const_spans.iter() { + if ACCEPTED_LANG_FEATURES.iter().any(|f| level.has_unstable_feature(f.name)) { + self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature { + span: *const_span, + item_sp, + }); + } } } @@ -303,19 +315,23 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // We subject these implicitly-const functions to recursive const stability. const_stable_indirect: true, promotable: false, - level: inherit_regular_stab.level, + level: inherit_regular_stab.level.clone(), }); } // Now that everything is computed, insert it into the table. - const_stab.inspect(|const_stab| { - self.index.const_stab_map.insert(def_id, *const_stab); + let const_stab = const_stab.map(|const_stab| { + let const_stab = self.tcx.arena.alloc(const_stab) as &_; + self.index.const_stab_map.insert(def_id, const_stab); + const_stab }); - if let Some(ConstStability { level: Unstable { unstables, .. }, .. }) = const_stab - && let Some(implied_by) = unstables.implied_by - { - self.index.implications.insert(implied_by, unstables.feature); + if let Some(ConstStability { level: Unstable { unstables, .. }, .. }) = const_stab { + for &Unstability { feature, implied_by, .. } in unstables { + if let Some(implied_by) = implied_by { + self.index.implications.insert(implied_by, feature); + } + } } // `impl const Trait for Type` items forward their const stability to their @@ -342,8 +358,8 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { fn recurse_with_stability_attrs( &mut self, depr: Option, - stab: Option, - const_stab: Option, + stab: Option<&'tcx Stability>, + const_stab: Option<&'tcx ConstStability>, f: impl FnOnce(&mut Self), ) { // These will be `Some` if this item changes the corresponding stability attribute. @@ -670,7 +686,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> { // stable (assuming they have not inherited instability from their parent). } -fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { +fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index<'_> { let mut index = Index { stab_map: Default::default(), const_stab_map: Default::default(), @@ -696,17 +712,17 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { // while maintaining the invariant that all sysroot crates are unstable // by default and are unable to be used. if tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { - let stability = Stability { + let stability = tcx.arena.alloc(Stability { level: attr::StabilityLevel::Unstable { - unstables: attr::Unstability { + unstables: smallvec![attr::Unstability { feature: sym::rustc_private, issue: NonZero::new(27812), implied_by: None, - }, + },], reason: UnstableReason::Default, is_soft: false, }, - }; + }); annotator.parent_stab = Some(stability); } @@ -787,7 +803,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { // error if all involved types and traits are stable, because // it will have no effect. // See: https://github.com/rust-lang/rust/issues/55436 - if let Some((Stability { level: attr::Unstable { .. }, .. }, span)) = stab { + if let Some((Stability { level: attr::Unstable { .. }, .. }, spans)) = stab { let mut c = CheckTraitImplStable { tcx: self.tcx, fully_stable: true }; c.visit_ty(self_ty); c.visit_trait_ref(t); @@ -798,7 +814,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { self.tcx.emit_node_span_lint( INEFFECTIVE_UNSTABLE_TRAIT_IMPL, item.hir_id(), - span, + spans.spans(), errors::IneffectiveUnstableImpl, ); } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index f8dcb8051c5ed..a02b26112a40e 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -7,7 +7,7 @@ use std::mem; use rustc_ast::expand::StrippedCfgItem; use rustc_ast::{self as ast, Crate, Inline, ItemKind, ModKind, NodeId, attr}; use rustc_ast_pretty::pprust; -use rustc_attr::StabilityLevel; +use rustc_attr::{StabilityLevel, Unstability}; use rustc_data_structures::intern::Interned; use rustc_data_structures::sync::Lrc; use rustc_errors::{Applicability, StashKey}; @@ -1002,33 +1002,41 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { node_id: NodeId, ) { let span = path.span; - if let Some(stability) = &ext.stability { - if let StabilityLevel::Unstable { unstables, reason, is_soft } = stability.level { - let rustc_attr::Unstability { feature, issue, implied_by } = unstables; - let is_allowed = - |feature| self.tcx.features().enabled(feature) || span.allows_unstable(feature); - let allowed_by_implication = implied_by.is_some_and(|feature| is_allowed(feature)); - if !is_allowed(feature) && !allowed_by_implication { - if is_soft { - self.lint_buffer.buffer_lint( - SOFT_UNSTABLE, - node_id, - span, - BuiltinLintDiag::SoftUnstableMacro { - feature, - reason: reason.to_opt_reason(), - }, - ); - } else { - stability::report_unstable( - self.tcx.sess, - feature, - reason.to_opt_reason(), - issue, - None, - span, - ); - } + if let Some(stability) = &ext.stability + && let StabilityLevel::Unstable { unstables, reason, is_soft } = &stability.level + { + let is_allowed = + |feature| self.tcx.features().enabled(feature) || span.allows_unstable(feature); + let allowed_by_implication = |unstability: &Unstability| { + unstability.implied_by.is_some_and(|feature| is_allowed(feature)) + }; + + let (missing_features, issues): (Vec<_>, Vec<_>) = unstables + .iter() + .filter(|u| !is_allowed(u.feature) && !allowed_by_implication(u)) + .map(|u| (u.feature, u.issue)) + .unzip(); + + if !missing_features.is_empty() { + if *is_soft { + self.lint_buffer.buffer_lint( + SOFT_UNSTABLE, + node_id, + span, + BuiltinLintDiag::SoftUnstableMacro { + features: missing_features, + reason: reason.to_opt_reason(), + }, + ); + } else { + stability::report_unstable( + self.tcx.sess, + missing_features, + reason.to_opt_reason(), + issues.into_iter().flatten().collect(), + vec![], + span, + ) } } } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 29fabdd1deb8d..d138dd2a368fb 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -25,6 +25,7 @@ use rustc_errors::{ Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort, FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle, }; +use rustc_feature::GateIssues; use rustc_macros::HashStable_Generic; pub use rustc_span::def_id::StableCrateId; use rustc_span::edition::Edition; @@ -45,7 +46,7 @@ use crate::config::{ SwitchWithOptPath, }; use crate::filesearch::FileSearch; -use crate::parse::{ParseSess, add_feature_diagnostics}; +use crate::parse::{ParseSess, add_feature_diagnostics_for_issues}; use crate::search_paths::SearchPath; use crate::{errors, filesearch, lint}; @@ -309,12 +310,29 @@ impl Session { /// `feature` must be a language feature. #[track_caller] pub fn create_feature_err<'a>(&'a self, err: impl Diagnostic<'a>, feature: Symbol) -> Diag<'a> { + self.create_features_err(err, &[feature]) + } + + /// `features` must be language features. + #[track_caller] + pub fn create_features_err<'a>( + &'a self, + err: impl Diagnostic<'a>, + features: &[Symbol], + ) -> Diag<'a> { let mut err = self.dcx().create_err(err); if err.code.is_none() { #[allow(rustc::diagnostic_outside_of_impl)] err.code(E0658); } - add_feature_diagnostics(&mut err, self, feature); + add_feature_diagnostics_for_issues( + &mut err, + self, + features, + GateIssues::Language, + false, + None, + ); err } diff --git a/tests/ui/stability-attribute/auxiliary/soft-unstable.rs b/tests/ui/stability-attribute/auxiliary/soft-unstable.rs new file mode 100644 index 0000000000000..4baf0b38270f5 --- /dev/null +++ b/tests/ui/stability-attribute/auxiliary/soft-unstable.rs @@ -0,0 +1,14 @@ +#![stable(feature = "stable_feature", since = "1.0.0")] +#![feature(staged_api)] +#![crate_type = "lib"] + +#[unstable(feature = "a", issue = "1", soft)] +#[unstable(feature = "b", issue = "2", reason = "reason", soft)] +#[macro_export] +macro_rules! mac { + () => () +} + +#[unstable(feature = "c", issue = "3", soft)] +#[unstable(feature = "d", issue = "4", reason = "reason", soft)] +pub fn something() {} diff --git a/tests/ui/stability-attribute/auxiliary/two-unstables.rs b/tests/ui/stability-attribute/auxiliary/two-unstables.rs new file mode 100644 index 0000000000000..b18cdcc6dfc57 --- /dev/null +++ b/tests/ui/stability-attribute/auxiliary/two-unstables.rs @@ -0,0 +1,27 @@ +#![stable(feature = "stable_feature", since = "1.0.0")] +#![feature(staged_api)] +#![crate_type = "lib"] + +#[unstable(feature = "a", issue = "1", reason = "reason")] +#[unstable(feature = "b", issue = "2")] +pub struct Foo; + +#[stable(feature = "stable_feature", since = "1.0.0")] +#[rustc_const_unstable(feature = "c", issue = "3", reason = "reason")] +#[rustc_const_unstable(feature = "d", issue = "4")] +pub const fn nothing() {} + +#[stable(feature = "stable_feature", since = "1.0.0")] +pub trait Trait { + #[stable(feature = "stable_feature", since = "1.0.0")] + #[rustc_default_body_unstable(feature = "e", issue = "5", reason = "reason")] + #[rustc_default_body_unstable(feature = "f", issue = "6")] + fn method() {} +} + +#[unstable(feature = "g", issue = "7", reason = "reason")] +#[unstable(feature = "h", issue = "8")] +#[macro_export] +macro_rules! mac { + () => () +} diff --git a/tests/ui/stability-attribute/soft-unstable.none.stderr b/tests/ui/stability-attribute/soft-unstable.none.stderr new file mode 100644 index 0000000000000..7e4b634f0283d --- /dev/null +++ b/tests/ui/stability-attribute/soft-unstable.none.stderr @@ -0,0 +1,43 @@ +error: use of unstable library features `a` and `b`: reason + --> $DIR/soft-unstable.rs:11:5 + | +LL | soft_unstable::mac!(); + | ^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #64266 + = note: `#[deny(soft_unstable)]` on by default + +error: use of unstable library features `c` and `d`: reason + --> $DIR/soft-unstable.rs:14:5 + | +LL | soft_unstable::something(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #64266 + +error: aborting due to 2 previous errors + +Future incompatibility report: Future breakage diagnostic: +error: use of unstable library features `a` and `b`: reason + --> $DIR/soft-unstable.rs:11:5 + | +LL | soft_unstable::mac!(); + | ^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #64266 + = note: `#[deny(soft_unstable)]` on by default + +Future breakage diagnostic: +error: use of unstable library features `c` and `d`: reason + --> $DIR/soft-unstable.rs:14:5 + | +LL | soft_unstable::something(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #64266 + = note: `#[deny(soft_unstable)]` on by default + diff --git a/tests/ui/stability-attribute/soft-unstable.rs b/tests/ui/stability-attribute/soft-unstable.rs new file mode 100644 index 0000000000000..e70df4c273d90 --- /dev/null +++ b/tests/ui/stability-attribute/soft-unstable.rs @@ -0,0 +1,17 @@ +// Test handling of soft-unstable items dependent on multiple features. +//@ aux-build:soft-unstable.rs +//@ revisions: all none +//@ [all]check-pass + +#![cfg_attr(all, feature(a, b, c, d))] + +extern crate soft_unstable; + +fn main() { + soft_unstable::mac!(); + //[none]~^ ERROR use of unstable library features `a` and `b`: reason [soft_unstable] + //[none]~| WARNING this was previously accepted by the compiler but is being phased out + soft_unstable::something(); + //[none]~^ ERROR use of unstable library features `c` and `d`: reason [soft_unstable] + //[none]~| WARNING this was previously accepted by the compiler but is being phased out +} diff --git a/tests/ui/stability-attribute/stability-attribute-sanity.rs b/tests/ui/stability-attribute/stability-attribute-sanity.rs index 7857a0603bd45..98b08b2604d4b 100644 --- a/tests/ui/stability-attribute/stability-attribute-sanity.rs +++ b/tests/ui/stability-attribute/stability-attribute-sanity.rs @@ -45,23 +45,23 @@ mod missing_version { fn f3() { } } -#[unstable(feature = "b", issue = "none")] -#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels [E0544] +#[stable(feature = "a", since = "4.4.4")] +#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels for feature `a` [E0544] fn multiple1() { } #[unstable(feature = "b", issue = "none")] -#[unstable(feature = "b", issue = "none")] //~ ERROR multiple stability levels [E0544] +#[unstable(feature = "b", issue = "none")] //~ ERROR multiple stability levels for feature `b` [E0544] fn multiple2() { } -#[stable(feature = "a", since = "4.4.4")] -#[stable(feature = "a", since = "4.4.4")] //~ ERROR multiple stability levels [E0544] -fn multiple3() { } +#[unstable(feature = "c", issue = "none")] +#[stable(feature = "c", since = "4.4.4")] //~ ERROR multiple stability levels for feature `c` [E0544] +fn multiple3() { } //~| ERROR feature `c` is declared stable, but was previously declared unstable #[stable(feature = "e", since = "b")] //~ ERROR 'since' must be a Rust version number, such as "1.31.0" #[deprecated(since = "5.5.5", note = "text")] #[deprecated(since = "5.5.5", note = "text")] //~ ERROR multiple `deprecated` attributes -#[rustc_const_unstable(feature = "c", issue = "none")] -#[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels +#[rustc_const_unstable(feature = "d", issue = "none")] +#[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels for feature `d` pub const fn multiple4() { } #[stable(feature = "a", since = "1.0.0")] //~ ERROR feature `a` is declared stable since 1.0.0 diff --git a/tests/ui/stability-attribute/stability-attribute-sanity.stderr b/tests/ui/stability-attribute/stability-attribute-sanity.stderr index c614fc2b9f7fa..264de8326f11f 100644 --- a/tests/ui/stability-attribute/stability-attribute-sanity.stderr +++ b/tests/ui/stability-attribute/stability-attribute-sanity.stderr @@ -76,22 +76,22 @@ error[E0543]: missing 'note' LL | #[deprecated(since = "5.5.5")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0544]: multiple stability levels +error[E0544]: multiple stability levels for feature `a` --> $DIR/stability-attribute-sanity.rs:49:1 | LL | #[stable(feature = "a", since = "4.4.4")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0544]: multiple stability levels +error[E0544]: multiple stability levels for feature `b` --> $DIR/stability-attribute-sanity.rs:53:1 | LL | #[unstable(feature = "b", issue = "none")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0544]: multiple stability levels +error[E0544]: multiple stability levels for feature `c` --> $DIR/stability-attribute-sanity.rs:57:1 | -LL | #[stable(feature = "a", since = "4.4.4")] +LL | #[stable(feature = "c", since = "4.4.4")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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" LL | #[stable(feature = "e", since = "b")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0544]: multiple stability levels +error[E0544]: multiple stability levels for feature `d` --> $DIR/stability-attribute-sanity.rs:64:1 | LL | #[rustc_const_unstable(feature = "d", issue = "none")] @@ -118,13 +118,19 @@ error[E0549]: deprecated attribute must be paired with either stable or unstable LL | #[deprecated(since = "5.5.5", note = "text")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +error[E0711]: feature `c` is declared stable, but was previously declared unstable + --> $DIR/stability-attribute-sanity.rs:57:1 + | +LL | #[stable(feature = "c", since = "4.4.4")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + error[E0711]: feature `a` is declared stable since 1.0.0, but was previously declared stable since 4.4.4 --> $DIR/stability-attribute-sanity.rs:67:1 | LL | #[stable(feature = "a", since = "1.0.0")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 20 previous errors +error: aborting due to 21 previous errors Some errors have detailed explanations: E0539, E0541, E0542, E0543, E0544, E0546, E0547, E0549, E0711. For more information about an error, try `rustc --explain E0539`. diff --git a/tests/ui/stability-attribute/two-unstables.none.stderr b/tests/ui/stability-attribute/two-unstables.none.stderr new file mode 100644 index 0000000000000..4aef8109caf13 --- /dev/null +++ b/tests/ui/stability-attribute/two-unstables.none.stderr @@ -0,0 +1,47 @@ +error[E0658]: use of unstable library features `g` and `h`: reason + --> $DIR/two-unstables.rs:24:5 + | +LL | two_unstables::mac!(); + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #7 for more information + = note: see issue #8 for more information + = help: add `#![feature(g, h)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library features `a` and `b`: reason + --> $DIR/two-unstables.rs:15:16 + | +LL | struct Wrapper(two_unstables::Foo); + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #1 for more information + = note: see issue #2 for more information + = help: add `#![feature(a, b)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0046]: not all trait items implemented, missing: `method` + --> $DIR/two-unstables.rs:19:1 + | +LL | impl two_unstables::Trait for Wrapper {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: default implementation of `method` is unstable + = note: use of unstable library features `e` and `f`: reason + = note: see issue #5 for more information + = note: see issue #6 for more information + = help: add `#![feature(e, f)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: `nothing` is not yet stable as a const fn + --> $DIR/two-unstables.rs:11:25 + | +LL | const USE_NOTHING: () = two_unstables::nothing(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(c, d)]` to the crate attributes to enable + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0046, E0658. +For more information about an error, try `rustc --explain E0046`. diff --git a/tests/ui/stability-attribute/two-unstables.rs b/tests/ui/stability-attribute/two-unstables.rs new file mode 100644 index 0000000000000..3c3678cf77bc5 --- /dev/null +++ b/tests/ui/stability-attribute/two-unstables.rs @@ -0,0 +1,27 @@ +// Test handling of unstable items dependent on multiple features. +//@ aux-build:two-unstables.rs +//@ revisions: all some none +//@ [all]check-pass + +#![cfg_attr(all, feature(a, b, c, d, e, f, g, h))] +#![cfg_attr(some, feature(a, d, e, h))] + +extern crate two_unstables; + +const USE_NOTHING: () = two_unstables::nothing(); +//[none]~^ ERROR `nothing` is not yet stable as a const fn +//[some]~^^ ERROR `nothing` is not yet stable as a const fn + +struct Wrapper(two_unstables::Foo); +//[none]~^ ERROR use of unstable library features `a` and `b`: reason [E0658] +//[some]~^^ ERROR use of unstable library feature `b`: reason [E0658] + +impl two_unstables::Trait for Wrapper {} +//[none]~^ ERROR not all trait items implemented, missing: `method` [E0046] +//[some]~^^ ERROR not all trait items implemented, missing: `method` [E0046] + +fn main() { + two_unstables::mac!(); + //[none]~^ ERROR use of unstable library features `g` and `h`: reason [E0658] + //[some]~^^ ERROR use of unstable library feature `g`: reason [E0658] +} diff --git a/tests/ui/stability-attribute/two-unstables.some.stderr b/tests/ui/stability-attribute/two-unstables.some.stderr new file mode 100644 index 0000000000000..4881ad15e09d4 --- /dev/null +++ b/tests/ui/stability-attribute/two-unstables.some.stderr @@ -0,0 +1,44 @@ +error[E0658]: use of unstable library feature `g`: reason + --> $DIR/two-unstables.rs:24:5 + | +LL | two_unstables::mac!(); + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #7 for more information + = help: add `#![feature(g)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: use of unstable library feature `b`: reason + --> $DIR/two-unstables.rs:15:16 + | +LL | struct Wrapper(two_unstables::Foo); + | ^^^^^^^^^^^^^^^^^^ + | + = note: see issue #2 for more information + = help: add `#![feature(b)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0046]: not all trait items implemented, missing: `method` + --> $DIR/two-unstables.rs:19:1 + | +LL | impl two_unstables::Trait for Wrapper {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: default implementation of `method` is unstable + = note: use of unstable library feature `f`: reason + = note: see issue #6 for more information + = help: add `#![feature(f)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: `nothing` is not yet stable as a const fn + --> $DIR/two-unstables.rs:11:25 + | +LL | const USE_NOTHING: () = two_unstables::nothing(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(c)]` to the crate attributes to enable + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0046, E0658. +For more information about an error, try `rustc --explain E0046`. From e614f5e2afdaf669466e2fe28fe8efc0d8ca0765 Mon Sep 17 00:00:00 2001 From: dianne Date: Fri, 27 Sep 2024 04:13:41 -0700 Subject: [PATCH 06/10] rustdoc: support multiple stability attributes --- compiler/rustc_attr/src/builtin.rs | 4 ++ src/librustdoc/clean/inline.rs | 6 +-- src/librustdoc/clean/types.rs | 6 +-- src/librustdoc/html/format.rs | 2 +- src/librustdoc/html/render/mod.rs | 51 ++++++++++++------- src/librustdoc/html/render/print_item.rs | 4 +- .../html/templates/short_item_info.html | 11 +--- .../html/templates/unstable_feature.html | 6 +++ src/librustdoc/passes/propagate_stability.rs | 16 +++--- tests/rustdoc/stability.rs | 8 +++ 10 files changed, 69 insertions(+), 45 deletions(-) create mode 100644 src/librustdoc/html/templates/unstable_feature.html diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 487ddd854ccfb..fac5b4adf2abf 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -89,6 +89,10 @@ impl Stability { pub fn unstable_features(&self) -> impl Iterator + use<'_> { self.level.unstable_features() } + + pub fn is_rustc_private(&self) -> bool { + self.level.has_unstable_feature(sym::rustc_private) + } } /// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes. diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 5ddd6188c2d86..4a4c75f973f20 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -432,10 +432,8 @@ pub(crate) fn build_impl( let associated_trait = tcx.impl_trait_ref(did).map(ty::EarlyBinder::skip_binder); // Do not inline compiler-internal items unless we're a compiler-internal crate. - let is_compiler_internal = |did| { - tcx.lookup_stability(did) - .is_some_and(|stab| stab.is_unstable() && stab.feature == sym::rustc_private) - }; + let is_compiler_internal = + |did| tcx.lookup_stability(did).is_some_and(|stab| stab.is_rustc_private()); let document_compiler_internal = is_compiler_internal(LOCAL_CRATE.as_def_id()); let is_directly_public = |cx: &mut DocContext<'_>, did| { cx.cache.effective_visibilities.is_directly_public(tcx, did) diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a10a6a92bf57e..c321673d9848e 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -385,8 +385,8 @@ impl Item { /// Returns the effective stability of the item. /// /// This method should only be called after the `propagate-stability` pass has been run. - pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option { - let stability = self.inner.stability; + pub(crate) fn stability(&self, tcx: TyCtxt<'_>) -> Option<&Stability> { + let stability = self.inner.stability.as_ref(); debug_assert!( stability.is_some() || self.def_id().is_none_or(|did| tcx.lookup_stability(did).is_none()), @@ -395,7 +395,7 @@ impl Item { stability } - pub(crate) fn const_stability(&self, tcx: TyCtxt<'_>) -> Option { + pub(crate) fn const_stability<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ConstStability> { self.def_id().and_then(|did| tcx.lookup_const_stability(did)) } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 2f9e7976ca142..dcb36ce32946d 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1649,7 +1649,7 @@ impl PrintWithSpace for hir::Mutability { pub(crate) fn print_constness_with_space( c: &hir::Constness, overall_stab: Option, - const_stab: Option, + const_stab: Option<&ConstStability>, ) -> &'static str { match c { hir::Constness::Const => match (overall_stab, const_stab) { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index f11356b44d885..e4962200e3e6d 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -53,7 +53,7 @@ use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::RustcVersion; -use rustc_span::symbol::{Symbol, sym}; +use rustc_span::symbol::Symbol; use rustc_span::{BytePos, DUMMY_SP, FileName, RealFileName}; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; @@ -675,17 +675,23 @@ enum ShortItemInfo { Deprecation { message: String, }, - /// The feature corresponding to an unstable item, and optionally - /// a tracking issue URL and number. + /// The features corresponding to an unstable item, and optionally + /// a tracking issue URL and number for each. Unstable { - feature: String, - tracking: Option<(String, u32)>, + features: Vec, }, Portability { message: String, }, } +#[derive(Template)] +#[template(path = "unstable_feature.html")] +struct UnstableFeature { + feature: String, + tracking: Option<(String, u32)>, +} + /// Render the stability, deprecation and portability information that is displayed at the top of /// the item's documentation. fn short_item_info( @@ -724,19 +730,24 @@ fn short_item_info( // Render unstable items. But don't render "rustc_private" crates (internal compiler crates). // Those crates are permanently unstable so it makes no sense to render "unstable" everywhere. - if let Some((StabilityLevel::Unstable { reason: _, issue, .. }, feature)) = item + if let Some(StabilityLevel::Unstable { unstables, .. }) = item .stability(cx.tcx()) .as_ref() - .filter(|stab| stab.feature != sym::rustc_private) - .map(|stab| (stab.level, stab.feature)) + .filter(|stab| !stab.is_rustc_private()) + .map(|stab| &stab.level) { - let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) - { - Some((url.clone(), issue.get())) - } else { - None + let track = |issue: Option>| { + if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { + Some((url.clone(), issue.get())) + } else { + None + } }; - extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking }); + let features = unstables + .iter() + .map(|u| UnstableFeature { feature: u.feature.to_string(), tracking: track(u.issue) }) + .collect(); + extra_info.push(ShortItemInfo::Unstable { features }); } if let Some(message) = portability(item, parent) { @@ -990,7 +1001,7 @@ fn assoc_method( fn render_stability_since_raw_with_extra( w: &mut Buffer, stable_version: Option, - const_stability: Option, + const_stability: Option<&ConstStability>, extra_class: &str, ) -> bool { let mut title = String::new(); @@ -1006,12 +1017,16 @@ fn render_stability_since_raw_with_extra( since_to_string(&since) .map(|since| (format!("const since {since}"), format!("const: {since}"))) } - Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => { + Some(ConstStability { level: StabilityLevel::Unstable { unstables, .. }, .. }) => { if stable_version.is_none() { // don't display const unstable if entirely unstable None } else { - let unstable = if let Some(n) = issue { + // if constness depends on multiple unstable features, only link to the first + // tracking issue found, to save space. the issue description should link to issues + // for any features it can intersect with + let feature_issue = unstables.iter().find_map(|u| u.issue.map(|n| (u.feature, n))); + let unstable = if let Some((feature, n)) = feature_issue { format!( " Option { fn render_stability_since_raw( w: &mut Buffer, ver: Option, - const_stability: Option, + const_stability: Option<&ConstStability>, ) -> bool { render_stability_since_raw_with_extra(w, ver, const_stability, "") } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index d247e90d298db..6ce2b759ec2e2 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -14,7 +14,7 @@ use rustc_hir::def_id::DefId; use rustc_index::IndexVec; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::hygiene::MacroKind; -use rustc_span::symbol::{Symbol, kw, sym}; +use rustc_span::symbol::{Symbol, kw}; use tracing::{debug, info}; use super::type_layout::document_type_layout; @@ -567,7 +567,7 @@ fn extra_info_tags<'a, 'tcx: 'a>( // to render "unstable" everywhere. let stability = import_def_id .map_or_else(|| item.stability(tcx), |import_did| tcx.lookup_stability(import_did)); - if stability.is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private) { + if stability.is_some_and(|s| s.is_unstable() && !s.is_rustc_private()) { write!(f, "{}", tag_html("unstable", "", "Experimental"))?; } diff --git a/src/librustdoc/html/templates/short_item_info.html b/src/librustdoc/html/templates/short_item_info.html index e76b98541cf61..e1ddc21a3ad35 100644 --- a/src/librustdoc/html/templates/short_item_info.html +++ b/src/librustdoc/html/templates/short_item_info.html @@ -4,18 +4,11 @@ 👎 {# #} {{message|safe}} {# #} - {% when Self::Unstable with { feature, tracking } %} + {% when Self::Unstable with { features } %}
{# #} 🔬 {# #} {# #} - This is a nightly-only experimental API. ({# #} - {{feature}} - {% match tracking %} - {% when Some with ((url, num)) %} -  #{{num}} - {% when None %} - {% endmatch %} - ) {# #} + This is a nightly-only experimental API. ({{features|join(", ")|safe}}) {# #} {# #}
{% when Self::Portability with { message } %} diff --git a/src/librustdoc/html/templates/unstable_feature.html b/src/librustdoc/html/templates/unstable_feature.html new file mode 100644 index 0000000000000..bd7674cea9628 --- /dev/null +++ b/src/librustdoc/html/templates/unstable_feature.html @@ -0,0 +1,6 @@ +{{feature}} +{% match tracking %} + {% when Some with ((url, num)) %} +  #{{num}} + {% when None %} +{% endmatch %} diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index a28487cc79e5d..cc4831c2f33df 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -26,7 +26,7 @@ pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate { } struct StabilityPropagator<'a, 'tcx> { - parent_stability: Option, + parent_stability: Option<&'tcx Stability>, cx: &'a mut DocContext<'tcx>, } @@ -85,7 +85,7 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { } }; - item.inner.stability = stability; + item.inner.stability = stability.cloned(); self.parent_stability = stability; let item = self.fold_item_recur(item); self.parent_stability = parent_stability; @@ -94,13 +94,13 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { } } -fn merge_stability( - own_stability: Option, - parent_stability: Option, -) -> Option { +fn merge_stability<'tcx>( + own_stability: Option<&'tcx Stability>, + parent_stability: Option<&'tcx Stability>, +) -> Option<&'tcx Stability> { if let Some(own_stab) = own_stability - && let StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } = - own_stab.level + && let &StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } = + &own_stab.level && let Some(parent_stab) = parent_stability && (parent_stab.is_unstable() || parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since)) diff --git a/tests/rustdoc/stability.rs b/tests/rustdoc/stability.rs index 550eb0bc13776..9da66a954307e 100644 --- a/tests/rustdoc/stability.rs +++ b/tests/rustdoc/stability.rs @@ -169,3 +169,11 @@ mod prim_i32 {} /// We currently don't document stability for keywords, but let's test it anyway. #[stable(feature = "rust1", since = "1.0.0")] mod if_keyword {} + +#[unstable(feature = "test", issue = "none")] +#[unstable(feature = "test2", issue = "none")] +pub trait UnstableTraitWithMultipleFeatures { + //@ has stability/trait.UnstableTraitWithMultipleFeatures.html \ + // '//span[@class="item-info"]//div[@class="stab unstable"]' \ + // 'This is a nightly-only experimental API. (test, test2)' +} From c7968f0a2780802d1a45ae89eaa6abb917f39798 Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 28 Sep 2024 01:43:42 -0700 Subject: [PATCH 07/10] rustdoc: report all unstable features needed for items --- src/librustdoc/passes/propagate_stability.rs | 61 +++++++++++++------- tests/rustdoc/stability.rs | 6 ++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/librustdoc/passes/propagate_stability.rs b/src/librustdoc/passes/propagate_stability.rs index cc4831c2f33df..02c615763f6bb 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -21,19 +21,17 @@ pub(crate) const PROPAGATE_STABILITY: Pass = Pass { }; pub(crate) fn propagate_stability(cr: Crate, cx: &mut DocContext<'_>) -> Crate { - let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID); + let crate_stability = cx.tcx.lookup_stability(CRATE_DEF_ID).cloned(); StabilityPropagator { parent_stability: crate_stability, cx }.fold_crate(cr) } struct StabilityPropagator<'a, 'tcx> { - parent_stability: Option<&'tcx Stability>, + parent_stability: Option, cx: &'a mut DocContext<'tcx>, } impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { fn fold_item(&mut self, mut item: Item) -> Option { - let parent_stability = self.parent_stability; - let stability = match item.item_id { ItemId::DefId(def_id) => { let own_stability = self.cx.tcx.lookup_stability(def_id); @@ -59,9 +57,7 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { | ItemKind::MacroItem(..) | ItemKind::ProcMacroItem(..) | ItemKind::ConstantItem(..) => { - // If any of the item's parents was stabilized later or is still unstable, - // then use the parent's stability instead. - merge_stability(own_stability, parent_stability) + merge_stability(own_stability, self.parent_stability.as_ref()) } // Don't inherit the parent's stability for these items, because they @@ -74,7 +70,7 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { | ItemKind::TyAssocTypeItem(..) | ItemKind::AssocTypeItem(..) | ItemKind::PrimitiveItem(..) - | ItemKind::KeywordItem => own_stability, + | ItemKind::KeywordItem => own_stability.cloned(), ItemKind::StrippedItem(..) => unreachable!(), } @@ -85,8 +81,8 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { } }; - item.inner.stability = stability.cloned(); - self.parent_stability = stability; + item.inner.stability = stability.clone(); + let parent_stability = std::mem::replace(&mut self.parent_stability, stability); let item = self.fold_item_recur(item); self.parent_stability = parent_stability; @@ -94,19 +90,44 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { } } -fn merge_stability<'tcx>( - own_stability: Option<&'tcx Stability>, - parent_stability: Option<&'tcx Stability>, -) -> Option<&'tcx Stability> { +fn merge_stability( + own_stability: Option<&Stability>, + parent_stability: Option<&Stability>, +) -> Option { if let Some(own_stab) = own_stability - && let &StabilityLevel::Stable { since: own_since, allowed_through_unstable_modules: false } = - &own_stab.level && let Some(parent_stab) = parent_stability - && (parent_stab.is_unstable() - || parent_stab.stable_since().is_some_and(|parent_since| parent_since > own_since)) { - parent_stability + match own_stab.level { + // If any of a stable item's parents were stabilized later or are still unstable, + // then use the parent's stability instead. + StabilityLevel::Stable { + since: own_since, + allowed_through_unstable_modules: false, + .. + } if parent_stab.is_unstable() + || parent_stab + .stable_since() + .is_some_and(|parent_since| parent_since > own_since) => + { + parent_stability.cloned() + } + + // If any of an unstable item's parents depend on other unstable features, + // then use those as well. + StabilityLevel::Unstable { unstables: ref own_gates, reason, is_soft } + if let StabilityLevel::Unstable { unstables: parent_gates, .. } = + &parent_stab.level => + { + let missing_unstables = parent_gates + .iter() + .filter(|p| !own_gates.iter().any(|u| u.feature == p.feature)); + let unstables = own_gates.iter().chain(missing_unstables).cloned().collect(); + Some(Stability { level: StabilityLevel::Unstable { unstables, reason, is_soft } }) + } + + _ => own_stability.cloned(), + } } else { - own_stability + own_stability.cloned() } } diff --git a/tests/rustdoc/stability.rs b/tests/rustdoc/stability.rs index 9da66a954307e..6740b347ac3a8 100644 --- a/tests/rustdoc/stability.rs +++ b/tests/rustdoc/stability.rs @@ -55,6 +55,12 @@ pub mod unstable { #[stable(feature = "rust1", since = "1.0.0")] pub fn foo() {} } + + //@ has stability/unstable/fn.nested_unstable.html \ + // '//span[@class="item-info"]//div[@class="stab unstable"]' \ + // 'This is a nightly-only experimental API. (test, unstable)' + #[unstable(feature = "test", issue = "none")] + pub fn nested_unstable() {} } #[unstable(feature = "unstable", issue = "none")] From a966f5e230903daf9682eaadbe154cb7414bed15 Mon Sep 17 00:00:00 2001 From: dianne Date: Tue, 22 Oct 2024 19:28:33 -0700 Subject: [PATCH 08/10] Add test for mixed stability levels --- .../auxiliary/mixed-levels.rs | 29 +++++++++++++++++++ tests/ui/stability-attribute/mixed-levels.rs | 13 +++++++++ .../stability-attribute/mixed-levels.stderr | 22 ++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 tests/ui/stability-attribute/auxiliary/mixed-levels.rs create mode 100644 tests/ui/stability-attribute/mixed-levels.rs create mode 100644 tests/ui/stability-attribute/mixed-levels.stderr diff --git a/tests/ui/stability-attribute/auxiliary/mixed-levels.rs b/tests/ui/stability-attribute/auxiliary/mixed-levels.rs new file mode 100644 index 0000000000000..0e8a6b8917d1e --- /dev/null +++ b/tests/ui/stability-attribute/auxiliary/mixed-levels.rs @@ -0,0 +1,29 @@ +//! definitions for ../mixed-levels.rs + +#![stable(feature = "stable_feature", since = "1.0.0")] +#![feature(staged_api)] +#![crate_type = "lib"] + +#[stable(feature = "stable_a", since = "1.0.0")] +#[stable(feature = "stable_b", since = "1.8.2")] +#[macro_export] +macro_rules! stable_mac { + () => () +} + +#[unstable(feature = "unstable_a", issue = "none")] +#[stable(feature = "stable_a", since = "1.0.0")] +#[macro_export] +macro_rules! unstable_mac { + () => () +} + +#[stable(feature = "stable_feature", since = "1.0.0")] +#[rustc_const_stable(feature = "stable_c", since = "1.8.2")] +#[rustc_const_stable(feature = "stable_d", since = "1.0.0")] +pub const fn const_stable_fn() {} + +#[stable(feature = "stable_feature", since = "1.0.0")] +#[rustc_const_unstable(feature = "unstable_c", issue = "none")] +#[rustc_const_stable(feature = "stable_c", since = "1.8.2")] +pub const fn const_unstable_fn() {} diff --git a/tests/ui/stability-attribute/mixed-levels.rs b/tests/ui/stability-attribute/mixed-levels.rs new file mode 100644 index 0000000000000..6948dc174afae --- /dev/null +++ b/tests/ui/stability-attribute/mixed-levels.rs @@ -0,0 +1,13 @@ +//! Test stability levels for items formerly dependent on multiple unstable features. +//@ aux-build:mixed-levels.rs + +extern crate mixed_levels; + +const USE_STABLE: () = mixed_levels::const_stable_fn(); +const USE_UNSTABLE: () = mixed_levels::const_unstable_fn(); +//~^ ERROR `const_unstable_fn` is not yet stable as a const fn + +fn main() { + mixed_levels::stable_mac!(); + mixed_levels::unstable_mac!(); //~ ERROR use of unstable library feature `unstable_a` [E0658] +} diff --git a/tests/ui/stability-attribute/mixed-levels.stderr b/tests/ui/stability-attribute/mixed-levels.stderr new file mode 100644 index 0000000000000..3d3e26f4e08a8 --- /dev/null +++ b/tests/ui/stability-attribute/mixed-levels.stderr @@ -0,0 +1,22 @@ +error[E0658]: use of unstable library feature `unstable_a` + --> $DIR/mixed-levels.rs:12:5 + | +LL | mixed_levels::unstable_mac!(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_a)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: `const_unstable_fn` is not yet stable as a const fn + --> $DIR/mixed-levels.rs:7:26 + | +LL | const USE_UNSTABLE: () = mixed_levels::const_unstable_fn(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: use of unstable library feature `unstable_c` + = help: add `#![feature(unstable_c)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. From edf10f896fbfc5426eac03e5fb2343a2b369d948 Mon Sep 17 00:00:00 2001 From: dianne Date: Sat, 26 Oct 2024 15:56:45 -0700 Subject: [PATCH 09/10] clippy: update `is_stable_const_fn` helper for multiple const-stability attrs --- src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index abadca7140019..cfa10044e6a52 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -394,7 +394,8 @@ fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool { msrv.meets(const_stab_rust_version) } else { // Unstable const fn, check if the feature is enabled. - tcx.features().enabled(const_stab.feature) && msrv.current().is_none() + tcx.features().all_enabled(const_stab.unstable_features()) + && msrv.current().is_none() } }) } From b23f28de9cd788de12086e2396abc8d425c34511 Mon Sep 17 00:00:00 2001 From: dianne Date: Sun, 27 Oct 2024 19:03:10 -0700 Subject: [PATCH 10/10] Add sanity checks for `reason` and `soft` on unstable attributes `reason` must appear at most once: it's the reason for the item being unstable, rather than a particular feature. This simplifies diagnostic formatting. `soft` must either be on all or no unstable attributes: it doesn't make sense for something to be partially soft, and allowing inconsistent softness markers would risk an item accidentally becoming properly unstable as its features stabilize. --- compiler/rustc_attr/messages.ftl | 6 ++++ compiler/rustc_attr/src/builtin.rs | 16 +++++++-- .../rustc_attr/src/session_diagnostics.rs | 14 ++++++++ .../stability-attribute/mixed-levels.stderr | 2 -- .../multiple-stability-attribute-sanity.rs | 24 +++++++++++++ ...multiple-stability-attribute-sanity.stderr | 36 +++++++++++++++++++ 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 tests/ui/stability-attribute/multiple-stability-attribute-sanity.rs create mode 100644 tests/ui/stability-attribute/multiple-stability-attribute-sanity.stderr diff --git a/compiler/rustc_attr/messages.ftl b/compiler/rustc_attr/messages.ftl index d8a1d18b5e7d0..0f6ebcf32e4b1 100644 --- a/compiler/rustc_attr/messages.ftl +++ b/compiler/rustc_attr/messages.ftl @@ -85,6 +85,9 @@ attr_multiple_item = attr_multiple_stability_levels = multiple stability levels for feature `{$feature}` +attr_multiple_unstable_reasons = + multiple reasons provided for unstability + attr_non_ident_feature = 'feature' is not an identifier @@ -97,6 +100,9 @@ attr_rustc_const_stable_indirect_pairing = attr_rustc_promotable_pairing = `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute +attr_soft_inconsistent = + `soft` must be present on either none or all of an item's `unstable` attributes + attr_soft_no_args = `soft` should not have any arguments diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index fac5b4adf2abf..c49791d81e77e 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -416,11 +416,21 @@ fn add_level( (_, UnstableReason::None) => {} (reason @ UnstableReason::None, _) => *reason = new_reason, _ => { - // TODO: sanity check for only one reason + sess.dcx() + .emit_err(session_diagnostics::MultipleUnstableReasons { span: attr.span }); } } - // TODO: sanity check for is_soft consistency - *is_soft |= new_soft; + // If any unstable attributes are marked 'soft', all should be. This keeps soft-unstable + // items from accidentally being made properly unstable as attributes are removed. + if *is_soft != new_soft { + let spans = stab_spans + .iter() + .filter(|(stab, _)| stab.is_unstable()) + .map(|&(_, sp)| sp) + .chain([attr.span]) + .collect(); + sess.dcx().emit_err(session_diagnostics::SoftInconsistent { spans }); + } } // an item with some stable and some unstable features is unstable (Some(Unstable { .. }), Stable { .. }) => {} diff --git a/compiler/rustc_attr/src/session_diagnostics.rs b/compiler/rustc_attr/src/session_diagnostics.rs index 1bf11e7196b4b..c6d388657bd3a 100644 --- a/compiler/rustc_attr/src/session_diagnostics.rs +++ b/compiler/rustc_attr/src/session_diagnostics.rs @@ -83,6 +83,13 @@ pub(crate) struct MultipleStabilityLevels { pub feature: Symbol, } +#[derive(Diagnostic)] +#[diag(attr_multiple_unstable_reasons)] +pub(crate) struct MultipleUnstableReasons { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(attr_invalid_issue_string, code = E0545)] pub(crate) struct InvalidIssueString { @@ -400,6 +407,13 @@ pub(crate) struct SoftNoArgs { pub span: Span, } +#[derive(Diagnostic)] +#[diag(attr_soft_inconsistent)] +pub(crate) struct SoftInconsistent { + #[primary_span] + pub spans: Vec, +} + #[derive(Diagnostic)] #[diag(attr_unknown_version_literal)] pub(crate) struct UnknownVersionLiteral { diff --git a/tests/ui/stability-attribute/mixed-levels.stderr b/tests/ui/stability-attribute/mixed-levels.stderr index 3d3e26f4e08a8..745c6751bb442 100644 --- a/tests/ui/stability-attribute/mixed-levels.stderr +++ b/tests/ui/stability-attribute/mixed-levels.stderr @@ -13,9 +13,7 @@ error: `const_unstable_fn` is not yet stable as a const fn LL | const USE_UNSTABLE: () = mixed_levels::const_unstable_fn(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: use of unstable library feature `unstable_c` = help: add `#![feature(unstable_c)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 2 previous errors diff --git a/tests/ui/stability-attribute/multiple-stability-attribute-sanity.rs b/tests/ui/stability-attribute/multiple-stability-attribute-sanity.rs new file mode 100644 index 0000000000000..f4c02d69ab325 --- /dev/null +++ b/tests/ui/stability-attribute/multiple-stability-attribute-sanity.rs @@ -0,0 +1,24 @@ +//! Checks that multiple stability attributes are used correctly together + +#![feature(staged_api)] + +#![stable(feature = "stable_test_feature", since = "1.0.0")] + +#[unstable(feature = "a", issue = "none", reason = "reason 1")] +#[unstable(feature = "b", issue = "none", reason = "reason 2")] //~ ERROR multiple reasons provided for unstability +fn f1() { } + +#[unstable(feature = "a", issue = "none", reason = "reason 1")] +#[unstable(feature = "b", issue = "none", reason = "reason 2")] //~ ERROR multiple reasons provided for unstability +#[unstable(feature = "c", issue = "none", reason = "reason 3")] //~ ERROR multiple reasons provided for unstability +fn f2() { } + +#[unstable(feature = "a", issue = "none")] //~ ERROR `soft` must be present on either none or all of an item's `unstable` attributes +#[unstable(feature = "b", issue = "none", soft)] +fn f3() { } + +#[unstable(feature = "a", issue = "none", soft)] //~ ERROR `soft` must be present on either none or all of an item's `unstable` attributes +#[unstable(feature = "b", issue = "none")] +fn f4() { } + +fn main() { } diff --git a/tests/ui/stability-attribute/multiple-stability-attribute-sanity.stderr b/tests/ui/stability-attribute/multiple-stability-attribute-sanity.stderr new file mode 100644 index 0000000000000..a7cb4849d1245 --- /dev/null +++ b/tests/ui/stability-attribute/multiple-stability-attribute-sanity.stderr @@ -0,0 +1,36 @@ +error: multiple reasons provided for unstability + --> $DIR/multiple-stability-attribute-sanity.rs:8:1 + | +LL | #[unstable(feature = "b", issue = "none", reason = "reason 2")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple reasons provided for unstability + --> $DIR/multiple-stability-attribute-sanity.rs:12:1 + | +LL | #[unstable(feature = "b", issue = "none", reason = "reason 2")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple reasons provided for unstability + --> $DIR/multiple-stability-attribute-sanity.rs:13:1 + | +LL | #[unstable(feature = "c", issue = "none", reason = "reason 3")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `soft` must be present on either none or all of an item's `unstable` attributes + --> $DIR/multiple-stability-attribute-sanity.rs:16:1 + | +LL | #[unstable(feature = "a", issue = "none")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[unstable(feature = "b", issue = "none", soft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `soft` must be present on either none or all of an item's `unstable` attributes + --> $DIR/multiple-stability-attribute-sanity.rs:20:1 + | +LL | #[unstable(feature = "a", issue = "none", soft)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[unstable(feature = "b", issue = "none")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors +