Skip to content

Commit b7debe3

Browse files
committed
Parse rustc version at compile time
1 parent dab7156 commit b7debe3

File tree

9 files changed

+124
-61
lines changed

9 files changed

+124
-61
lines changed

compiler/rustc_attr/src/builtin.rs

+13-28
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ use rustc_session::config::ExpectedValues;
1010
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
1111
use rustc_session::lint::BuiltinLintDiagnostics;
1212
use rustc_session::parse::{feature_err, ParseSess};
13-
use rustc_session::Session;
13+
use rustc_session::{RustcVersion, Session};
1414
use rustc_span::hygiene::Transparency;
1515
use rustc_span::{symbol::sym, symbol::Symbol, Span};
16-
use std::fmt::{self, Display};
1716
use std::num::NonZeroU32;
1817

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

27-
pub const CURRENT_RUSTC_VERSION: &str = env!("CFG_RELEASE");
28-
2926
pub fn is_builtin_attr(attr: &Attribute) -> bool {
3027
attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
3128
}
@@ -153,7 +150,7 @@ pub enum StabilityLevel {
153150
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
154151
#[derive(HashStable_Generic)]
155152
pub enum Since {
156-
Version(Version),
153+
Version(RustcVersion),
157154
/// Stabilized in the upcoming version, whatever number that is.
158155
Current,
159156
/// Failed to parse a stabilization version.
@@ -382,7 +379,7 @@ fn parse_stability(sess: &Session, attr: &Attribute) -> Option<(Symbol, Stabilit
382379
let since = if let Some(since) = since {
383380
if since.as_str() == VERSION_PLACEHOLDER {
384381
Since::Current
385-
} else if let Some(version) = parse_version(since.as_str(), false) {
382+
} else if let Some(version) = parse_version(since) {
386383
Since::Version(version)
387384
} else {
388385
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
567564
}
568565
}
569566

570-
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
571-
#[derive(HashStable_Generic)]
572-
pub struct Version {
573-
pub major: u16,
574-
pub minor: u16,
575-
pub patch: u16,
576-
}
577-
578-
fn parse_version(s: &str, allow_appendix: bool) -> Option<Version> {
579-
let mut components = s.split('-');
567+
/// Parse a rustc version number written inside string literal in an attribute,
568+
/// like appears in `since = "1.0.0"`. Suffixes like "-dev" and "-nightly" are
569+
/// not accepted in this position, unlike when parsing CFG_RELEASE.
570+
fn parse_version(s: Symbol) -> Option<RustcVersion> {
571+
let mut components = s.as_str().split('-');
580572
let d = components.next()?;
581-
if !allow_appendix && components.next().is_some() {
573+
if components.next().is_some() {
582574
return None;
583575
}
584576
let mut digits = d.splitn(3, '.');
585577
let major = digits.next()?.parse().ok()?;
586578
let minor = digits.next()?.parse().ok()?;
587579
let patch = digits.next().unwrap_or("0").parse().ok()?;
588-
Some(Version { major, minor, patch })
589-
}
590-
591-
impl Display for Version {
592-
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
593-
write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
594-
}
580+
Some(RustcVersion { major, minor, patch })
595581
}
596582

597583
/// Evaluate a cfg-like condition (with `any` and `all`), using `eval` to
@@ -623,17 +609,16 @@ pub fn eval_condition(
623609
return false;
624610
}
625611
};
626-
let Some(min_version) = parse_version(min_version.as_str(), false) else {
612+
let Some(min_version) = parse_version(*min_version) else {
627613
sess.emit_warning(session_diagnostics::UnknownVersionLiteral { span: *span });
628614
return false;
629615
};
630-
let rustc_version = parse_version(CURRENT_RUSTC_VERSION, true).unwrap();
631616

632617
// See https://github.com/rust-lang/rust/issues/64796#issuecomment-640851454 for details
633618
if sess.assume_incomplete_release {
634-
rustc_version > min_version
619+
RustcVersion::CURRENT > min_version
635620
} else {
636-
rustc_version >= min_version
621+
RustcVersion::CURRENT >= min_version
637622
}
638623
}
639624
ast::MetaItemKind::List(mis) => {

compiler/rustc_attr/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ pub use StabilityLevel::*;
2727

2828
pub use rustc_ast::attr::*;
2929

30-
pub(crate) use rustc_ast::HashStableContext;
30+
pub(crate) use rustc_session::HashStableContext;
3131

3232
fluent_messages! { "../messages.ftl" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::Span;
3+
use quote::quote;
4+
use syn::parse::{Parse, ParseStream};
5+
use syn::{parenthesized, parse_macro_input, LitStr, Token};
6+
7+
pub struct Input {
8+
variable: LitStr,
9+
}
10+
11+
mod kw {
12+
syn::custom_keyword!(env);
13+
}
14+
15+
impl Parse for Input {
16+
// Input syntax is `env!("CFG_RELEASE")` to facilitate grepping.
17+
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
18+
let paren;
19+
input.parse::<kw::env>()?;
20+
input.parse::<Token![!]>()?;
21+
parenthesized!(paren in input);
22+
let variable: LitStr = paren.parse()?;
23+
Ok(Input { variable })
24+
}
25+
}
26+
27+
pub(crate) fn current_version(input: TokenStream) -> TokenStream {
28+
let input = parse_macro_input!(input as Input);
29+
30+
TokenStream::from(match RustcVersion::parse_env_var(&input.variable) {
31+
Ok(RustcVersion { major, minor, patch }) => quote!(
32+
Self { major: #major, minor: #minor, patch: #patch }
33+
),
34+
Err(err) => syn::Error::new(Span::call_site(), err).into_compile_error(),
35+
})
36+
}
37+
38+
struct RustcVersion {
39+
major: u16,
40+
minor: u16,
41+
patch: u16,
42+
}
43+
44+
impl RustcVersion {
45+
fn parse_env_var(env_var: &LitStr) -> Result<Self, Box<dyn std::error::Error>> {
46+
let value = proc_macro::tracked_env::var(env_var.value())?;
47+
Self::parse_str(&value)
48+
.ok_or_else(|| format!("failed to parse rustc version: {:?}", value).into())
49+
}
50+
51+
fn parse_str(value: &str) -> Option<Self> {
52+
// Ignore any suffixes such as "-dev" or "-nightly".
53+
let mut components = value.split('-').next().unwrap().splitn(3, '.');
54+
let major = components.next()?.parse().ok()?;
55+
let minor = components.next()?.parse().ok()?;
56+
let patch = components.next().unwrap_or("0").parse().ok()?;
57+
Some(RustcVersion { major, minor, patch })
58+
}
59+
}

compiler/rustc_macros/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use synstructure::decl_derive;
1515

1616
use proc_macro::TokenStream;
1717

18+
mod current_version;
1819
mod diagnostics;
1920
mod hash_stable;
2021
mod lift;
@@ -25,6 +26,11 @@ mod symbols;
2526
mod type_foldable;
2627
mod type_visitable;
2728

29+
#[proc_macro]
30+
pub fn current_rustc_version(input: TokenStream) -> TokenStream {
31+
current_version::current_version(input)
32+
}
33+
2834
#[proc_macro]
2935
pub fn rustc_queries(input: TokenStream) -> TokenStream {
3036
query::rustc_queries(input)

compiler/rustc_middle/src/middle/stability.rs

+12-15
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_middle::ty::print::with_no_trimmed_paths;
1616
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
1717
use rustc_session::lint::{BuiltinLintDiagnostics, Level, Lint, LintBuffer};
1818
use rustc_session::parse::feature_err_issue;
19-
use rustc_session::Session;
19+
use rustc_session::{RustcVersion, Session};
2020
use rustc_span::symbol::{sym, Symbol};
2121
use rustc_span::Span;
2222
use std::num::NonZeroU32;
@@ -129,11 +129,6 @@ pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
129129
let is_since_rustc_version = depr.is_since_rustc_version;
130130
let since = depr.since.as_ref().map(Symbol::as_str);
131131

132-
fn parse_version(ver: &str) -> Vec<u32> {
133-
// We ignore non-integer components of the version (e.g., "nightly").
134-
ver.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect()
135-
}
136-
137132
if !is_since_rustc_version {
138133
// The `since` field doesn't have semantic purpose without `#![staged_api]`.
139134
return true;
@@ -144,16 +139,18 @@ pub fn deprecation_in_effect(depr: &Deprecation) -> bool {
144139
return false;
145140
}
146141

147-
if let Some(rustc) = option_env!("CFG_RELEASE") {
148-
let since: Vec<u32> = parse_version(&since);
149-
let rustc: Vec<u32> = parse_version(rustc);
150-
// We simply treat invalid `since` attributes as relating to a previous
151-
// Rust version, thus always displaying the warning.
152-
if since.len() != 3 {
153-
return true;
154-
}
155-
return since <= rustc;
142+
// We ignore non-integer components of the version (e.g., "nightly").
143+
let since: Vec<u16> =
144+
since.split(|c| c == '.' || c == '-').flat_map(|s| s.parse()).collect();
145+
146+
// We simply treat invalid `since` attributes as relating to a previous
147+
// Rust version, thus always displaying the warning.
148+
if since.len() != 3 {
149+
return true;
156150
}
151+
152+
let rustc = RustcVersion::CURRENT;
153+
return since.as_slice() <= &[rustc.major, rustc.minor, rustc.patch];
157154
};
158155

159156
// Assume deprecation is in effect if "since" field is missing

compiler/rustc_session/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub mod output;
4343

4444
pub use getopts;
4545

46+
mod version;
47+
pub use version::RustcVersion;
48+
4649
fluent_messages! { "../messages.ftl" }
4750

4851
/// Requirements for a `StableHashingContext` to be used in this crate.

compiler/rustc_session/src/version.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::fmt::{self, Display};
2+
3+
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
4+
#[derive(HashStable_Generic)]
5+
pub struct RustcVersion {
6+
pub major: u16,
7+
pub minor: u16,
8+
pub patch: u16,
9+
}
10+
11+
impl RustcVersion {
12+
pub const CURRENT: Self = current_rustc_version!(env!("CFG_RELEASE"));
13+
}
14+
15+
impl Display for RustcVersion {
16+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
17+
write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
18+
}
19+
}

src/librustdoc/html/render/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,14 @@ use std::str;
4848
use std::string::ToString;
4949

5050
use askama::Template;
51-
use rustc_attr::{ConstStability, Deprecation, Since, StabilityLevel, CURRENT_RUSTC_VERSION};
51+
use rustc_attr::{ConstStability, Deprecation, Since, StabilityLevel};
5252
use rustc_data_structures::captures::Captures;
5353
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5454
use rustc_hir::def_id::{DefId, DefIdSet};
5555
use rustc_hir::Mutability;
5656
use rustc_middle::middle::stability;
5757
use rustc_middle::ty::{self, TyCtxt};
58+
use rustc_session::RustcVersion;
5859
use rustc_span::{
5960
symbol::{sym, Symbol},
6061
BytePos, FileName, RealFileName,
@@ -979,7 +980,7 @@ fn render_stability_since_raw_with_extra(
979980
fn since_to_string(since: &Since) -> Option<String> {
980981
match since {
981982
Since::Version(since) => Some(since.to_string()),
982-
Since::Current => Some(CURRENT_RUSTC_VERSION.to_owned()),
983+
Since::Current => Some(RustcVersion::CURRENT.to_string()),
983984
Since::Err => None,
984985
}
985986
}

src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs

+8-15
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

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

374374
let const_stab_rust_version = match since {
375-
Since::Version(version) => RustcVersion::new(
376-
u32::from(version.major),
377-
u32::from(version.minor),
378-
u32::from(version.patch),
379-
),
380-
Since::Current => {
381-
// HACK(nilstrieb): CURRENT_RUSTC_VERSION can return versions like 1.66.0-dev.
382-
// `rustc-semver` doesn't accept the `-dev` version number so we have to strip it off.
383-
let short_version = CURRENT_RUSTC_VERSION.split('-').next().unwrap();
384-
RustcVersion::parse(short_version).unwrap_or_else(|err| {
385-
panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{CURRENT_RUSTC_VERSION}`, {err:?}")
386-
})
387-
},
375+
Since::Version(version) => version,
376+
Since::Current => rustc_session::RustcVersion::CURRENT,
388377
Since::Err => return false,
389378
};
390379

391-
msrv.meets(const_stab_rust_version)
380+
msrv.meets(RustcVersion::new(
381+
u32::from(const_stab_rust_version.major),
382+
u32::from(const_stab_rust_version.minor),
383+
u32::from(const_stab_rust_version.patch),
384+
))
392385
} else {
393386
// Unstable const fn with the feature enabled.
394387
msrv.current().is_none()

0 commit comments

Comments
 (0)