diff --git a/Cargo.lock b/Cargo.lock index e5025a1ba4f9c..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", ] @@ -3683,6 +3685,7 @@ dependencies = [ name = "rustc_feature" version = "0.0.0" dependencies = [ + "either", "rustc_data_structures", "rustc_span", ] @@ -4217,6 +4220,7 @@ dependencies = [ "rustc_span", "rustc_target", "rustc_trait_selection", + "smallvec", "tracing", ] 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_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..0f6ebcf32e4b1 100644 --- a/compiler/rustc_attr/messages.ftl +++ b/compiler/rustc_attr/messages.ftl @@ -83,7 +83,10 @@ attr_multiple_item = multiple '{$item}' items attr_multiple_stability_levels = - 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 94f9727eb7fbe..c49791d81e77e 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,11 +67,10 @@ 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, - pub feature: Symbol, } impl Stability { @@ -85,14 +85,22 @@ impl Stability { pub fn stable_since(&self) -> Option { self.level.stable_since() } + + 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. -#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)] +/// For details see [the dev guide](https://rustc-dev-guide.rust-lang.org/stability.html#rustc_const_unstable). +#[derive(Encodable, Decodable, 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 @@ -107,47 +115,30 @@ 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, - pub feature: Symbol, } /// 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: SmallVec<[Unstability; 1]>, /// 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 { @@ -174,15 +165,59 @@ 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 +#[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)] @@ -211,53 +246,50 @@ 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 stab: Option<(Stability, 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 stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); - break; - } - - if let Some((feature, level)) = parse_unstability(sess, attr) { - stab = Some((Stability { level, feature }, attr.span)); - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability) } sym::stable => { - if stab.is_some() { - sess.dcx() - .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)); - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability) } _ => {} } } 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 }); @@ -265,7 +297,7 @@ pub fn find_stability( } } - stab + Some((Stability { level: level? }, stab_spans)) } /// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable` @@ -274,8 +306,10 @@ pub fn find_const_stability( sess: &Session, attrs: &[Attribute], item_sp: Span, -) -> Option<(ConstStability, Span)> { - let mut const_stab: Option<(ConstStability, 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; @@ -284,77 +318,34 @@ 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 }); - break; - } - - if let Some((feature, level)) = parse_unstability(sess, attr) { - const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, - attr.span, - )); - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability) } sym::rustc_const_stable => { - if const_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); - break; - } - if let Some((feature, level)) = parse_stability(sess, attr) { - const_stab = Some(( - ConstStability { - level, - feature, - const_stable_indirect: false, - promotable: false, - }, - attr.span, - )); - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_stability) } _ => {} } } // 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) = 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_spans)) + } 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 @@ -369,12 +360,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`. @@ -382,24 +368,82 @@ pub fn unmarked_crate_const_stab( pub fn find_body_stability( sess: &Session, attrs: &[Attribute], -) -> Option<(DefaultBodyStability, Span)> { - let mut body_stab: Option<(DefaultBodyStability, 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 body_stab.is_some() { - sess.dcx() - .emit_err(session_diagnostics::MultipleStabilityLevels { span: attr.span }); - break; - } + add_level(sess, attr, &mut level, &mut stab_spans, &mut features, parse_unstability); + } + } - if let Some((feature, level)) = parse_unstability(sess, attr) { - body_stab = Some((DefaultBodyStability { level, feature }, attr.span)); + Some((DefaultBodyStability { level: level? }, stab_spans)) +} + +/// 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, + 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 }); + } + 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, + _ => { + sess.dcx() + .emit_err(session_diagnostics::MultipleUnstableReasons { span: attr.span }); + } + } + // 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 { .. }) => {} + (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; + } } - - body_stab } fn insert_or_error(sess: &Session, meta: &MetaItem, item: &mut Option) -> Option<()> { @@ -474,10 +518,10 @@ 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((feature, StabilityLevel::Stable { + since, + allowed_through_unstable_modules: false, + })), Err(ErrorGuaranteed { .. }) => None, } } @@ -563,13 +607,12 @@ fn parse_unstability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabil match (feature, issue) { (Ok(feature), Ok(_)) => { - let level = StabilityLevel::Unstable { + let unstability = Unstability { feature, issue: issue_num, implied_by }; + Some((feature, StabilityLevel::Unstable { + unstables: smallvec![unstability], reason: UnstableReason::from_opt_reason(reason), - issue: issue_num, is_soft, - implied_by, - }; - Some((feature, 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..c6d388657bd3a 100644 --- a/compiler/rustc_attr/src/session_diagnostics.rs +++ b/compiler/rustc_attr/src/session_diagnostics.rs @@ -79,6 +79,15 @@ pub(crate) struct MissingNote { pub(crate) struct MultipleStabilityLevels { #[primary_span] pub span: Span, + + pub feature: Symbol, +} + +#[derive(Diagnostic)] +#[diag(attr_multiple_unstable_reasons)] +pub(crate) struct MultipleUnstableReasons { + #[primary_span] + pub span: Span, } #[derive(Diagnostic)] @@ -398,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/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 8e96d365bebec..57d4560849069 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}; @@ -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; } @@ -744,13 +756,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, + features: unstables.iter().map(|u| u.feature).collect(), const_stable_indirect: is_const_stable, }); } @@ -796,11 +807,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { } } Some(ConstStability { - level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. }, - feature, + level: StabilityLevel::Unstable { 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/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_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 375cbfd1c4fb8..8b293c018d87d 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -294,37 +294,29 @@ 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, }); 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), + &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/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/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs index 565c3c0425256..550b6da5f669c 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 { features, reason } => { + rustc_middle::error::SoftUnstableLibraryFeature::new(features, reason) + .decorate_lint(diag); } BuiltinLintDiag::AvoidUsingIntelSyntax => { lints::AvoidIntelSyntax.decorate_lint(diag); 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_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..cdebd337a7257 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 { + features: Vec, + reason: Option, + }, AvoidUsingIntelSyntax, AvoidUsingAttSyntax, IncompleteInclude, 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/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/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 5c2aa0005d405..d8cb32c43205e 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(features: Vec, reason: Option, span: Span) -> Self { + let SoftUnstableLibraryFeature { features, count, reason } = + SoftUnstableLibraryFeature::new(features, 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(features: Vec, reason: Option) -> Self { + SoftUnstableLibraryFeature { + count: features.len(), + features: features.into(), + 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 94d13021612be..12d9f8eadc2d6 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,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_issue; +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)] @@ -60,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 @@ -82,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() } @@ -102,28 +106,26 @@ impl Index { pub fn report_unstable( sess: &Session, - feature: Symbol, + features: Vec, reason: Option, - issue: Option>, - suggestion: Option<(Span, String, String, Applicability)>, - is_soft: bool, + issues: Vec>, + suggestions: Vec<(Span, String, String, Applicability)>, 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 mut err = feature_err_issue(sess, feature, span, GateIssue::Library(issue), msg); - if let Some((inner_types, msg, sugg, applicability)) = suggestion { - err.span_suggestion(inner_types, msg, sugg, applicability); - } - err.emit(); + let mut err = + sess.dcx().create_err(UnstableLibraryFeatureError::new(features.clone(), reason, span)); + add_feature_diagnostics_for_issues( + &mut err, + sess, + &features, + GateIssues::Library(issues), + false, + None, + ); + for (inner_types, msg, sugg, applicability) in suggestions { + err.span_suggestion(inner_types, msg, sugg, applicability); } + err.emit(); } fn deprecation_lint(is_in_effect: bool) -> &'static Lint { @@ -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 @@ -391,55 +393,65 @@ impl<'tcx> TyCtxt<'tcx> { ); match stability { - Some(Stability { - level: attr::Unstable { reason, issue, is_soft, implied_by }, - feature, - .. - }) => { - if span.allows_unstable(feature) { - debug!("stability: skipping span={:?} since it is internal", span); - return EvalResult::Allow; - } - if self.features().enabled(feature) { + Some(Stability { level: attr::Unstable { unstables, reason, is_soft } }) => { + 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(_) => { @@ -454,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 { @@ -474,24 +486,33 @@ impl<'tcx> TyCtxt<'tcx> { ); match stability { - Some(DefaultBodyStability { - level: attr::Unstable { reason, issue, is_soft, .. }, - feature, - }) => { - 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; + Some(DefaultBodyStability { level: attr::Unstable { unstables, reason, is_soft } }) => { + 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,26 +590,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 { 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(features, reason), + ); + } else { + 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 2ba1bf2822fbb..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(()) } @@ -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_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 4a793f1875ec8..22181bcf2e035 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}; @@ -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 Stability { level: Unstable { .. }, feature } = stab { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == 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 Stability { level: Unstable { implied_by: Some(implied_by), .. }, feature } = - stab - { - 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 { .. }, feature, .. }, const_span)) = - const_stab - { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == 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,23 +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, - feature: inherit_regular_stab.feature, + 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 { implied_by: Some(implied_by), .. }, - feature, - .. - }) = const_stab - { - self.index.implications.insert(implied_by, 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 @@ -346,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. @@ -674,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(), @@ -700,15 +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: smallvec![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); } @@ -789,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); @@ -800,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 0b4d0e04c295c..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,37 +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 { reason, issue, is_soft, implied_by } = stability.level - { - let feature = stability.feature; - - 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 { - let lint_buffer = &mut self.lint_buffer; - let soft_handler = |lint, span, msg: String| { - lint_buffer.buffer_lint( - lint, - node_id, - span, - BuiltinLintDiag::UnstableFeature( - // FIXME make this translatable - msg.into(), - ), - ) - }; + 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, - feature, + missing_features, reason.to_opt_reason(), - issue, - None, - is_soft, + issues.into_iter().flatten().collect(), + vec![], span, - soft_handler, - ); + ) } } } 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 { 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/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..02c615763f6bb 100644 --- a/src/librustdoc/passes/propagate_stability.rs +++ b/src/librustdoc/passes/propagate_stability.rs @@ -21,7 +21,7 @@ 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) } @@ -32,8 +32,6 @@ struct StabilityPropagator<'a, '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; - 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; @@ -95,18 +91,43 @@ impl<'a, 'tcx> DocFolder for StabilityPropagator<'a, 'tcx> { } fn merge_stability( - own_stability: Option, - parent_stability: Option, + 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/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() } }) } diff --git a/tests/rustdoc/stability.rs b/tests/rustdoc/stability.rs index 550eb0bc13776..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")] @@ -169,3 +175,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)' +} 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/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/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..745c6751bb442 --- /dev/null +++ b/tests/ui/stability-attribute/mixed-levels.stderr @@ -0,0 +1,20 @@ +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(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_c)]` to the crate attributes to enable + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. 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 + 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`.