Skip to content

Store #[stable] attribute's since value in structured form #117148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Oct 26, 2023
Merged
56 changes: 39 additions & 17 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
@@ -13,6 +13,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::{self, Display};
use std::num::NonZeroU32;

use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
@@ -23,9 +24,10 @@ use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
/// For more, see [this pull request](https://github.com/rust-lang/rust/pull/100591).
pub const VERSION_PLACEHOLDER: &str = "CURRENT_RUSTC_VERSION";

pub const CURRENT_RUSTC_VERSION: &str = env!("CFG_RELEASE");

pub fn rust_version_symbol() -> Symbol {
let version = option_env!("CFG_RELEASE").unwrap_or("<current>");
Symbol::intern(&version)
Symbol::intern(CURRENT_RUSTC_VERSION)
}

pub fn is_builtin_attr(attr: &Attribute) -> bool {
@@ -144,13 +146,24 @@ pub enum StabilityLevel {
/// `#[stable]`
Stable {
/// Rust release which stabilized this feature.
since: Symbol,
since: Since,
/// Is this item allowed to be referred to on stable, despite being contained in unstable
/// modules?
allowed_through_unstable_modules: bool,
},
}

/// Rust release in which a feature is stabilized.
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(HashStable_Generic)]
pub enum Since {
Version(Version),
/// Stabilized in the upcoming version, whatever number that is.
Current,
/// Failed to parse a stabilization version.
Err,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this hold an ErrorGuaranteed or not useful?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I based this on the ExprKind::Err from rustc_ast, which does not hold ErrorGuaranteed.

/// Placeholder for an expression that wasn't syntactically well formed in some way.
Err,

}

