From b7debe34e6fb2d329e21739c97ce3d7161303af5 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Thu, 26 Oct 2023 17:18:21 -0700 Subject: [PATCH] Parse rustc version at compile time --- compiler/rustc_attr/src/builtin.rs | 41 ++++--------- compiler/rustc_attr/src/lib.rs | 2 +- compiler/rustc_macros/src/current_version.rs | 59 +++++++++++++++++++ compiler/rustc_macros/src/lib.rs | 6 ++ compiler/rustc_middle/src/middle/stability.rs | 27 ++++----- compiler/rustc_session/src/lib.rs | 3 + compiler/rustc_session/src/version.rs | 19 ++++++ src/librustdoc/html/render/mod.rs | 5 +- .../clippy_utils/src/qualify_min_const_fn.rs | 23 +++----- 9 files changed, 124 insertions(+), 61 deletions(-) create mode 100644 compiler/rustc_macros/src/current_version.rs create mode 100644 compiler/rustc_session/src/version.rs diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 363ba0d4a815b..ff23e3da43800 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -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}; @@ -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)) } @@ -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. @@ -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 }); @@ -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 { - 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 { + 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 @@ -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) => { diff --git a/compiler/rustc_attr/src/lib.rs b/compiler/rustc_attr/src/lib.rs index 53e3eaaab3768..868c041225581 100644 --- a/compiler/rustc_attr/src/lib.rs +++ b/compiler/rustc_attr/src/lib.rs @@ -27,6 +27,6 @@ pub use StabilityLevel::*; pub use rustc_ast::attr::*; -pub(crate) use rustc_ast::HashStableContext; +pub(crate) use rustc_session::HashStableContext; fluent_messages! { "../messages.ftl" } diff --git a/compiler/rustc_macros/src/current_version.rs b/compiler/rustc_macros/src/current_version.rs new file mode 100644 index 0000000000000..5e3b91c17bf6c --- /dev/null +++ b/compiler/rustc_macros/src/current_version.rs @@ -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 { + let paren; + input.parse::()?; + input.parse::()?; + 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> { + 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 { + // 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 }) + } +} diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 776ba8f9ca11a..193dbd75fbd57 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -15,6 +15,7 @@ use synstructure::decl_derive; use proc_macro::TokenStream; +mod current_version; mod diagnostics; mod hash_stable; mod lift; @@ -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) diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 84893b8e627a9..20547696d5a73 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -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; @@ -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 { - // 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; @@ -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 = 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; + // We ignore non-integer components of the version (e.g., "nightly"). + let since: Vec = + 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 diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index 7da0bcf01bfbf..17ac3e991c58c 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -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. diff --git a/compiler/rustc_session/src/version.rs b/compiler/rustc_session/src/version.rs new file mode 100644 index 0000000000000..1ad8620bfba55 --- /dev/null +++ b/compiler/rustc_session/src/version.rs @@ -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) + } +} diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e01341acf438e..88d01b7a5e57d 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -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, @@ -979,7 +980,7 @@ fn render_stability_since_raw_with_extra( fn since_to_string(since: &Since) -> Option { 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, } } 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 1e465ac91b76e..31f7b87de635e 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 @@ -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; @@ -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()