diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 24da75114a656..0c1be0c9c4135 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -9,6 +9,7 @@ use rustc_session::parse::{feature_err, ParseSess}; use rustc_session::Session; use rustc_span::hygiene::Transparency; use rustc_span::{symbol::sym, symbol::Symbol, Span}; +use std::fmt; use std::num::NonZeroU32; pub fn is_builtin_attr(attr: &Attribute) -> bool { @@ -18,7 +19,7 @@ pub fn is_builtin_attr(attr: &Attribute) -> bool { enum AttrError { MultipleItem(String), UnknownMetaItem(String, &'static [&'static str]), - MissingSince, + InvalidSince, NonIdentFeature, MissingFeature, MultipleStabilityLevels, @@ -37,8 +38,8 @@ fn handle_errors(sess: &ParseSess, span: Span, error: AttrError) { .span_label(span, format!("expected one of {}", expected.join(", "))) .emit(); } - AttrError::MissingSince => { - struct_span_err!(diag, span, E0542, "missing 'since'").emit(); + AttrError::InvalidSince => { + struct_span_err!(diag, span, E0542, "invalid 'since'").emit(); } AttrError::NonIdentFeature => { struct_span_err!(diag, span, E0546, "'feature' is not an identifier").emit(); @@ -158,7 +159,7 @@ pub struct ConstStability { pub enum StabilityLevel { // Reason for the current stability level and the relevant rust-lang issue Unstable { reason: Option, issue: Option, is_soft: bool }, - Stable { since: Symbol }, + Stable { since: Version }, } impl StabilityLevel { @@ -428,7 +429,7 @@ where } } - match (feature, since) { + match (feature, since.and_then(|s| parse_version(&s.as_str(), false))) { (Some(feature), Some(since)) => { let level = Stable { since }; if sym::stable == meta_name { @@ -443,7 +444,7 @@ where continue; } _ => { - handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince); + handle_errors(&sess.parse_sess, attr.span, AttrError::InvalidSince); continue; } } @@ -525,11 +526,18 @@ fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &F } } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct Version { - major: u16, - minor: u16, - patch: u16, +#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(HashStable_Generic)] +pub struct Version { + major: u8, + minor: u8, + patch: u8, +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } } fn parse_version(s: &str, allow_appendix: bool) -> Option { @@ -648,30 +656,56 @@ pub fn eval_condition( } #[derive(Debug, Encodable, Decodable, Clone, HashStable_Generic)] -pub struct Deprecation { - pub since: Option, - /// The note to issue a reason. - pub note: Option, - /// A text snippet used to completely replace any use of the deprecated item in an expression. - /// - /// This is currently unstable. - pub suggestion: Option, - - /// Whether to treat the since attribute as being a Rust version identifier - /// (rather than an opaque string). - pub is_since_rustc_version: bool, +pub enum DeprKind { + /// The stable #[deprecated] attribute + Deprecated { + /// Opaque, unvalidated string + since: Option, + /// Note displayed alongside deprecation warning + note: Option, + }, + /// The compiler-only #[rustc_deprecated] attribute + RustcDeprecated { + /// Whether or not the deprecation is currently in effect, + /// i.e. whether `since` is at least the current Rust version. + now: bool, + /// `None` if `since="TBD"`, otherwise a Rust version triple "X.Y.Z" + since: Option, + /// Note displayed alongside deprecation warning + note: Symbol, + /// A text snippet used to completely replace any use + /// of the deprecated item in an expression. + /// Currently unstable. + suggestion: Option, + }, +} + +impl DeprKind { + pub fn note(&self) -> Option { + match *self { + Self::Deprecated { note, .. } => note, + Self::RustcDeprecated { note, .. } => Some(note), + } + } + + pub fn suggestion(&self) -> Option { + match *self { + Self::Deprecated { .. } => None, + Self::RustcDeprecated { suggestion, .. } => suggestion, + } + } } /// Finds the deprecation attribute. `None` if none exists. -pub fn find_deprecation(sess: &Session, attrs: &[Attribute]) -> Option<(Deprecation, Span)> { +pub fn find_deprecation(sess: &Session, attrs: &[Attribute]) -> Option<(DeprKind, Span)> { find_deprecation_generic(sess, attrs.iter()) } -fn find_deprecation_generic<'a, I>(sess: &Session, attrs_iter: I) -> Option<(Deprecation, Span)> +fn find_deprecation_generic<'a, I>(sess: &Session, attrs_iter: I) -> Option<(DeprKind, Span)> where I: Iterator, { - let mut depr: Option<(Deprecation, Span)> = None; + let mut depr: Option<(DeprKind, Span)> = None; let diagnostic = &sess.parse_sess.span_diagnostic; 'outer: for attr in attrs_iter { @@ -786,26 +820,53 @@ where } } - if suggestion.is_some() && sess.check_name(attr, sym::deprecated) { - unreachable!("only allowed on rustc_deprecated") - } + depr = if sess.check_name(attr, sym::deprecated) { + Some((DeprKind::Deprecated { since, note }, attr.span)) + } else { + let since = match since { + None => { + // `since` field must be present + handle_errors(&sess.parse_sess, attr.span, AttrError::InvalidSince); + continue; + } + Some(since_sym) => { + if since_sym == sym::TBD { + None // "TBD" is the only non-version allowed for the `since` field + } else { + if let Some(since_ver) = parse_version(&since_sym.as_str(), false) { + Some(since_ver) + } else { + // `since` field must be a valid version + handle_errors(&sess.parse_sess, attr.span, AttrError::InvalidSince); + continue; + } + } + } + }; - if sess.check_name(attr, sym::rustc_deprecated) { - if since.is_none() { - handle_errors(&sess.parse_sess, attr.span, AttrError::MissingSince); - continue; - } + let note = match note { + None => { + struct_span_err!(diagnostic, attr.span, E0543, "missing 'reason'").emit(); + continue; + } + Some(n) => n, + }; - if note.is_none() { - struct_span_err!(diagnostic, attr.span, E0543, "missing 'reason'").emit(); - continue; - } - } + // Whether the deprecation is currently active + let now = match since { + None => false, // "TBD", therefore deprecated_in_future + Some(since_ver) => { + match option_env!("CFG_RELEASE").and_then(|s| parse_version(s, true)) { + None => true, // if invalid Rust version, assume deprecation is in effect + Some(rust_ver) => rust_ver > since_ver, + } + } + }; - sess.mark_attr_used(&attr); + Some((DeprKind::RustcDeprecated { now, since, note, suggestion }, attr.span)) + }; - let is_since_rustc_version = sess.check_name(attr, sym::rustc_deprecated); - depr = Some((Deprecation { since, note, suggestion, is_since_rustc_version }, attr.span)); + sess.mark_attr_used(&attr); } depr diff --git a/compiler/rustc_attr/src/lib.rs b/compiler/rustc_attr/src/lib.rs index 149a950f7d417..2fb11ab1287ff 100644 --- a/compiler/rustc_attr/src/lib.rs +++ b/compiler/rustc_attr/src/lib.rs @@ -12,6 +12,7 @@ extern crate rustc_macros; mod builtin; pub use builtin::*; +pub use DeprKind::*; pub use IntType::*; pub use ReprAttr::*; pub use StabilityLevel::*; diff --git a/compiler/rustc_error_codes/src/error_codes/E0542.md b/compiler/rustc_error_codes/src/error_codes/E0542.md index 7cb58f9d0cb74..97430638c855b 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0542.md +++ b/compiler/rustc_error_codes/src/error_codes/E0542.md @@ -1,42 +1,48 @@ -The `since` value is missing in a stability attribute. +The `since` value in a stability attribute is either missing +or does not parse to a version string. Erroneous code example: ```compile_fail,E0542 #![feature(staged_api)] -#![stable(since = "1.0.0", feature = "test")] +#![stable(since = "1.0.0", feature = "foo")] -#[stable(feature = "_stable_fn")] // invalid -fn _stable_fn() {} +#[stable(feature = "foo")] // missing +fn _stable1() {} -#[rustc_const_stable(feature = "_stable_const_fn")] // invalid -fn _stable_const_fn() {} +#[stable(feature = "foo", since = "bar")] // invalid +fn _stable2() {} -#[stable(feature = "_deprecated_fn", since = "0.1.0")] -#[rustc_deprecated( - reason = "explanation for deprecation" -)] // invalid -fn _deprecated_fn() {} +#[rustc_const_stable(feature = "foo")] // missing +fn _const1() {} + +#[rustc_const_stable(feature = "foo", since = "bar")] // invalid +fn _const2() {} + +#[stable(feature = "foo", since = "1.0.0")] +#[rustc_deprecated(reason = "bar")] // missing +fn _deprecated1() {} + +#[stable(feature = "foo", since = "1.0.0")] +#[rustc_deprecated(reason = "qux", since = "bar")] // invalid +fn _deprecated2() {} ``` To fix this issue, you need to provide the `since` field. Example: ``` #![feature(staged_api)] -#![stable(since = "1.0.0", feature = "test")] +#![stable(since = "1.0.0", feature = "foo")] -#[stable(feature = "_stable_fn", since = "1.0.0")] // ok! -fn _stable_fn() {} +#[stable(feature = "foo", since = "1.0.0")] // ok! +fn _stable() {} -#[rustc_const_stable(feature = "_stable_const_fn", since = "1.0.0")] // ok! -fn _stable_const_fn() {} +#[rustc_const_stable(feature = "foo", since = "1.0.0")] // ok! +fn _const() {} -#[stable(feature = "_deprecated_fn", since = "0.1.0")] -#[rustc_deprecated( - since = "1.0.0", - reason = "explanation for deprecation" -)] // ok! -fn _deprecated_fn() {} +#[stable(feature = "foo", since = "1.0.0")] +#[rustc_deprecated(reason = "qux", since = "1.0.0")] // ok! +fn _deprecated() {} ``` See the [How Rust is Made and “Nightly Rust”][how-rust-made-nightly] appendix diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index ca304c05cdce3..ca953c61ebcf2 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -6,7 +6,7 @@ use rustc_ast::token::{self, Nonterminal}; use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, TokenStream}; use rustc_ast::visit::{AssocCtxt, Visitor}; use rustc_ast::{self as ast, Attribute, NodeId, PatKind}; -use rustc_attr::{self as attr, Deprecation, HasAttrs, Stability}; +use rustc_attr::{self as attr, DeprKind, HasAttrs, Stability}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::{self, Lrc}; use rustc_errors::{DiagnosticBuilder, ErrorReported}; @@ -705,7 +705,7 @@ pub struct SyntaxExtension { /// The macro's stability info. pub stability: Option, /// The macro's deprecation info. - pub deprecation: Option, + pub deprecation: Option, /// Names of helper attributes registered by this macro. pub helper_attrs: Vec, /// Edition of the crate in which this macro is defined. diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index e3c3539079857..7999721d3f010 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -917,7 +917,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> { self.root.tables.const_stability.get(self, id).map(|stab| stab.decode(self)) } - fn get_deprecation(&self, id: DefIndex) -> Option { + fn get_deprecation(&self, id: DefIndex) -> Option { self.root.tables.deprecation.get(self, id).map(|depr| depr.decode(self)) } diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index b44c3bfcac647..cc88da6fb560a 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -288,7 +288,7 @@ define_tables! { children: Table>, stability: Table>, const_stability: Table>, - deprecation: Table>, + deprecation: Table>, ty: Table)>, fn_sig: Table)>, impl_trait_ref: Table)>, diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 89ca8eed39a9b..039fa9420d8e7 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -5,7 +5,7 @@ pub use self::StabilityLevel::*; use crate::ty::{self, TyCtxt}; use rustc_ast::NodeId; -use rustc_attr::{self as attr, ConstStability, Deprecation, Stability}; +use rustc_attr::{self as attr, ConstStability, DeprKind, Stability}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_feature::GateIssue; @@ -39,18 +39,18 @@ impl StabilityLevel { #[derive(Clone, HashStable, Debug)] pub struct DeprecationEntry { /// The metadata of the attribute associated with this entry. - pub attr: Deprecation, + pub attr: DeprKind, /// The `DefId` where the attr was originally attached. `None` for non-local /// `DefId`'s. origin: Option, } impl DeprecationEntry { - pub fn local(attr: Deprecation, id: HirId) -> DeprecationEntry { + pub fn local(attr: DeprKind, id: HirId) -> DeprecationEntry { DeprecationEntry { attr, origin: Some(id) } } - pub fn external(attr: Deprecation) -> DeprecationEntry { + pub fn external(attr: DeprKind) -> DeprecationEntry { DeprecationEntry { attr, origin: None } } @@ -129,42 +129,6 @@ pub fn report_unstable( } } -/// Checks whether an item marked with `deprecated(since="X")` is currently -/// deprecated (i.e., whether X is not greater than the current rustc version). -pub fn deprecation_in_effect(is_since_rustc_version: bool, since: Option<&str>) -> bool { - fn parse_version(ver: &str) -> Vec { - // We ignore non-integer components of the version (e.g., "nightly"). - ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect() - } - - if !is_since_rustc_version { - // The `since` field doesn't have semantic purpose in the stable `deprecated` - // attribute, only in `rustc_deprecated`. - return true; - } - - if let Some(since) = since { - if since == "TBD" { - return false; - } - - if let Some(rustc) = option_env!("CFG_RELEASE") { - let since: Vec = parse_version(&since); - let rustc: Vec = parse_version(rustc); - // We simply treat invalid `since` attributes as relating to a previous - // Rust version, thus always displaying the warning. - if since.len() != 3 { - return true; - } - return since <= rustc; - } - }; - - // Assume deprecation is in effect if "since" field is missing - // or if we can't determine the current Rust version. - true -} - pub fn deprecation_suggestion( diag: &mut DiagnosticBuilder<'_>, kind: &str, @@ -181,33 +145,34 @@ pub fn deprecation_suggestion( } } -pub fn deprecation_message(depr: &Deprecation, kind: &str, path: &str) -> (String, &'static Lint) { - let since = depr.since.map(Symbol::as_str); - let (message, lint) = if deprecation_in_effect(depr.is_since_rustc_version, since.as_deref()) { - (format!("use of deprecated {} `{}`", kind, path), DEPRECATED) - } else { - ( - if since.as_deref() == Some("TBD") { - format!( - "use of {} `{}` that will be deprecated in a future Rust version", - kind, path - ) - } else { - format!( - "use of {} `{}` that will be deprecated in future version {}", - kind, - path, - since.unwrap() - ) - }, - DEPRECATED_IN_FUTURE, - ) - }; - let message = match depr.note { - Some(reason) => format!("{}: {}", message, reason), - None => message, +pub fn deprecation_message(depr: &DeprKind, kind: &str, path: &str) -> String { + let mut message = match depr { + attr::Deprecated { .. } | attr::RustcDeprecated { now: true, .. } => { + format!("use of deprecated {} `{}`", kind, path) + } + attr::RustcDeprecated { now: false, since: None, .. } => { + format!("use of {} `{}` that will be deprecated in a future Rust version", kind, path) + } + attr::RustcDeprecated { now: false, since: Some(since), .. } => { + format!( + "use of {} `{}` that will be deprecated in future version {}", + kind, path, since + ) + } }; - (message, lint) + + if let Some(note) = depr.note() { + message = format!("{}: {}", message, note); + } + + message +} + +pub fn deprecation_lint(depr: &DeprKind) -> &'static Lint { + match depr { + attr::RustcDeprecated { now: false, .. } => DEPRECATED_IN_FUTURE, + attr::Deprecated { .. } | attr::RustcDeprecated { now: true, .. } => DEPRECATED, + } } pub fn early_report_deprecation( @@ -302,14 +267,15 @@ impl<'tcx> TyCtxt<'tcx> { // // #[rustc_deprecated] however wants to emit down the whole // hierarchy. - if !skip || depr_entry.attr.is_since_rustc_version { + if !skip || matches!(depr_entry.attr, attr::RustcDeprecated { .. }) { let path = &with_no_trimmed_paths(|| self.def_path_str(def_id)); let kind = self.def_kind(def_id).descr(def_id); - let (message, lint) = deprecation_message(&depr_entry.attr, kind, path); + let message = deprecation_message(&depr_entry.attr, kind, path); + let lint = deprecation_lint(&depr_entry.attr); late_report_deprecation( self, &message, - depr_entry.attr.suggestion, + depr_entry.attr.suggestion(), lint, span, id, @@ -421,7 +387,7 @@ impl<'tcx> TyCtxt<'tcx> { } } - pub fn lookup_deprecation(self, id: DefId) -> Option { + pub fn lookup_deprecation(self, id: DefId) -> Option { self.lookup_deprecation_entry(id).map(|depr| depr.attr) } } diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index f538427efd9ff..24f38c6af8dd1 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -21,7 +21,6 @@ use rustc_session::Session; use rustc_span::symbol::{sym, Symbol}; use rustc_span::{Span, DUMMY_SP}; -use std::cmp::Ordering; use std::mem::replace; use std::num::NonZeroU32; @@ -167,7 +166,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { } } - if let Some((rustc_attr::Deprecation { is_since_rustc_version: true, .. }, span)) = &depr { + if let Some((attr::RustcDeprecated { .. }, span)) = &depr { if stab.is_none() { struct_span_err!( self.tcx.sess, @@ -193,40 +192,15 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { // Check if deprecated_since < stable_since. If it is, // this is *almost surely* an accident. - if let (&Some(dep_since), &attr::Stable { since: stab_since }) = - (&depr.as_ref().and_then(|(d, _)| d.since), &stab.level) + if let ( + &Some((attr::RustcDeprecated { since: Some(depr_since), .. }, _)), + &attr::Stable { since: stab_since }, + ) = (&depr, &stab.level) { - // Explicit version of iter::order::lt to handle parse errors properly - for (dep_v, stab_v) in - dep_since.as_str().split('.').zip(stab_since.as_str().split('.')) - { - match stab_v.parse::() { - Err(_) => { - self.tcx.sess.span_err(item_sp, "Invalid stability version found"); - break; - } - Ok(stab_vp) => match dep_v.parse::() { - Ok(dep_vp) => match dep_vp.cmp(&stab_vp) { - Ordering::Less => { - self.tcx.sess.span_err( - item_sp, - "An API can't be stabilized after it is deprecated", - ); - break; - } - Ordering::Equal => continue, - Ordering::Greater => break, - }, - Err(_) => { - if dep_v != "TBD" { - self.tcx - .sess - .span_err(item_sp, "Invalid deprecation version found"); - } - break; - } - }, - } + if depr_since < stab_since { + self.tcx + .sess + .span_err(item_sp, "An API can't be stabilized after it is deprecated"); } } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index f7010ca94bd2c..3051c614996ab 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -1089,11 +1089,12 @@ impl<'a> Resolver<'a> { } if let Some(depr) = &ext.deprecation { let path = pprust::path_to_string(&path); - let (message, lint) = stability::deprecation_message(depr, "macro", &path); + let message = stability::deprecation_message(depr, "macro", &path); + let lint = stability::deprecation_lint(depr); stability::early_report_deprecation( &mut self.lint_buffer, &message, - depr.suggestion, + depr.suggestion(), lint, span, node_id, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 653d70b6cf244..06c98934f96f3 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -197,6 +197,7 @@ symbols! { StructuralEq, StructuralPartialEq, Sync, + TBD, Target, Try, Ty, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 9a2319f6e379d..95ad82dfa4991 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -12,7 +12,7 @@ use arrayvec::ArrayVec; use rustc_ast::attr; use rustc_ast::util::comments::beautify_doc_string; use rustc_ast::{self as ast, AttrStyle}; -use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel}; +use rustc_attr::{ConstStability, DeprKind, Stability, StabilityLevel, Version}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_feature::UnstableFeatures; use rustc_hir as hir; @@ -114,7 +114,7 @@ impl Item { if self.is_fake() { None } else { tcx.lookup_const_stability(self.def_id) } } - crate fn deprecation(&self, tcx: TyCtxt<'_>) -> Option { + crate fn deprecation(&self, tcx: TyCtxt<'_>) -> Option { if self.is_fake() { None } else { tcx.lookup_deprecation(self.def_id) } } @@ -257,16 +257,16 @@ impl Item { }) } - crate fn stable_since(&self, tcx: TyCtxt<'_>) -> Option { + crate fn stable_since(&self, tcx: TyCtxt<'_>) -> Option { match self.stability(tcx)?.level { - StabilityLevel::Stable { since, .. } => Some(since.as_str()), + StabilityLevel::Stable { since, .. } => Some(since), StabilityLevel::Unstable { .. } => None, } } - crate fn const_stable_since(&self, tcx: TyCtxt<'_>) -> Option { + crate fn const_stable_since(&self, tcx: TyCtxt<'_>) -> Option { match self.const_stability(tcx)?.level { - StabilityLevel::Stable { since, .. } => Some(since.as_str()), + StabilityLevel::Stable { since, .. } => Some(since), StabilityLevel::Unstable { .. } => None, } } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 7f122bb8cb5ae..73bf2b913c85a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -48,14 +48,13 @@ use std::sync::Arc; use itertools::Itertools; use rustc_ast_pretty::pprust; -use rustc_attr::{Deprecation, StabilityLevel}; +use rustc_attr::{self as attr, StabilityLevel, Version}; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::Mutability; -use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::edition::Edition; @@ -1761,8 +1760,8 @@ fn print_item(cx: &Context<'_>, item: &clean::Item, buf: &mut Buffer) { buf.write_str(""); render_stability_since_raw( buf, - item.stable_since(cx.tcx()).as_deref(), - item.const_stable_since(cx.tcx()).as_deref(), + item.stable_since(cx.tcx()), + item.const_stable_since(cx.tcx()), None, None, ); @@ -2240,13 +2239,10 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) -> // The trailing space after each tag is to space it properly against the rest of the docs. if let Some(depr) = &item.deprecation(tcx) { - let mut message = "Deprecated"; - if !stability::deprecation_in_effect( - depr.is_since_rustc_version, - depr.since.map(|s| s.as_str()).as_deref(), - ) { - message = "Deprecation planned"; - } + let message = match depr { + attr::Deprecated { .. } | attr::RustcDeprecated { now: true, .. } => "Deprecated", + attr::RustcDeprecated { now: false, .. } => "Deprecation planned", + }; tags += &tag_html("deprecated", "", message); } @@ -2300,27 +2296,22 @@ fn short_item_info( let mut extra_info = vec![]; let error_codes = cx.shared.codes; - if let Some(Deprecation { note, since, is_since_rustc_version, suggestion: _ }) = - item.deprecation(cx.tcx()) - { - // We display deprecation messages for #[deprecated] and #[rustc_deprecated] - // but only display the future-deprecation messages for #[rustc_deprecated]. - let mut message = if let Some(since) = since { - let since = &since.as_str(); - if !stability::deprecation_in_effect(is_since_rustc_version, Some(since)) { - if *since == "TBD" { - String::from("Deprecating in a future Rust version") - } else { - format!("Deprecating in {}", Escape(since)) - } - } else { - format!("Deprecated since {}", Escape(since)) + if let Some(depr) = item.deprecation(cx.tcx()) { + let mut message = match depr { + attr::Deprecated { since: None, .. } => String::from("Deprecated"), + attr::Deprecated { since: Some(since), .. } => { + format!("Deprecated since {}", Escape(&since.as_str())) } - } else { - String::from("Deprecated") + attr::RustcDeprecated { now: true, since: Some(since), .. } => { + format!("Deprecated since {}", since) + } + attr::RustcDeprecated { now: false, since: Some(since), .. } => { + format!("Deprecating in {}", since) + } + _ => String::from("Deprecating in a future Rust version"), }; - if let Some(note) = note { + if let Some(note) = depr.note() { let note = note.as_str(); let mut ids = cx.id_map.borrow_mut(); let html = MarkdownHtml( @@ -2330,8 +2321,9 @@ fn short_item_info( cx.shared.edition, &cx.shared.playground, ); - message.push_str(&format!(": {}", html.into_string())); + message = format!("{}: {}", message, html.into_string()); } + extra_info.push(format!( "
👎 {}
", message, @@ -2494,8 +2486,8 @@ fn render_implementor( trait_, AssocItemLink::Anchor(None), RenderMode::Normal, - trait_.stable_since(cx.tcx()).as_deref(), - trait_.const_stable_since(cx.tcx()).as_deref(), + trait_.stable_since(cx.tcx()), + trait_.const_stable_since(cx.tcx()), false, Some(use_absolute), false, @@ -2523,8 +2515,8 @@ fn render_impls( containing_item, assoc_link, RenderMode::Normal, - containing_item.stable_since(cx.tcx()).as_deref(), - containing_item.const_stable_since(cx.tcx()).as_deref(), + containing_item.stable_since(cx.tcx()), + containing_item.const_stable_since(cx.tcx()), true, None, false, @@ -2777,8 +2769,8 @@ fn item_trait(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Tra it, assoc_link, RenderMode::Normal, - implementor.impl_item.stable_since(cx.tcx()).as_deref(), - implementor.impl_item.const_stable_since(cx.tcx()).as_deref(), + implementor.impl_item.stable_since(cx.tcx()), + implementor.impl_item.const_stable_since(cx.tcx()), false, None, true, @@ -2923,14 +2915,11 @@ fn assoc_type( fn render_stability_since_raw( w: &mut Buffer, - ver: Option<&str>, - const_ver: Option<&str>, - containing_ver: Option<&str>, - containing_const_ver: Option<&str>, + ver: Option, + const_ver: Option, + containing_ver: Option, + containing_const_ver: Option, ) { - let ver = ver.filter(|inner| !inner.is_empty()); - let const_ver = const_ver.filter(|inner| !inner.is_empty()); - match (ver, const_ver) { (Some(v), Some(cv)) if const_ver != containing_const_ver => { write!( @@ -2958,10 +2947,10 @@ fn render_stability_since( ) { render_stability_since_raw( w, - item.stable_since(tcx).as_deref(), - item.const_stable_since(tcx).as_deref(), - containing_item.stable_since(tcx).as_deref(), - containing_item.const_stable_since(tcx).as_deref(), + item.stable_since(tcx), + item.const_stable_since(tcx), + containing_item.stable_since(tcx), + containing_item.const_stable_since(tcx), ) } @@ -3540,8 +3529,8 @@ fn render_assoc_items( containing_item, AssocItemLink::Anchor(None), render_mode, - containing_item.stable_since(cx.tcx()).as_deref(), - containing_item.const_stable_since(cx.tcx()).as_deref(), + containing_item.stable_since(cx.tcx()), + containing_item.const_stable_since(cx.tcx()), true, None, false, @@ -3745,8 +3734,8 @@ fn render_impl( parent: &clean::Item, link: AssocItemLink<'_>, render_mode: RenderMode, - outer_version: Option<&str>, - outer_const_version: Option<&str>, + outer_version: Option, + outer_const_version: Option, show_def_docs: bool, use_absolute: Option, is_on_foreign_type: bool, @@ -3807,8 +3796,8 @@ fn render_impl( write!(w, "", id); render_stability_since_raw( w, - i.impl_item.stable_since(cx.tcx()).as_deref(), - i.impl_item.const_stable_since(cx.tcx()).as_deref(), + i.impl_item.stable_since(cx.tcx()), + i.impl_item.const_stable_since(cx.tcx()), outer_version, outer_const_version, ); @@ -3847,8 +3836,8 @@ fn render_impl( link: AssocItemLink<'_>, render_mode: RenderMode, is_default_item: bool, - outer_version: Option<&str>, - outer_const_version: Option<&str>, + outer_version: Option, + outer_const_version: Option, trait_: Option<&clean::Trait>, show_def_docs: bool, ) { @@ -3881,8 +3870,8 @@ fn render_impl( w.write_str(""); render_stability_since_raw( w, - item.stable_since(cx.tcx()).as_deref(), - item.const_stable_since(cx.tcx()).as_deref(), + item.stable_since(cx.tcx()), + item.const_stable_since(cx.tcx()), outer_version, outer_const_version, ); @@ -3911,8 +3900,8 @@ fn render_impl( w.write_str(""); render_stability_since_raw( w, - item.stable_since(cx.tcx()).as_deref(), - item.const_stable_since(cx.tcx()).as_deref(), + item.stable_since(cx.tcx()), + item.const_stable_since(cx.tcx()), outer_version, outer_const_version, ); @@ -3991,8 +3980,8 @@ fn render_impl( i: &clean::Impl, parent: &clean::Item, render_mode: RenderMode, - outer_version: Option<&str>, - outer_const_version: Option<&str>, + outer_version: Option, + outer_const_version: Option, show_def_docs: bool, ) { for trait_item in &t.items { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index b248fcdefbe7f..ab4b62f8956fc 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -7,6 +7,7 @@ use std::convert::From; use rustc_ast::ast; +use rustc_attr::{self as attr, DeprKind}; use rustc_hir::def::CtorKind; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{DefId, CRATE_DEF_INDEX}; @@ -87,10 +88,14 @@ impl JsonRenderer<'_> { } } -crate fn from_deprecation(deprecation: rustc_attr::Deprecation) -> Deprecation { - #[rustfmt::skip] - let rustc_attr::Deprecation { since, note, is_since_rustc_version: _, suggestion: _ } = deprecation; - Deprecation { since: since.map(|s| s.to_string()), note: note.map(|s| s.to_string()) } +crate fn from_deprecation(depr: DeprKind) -> Deprecation { + let since = match depr { + attr::Deprecated { since, .. } => since.map(|s| s.to_string()), + attr::RustcDeprecated { since: Some(since), .. } => Some(since.to_string()), + attr::RustcDeprecated { since: None, .. } => Some(String::from("TBD")), + }; + + Deprecation { since, note: depr.note().map(|s| s.to_string()) } } impl From for GenericArgs { diff --git a/src/test/rustdoc/implementor-stable-version.rs b/src/test/rustdoc/implementor-stable-version.rs index 0a065d8095bf2..1238c5b4f372c 100644 --- a/src/test/rustdoc/implementor-stable-version.rs +++ b/src/test/rustdoc/implementor-stable-version.rs @@ -2,18 +2,18 @@ #![feature(staged_api)] -#[stable(feature = "bar", since = "OLD 1.0")] +#[stable(feature = "bar", since = "1.0.0")] pub trait Bar {} -#[stable(feature = "baz", since = "OLD 1.0")] +#[stable(feature = "baz", since = "1.0.0")] pub trait Baz {} pub struct Foo; -// @has foo/trait.Bar.html '//div[@id="implementors-list"]//span[@class="since"]' 'NEW 2.0' -#[stable(feature = "foobar", since = "NEW 2.0")] +// @has foo/trait.Bar.html '//div[@id="implementors-list"]//span[@class="since"]' '2.0.0' +#[stable(feature = "foobar", since = "2.0.0")] impl Bar for Foo {} -// @!has foo/trait.Baz.html '//div[@id="implementors-list"]//span[@class="since"]' 'OLD 1.0' -#[stable(feature = "foobaz", since = "OLD 1.0")] +// @!has foo/trait.Baz.html '//div[@id="implementors-list"]//span[@class="since"]' '1.0.0' +#[stable(feature = "foobaz", since = "1.0.0")] impl Baz for Foo {} diff --git a/src/test/ui/reachable-unnameable-type-alias.rs b/src/test/ui/reachable-unnameable-type-alias.rs index 461355f87cf17..01ea1d3f89753 100644 --- a/src/test/ui/reachable-unnameable-type-alias.rs +++ b/src/test/ui/reachable-unnameable-type-alias.rs @@ -1,14 +1,14 @@ // run-pass #![feature(staged_api)] -#![stable(feature = "a", since = "b")] +#![stable(feature = "a", since = "1.0.0")] mod inner_private_module { // UnnameableTypeAlias isn't marked as reachable, so no stability annotation is required here pub type UnnameableTypeAlias = u8; } -#[stable(feature = "a", since = "b")] +#[stable(feature = "a", since = "1.0.0")] pub fn f() -> inner_private_module::UnnameableTypeAlias { 0 } diff --git a/src/test/ui/stability-attribute/stability-attribute-issue-43027.rs b/src/test/ui/stability-attribute/stability-attribute-issue-43027.rs index 0b243bb52119b..abdf3f397aa51 100644 --- a/src/test/ui/stability-attribute/stability-attribute-issue-43027.rs +++ b/src/test/ui/stability-attribute/stability-attribute-issue-43027.rs @@ -1,7 +1,7 @@ #![feature(staged_api)] -#![stable(feature = "test", since = "0")] +#![stable(feature = "test", since = "1.0.0")] -#[stable(feature = "test", since = "0")] +#[stable(feature = "test", since = "1.0.0")] pub struct Reverse(pub T); //~ ERROR field has missing stability attribute fn main() { diff --git a/src/test/ui/stability-attribute/stability-attribute-sanity.rs b/src/test/ui/stability-attribute/stability-attribute-sanity.rs index 0c40f8ae1c67e..734a6bed39ea6 100644 --- a/src/test/ui/stability-attribute/stability-attribute-sanity.rs +++ b/src/test/ui/stability-attribute/stability-attribute-sanity.rs @@ -5,19 +5,19 @@ #![stable(feature = "rust1", since = "1.0.0")] mod bogus_attribute_types_1 { - #[stable(feature = "a", since = "b", reason)] //~ ERROR unknown meta item 'reason' [E0541] + #[stable(feature = "a", since = "1.0.0", reason)] //~ ERROR unknown meta item 'reason' [E0541] fn f1() { } #[stable(feature = "a", since)] //~ ERROR incorrect meta item [E0539] fn f2() { } - #[stable(feature, since = "a")] //~ ERROR incorrect meta item [E0539] + #[stable(feature, since = "1.0.0")] //~ ERROR incorrect meta item [E0539] fn f3() { } #[stable(feature = "a", since(b))] //~ ERROR incorrect meta item [E0539] fn f5() { } - #[stable(feature(b), since = "a")] //~ ERROR incorrect meta item [E0539] + #[stable(feature(b), since = "1.0.0")] //~ ERROR incorrect meta item [E0539] fn f6() { } } @@ -28,49 +28,55 @@ mod missing_feature_names { #[unstable(feature = "b")] //~ ERROR missing 'issue' [E0547] fn f2() { } - #[stable(since = "a")] //~ ERROR missing 'feature' [E0546] + #[stable(since = "1.0.0")] //~ ERROR missing 'feature' [E0546] fn f3() { } } mod missing_version { - #[stable(feature = "a")] //~ ERROR missing 'since' [E0542] + #[stable(feature = "a")] //~ ERROR invalid 'since' [E0542] fn f1() { } - #[stable(feature = "a", since = "b")] - #[rustc_deprecated(reason = "a")] //~ ERROR missing 'since' [E0542] + #[stable(feature = "a", since = "1.0.0")] + #[rustc_deprecated(reason = "a")] //~ ERROR invalid 'since' [E0542] fn f2() { } - #[stable(feature = "a", since = "b")] - #[rustc_deprecated(since = "a")] //~ ERROR missing 'reason' [E0543] + #[stable(feature = "a", since = "1.0.0")] + #[rustc_deprecated(since = "1.0.0")] //~ ERROR missing 'reason' [E0543] fn f3() { } } #[unstable(feature = "b", issue = "none")] -#[stable(feature = "a", since = "b")] //~ ERROR multiple stability levels [E0544] +#[stable(feature = "a", since = "1.0.0")] //~ ERROR multiple stability levels [E0544] fn multiple1() { } #[unstable(feature = "b", issue = "none")] #[unstable(feature = "b", issue = "none")] //~ ERROR multiple stability levels [E0544] fn multiple2() { } -#[stable(feature = "a", since = "b")] -#[stable(feature = "a", since = "b")] //~ ERROR multiple stability levels [E0544] +#[stable(feature = "a", since = "1.0.0")] +#[stable(feature = "a", since = "1.0.0")] //~ ERROR multiple stability levels [E0544] fn multiple3() { } -#[stable(feature = "a", since = "b")] -#[rustc_deprecated(since = "b", reason = "text")] -#[rustc_deprecated(since = "b", reason = "text")] //~ ERROR multiple deprecated attributes +#[stable(feature = "a", since = "1.0.0")] +#[rustc_deprecated(since = "1.0.0", reason = "text")] +#[rustc_deprecated(since = "1.0.0", reason = "text")] //~ ERROR multiple deprecated attributes #[rustc_const_unstable(feature = "c", issue = "none")] #[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels pub const fn multiple4() { } -//~^ ERROR Invalid stability version found + +#[stable(feature = "a", since = "invalid")] //~ ERROR invalid 'since' [E0542] +fn invalid_stability_version() {} #[stable(feature = "a", since = "1.0.0")] -#[rustc_deprecated(since = "invalid", reason = "text")] -fn invalid_deprecation_version() {} //~ ERROR Invalid deprecation version found +#[rustc_deprecated(since = "invalid", reason = "text")] //~ ERROR invalid 'since' [E0542] +fn invalid_deprecation_version() {} -#[rustc_deprecated(since = "a", reason = "text")] +#[rustc_deprecated(since = "1.0.0", reason = "text")] fn deprecated_without_unstable_or_stable() { } //~^^ ERROR rustc_deprecated attribute must be paired with either stable or unstable attribute +#[stable(feature = "a", since = "2.0.0")] +#[rustc_deprecated(since = "1.0.0", reason = "text")] +fn deprecated_before_stabilized() {} //~ ERROR An API can't be stabilized after it is deprecated + fn main() { } diff --git a/src/test/ui/stability-attribute/stability-attribute-sanity.stderr b/src/test/ui/stability-attribute/stability-attribute-sanity.stderr index 03fb80bb90abc..22ebd9cd31573 100644 --- a/src/test/ui/stability-attribute/stability-attribute-sanity.stderr +++ b/src/test/ui/stability-attribute/stability-attribute-sanity.stderr @@ -1,8 +1,8 @@ error[E0541]: unknown meta item 'reason' - --> $DIR/stability-attribute-sanity.rs:8:42 + --> $DIR/stability-attribute-sanity.rs:8:46 | -LL | #[stable(feature = "a", since = "b", reason)] - | ^^^^^^ expected one of `since`, `note` +LL | #[stable(feature = "a", since = "1.0.0", reason)] + | ^^^^^^ expected one of `since`, `note` error[E0539]: incorrect meta item --> $DIR/stability-attribute-sanity.rs:11:29 @@ -13,7 +13,7 @@ LL | #[stable(feature = "a", since)] error[E0539]: incorrect meta item --> $DIR/stability-attribute-sanity.rs:14:14 | -LL | #[stable(feature, since = "a")] +LL | #[stable(feature, since = "1.0.0")] | ^^^^^^^ error[E0539]: incorrect meta item @@ -25,7 +25,7 @@ LL | #[stable(feature = "a", since(b))] error[E0539]: incorrect meta item --> $DIR/stability-attribute-sanity.rs:20:14 | -LL | #[stable(feature(b), since = "a")] +LL | #[stable(feature(b), since = "1.0.0")] | ^^^^^^^^^^ error[E0546]: missing 'feature' @@ -43,16 +43,16 @@ LL | #[unstable(feature = "b")] error[E0546]: missing 'feature' --> $DIR/stability-attribute-sanity.rs:31:5 | -LL | #[stable(since = "a")] - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | #[stable(since = "1.0.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0542]: missing 'since' +error[E0542]: invalid 'since' --> $DIR/stability-attribute-sanity.rs:36:5 | LL | #[stable(feature = "a")] | ^^^^^^^^^^^^^^^^^^^^^^^^ -error[E0542]: missing 'since' +error[E0542]: invalid 'since' --> $DIR/stability-attribute-sanity.rs:40:5 | LL | #[rustc_deprecated(reason = "a")] @@ -61,14 +61,14 @@ LL | #[rustc_deprecated(reason = "a")] error[E0543]: missing 'reason' --> $DIR/stability-attribute-sanity.rs:44:5 | -LL | #[rustc_deprecated(since = "a")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_deprecated(since = "1.0.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0544]: multiple stability levels --> $DIR/stability-attribute-sanity.rs:49:1 | -LL | #[stable(feature = "a", since = "b")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[stable(feature = "a", since = "1.0.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0544]: multiple stability levels --> $DIR/stability-attribute-sanity.rs:53:1 @@ -79,16 +79,16 @@ LL | #[unstable(feature = "b", issue = "none")] error[E0544]: multiple stability levels --> $DIR/stability-attribute-sanity.rs:57:1 | -LL | #[stable(feature = "a", since = "b")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[stable(feature = "a", since = "1.0.0")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0550]: multiple deprecated attributes --> $DIR/stability-attribute-sanity.rs:62:1 | -LL | #[rustc_deprecated(since = "b", reason = "text")] - | ------------------------------------------------- first deprecation attribute -LL | #[rustc_deprecated(since = "b", reason = "text")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ repeated deprecation attribute +LL | #[rustc_deprecated(since = "1.0.0", reason = "text")] + | ----------------------------------------------------- first deprecation attribute +LL | #[rustc_deprecated(since = "1.0.0", reason = "text")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ repeated deprecation attribute error[E0544]: multiple stability levels --> $DIR/stability-attribute-sanity.rs:64:1 @@ -96,25 +96,31 @@ error[E0544]: multiple stability levels LL | #[rustc_const_unstable(feature = "d", issue = "none")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: Invalid stability version found - --> $DIR/stability-attribute-sanity.rs:65:1 +error[E0542]: invalid 'since' + --> $DIR/stability-attribute-sanity.rs:67:1 | -LL | pub const fn multiple4() { } - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[stable(feature = "a", since = "invalid")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: Invalid deprecation version found - --> $DIR/stability-attribute-sanity.rs:70:1 +error[E0542]: invalid 'since' + --> $DIR/stability-attribute-sanity.rs:71:1 | -LL | fn invalid_deprecation_version() {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_deprecated(since = "invalid", reason = "text")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0549]: rustc_deprecated attribute must be paired with either stable or unstable attribute - --> $DIR/stability-attribute-sanity.rs:72:1 + --> $DIR/stability-attribute-sanity.rs:74:1 + | +LL | #[rustc_deprecated(since = "1.0.0", reason = "text")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: An API can't be stabilized after it is deprecated + --> $DIR/stability-attribute-sanity.rs:80:1 | -LL | #[rustc_deprecated(since = "a", reason = "text")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | fn deprecated_before_stabilized() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors Some errors have detailed explanations: E0539, E0541, E0542, E0543, E0546, E0547, E0549, E0550. For more information about an error, try `rustc --explain E0539`. diff --git a/src/test/ui/stability-attribute/stability-attribute-trait-impl.rs b/src/test/ui/stability-attribute/stability-attribute-trait-impl.rs index 656564fc9e3f8..ddfe1c59a341d 100644 --- a/src/test/ui/stability-attribute/stability-attribute-trait-impl.rs +++ b/src/test/ui/stability-attribute/stability-attribute-trait-impl.rs @@ -1,12 +1,12 @@ #![feature(staged_api)] -#[stable(feature = "x", since = "1")] +#[stable(feature = "x", since = "1.0.0")] struct StableType; #[unstable(feature = "x", issue = "none")] struct UnstableType; -#[stable(feature = "x", since = "1")] +#[stable(feature = "x", since = "1.0.0")] trait StableTrait {} #[unstable(feature = "x", issue = "none")]