impl StabilityLevel {
pub fn is_unstable(&self) -> bool {
matches!(self, StabilityLevel::Unstable { .. })
@@ -372,22 +385,24 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit

let since = if let Some(since) = since {
if since.as_str() == VERSION_PLACEHOLDER {
Ok(rust_version_symbol())
} else if parse_version(since.as_str(), false).is_some() {
Ok(since)
Since::Current
} else if let Some(version) = parse_version(since.as_str(), false) {
Since::Version(version)
} else {
Err(sess.emit_err(session_diagnostics::InvalidSince { span: attr.span }))
sess.emit_err(session_diagnostics::InvalidSince { span: attr.span });
Since::Err
}
} else {
Err(sess.emit_err(session_diagnostics::MissingSince { span: attr.span }))
sess.emit_err(session_diagnostics::MissingSince { span: attr.span });
Since::Err
};

match (feature, since) {
(Ok(feature), Ok(since)) => {
match feature {
Ok(feature) => {
let level = StabilityLevel::Stable { since, allowed_through_unstable_modules: false };
Some((feature, level))
}
(Err(ErrorGuaranteed { .. }), _) | (_, Err(ErrorGuaranteed { .. })) => None,
Err(ErrorGuaranteed { .. }) => None,
}
}

@@ -556,11 +571,12 @@ 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 {
pub major: u16,
pub minor: u16,
pub patch: u16,
}

fn parse_version(s: &str, allow_appendix: bool) -> Option<Version> {
@@ -576,6 +592,12 @@ fn parse_version(s: &str, allow_appendix: bool) -> Option<Version> {
Some(Version { major, minor, patch })
}

impl Display for Version {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
/// evaluate individual items.
pub fn eval_condition(
@@ -609,7 +631,7 @@ pub fn eval_condition(
sess.emit_warning(session_diagnostics::UnknownVersionLiteral { span: *span });
return false;
};
let rustc_version = parse_version(env!("CFG_RELEASE"), true).unwrap();
let rustc_version = parse_version(CURRENT_RUSTC_VERSION, true).unwrap();

// See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
if sess.assume_incomplete_release {
5 changes: 0 additions & 5 deletions compiler/rustc_passes/messages.ftl
Original file line number Diff line number Diff line change
@@ -402,11 +402,6 @@ passes_invalid_macro_export_arguments = `{$name}` isn't a valid `#[macro_export]
passes_invalid_macro_export_arguments_too_many_items = `#[macro_export]` can only take 1 or 0 arguments
passes_invalid_stability =
invalid stability version found
.label = invalid stability version
.item = the stability attribute annotates this item
passes_lang_item_fn_with_target_feature =
`{$name}` language item function is not allowed to have `#[target_feature]`
.label = `{$name}` language item function is not allowed to have `#[target_feature]`
10 changes: 0 additions & 10 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1504,16 +1504,6 @@ pub struct UselessStability {
pub item_sp: Span,
}

#[derive(Diagnostic)]
#[diag(passes_invalid_stability)]
pub struct InvalidStability {
#[primary_span]
#[label]
pub span: Span,
#[label(passes_item)]
pub item_sp: Span,
}

#[derive(Diagnostic)]
#[diag(passes_cannot_stabilize_deprecated)]
pub struct CannotStabilizeDeprecated {
64 changes: 35 additions & 29 deletions compiler/rustc_passes/src/stability.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
use crate::errors;
use rustc_attr::{
self as attr, rust_version_symbol, ConstStability, Stability, StabilityLevel, Unstable,
self as attr, rust_version_symbol, ConstStability, Since, Stability, StabilityLevel, Unstable,
UnstableReason, VERSION_PLACEHOLDER,
};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
@@ -226,37 +226,43 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
if let (&Some(dep_since), &attr::Stable { since: stab_since, .. }) =
(&depr.as_ref().and_then(|(d, _)| d.since), &stab.level)
{
// Explicit version of iter::order::lt to handle parse errors properly
for (dep_v, stab_v) in
iter::zip(dep_since.as_str().split('.'), stab_since.as_str().split('.'))
{
match stab_v.parse::<u64>() {
Err(_) => {
self.tcx.sess.emit_err(errors::InvalidStability { span, item_sp });
break;
}
Ok(stab_vp) => match dep_v.parse::<u64>() {
Ok(dep_vp) => match dep_vp.cmp(&stab_vp) {
Ordering::Less => {
self.tcx.sess.emit_err(errors::CannotStabilizeDeprecated {
span,
item_sp,
});
match stab_since {
Since::Current => {
self.tcx.sess.emit_err(errors::CannotStabilizeDeprecated { span, item_sp });
}
Since::Version(stab_since) => {
// Explicit version of iter::order::lt to handle parse errors properly
for (dep_v, stab_v) in iter::zip(
dep_since.as_str().split('.'),
[stab_since.major, stab_since.minor, stab_since.patch],
) {
match dep_v.parse::<u64>() {
Ok(dep_vp) => match dep_vp.cmp(&u64::from(stab_v)) {
Ordering::Less => {
self.tcx.sess.emit_err(errors::CannotStabilizeDeprecated {
span,
item_sp,
});
break;
}
Ordering::Equal => continue,
Ordering::Greater => break,
},
Err(_) => {
if dep_v != "TBD" {
self.tcx.sess.emit_err(errors::InvalidDeprecationVersion {
span,
item_sp,
});
}
break;
}
Ordering::Equal => continue,
Ordering::Greater => break,
},
Err(_) => {
if dep_v != "TBD" {
self.tcx.sess.emit_err(errors::InvalidDeprecationVersion {
span,
item_sp,
});
}
break;
}
},
}
}
Since::Err => {
// An error already reported. Assume the unparseable stabilization
// version is older than the deprecation version.
}
}
}
6 changes: 3 additions & 3 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use thin_vec::ThinVec;

use rustc_ast as ast;
use rustc_ast_pretty::pprust;
use rustc_attr::{ConstStability, Deprecation, Stability, StabilityLevel};
use rustc_attr::{ConstStability, Deprecation, Since, Stability, StabilityLevel};
use rustc_const_eval::const_eval::is_unstable_const_fn;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
@@ -585,14 +585,14 @@ impl Item {
})
}

pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<Symbol> {
pub(crate) fn stable_since(&self, tcx: TyCtxt<'_>) -> Option<Since> {
match self.stability(tcx)?.level {
StabilityLevel::Stable { since, .. } => Some(since),
StabilityLevel::Unstable { .. } => None,
}
}

pub(crate) fn const_stable_since(&self, tcx: TyCtxt<'_>) -> Option<Symbol> {
pub(crate) fn const_stable_since(&self, tcx: TyCtxt<'_>) -> Option<Since> {
match self.const_stability(tcx)?.level {
StabilityLevel::Stable { since, .. } => Some(since),
StabilityLevel::Unstable { .. } => None,
31 changes: 22 additions & 9 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ use std::str;
use std::string::ToString;

use askama::Template;
use rustc_attr::{ConstStability, Deprecation, StabilityLevel};
use rustc_attr::{ConstStability, Deprecation, Since, StabilityLevel, CURRENT_RUSTC_VERSION};
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::{DefId, DefIdSet};
@@ -911,13 +911,17 @@ fn assoc_method(
/// consequence of the above rules.
fn render_stability_since_raw_with_extra(
w: &mut Buffer,
ver: Option<Symbol>,
ver: Option<Since>,
const_stability: Option<ConstStability>,
containing_ver: Option<Symbol>,
containing_const_ver: Option<Symbol>,
containing_ver: Option<Since>,
containing_const_ver: Option<Since>,
extra_class: &str,
) -> bool {
let stable_version = ver.filter(|inner| !inner.is_empty() && Some(*inner) != containing_ver);
let stable_version = if ver != containing_ver && let Some(ver) = &ver {
since_to_string(ver)
} else {
None
};

let mut title = String::new();
let mut stability = String::new();
@@ -931,7 +935,8 @@ fn render_stability_since_raw_with_extra(
Some(ConstStability { level: StabilityLevel::Stable { since, .. }, .. })
if Some(since) != containing_const_ver =>
{
Some((format!("const since {since}"), format!("const: {since}")))
since_to_string(&since)
.map(|since| (format!("const since {since}"), format!("const: {since}")))
}
Some(ConstStability { level: StabilityLevel::Unstable { issue, .. }, feature, .. }) => {
let unstable = if let Some(n) = issue {
@@ -971,13 +976,21 @@ fn render_stability_since_raw_with_extra(
!stability.is_empty()
}

fn since_to_string(since: &Since) -> Option<String> {
match since {
Since::Version(since) => Some(since.to_string()),
Since::Current => Some(CURRENT_RUSTC_VERSION.to_owned()),
Since::Err => None,
}
}

#[inline]
fn render_stability_since_raw(
w: &mut Buffer,
ver: Option<Symbol>,
ver: Option<Since>,
const_stability: Option<ConstStability>,
containing_ver: Option<Symbol>,
containing_const_ver: Option<Symbol>,
containing_ver: Option<Since>,
containing_const_ver: Option<Since>,
) -> bool {
render_stability_since_raw_with_extra(
w,
30 changes: 18 additions & 12 deletions src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@

use crate::msrvs::Msrv;
use hir::LangItem;
use rustc_attr::{Since, CURRENT_RUSTC_VERSION};
use rustc_const_eval::transform::check_consts::ConstCx;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@@ -370,19 +371,24 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
// function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.

// HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev. `rustc-semver`
// doesn't accept the `-dev` version number so we have to strip it off.
let short_version = since
.as_str()
.split('-')
.next()
.expect("rustc_attr::StabilityLevel::Stable::since` is empty");
let const_stab_rust_version = match since {
Since::Version(version) => RustcVersion::new(
u32::from(version.major),
u32::from(version.minor),
u32::from(version.patch),
),
Since::Current => {
// HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev.
// `rustc-semver` doesn't accept the `-dev` version number so we have to strip it off.
let short_version = CURRENT_RUSTC_VERSION.split('-').next().unwrap();
RustcVersion::parse(short_version).unwrap_or_else(|err| {
panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{CURRENT_RUSTC_VERSION}`, {err:?}")
})
},
Since::Err => return false,
};

let since = rustc_span::Symbol::intern(short_version);

msrv.meets(RustcVersion::parse(since.as_str()).unwrap_or_else(|err| {
panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{since}`, {err:?}")
}))
msrv.meets(const_stab_rust_version)
} else {
// Unstable const fn with the feature enabled.
msrv.current().is_none()
12 changes: 6 additions & 6 deletions tests/rustdoc/html-no-source.rs
Original file line number Diff line number Diff line change
@@ -11,20 +11,20 @@
// @files 'src/foo' '[]'

// @has foo/fn.foo.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "bar", since = "1.0")]
pub fn foo() {}

// @has foo/struct.Bar.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "bar", since = "1.0")]
pub struct Bar;

impl Bar {
// @has - '//*[@id="method.bar"]/*[@class="since rightside"]' '2.0'
// @!has - '//*[@id="method.bar"]/*[@class="rightside"]' '2.0 ·'
// @has - '//*[@id="method.bar"]/*[@class="since rightside"]' '2.0.0'
// @!has - '//*[@id="method.bar"]/*[@class="rightside"]' '2.0.0 ·'
#[stable(feature = "foobar", since = "2.0")]
pub fn bar() {}
}
10 changes: 5 additions & 5 deletions tests/rustdoc/source-version-separator.rs
Original file line number Diff line number Diff line change
@@ -3,23 +3,23 @@
#![feature(staged_api)]

// @has foo/trait.Bar.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "bar", since = "1.0")]
pub trait Bar {
// @has - '//*[@id="tymethod.foo"]/*[@class="rightside"]' '3.0 · source'
// @has - '//*[@id="tymethod.foo"]/*[@class="rightside"]' '3.0.0 · source'
#[stable(feature = "foobar", since = "3.0")]
fn foo();
}

// @has - '//div[@id="implementors-list"]//*[@class="rightside"]' '4.0 · source'
// @has - '//div[@id="implementors-list"]//*[@class="rightside"]' '4.0.0 · source'

// @has foo/struct.Foo.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "baz", since = "1.0")]
pub struct Foo;

impl Foo {
// @has - '//*[@id="method.foofoo"]/*[@class="rightside"]' '3.0 · source'
// @has - '//*[@id="method.foofoo"]/*[@class="rightside"]' '3.0.0 · source'
#[stable(feature = "foobar", since = "3.0")]
pub fn foofoo() {}
}
12 changes: 6 additions & 6 deletions tests/rustdoc/version-separator-without-source.rs
Original file line number Diff line number Diff line change
@@ -4,20 +4,20 @@
#![crate_name = "foo"]

// @has foo/fn.foo.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "bar", since = "1.0")]
pub fn foo() {}

// @has foo/struct.Bar.html
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0 · source · '
// @has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · '
// @!has - '//div[@class="main-heading"]/*[@class="out-of-band"]' '1.0.0 · source · '
#[stable(feature = "bar", since = "1.0")]
pub struct Bar;

impl Bar {
// @has - '//*[@id="method.bar"]/*[@class="since rightside"]' '2.0'
// @!has - '//*[@id="method.bar"]/*[@class="rightside"]' '2.0 ·'
// @has - '//*[@id="method.bar"]/*[@class="since rightside"]' '2.0.0'
// @!has - '//*[@id="method.bar"]/*[@class="rightside"]' '2.0.0 ·'
#[stable(feature = "foobar", since = "2.0")]
pub fn bar() {}
}
3 changes: 1 addition & 2 deletions tests/ui/stability-attribute/stability-attribute-sanity.rs
Original file line number Diff line number Diff line change
@@ -60,10 +60,9 @@ fn multiple3() { }
#[stable(feature = "e", since = "b")] //~ ERROR 'since' must be a Rust version number, such as "1.31.0"
#[deprecated(since = "b", note = "text")]
#[deprecated(since = "b", note = "text")] //~ ERROR multiple `deprecated` attributes
//~^ ERROR deprecated attribute must be paired with either stable or unstable attribute
#[rustc_const_unstable(feature = "c", issue = "none")]
#[rustc_const_unstable(feature = "d", issue = "none")] //~ ERROR multiple stability levels
pub const fn multiple4() { } //~ ERROR function has missing stability attribute
pub const fn multiple4() { }

#[stable(feature = "a", since = "1.0.0")] //~ ERROR invalid deprecation version found
//~^ ERROR feature `a` is declared stable since 1.0.0
22 changes: 5 additions & 17 deletions tests/ui/stability-attribute/stability-attribute-sanity.stderr
Original file line number Diff line number Diff line change
@@ -101,19 +101,13 @@ LL | #[stable(feature = "e", since = "b")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0544]: multiple stability levels
--> $DIR/stability-attribute-sanity.rs:65:1
--> $DIR/stability-attribute-sanity.rs:64:1
|
LL | #[rustc_const_unstable(feature = "d", issue = "none")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0549]: deprecated attribute must be paired with either stable or unstable attribute
--> $DIR/stability-attribute-sanity.rs:62:1
|
LL | #[deprecated(since = "b", note = "text")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: invalid deprecation version found
--> $DIR/stability-attribute-sanity.rs:68:1
--> $DIR/stability-attribute-sanity.rs:67:1
|
LL | #[stable(feature = "a", since = "1.0.0")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid deprecation version
@@ -122,24 +116,18 @@ LL | fn invalid_deprecation_version() {}
| ----------------------------------- the stability attribute annotates this item

error[E0549]: deprecated attribute must be paired with either stable or unstable attribute
--> $DIR/stability-attribute-sanity.rs:73:1
--> $DIR/stability-attribute-sanity.rs:72:1
|
LL | #[deprecated(since = "a", note = "text")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: function has missing stability attribute
--> $DIR/stability-attribute-sanity.rs:66:1
|
LL | pub const fn multiple4() { }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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:68:1
--> $DIR/stability-attribute-sanity.rs:67:1
|
LL | #[stable(feature = "a", since = "1.0.0")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 22 previous errors
error: aborting due to 20 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`.