diff --git a/Cargo.lock b/Cargo.lock index e4dcf13a84b73..9efd393270d38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3596,6 +3596,7 @@ dependencies = [ "rustc_target", "rustc_trait_selection", "rustc_ty_utils", + "serde", "serde_json", "shlex", "time", @@ -4012,6 +4013,7 @@ dependencies = [ "rustc_span", "rustc_target", "rustc_type_ir", + "serde", "tempfile", "tracing", ] diff --git a/compiler/rustc_driver_impl/Cargo.toml b/compiler/rustc_driver_impl/Cargo.toml index 81f15ebcbf80a..9d37e96460850 100644 --- a/compiler/rustc_driver_impl/Cargo.toml +++ b/compiler/rustc_driver_impl/Cargo.toml @@ -47,6 +47,7 @@ rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_ty_utils = { path = "../rustc_ty_utils" } +serde = { version = "1.0.125", features = [ "derive" ] } serde_json = "1.0.59" shlex = "1.0" time = { version = "0.3.36", default-features = false, features = ["alloc", "formatting", "macros"] } diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index c270ce16726d1..abfd91642fb0f 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -21,10 +21,11 @@ use std::cmp::max; use std::collections::BTreeMap; +use std::error::Error; use std::ffi::OsString; use std::fmt::Write as _; use std::fs::{self, File}; -use std::io::{self, IsTerminal, Read, Write}; +use std::io::{self, BufWriter, IsTerminal, Read, Write, stdout}; use std::panic::{self, PanicHookInfo, catch_unwind}; use std::path::PathBuf; use std::process::{self, Command, Stdio}; @@ -45,7 +46,7 @@ use rustc_errors::registry::Registry; use rustc_errors::{ ColorConfig, DiagCtxt, ErrCode, ErrorGuaranteed, FatalError, PResult, markdown, }; -use rustc_feature::find_gated_cfg; +use rustc_feature::{LangFeaturesStatus, find_gated_cfg}; use rustc_interface::util::{self, get_codegen_backend}; use rustc_interface::{Linker, Queries, interface, passes}; use rustc_lint::unerased_lint_store; @@ -377,6 +378,11 @@ fn run_compiler( return early_exit(); } + if sess.opts.unstable_opts.dump_features_status { + dump_features_status(sess, codegen_backend); + return early_exit(); + } + if !has_input { #[allow(rustc::diagnostic_outside_of_impl)] sess.dcx().fatal("no input filename given"); // this is fatal @@ -1571,6 +1577,30 @@ fn report_ice( } } +fn dump_features_status(sess: &Session, codegen_backend: &dyn CodegenBackend) { + #[derive(serde::Serialize)] + struct FeaturesStatus { + lang_features_status: LangFeaturesStatus, + lib_features_status: locator::LibFeaturesStatus, + } + + let result: Result<(), Box> = try { + let lang_features_status = rustc_feature::lang_features_status(); + let lib_features_status = + rustc_metadata::lib_features_status(sess, &*codegen_backend.metadata_loader())?; + let value = FeaturesStatus { lang_features_status, lib_features_status }; + let writer = stdout(); + let writer = BufWriter::new(writer); + serde_json::to_writer_pretty(writer, &value)?; + }; + + // this happens before the global context and more importantly the diagnostic context is setup, + // so we can't report with the proper machinery + if let Err(error) = result { + panic!("cannot emit feature status json: {error}") + } +} + /// This allows tools to enable rust logging without having to magically match rustc's /// tracing crate version. pub fn init_rustc_env_logger(early_dcx: &EarlyDiagCtxt) { diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 5d27b8f542cbb..d9b1759654b21 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -132,6 +132,62 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option>, + }, + Accepted { + symbol: String, + since: &'static str, + issue: Option>, + }, + Removed { + symbol: String, + since: &'static str, + issue: Option>, + reason: Option<&'static str>, + }, +} + +#[derive(serde::Serialize)] +pub struct LangFeaturesStatus { + lang_features: Vec, +} + +/// Extract the status of all lang features as a json serializable struct +pub fn lang_features_status() -> LangFeaturesStatus { + let unstable_lang_features = + UNSTABLE_LANG_FEATURES.iter().map(|feature| LangFeature::Unstable { + status: Features::feature_status(feature.name), + symbol: feature.name.to_string(), + since: feature.since, + issue: feature.issue, + }); + + let accepted_lang_features = + ACCEPTED_LANG_FEATURES.iter().map(|feature| LangFeature::Accepted { + symbol: feature.name.to_string(), + since: feature.since, + issue: feature.issue, + }); + + let removed_lang_features = REMOVED_LANG_FEATURES.iter().map(|removed| LangFeature::Removed { + symbol: removed.feature.name.to_string(), + since: removed.feature.since, + issue: removed.feature.issue, + reason: removed.reason, + }); + + let lang_features = + unstable_lang_features.chain(accepted_lang_features).chain(removed_lang_features).collect(); + + LangFeaturesStatus { lang_features } +} + pub use accepted::ACCEPTED_LANG_FEATURES; pub use builtin_attrs::{ AttributeDuplicates, AttributeGate, AttributeSafety, AttributeTemplate, AttributeType, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index e3dc73c14014f..2758fd0e6258f 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -99,6 +99,18 @@ impl Features { } } +macro_rules! status_to_str { + (unstable) => { + "default" + }; + (incomplete) => { + "incomplete" + }; + (internal) => { + "internal" + }; +} + macro_rules! declare_features { ($( $(#[doc = $doc:tt])* ($status:ident, $feature:ident, $ver:expr, $issue:expr), @@ -159,6 +171,15 @@ macro_rules! declare_features { _ => panic!("`{}` was not listed in `declare_features`", feature), } } + + pub fn feature_status(feature: Symbol) -> &'static str { + match feature { + $( + sym::$feature => status_to_str!($status), + )* + _ => panic!("`{}` was not listed in `declare_features`", feature), + } + } } }; } diff --git a/compiler/rustc_metadata/Cargo.toml b/compiler/rustc_metadata/Cargo.toml index 12519be9870b2..b431346a7a946 100644 --- a/compiler/rustc_metadata/Cargo.toml +++ b/compiler/rustc_metadata/Cargo.toml @@ -28,6 +28,7 @@ rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } rustc_type_ir = { path = "../rustc_type_ir" } +serde = { version = "1.0.125", features = [ "derive" ] } tempfile = "3.2" tracing = "0.1" # tidy-alphabetical-end diff --git a/compiler/rustc_metadata/src/lib.rs b/compiler/rustc_metadata/src/lib.rs index b817b4cb7b83c..f20e3f352b3cc 100644 --- a/compiler/rustc_metadata/src/lib.rs +++ b/compiler/rustc_metadata/src/lib.rs @@ -5,6 +5,7 @@ #![feature(coroutines)] #![feature(decl_macro)] #![feature(error_iter)] +#![feature(error_reporter)] #![feature(extract_if)] #![feature(file_buffered)] #![feature(if_let_guard)] @@ -35,6 +36,7 @@ pub mod locator; pub use creader::{DylibError, load_symbol_from_dylib}; pub use fs::{METADATA_FILENAME, emit_wrapper_file}; +pub use locator::lib_features_status; pub use native_libs::{ find_native_static_library, try_find_native_dynamic_library, try_find_native_static_library, walk_native_lib_search_dirs, diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index d59ec7af6ecc0..43a0e8d49d7b3 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -213,6 +213,7 @@ //! metadata::locator or metadata::creader for all the juicy details! use std::borrow::Cow; +use std::error::Error; use std::io::{Result as IoResult, Write}; use std::ops::Deref; use std::path::{Path, PathBuf}; @@ -224,6 +225,7 @@ use rustc_data_structures::owned_slice::slice_owned; use rustc_data_structures::svh::Svh; use rustc_errors::{DiagArgValue, IntoDiagArg}; use rustc_fs_util::try_canonicalize; +use rustc_middle::middle::lib_features::FeatureStability; use rustc_session::Session; use rustc_session::cstore::CrateSource; use rustc_session::filesearch::FileSearch; @@ -864,6 +866,88 @@ fn get_metadata_section<'p>( } } +#[derive(serde::Serialize)] +enum LibFeature { + Core { name: String, status: Status }, + Std { name: String, status: Status }, +} + +#[derive(serde::Serialize)] +enum Status { + AcceptedSince(String), + Unstable, +} + +#[derive(serde::Serialize)] +pub struct LibFeaturesStatus { + lib_features: Vec, +} + +pub fn lib_features_status( + sess: &Session, + metadata_loader: &dyn MetadataLoader, +) -> Result> { + let is_rlib = true; + let hash = None; + let extra_filename = None; + let path_kind = PathKind::Crate; + let extra_prefix = ""; + + let mut core_locator = CrateLocator::new( + sess, + metadata_loader, + rustc_span::sym::core, + is_rlib, + hash, + extra_filename, + path_kind, + ); + let Ok(Some(core)) = core_locator.find_library_crate(extra_prefix, &mut FxHashSet::default()) + else { + Err("unable to locate core library")? + }; + let mut std_locator = CrateLocator::new( + sess, + metadata_loader, + rustc_span::sym::std, + is_rlib, + hash, + extra_filename, + path_kind, + ); + let Ok(Some(std)) = std_locator.find_library_crate(extra_prefix, &mut FxHashSet::default()) + else { + Err("unable to locate standard library")? + }; + + let core_features = + core.metadata.dump_crate_features_json().into_iter().map(|(name, status)| { + LibFeature::Core { + name, + status: match status { + FeatureStability::AcceptedSince(symbol) => { + Status::AcceptedSince(symbol.to_string()) + } + FeatureStability::Unstable => Status::Unstable, + }, + } + }); + let std_features = + std.metadata.dump_crate_features_json().into_iter().map(|(name, status)| LibFeature::Std { + name, + status: match status { + FeatureStability::AcceptedSince(symbol) => { + Status::AcceptedSince(symbol.to_string()) + } + FeatureStability::Unstable => Status::Unstable, + }, + }); + + let lib_features = core_features.chain(std_features).collect(); + + Ok(LibFeaturesStatus { lib_features }) +} + /// A diagnostic function for dumping crate metadata to an output stream. pub fn list_file_metadata( target: &Target, diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 87357b74c411e..d3bc7d76e2375 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -935,6 +935,14 @@ impl MetadataBlob { Ok(()) } + + pub(crate) fn dump_crate_features_json(&self) -> Vec<(String, FeatureStability)> { + let root = self.get_root(); + root.lib_features + .decode(self) + .map(|(symbol, status)| (symbol.to_string(), status)) + .collect() + } } impl CrateRoot { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index cbfe5d22f1dbc..1a8d21011c734 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1730,6 +1730,8 @@ options! { dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ (default: no)"), + dump_features_status: bool = (false, parse_bool, [UNTRACKED], + "dump the status of rust language and library features as json"), dump_mir: Option = (None, parse_opt_string, [UNTRACKED], "dump MIR state to file. `val` is used to select which passes and functions to dump. For example: