Skip to content
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

Parse rustc version at compile time #117256

Merged
merged 1 commit into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 13 additions & 28 deletions compiler/rustc_attr/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ use rustc_session::config::ExpectedValues;
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_session::parse::{feature_err, ParseSess};
use rustc_session::Session;
use rustc_session::{RustcVersion, 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};
Expand All @@ -24,8 +23,6 @@ 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 is_builtin_attr(attr: &Attribute) -> bool {
attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
}
Expand Down Expand Up @@ -153,7 +150,7 @@ pub enum StabilityLevel {
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(HashStable_Generic)]
pub enum Since {
Version(Version),
Version(RustcVersion),
/// Stabilized in the upcoming version, whatever number that is.
Current,
/// Failed to parse a stabilization version.
Expand Down Expand Up @@ -382,7 +379,7 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit
let since = if let Some(since) = since {
if since.as_str() == VERSION_PLACEHOLDER {
Since::Current
} else if let Some(version) = parse_version(since.as_str(), false) {
} else if let Some(version) = parse_version(since) {
Since::Version(version)
} else {
sess.emit_err(session_diagnostics::InvalidSince { span: attr.span });
Expand Down Expand Up @@ -567,31 +564,20 @@ fn gate_cfg(gated_cfg: &GatedCfg, cfg_span: Span, sess: &ParseSess, features: &F
}
}

#[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> {
let mut components = s.split('-');
/// Parse a rustc version number written inside string literal in an attribute,
/// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are
/// not accepted in this position, unlike when parsing CFG_RELEASE.
fn parse_version(s: Symbol) -> Option<RustcVersion> {
let mut components = s.as_str().split('-');
let d = components.next()?;
if !allow_appendix && components.next().is_some() {
if components.next().is_some() {
return None;
}
let mut digits = d.splitn(3, '.');
let major = digits.next()?.parse().ok()?;
let minor = digits.next()?.parse().ok()?;
let patch = digits.next().unwrap_or("0").parse().ok()?;
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)
}
Some(RustcVersion { major, minor, patch })
}

/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
Expand Down Expand Up @@ -623,17 +609,16 @@ pub fn eval_condition(
return false;
}
};
let Some(min_version) = parse_version(min_version.as_str(), false) else {
let Some(min_version) = parse_version(*min_version) else {
sess.emit_warning(session_diagnostics::UnknownVersionLiteral { span: *span });
return false;
};
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 {
rustc_version > min_version
RustcVersion::CURRENT > min_version
} else {
rustc_version >= min_version
RustcVersion::CURRENT >= min_version
}
}
ast::MetaItemKind::List(mis) => {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ pub use StabilityLevel::*;

pub use rustc_ast::attr::*;

pub(crate) use rustc_ast::HashStableContext;
pub(crate) use rustc_session::HashStableContext;
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 don't fully understand how this works, but without this change, the PR does not compile. Hopefully it is okay.

error[E0277]: the trait bound `__CTX: rustc_session::HashStableContext` is not satisfied
   --> compiler/rustc_attr/src/builtin.rs:151:10
    |
151 |   #[derive(HashStable_Generic)]
    |            ^^^^^^^^^^^^^^^^^^
    |            |
    |            the trait `rustc_session::HashStableContext` is not implemented for `__CTX`
    |            in this derive macro expansion
    |
   ::: .cargo/registry/src/index.crates.io-6f17d22bba15001f/synstructure-0.13.0/src/macros.rs:94:9
    |
94  | /         pub fn $derives(
95  | |             i: $crate::macros::TokenStream
96  | |         ) -> $crate::macros::TokenStream {
    | |________________________________________- in this expansion of `#[derive(HashStable_Generic)]`
    |
    = note: required for `RustcVersion` to implement `HashStable<__CTX>`

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is fine.

Copy link
Member

Choose a reason for hiding this comment

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

It's because of the HashStable_Generic derive on RustcVersion in rustc_session. If you really wanted to avoid this, you'd need to move that data structure from rustc_session to rustc_ast or derive it manually.

Deriving it manually may be easier since it's just a few u16s....?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah yes, this handwritten impl gets the job done: 0fc378a

// (in compiler/rustc_session/src/version.rs)

// Handwritten because #[derive(HashStable_Generic)] would generate a stricter
// `Ctx: rustc_session::HashStableContext` bound.
impl<Ctx> HashStable<Ctx> for RustcVersion
where
    Ctx: rustc_ast::HashStableContext,
{
    fn hash_stable(&self, hcx: &mut Ctx, hasher: &mut StableHasher) {
        self.major.hash_stable(hcx, hasher);
        self.minor.hash_stable(hcx, hasher);
        self.patch.hash_stable(hcx, hasher);
    }
}

But in the absence of any known downside of changing rustc_attr::HashStableContext, I think I would go for keeping the derived impl. I was not able to find any evidence of dyn HashStableContext in use anywhere, so I don't see any way that this change could bloat vtables, for example.


fluent_messages! { "../messages.ftl" }
59 changes: 59 additions & 0 deletions compiler/rustc_macros/src/current_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{parenthesized, parse_macro_input, LitStr, Token};

pub struct Input {
variable: LitStr,
}

mod kw {
syn::custom_keyword!(env);
}

impl Parse for Input {
// Input syntax is `env!("CFG_RELEASE")` to facilitate grepping.
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let paren;
input.parse::<kw::env>()?;
input.parse::<Token![!]>()?;
parenthesized!(paren in input);
let variable: LitStr = paren.parse()?;
Ok(Input { variable })
}
}

pub(crate) fn current_version(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Input);

TokenStream::from(match RustcVersion::parse_env_var(&input.variable) {
Ok(RustcVersion { major, minor, patch }) => quote!(
Self { major: #major, minor: #minor, patch: #patch }
),
Err(err) => syn::Error::new(Span::call_site(), err).into_compile_error(),
})
}

struct RustcVersion {
major: u16,
minor: u16,
patch: u16,
}

impl RustcVersion {
fn parse_env_var(env_var: &LitStr) -> Result<Self, Box<dyn std::error::Error>> {
let value = proc_macro::tracked_env::var(env_var.value())?;
Self::parse_str(&value)
.ok_or_else(|| format!("failed to parse rustc version: {:?}", value).into())
}

fn parse_str(value: &str) -> Option<Self> {
// Ignore any suffixes such as "-dev" or "-nightly".
let mut components = value.split('-').next().unwrap().splitn(3, '.');
let major = components.next()?.parse().ok()?;
let minor = components.next()?.parse().ok()?;
let patch = components.next().unwrap_or("0").parse().ok()?;
Some(RustcVersion { major, minor, patch })
}
}
6 changes: 6 additions & 0 deletions compiler/rustc_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use synstructure::decl_derive;

use proc_macro::TokenStream;

mod current_version;
mod diagnostics;
mod hash_stable;
mod lift;
Expand All @@ -25,6 +26,11 @@ mod symbols;
mod type_foldable;
mod type_visitable;

#[proc_macro]
pub fn current_rustc_version(input: TokenStream) -> TokenStream {
current_version::current_version(input)
}

#[proc_macro]
pub fn rustc_queries(input: TokenStream) -> TokenStream {
query::rustc_queries(input)
Expand Down
27 changes: 12 additions & 15 deletions compiler/rustc_middle/src/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
use rustc_session::lint::{BuiltinLintDiagnostics, Level, Lint, LintBuffer};
use rustc_session::parse::feature_err_issue;
use rustc_session::Session;
use rustc_session::{RustcVersion, Session};
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use std::num::NonZeroU32;
Expand Down Expand Up @@ -129,11 +129,6 @@ pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
let is_since_rustc_version = depr.is_since_rustc_version;
let since = depr.since.as_ref().map(Symbol::as_str);

fn parse_version(ver: &str) -> Vec<u32> {
// 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 without `#![staged_api]`.
return true;
Expand All @@ -144,16 +139,18 @@ pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
return false;
}

if let Some(rustc) = option_env!("CFG_RELEASE") {
let since: Vec<u32> = parse_version(&since);
let rustc: Vec<u32> = 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;
// We ignore non-integer components of the version (e.g., "nightly").
let since: Vec<u16> =
since.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect();

// We simply treat invalid `since` attributes as relating to a previous
// Rust version, thus always displaying the warning.
if since.len() != 3 {
return true;
}

let rustc = RustcVersion::CURRENT;
return since.as_slice() <= &[rustc.major, rustc.minor, rustc.patch];
};

// Assume deprecation is in effect if "since" field is missing
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub mod output;

pub use getopts;

mod version;
pub use version::RustcVersion;

fluent_messages! { "../messages.ftl" }

/// Requirements for a `StableHashingContext` to be used in this crate.
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_session/src/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::fmt::{self, Display};

#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(HashStable_Generic)]
pub struct RustcVersion {
pub major: u16,
pub minor: u16,
pub patch: u16,
}

impl RustcVersion {
pub const CURRENT: Self = current_rustc_version!(env!("CFG_RELEASE"));
}

impl Display for RustcVersion {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
5 changes: 3 additions & 2 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ use std::str;
use std::string::ToString;

use askama::Template;
use rustc_attr::{ConstStability, Deprecation, Since, StabilityLevel, CURRENT_RUSTC_VERSION};
use rustc_attr::{ConstStability, Deprecation, Since, StabilityLevel};
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::Mutability;
use rustc_middle::middle::stability;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::RustcVersion;
use rustc_span::{
symbol::{sym, Symbol},
BytePos, FileName, RealFileName,
Expand Down Expand Up @@ -979,7 +980,7 @@ fn render_stability_since_raw_with_extra(
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::Current => Some(RustcVersion::CURRENT.to_string()),
Since::Err => None,
}
}
Expand Down
23 changes: 8 additions & 15 deletions src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use crate::msrvs::Msrv;
use hir::LangItem;
use rustc_attr::{Since, CURRENT_RUSTC_VERSION};
use rustc_attr::Since;
use rustc_const_eval::transform::check_consts::ConstCx;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -372,23 +372,16 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.

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::Version(version) => version,
Since::Current => rustc_session::RustcVersion::CURRENT,
Since::Err => return false,
};

msrv.meets(const_stab_rust_version)
msrv.meets(RustcVersion::new(
u32::from(const_stab_rust_version.major),
u32::from(const_stab_rust_version.minor),
u32::from(const_stab_rust_version.patch),
))
} else {
// Unstable const fn with the feature enabled.
msrv.current().is_none()
Expand Down
Loading