From f9679fa4df238fff9060178ccca9adcb6a795a7d Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 8 Jul 2026 19:07:34 +0000 Subject: [PATCH 01/19] Split IncrCompSession out of Session This will allow introducing a separate incr comp session dir for the post LTO artifacts in the future. In addition it statically encodes the lifetime of the incr comp session rather than requiring an enum behind a mutex stored in the Session. --- compiler/rustc_codegen_cranelift/src/lib.rs | 5 +- compiler/rustc_codegen_gcc/src/lib.rs | 5 +- compiler/rustc_codegen_llvm/src/lib.rs | 5 +- compiler/rustc_codegen_ssa/src/back/write.rs | 25 ++++- .../rustc_codegen_ssa/src/traits/backend.rs | 3 +- compiler/rustc_driver_impl/src/lib.rs | 4 +- compiler/rustc_incremental/src/persist/fs.rs | 48 ++++---- .../rustc_incremental/src/persist/load.rs | 105 ++++++++++-------- .../rustc_incremental/src/persist/save.rs | 24 ++-- .../src/persist/work_product.rs | 13 ++- compiler/rustc_interface/src/passes.rs | 16 ++- compiler/rustc_interface/src/queries.rs | 20 +++- compiler/rustc_interface/src/util.rs | 3 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- compiler/rustc_middle/src/ty/context.rs | 5 +- compiler/rustc_session/src/session.rs | 65 ++--------- src/librustdoc/doctest.rs | 55 ++++----- src/librustdoc/lib.rs | 33 +++--- .../codegen-backend/auxiliary/the_backend.rs | 3 +- tests/ui-fulldeps/run-compiler-twice.rs | 11 +- 20 files changed, 235 insertions(+), 215 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index ba586f83ba30d..8ee0e71d82dec 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -41,8 +41,8 @@ use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig, back}; use rustc_log::tracing::info; use rustc_middle::dep_graph::WorkProductMap; -use rustc_session::Session; use rustc_session::config::{NATIVE_CPU, OutputFilenames}; +use rustc_session::{IncrCompSession, Session}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{Arch, CfgAbi, Env, Os}; @@ -233,13 +233,14 @@ impl CodegenBackend for CraneliftCodegenBackend { &self, ongoing_codegen: Box, sess: &Session, + incr_comp_session: Option<&IncrCompSession>, _outputs: &OutputFilenames, crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { ongoing_codegen .downcast::>() .unwrap() - .join(sess, crate_info) + .join(sess, incr_comp_session, crate_info) } fn fallback_intrinsics(&self) -> Vec { diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 4cc4a2d258d14..c570f4e2165b2 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -94,8 +94,8 @@ use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; -use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames}; +use rustc_session::{IncrCompSession, Session}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{Arch, RelocModel}; use tempfile::TempDir; @@ -297,13 +297,14 @@ impl CodegenBackend for GccCodegenBackend { &self, ongoing_codegen: Box, sess: &Session, + incr_comp_session: Option<&IncrCompSession>, _outputs: &OutputFilenames, crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { ongoing_codegen .downcast::>() .expect("Expected GccCodegenBackend's OngoingCodegen, found Box") - .join(sess, crate_info) + .join(sess, incr_comp_session, crate_info) } fn target_config(&self, sess: &Session) -> TargetConfig { diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1dd460c409737..3c095d9e07ad0 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -38,8 +38,8 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; -use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest}; +use rustc_session::{IncrCompSession, Session}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{RelocModel, TlsModel}; @@ -379,13 +379,14 @@ impl CodegenBackend for LlvmCodegenBackend { &self, ongoing_codegen: Box, sess: &Session, + incr_comp_session: Option<&IncrCompSession>, outputs: &OutputFilenames, crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { let (compiled_modules, work_products) = ongoing_codegen .downcast::>() .expect("Expected LlvmCodegenBackend's OngoingCodegen, found Box") - .join(sess, crate_info); + .join(sess, incr_comp_session, crate_info); if sess.opts.unstable_opts.llvm_time_trace { sess.time("llvm_dump_timing_file", || { diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 1db2321f7b249..750a4e8c3bde6 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -21,11 +21,11 @@ use rustc_metadata::fs::copy_to_stdout; use rustc_middle::bug; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use rustc_session::config::{ self, CrateType, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, SwitchWithOptPath, }; +use rustc_session::{IncrCompSession, Session}; use rustc_span::source_map::SourceMap; use rustc_span::{FileName, InnerSpan, Span, SpanData}; use rustc_target::spec::{MergeFunctions, SanitizerSet}; @@ -461,6 +461,7 @@ pub(crate) fn start_async_codegen( fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, + incr_comp_session: Option<&IncrCompSession>, compiled_modules: &CompiledModules, ) -> WorkProductMap { let mut work_products = WorkProductMap::default(); @@ -494,6 +495,7 @@ fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( } let (id, product) = copy_cgu_workproduct_to_incr_comp_cache_dir( sess, + incr_comp_session.unwrap(), &module.name, files.as_slice(), &module.links_from_incr_cache, @@ -1286,7 +1288,10 @@ fn start_executing_work( time_trace: sess.opts.unstable_opts.llvm_time_trace, remark: sess.opts.cg.remark.clone(), remark_dir, - incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()), + incr_comp_session_dir: tcx + .incr_comp_session + .as_ref() + .map(|incr_comp_session| incr_comp_session.session_directory.clone()), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, opt_level, @@ -2118,7 +2123,12 @@ pub struct OngoingCodegen { } impl OngoingCodegen { - pub fn join(self, sess: &Session, crate_info: &CrateInfo) -> (CompiledModules, WorkProductMap) { + pub fn join( + self, + sess: &Session, + incr_comp_session: Option<&IncrCompSession>, + crate_info: &CrateInfo, + ) -> (CompiledModules, WorkProductMap) { self.shared_emitter_main.check(sess, true); let maybe_lto_modules = sess.time("join_worker_thread", || match self.coordinator.join() { @@ -2196,8 +2206,11 @@ impl OngoingCodegen { // out deterministic results. compiled_modules.modules.sort_by(|a, b| a.name.cmp(&b.name)); - let work_products = - copy_all_cgu_workproducts_to_incr_comp_cache_dir(sess, &compiled_modules); + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( + sess, + incr_comp_session, + &compiled_modules, + ); produce_final_output_artifacts(sess, &compiled_modules, &self.output_filenames); (compiled_modules, work_products) @@ -2249,7 +2262,7 @@ pub(crate) fn submit_pre_lto_module_to_llvm( module: CachedModuleCodegen, ) { let filename = pre_lto_bitcode_filename(&module.name); - let bitcode_path = in_incr_comp_dir_sess(tcx.sess, &filename); + let bitcode_path = in_incr_comp_dir_sess(tcx.incr_comp_session.unwrap(), &filename); // Schedule the module to be loaded drop( coordinator diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 6014f1af4bfc3..85882af9e7cd0 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -8,8 +8,8 @@ use rustc_metadata::creader::MetadataLoaderDyn; use rustc_middle::dep_graph::WorkProductMap; use rustc_middle::ty::TyCtxt; use rustc_middle::util::Providers; -use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, PrintRequest}; +use rustc_session::{IncrCompSession, Session}; use rustc_span::Symbol; use super::CodegenObject; @@ -127,6 +127,7 @@ pub trait CodegenBackend { &self, ongoing_codegen: Box, sess: &Session, + incr_comp_session: Option<&IncrCompSession>, outputs: &OutputFilenames, crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap); diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 4411ecb4f128b..6274397fe2b6a 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -336,8 +336,8 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send)) // Linking is done outside the `compiler.enter()` so that the // `GlobalCtxt` within `Queries` can be freed as early as possible. - if let Some(linker) = linker { - linker.link(sess, codegen_backend); + if let (Some(linker), incr_comp_session) = linker { + linker.link(sess, incr_comp_session, codegen_backend); } }) } diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index c40aa49c29d11..de543ef0c53bc 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -116,7 +116,7 @@ use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_data_structures::{base_n, flock}; use rustc_fs_util::{LinkOrCopy, link_or_copy, try_canonicalize}; use rustc_middle::bug; -use rustc_session::{Session, StableCrateId}; +use rustc_session::{IncrCompSession, Session, StableCrateId}; use rustc_span::Symbol; use tracing::debug; @@ -138,25 +138,25 @@ const QUERY_CACHE_FILENAME: &str = "query-cache.bin"; const INT_ENCODE_BASE: usize = base_n::CASE_INSENSITIVE; /// Returns the path to a session's dependency graph. -pub(crate) fn dep_graph_path(sess: &Session) -> PathBuf { - in_incr_comp_dir_sess(sess, DEP_GRAPH_FILENAME) +pub(crate) fn dep_graph_path(incr_comp_session: &IncrCompSession) -> PathBuf { + in_incr_comp_dir_sess(incr_comp_session, DEP_GRAPH_FILENAME) } /// Returns the path to a session's staging dependency graph. /// /// On the difference between dep-graph and staging dep-graph, /// see `build_dep_graph`. -pub(crate) fn staging_dep_graph_path(sess: &Session) -> PathBuf { - in_incr_comp_dir_sess(sess, STAGING_DEP_GRAPH_FILENAME) +pub(crate) fn staging_dep_graph_path(incr_comp_session: &IncrCompSession) -> PathBuf { + in_incr_comp_dir_sess(incr_comp_session, STAGING_DEP_GRAPH_FILENAME) } -pub(crate) fn work_products_path(sess: &Session) -> PathBuf { - in_incr_comp_dir_sess(sess, WORK_PRODUCTS_FILENAME) +pub(crate) fn work_products_path(incr_comp_session: &IncrCompSession) -> PathBuf { + in_incr_comp_dir_sess(incr_comp_session, WORK_PRODUCTS_FILENAME) } /// Returns the path to a session's query cache. -pub(crate) fn query_cache_path(sess: &Session) -> PathBuf { - in_incr_comp_dir_sess(sess, QUERY_CACHE_FILENAME) +pub(crate) fn query_cache_path(incr_comp_session: &IncrCompSession) -> PathBuf { + in_incr_comp_dir_sess(incr_comp_session, QUERY_CACHE_FILENAME) } /// Locks a given session directory. @@ -183,8 +183,8 @@ fn lock_file_path(session_dir: &Path) -> PathBuf { /// Returns the path for a given filename within the incremental compilation directory /// in the current session. -pub fn in_incr_comp_dir_sess(sess: &Session, file_name: &str) -> PathBuf { - sess.incr_comp_session_dir().join(file_name) +pub fn in_incr_comp_dir_sess(incr_comp_session: &IncrCompSession, file_name: &str) -> PathBuf { + incr_comp_session.session_directory.join(file_name) } /// Allocates the private session directory. @@ -206,7 +206,7 @@ pub(crate) fn prepare_session_directory( sess: &Session, crate_name: Symbol, stable_crate_id: StableCrateId, -) { +) -> IncrCompSession { assert!(sess.opts.incremental.is_some()); let _timer = sess.timer("incr_comp_prepare_session_directory"); @@ -257,8 +257,7 @@ pub(crate) fn prepare_session_directory( directory." ); - sess.init_incr_comp_session(session_dir, directory_lock); - return; + return IncrCompSession { session_directory: session_dir, _lock_file: directory_lock }; }; debug!("attempting to copy data from source: {}", source_directory.display()); @@ -271,8 +270,7 @@ pub(crate) fn prepare_session_directory( sess.dcx().emit_warn(diagnostics::HardLinkFailed { path: &session_dir }); } - sess.init_incr_comp_session(session_dir, directory_lock); - return; + return IncrCompSession { session_directory: session_dir, _lock_file: directory_lock }; } else { debug!("copying failed - trying next directory"); @@ -295,18 +293,23 @@ pub(crate) fn prepare_session_directory( /// This function finalizes and thus 'publishes' the session directory by /// renaming it to `s-{timestamp}-{svh}` and releasing the file lock. /// This must not be called if there have been any compilation errors. -pub fn finalize_session_directory(sess: &Session, svh: Option) { +pub fn finalize_session_directory( + sess: &Session, + incr_comp_session: Option, + svh: Option, +) { assert!(sess.dcx().has_errors_or_delayed_bugs().is_none()); if sess.opts.incremental.is_none() { return; } + let incr_comp_session = incr_comp_session.unwrap(); // The svh is always produced when incr. comp. is enabled. let svh = svh.unwrap(); let _timer = sess.timer("incr_comp_finalize_session_directory"); - let incr_comp_session_dir: PathBuf = sess.incr_comp_session_dir().clone(); + let incr_comp_session_dir = incr_comp_session.session_directory.clone(); debug!("finalize_session_directory() - session directory: {}", incr_comp_session_dir.display()); @@ -342,14 +345,15 @@ pub fn finalize_session_directory(sess: &Session, svh: Option) { } } - // This unlocks the directory - sess.finalize_incr_comp_session(); + drop(incr_comp_session); // Unlock incr comp session dir let _ = garbage_collect_session_directories(sess, &new_path); } -pub(crate) fn delete_all_session_dir_contents(sess: &Session) -> io::Result<()> { - let sess_dir_iterator = sess.incr_comp_session_dir().read_dir()?; +pub(crate) fn delete_all_session_dir_contents( + incr_comp_session: &IncrCompSession, +) -> io::Result<()> { + let sess_dir_iterator = incr_comp_session.session_directory.read_dir()?; for entry in sess_dir_iterator { let entry = entry?; safe_remove_file(&entry.path())? diff --git a/compiler/rustc_incremental/src/persist/load.rs b/compiler/rustc_incremental/src/persist/load.rs index 352ee59aaa0d4..3cf08961ed7b3 100644 --- a/compiler/rustc_incremental/src/persist/load.rs +++ b/compiler/rustc_incremental/src/persist/load.rs @@ -11,7 +11,7 @@ use rustc_middle::query::on_disk_cache::OnDiskCache; use rustc_serialize::opaque::{FileEncoder, MemDecoder}; use rustc_serialize::{Decodable, Encodable}; use rustc_session::config::IncrementalStateAssertion; -use rustc_session::{Session, StableCrateId}; +use rustc_session::{IncrCompSession, Session, StableCrateId}; use rustc_span::Symbol; use tracing::{debug, warn}; @@ -32,56 +32,55 @@ enum LoadResult { IoError { path: PathBuf, err: io::Error }, } -fn delete_dirty_work_product(sess: &Session, swp: SerializedWorkProduct) { +fn delete_dirty_work_product( + sess: &Session, + incr_comp_session: &IncrCompSession, + swp: SerializedWorkProduct, +) { debug!("delete_dirty_work_product({:?})", swp); - work_product::delete_workproduct_files(sess, &swp.work_product); + work_product::delete_workproduct_files(sess, incr_comp_session, &swp.work_product); } -fn load_dep_graph(sess: &Session) -> LoadResult { +fn load_dep_graph(sess: &Session, incr_comp_session: &IncrCompSession) -> LoadResult { assert!(sess.opts.incremental.is_some()); let _timer = sess.prof.generic_activity("incr_comp_prepare_load_dep_graph"); // Calling `sess.incr_comp_session_dir()` will panic if `sess.opts.incremental.is_none()`. // Fortunately, we just checked that this isn't the case. - let path = dep_graph_path(sess); + let path = dep_graph_path(incr_comp_session); let expected_hash = sess.opts.dep_tracking_hash(false); let mut prev_work_products = UnordMap::default(); - // If we are only building with -Zquery-dep-graph but without an actual - // incr. comp. session directory, we skip this. Otherwise we'd fail - // when trying to load work products. - if sess.incr_comp_session_dir_opt().is_some() { - let work_products_path = work_products_path(sess); - - if let Ok(OpenFile { mmap, start_pos }) = - file_format::open_incremental_file(sess, &work_products_path) - { - // Decode the list of work_products - let Ok(mut work_product_decoder) = MemDecoder::new(&mmap[..], start_pos) else { - sess.dcx().emit_warn(diagnostics::CorruptFile { path: &work_products_path }); - return LoadResult::DataOutOfDate; - }; - let work_products: Vec = - Decodable::decode(&mut work_product_decoder); - - for swp in work_products { - let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| { - let exists = in_incr_comp_dir_sess(sess, path).exists(); - if !exists && sess.opts.unstable_opts.incremental_info { - eprintln!("incremental: could not find file for work product: {path}",); - } - exists - }); - - if all_files_exist { - debug!("reconcile_work_products: all files for {:?} exist", swp); - prev_work_products.insert(swp.id, swp.work_product); - } else { - debug!("reconcile_work_products: some file for {:?} does not exist", swp); - delete_dirty_work_product(sess, swp); + let work_products_path = work_products_path(incr_comp_session); + + if let Ok(OpenFile { mmap, start_pos }) = + file_format::open_incremental_file(sess, &work_products_path) + { + // Decode the list of work_products + let Ok(mut work_product_decoder) = MemDecoder::new(&mmap[..], start_pos) else { + sess.dcx().emit_warn(diagnostics::CorruptFile { path: &work_products_path }); + return LoadResult::DataOutOfDate; + }; + let work_products: Vec = + Decodable::decode(&mut work_product_decoder); + + for swp in work_products { + let all_files_exist = swp.work_product.saved_files.items().all(|(_, path)| { + let exists = in_incr_comp_dir_sess(incr_comp_session, path).exists(); + if !exists && sess.opts.unstable_opts.incremental_info { + eprintln!("incremental: could not find file for work product: {path}",); } + exists + }); + + if all_files_exist { + debug!("reconcile_work_products: all files for {:?} exist", swp); + prev_work_products.insert(swp.id, swp.work_product); + } else { + debug!("reconcile_work_products: some file for {:?} does not exist", swp); + delete_dirty_work_product(sess, incr_comp_session, swp); } } } @@ -124,14 +123,18 @@ fn load_dep_graph(sess: &Session) -> LoadResult { /// If we are not in incremental compilation mode, returns `None`. /// Otherwise, tries to load the query result cache from disk, /// creating an empty cache if it could not be loaded. -pub fn load_query_result_cache(sess: &Session) -> Option { +pub fn load_query_result_cache( + sess: &Session, + incr_comp_session: Option<&IncrCompSession>, +) -> Option { if sess.opts.incremental.is_none() { return None; } + let incr_comp_session = incr_comp_session.unwrap(); let _prof_timer = sess.prof.generic_activity("incr_comp_load_query_result_cache"); - let path = query_cache_path(sess); + let path = query_cache_path(incr_comp_session); match file_format::open_incremental_file(sess, &path) { Ok(OpenFile { mmap, start_pos }) => { let cache = OnDiskCache::new(sess, mmap, start_pos).unwrap_or_else(|()| { @@ -181,18 +184,20 @@ pub fn setup_dep_graph( sess: &Session, crate_name: Symbol, stable_crate_id: StableCrateId, -) -> DepGraph { +) -> (DepGraph, Option) { if sess.opts.incremental.is_none() { - return DepGraph::new_disabled(); + return (DepGraph::new_disabled(), None); } // `load_dep_graph` can only be called after `prepare_session_directory`. - prepare_session_directory(sess, crate_name, stable_crate_id); + let incr_comp_session = prepare_session_directory(sess, crate_name, stable_crate_id); // Try to load the previous session's dep graph and work products. - let load_result = load_dep_graph(sess); + let load_result = load_dep_graph(sess, &incr_comp_session); sess.time("incr_comp_garbage_collect_session_directories", || { - if let Err(e) = garbage_collect_session_directories(sess, &sess.incr_comp_session_dir()) { + if let Err(e) = + garbage_collect_session_directories(sess, &incr_comp_session.session_directory) + { warn!( "Error while trying to garbage collect incremental compilation \ cache directory: {e}", @@ -209,9 +214,11 @@ pub fn setup_dep_graph( Default::default() } LoadResult::DataOutOfDate => { - if let Err(err) = delete_all_session_dir_contents(sess) { - sess.dcx() - .emit_err(diagnostics::DeleteIncompatible { path: dep_graph_path(sess), err }); + if let Err(err) = delete_all_session_dir_contents(&incr_comp_session) { + sess.dcx().emit_err(diagnostics::DeleteIncompatible { + path: dep_graph_path(&incr_comp_session), + err, + }); } Default::default() } @@ -219,7 +226,7 @@ pub fn setup_dep_graph( }; // Stream the dep-graph to an alternate file, to avoid overwriting anything in case of errors. - let path_buf = staging_dep_graph_path(sess); + let path_buf = staging_dep_graph_path(&incr_comp_session); let mut encoder = FileEncoder::new(&path_buf).unwrap_or_else(|err| { // We're in incremental mode but couldn't set up streaming output of the dep graph. @@ -232,5 +239,5 @@ pub fn setup_dep_graph( // First encode the commandline arguments hash sess.opts.dep_tracking_hash(false).encode(&mut encoder); - DepGraph::new(sess, prev_graph, prev_work_products, encoder) + (DepGraph::new(sess, prev_graph, prev_work_products, encoder), Some(incr_comp_session)) } diff --git a/compiler/rustc_incremental/src/persist/save.rs b/compiler/rustc_incremental/src/persist/save.rs index 544ab66766f39..12f674fe2a859 100644 --- a/compiler/rustc_incremental/src/persist/save.rs +++ b/compiler/rustc_incremental/src/persist/save.rs @@ -6,7 +6,7 @@ use rustc_middle::query::on_disk_cache; use rustc_middle::ty::TyCtxt; use rustc_serialize::Encodable as RustcEncodable; use rustc_serialize::opaque::FileEncoder; -use rustc_session::Session; +use rustc_session::{IncrCompSession, Session}; use tracing::debug; use super::data::*; @@ -34,9 +34,10 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) { return; } - let query_cache_path = query_cache_path(sess); - let dep_graph_path = dep_graph_path(sess); - let staging_dep_graph_path = staging_dep_graph_path(sess); + let incr_comp_session = tcx.incr_comp_session.unwrap(); + let query_cache_path = query_cache_path(incr_comp_session); + let dep_graph_path = dep_graph_path(incr_comp_session); + let staging_dep_graph_path = staging_dep_graph_path(incr_comp_session); sess.time("assert_dep_graph", || assert_dep_graph(tcx)); sess.time("check_clean", || clean::check_clean_annotations(tcx)); @@ -91,6 +92,7 @@ pub(crate) fn save_dep_graph(tcx: TyCtxt<'_>) { /// Saves the work product index. pub fn save_work_product_index( sess: &Session, + incr_comp_session: Option<&IncrCompSession>, dep_graph: &DepGraph, new_work_products: WorkProductMap, ) { @@ -104,7 +106,7 @@ pub fn save_work_product_index( debug!("save_work_product_index()"); dep_graph.assert_ignored(); - let path = work_products_path(sess); + let path = work_products_path(incr_comp_session.unwrap()); file_format::save_in(sess, path, "work product index", |mut e| { encode_work_product_index(&new_work_products, &mut e); e.finish() @@ -116,9 +118,13 @@ pub fn save_work_product_index( let previous_work_products = dep_graph.previous_work_products(); for (id, wp) in previous_work_products.to_sorted_stable_ord() { if !new_work_products.contains_key(id) { - work_product::delete_workproduct_files(sess, wp); + work_product::delete_workproduct_files(sess, incr_comp_session.unwrap(), wp); debug_assert!( - !wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) + !wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess( + incr_comp_session.unwrap(), + path + ) + .exists()) ); } } @@ -126,7 +132,9 @@ pub fn save_work_product_index( // Check that we did not delete one of the current work-products: debug_assert!({ new_work_products.items().all(|(_, wp)| { - wp.saved_files.items().all(|(_, path)| in_incr_comp_dir_sess(sess, path).exists()) + wp.saved_files + .items() + .all(|(_, path)| in_incr_comp_dir_sess(incr_comp_session.unwrap(), path).exists()) }) }); } diff --git a/compiler/rustc_incremental/src/persist/work_product.rs b/compiler/rustc_incremental/src/persist/work_product.rs index 910860bfafd6e..7bb66fee4d1a3 100644 --- a/compiler/rustc_incremental/src/persist/work_product.rs +++ b/compiler/rustc_incremental/src/persist/work_product.rs @@ -8,7 +8,7 @@ use std::path::{Path, PathBuf}; use rustc_data_structures::unord::UnordMap; use rustc_fs_util::link_or_copy; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; -use rustc_session::Session; +use rustc_session::{IncrCompSession, Session}; use tracing::debug; use crate::diagnostics; @@ -20,6 +20,7 @@ use crate::persist::fs::*; /// Panics when incr comp is disabled. pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( sess: &Session, + incr_comp_session: &IncrCompSession, cgu_name: &str, files: &[(&'static str, &Path)], known_links: &[PathBuf], @@ -30,7 +31,7 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( let mut saved_files = UnordMap::default(); for (ext, path) in files { let file_name = format!("{cgu_name}.{ext}"); - let path_in_incr_dir = in_incr_comp_dir_sess(sess, &file_name); + let path_in_incr_dir = in_incr_comp_dir_sess(incr_comp_session, &file_name); if known_links.contains(&path_in_incr_dir) { let _ = saved_files.insert(ext.to_string(), file_name); continue; @@ -56,9 +57,13 @@ pub fn copy_cgu_workproduct_to_incr_comp_cache_dir( } /// Removes files for a given work product. -pub(crate) fn delete_workproduct_files(sess: &Session, work_product: &WorkProduct) { +pub(crate) fn delete_workproduct_files( + sess: &Session, + incr_comp_session: &IncrCompSession, + work_product: &WorkProduct, +) { for (_, path) in work_product.saved_files.items().into_sorted_stable_ord() { - let path = in_incr_comp_dir_sess(sess, path); + let path = in_incr_comp_dir_sess(incr_comp_session, path); if let Err(err) = std_fs::remove_file(&path) { sess.dcx().emit_warn(diagnostics::DeleteWorkProduct { path: &path, err }); } diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 2f32a6b208b6c..498b59cf5be9f 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -35,12 +35,12 @@ use rustc_parse::lexer::StripTokens; use rustc_parse::{new_parser_from_file, new_parser_from_source_str, unwrap_or_emit_fatal}; use rustc_passes::{abi_test, input_stats, layout_test}; use rustc_resolve::{Resolver, ResolverOutputs}; -use rustc_session::Session; use rustc_session::config::{CrateType, Input, OutFileName, OutputFilenames, OutputType}; use rustc_session::cstore::Untracked; use rustc_session::diagnostics::feature_err; use rustc_session::output::{filename_for_input, invalid_output_for_target}; use rustc_session::search_paths::PathKind; +use rustc_session::{IncrCompSession, Session}; use rustc_span::{ DUMMY_SP, ErrorGuaranteed, ExpnKind, SourceFileHash, SourceFileHashAlgorithm, Span, Symbol, sym, }; @@ -929,7 +929,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( compiler: &Compiler, krate: rustc_ast::Crate, f: F, -) -> T { +) -> (T, Option) { let sess = &compiler.sess; let pre_configured_attrs = rustc_expand::config::pre_configure_attrs(sess, &krate.attrs); @@ -951,7 +951,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( let outputs = util::build_output_filenames(&pre_configured_attrs, sess); - let dep_graph = setup_dep_graph(sess, crate_name, stable_crate_id); + let (dep_graph, incr_comp_session) = setup_dep_graph(sess, crate_name, stable_crate_id); let cstore = FreezeLock::new(Box::new(CStore::new(compiler.codegen_backend.metadata_loader())) as _); @@ -966,7 +966,8 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( // incr. comp. yet. dep_graph.assert_ignored(); - let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(sess); + let query_result_on_disk_cache = + rustc_incremental::load_query_result_cache(sess, incr_comp_session.as_ref()); let codegen_backend = &compiler.codegen_backend; let mut providers = *DEFAULT_QUERY_PROVIDERS; @@ -993,7 +994,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( let arena = WorkerLocal::new(|_| Arena::default()); let hir_arena = WorkerLocal::new(|_| rustc_hir::Arena::default()); - TyCtxt::create_global_ctxt( + let res = TyCtxt::create_global_ctxt( &gcx_cell, &compiler.sess, crate_types, @@ -1001,6 +1002,7 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( &arena, &hir_arena, untracked, + incr_comp_session.as_ref(), dep_graph, rustc_query_impl::make_dep_kind_vtables(&arena), rustc_query_impl::query_system( @@ -1046,7 +1048,9 @@ pub fn create_and_enter_global_ctxt FnOnce(TyCtxt<'tcx>) -> T>( tcx.finish(); res }, - ) + ); + + (res, incr_comp_session) } struct DiagCallback<'tcx> { diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 24e033bdee088..490888f87b38e 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -9,8 +9,8 @@ use rustc_hir::def_id::LOCAL_CRATE; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{DepGraph, WorkProductMap}; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use rustc_session::config::{self, OutputFilenames, OutputType}; +use rustc_session::{IncrCompSession, Session}; use crate::diagnostics::FailedWritingFile; use crate::passes; @@ -46,7 +46,12 @@ impl Linker { } } - pub fn link(self, sess: &Session, codegen_backend: &dyn CodegenBackend) { + pub fn link( + self, + sess: &Session, + incr_comp_session: Option, + codegen_backend: &dyn CodegenBackend, + ) { let (compiled_modules, mut work_products) = sess.time("finish_ongoing_codegen", || { match self.ongoing_codegen.downcast::() { // This was a check only build @@ -55,6 +60,7 @@ impl Linker { Err(ongoing_codegen) => codegen_backend.join_codegen( ongoing_codegen, sess, + incr_comp_session.as_ref(), &self.output_filenames, &self.crate_info, ), @@ -92,6 +98,7 @@ impl Linker { { let (id, product) = rustc_incremental::copy_cgu_workproduct_to_incr_comp_cache_dir( sess, + incr_comp_session.as_ref().unwrap(), "metadata", &[("rmeta", path)], &[], @@ -106,7 +113,12 @@ impl Linker { let _timer = sess.timer("link"); sess.time("serialize_work_products", || { - rustc_incremental::save_work_product_index(sess, &self.dep_graph, work_products) + rustc_incremental::save_work_product_index( + sess, + incr_comp_session.as_ref(), + &self.dep_graph, + work_products, + ) }); let prof = sess.prof.clone(); @@ -114,7 +126,7 @@ impl Linker { // Now that we won't touch anything in the incremental compilation directory // any more, we can finalize it (which involves renaming it) - rustc_incremental::finalize_session_directory(sess, self.crate_hash); + rustc_incremental::finalize_session_directory(sess, incr_comp_session, self.crate_hash); if !sess .opts diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 39c5ee8193256..7b6166c8ac9c6 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -23,7 +23,7 @@ use rustc_query_impl::{CollectActiveJobsKind, collect_active_query_jobs}; use rustc_session::config::{ Cfg, CrateType, OutFileName, OutputFilenames, OutputTypes, Sysroot, host_tuple, }; -use rustc_session::{EarlyDiagCtxt, Session, filesearch}; +use rustc_session::{EarlyDiagCtxt, IncrCompSession, Session, filesearch}; use rustc_span::edition::Edition; use rustc_span::source_map::SourceMapInputs; use rustc_span::{SessionGlobals, Symbol, sym}; @@ -413,6 +413,7 @@ impl CodegenBackend for DummyCodegenBackend { &self, ongoing_codegen: Box, _sess: &Session, + _incr_comp_session: Option<&IncrCompSession>, _outputs: &OutputFilenames, _crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 8fa0c1b2dcdd8..c1dd65370d278 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2471,7 +2471,7 @@ pub fn encode_metadata(tcx: TyCtxt<'_>, path: &Path, ref_path: Option<&Path>) { && tcx.dep_graph.try_mark_green(tcx, &dep_node).is_some() { let saved_path = &work_product.saved_files["rmeta"]; - let incr_comp_session_dir = tcx.sess.incr_comp_session_dir(); + let incr_comp_session_dir = &tcx.incr_comp_session.unwrap().session_directory; let source_file_in_incr_dir = &incr_comp_session_dir.join(saved_path); debug!("copying preexisting metadata from {source_file_in_incr_dir:?} to {path:?}"); match rustc_fs_util::link_or_copy(&source_file_in_incr_dir, path) { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 4ae165cb015bd..92596787097a3 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -36,10 +36,10 @@ use rustc_hir::lang_items::LangItem; use rustc_hir::{self as hir, CRATE_HIR_ID, HirId, Node, TraitCandidate, find_attr}; use rustc_index::IndexVec; use rustc_macros::Diagnostic; -use rustc_session::Session; use rustc_session::config::CrateType; use rustc_session::cstore::{CrateStoreDyn, Untracked}; use rustc_session::lint::Lint; +use rustc_session::{IncrCompSession, Session}; use rustc_span::def_id::{CRATE_DEF_ID, DefPathHash, StableCrateId}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use rustc_type_ir::TyKind::*; @@ -712,6 +712,7 @@ pub struct GlobalCtxt<'tcx> { /// `rustc_symbol_mangling` crate for more information. stable_crate_id: StableCrateId, + pub incr_comp_session: Option<&'tcx IncrCompSession>, pub dep_graph: DepGraph, pub prof: SelfProfilerRef, @@ -935,6 +936,7 @@ impl<'tcx> TyCtxt<'tcx> { arena: &'tcx WorkerLocal>, hir_arena: &'tcx WorkerLocal>, untracked: Untracked, + incr_comp_session: Option<&'tcx IncrCompSession>, dep_graph: DepGraph, dep_kind_vtables: &'tcx [DepKindVTable<'tcx>], query_system: QuerySystem<'tcx>, @@ -957,6 +959,7 @@ impl<'tcx> TyCtxt<'tcx> { arena, hir_arena, interners, + incr_comp_session, dep_graph, hooks, prof: sess.prof.clone(), diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index eebead6fc1f47..975b09f84cb0d 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -7,9 +7,7 @@ use std::{env, io}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef}; -use rustc_data_structures::sync::{ - AppendOnlyVec, DynSend, DynSync, Lock, MappedReadGuard, ReadGuard, RwLock, -}; +use rustc_data_structures::sync::{AppendOnlyVec, DynSend, DynSync, Lock}; use rustc_data_structures::{Limit, flock}; use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; use rustc_errors::codes::*; @@ -341,8 +339,6 @@ pub struct Session { /// Input, input file path and output file path to this compilation process. pub io: CompilerIO, - incr_comp_session: RwLock, - /// Used by `-Z self-profile`. pub prof: SelfProfilerRef, @@ -688,45 +684,6 @@ impl Session { } } - pub fn init_incr_comp_session(&self, session_dir: PathBuf, lock_file: flock::Lock) { - let mut incr_comp_session = self.incr_comp_session.borrow_mut(); - - if let IncrCompSession::NotInitialized = *incr_comp_session { - } else { - panic!("Trying to initialize IncrCompSession `{:?}`", *incr_comp_session) - } - - *incr_comp_session = - IncrCompSession::Active { session_directory: session_dir, _lock_file: lock_file }; - } - - pub fn finalize_incr_comp_session(&self) { - let mut incr_comp_session = self.incr_comp_session.borrow_mut(); - - if let IncrCompSession::Active { .. } = *incr_comp_session { - } else { - panic!("trying to finalize `IncrCompSession` `{:?}`", *incr_comp_session); - } - - // Note: this will also drop the lock file, thus unlocking the directory. - *incr_comp_session = IncrCompSession::FinalizedOrRemoved; - } - - pub fn incr_comp_session_dir(&self) -> MappedReadGuard<'_, PathBuf> { - let incr_comp_session = self.incr_comp_session.borrow(); - ReadGuard::map(incr_comp_session, |incr_comp_session| match incr_comp_session { - IncrCompSession::NotInitialized | IncrCompSession::FinalizedOrRemoved => panic!( - "trying to get session directory from `IncrCompSession`: {:?}", - incr_comp_session, - ), - IncrCompSession::Active { session_directory, .. } => session_directory, - }) - } - - pub fn incr_comp_session_dir_opt(&self) -> Option> { - self.opts.incremental.as_ref().map(|_| self.incr_comp_session_dir()) - } - /// Is this edition 2015? pub fn is_rust_2015(&self) -> bool { self.edition().is_rust_2015() @@ -1355,7 +1312,6 @@ pub fn build_session( check_config: CheckCfg::default(), proc_macro_quoted_spans: Default::default(), io, - incr_comp_session: RwLock::new(IncrCompSession::NotInitialized), prof, timings, code_stats: Default::default(), @@ -1689,20 +1645,15 @@ fn validate_commandline_args_with_session_available(sess: &Session) { } /// Holds data on the current incremental compilation session, if there is one. -#[derive(Debug)] -enum IncrCompSession { - /// This is the state the session will be in until the incr. comp. dir is - /// needed. - NotInitialized, - /// This is the state during which the session directory is private and can - /// be modified. `_lock_file` is never directly used, but its presence +pub struct IncrCompSession { + /// The directory containing all cached data. Cached data from a previous + /// session can be read out of it and new data for the current session will + /// be written into it. + pub session_directory: PathBuf, + /// `_lock_file` is never directly used, but its presence /// alone has an effect, because the file will unlock when the session is /// dropped. - Active { session_directory: PathBuf, _lock_file: flock::Lock }, - /// This is the state after the session directory has been finalized or - /// removed after errors. In this state, the contents of the directory must - /// not be modified any more. - FinalizedOrRemoved, + pub _lock_file: flock::Lock, } /// A wrapper around an [`DiagCtxt`] that is used for early error emissions. diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 2b7f9c4dbb7fa..7ba409626ea89 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -217,34 +217,37 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions let result = interface::run_compiler(config, |compiler| { let krate = rustc_interface::passes::parse(&compiler.sess); - let collector = rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { - let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); - let opts = scrape_test_config(tcx, crate_name, args_path); - - let hir_collector = HirCollector::new( - ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()), - tcx, - ); - let tests = hir_collector.collect_crate(); - if extract_doctests { - let mut collector = extracted::ExtractedDocTests::new(); - tests.into_iter().for_each(|t| collector.add_test(t, &opts, &options)); - - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - if let Err(error) = serde_json::ser::to_writer(&mut stdout, &collector) { - eprintln!(); - Err(format!("Failed to generate JSON output for doctests: {error:?}")) + let (collector, _incr_comp_session) = + rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { + let crate_name = tcx.crate_name(LOCAL_CRATE).to_string(); + let opts = scrape_test_config(tcx, crate_name, args_path); + + let hir_collector = HirCollector::new( + ErrorCodes::from(compiler.sess.opts.unstable_features.is_nightly_build()), + tcx, + ); + let tests = hir_collector.collect_crate(); + if extract_doctests { + let mut collector = extracted::ExtractedDocTests::new(); + tests.into_iter().for_each(|t| collector.add_test(t, &opts, &options)); + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + if let Err(error) = serde_json::ser::to_writer(&mut stdout, &collector) { + eprintln!(); + Err(format!("Failed to generate JSON output for doctests: {error:?}")) + } else { + Ok(None) + } } else { - Ok(None) - } - } else { - let mut collector = CreateRunnableDocTests::new(options, opts); - tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx()))); + let mut collector = CreateRunnableDocTests::new(options, opts); + tests + .into_iter() + .for_each(|t| collector.add_test(t, Some(compiler.sess.dcx()))); - Ok(Some(collector)) - } - }); + Ok(Some(collector)) + } + }); compiler.sess.dcx().abort_if_errors(); collector diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index be830cad6c735..5fddb432edcd1 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -882,21 +882,24 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { id: ast::DUMMY_NODE_ID, is_placeholder: false, }; - rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { - let has_dep_info = render_options.dep_info().is_some(); - if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) { - markdown::render_and_write(file, render_options, edition)?; - } - if has_dep_info { - // Register the loaded external files in the source map so they show up in depinfo. - // We can't load them via the source map because it gets created after we process the options. - for external_path in &loaded_paths { - let _ = compiler.sess.source_map().load_binary_file(external_path); + let (res, _incr_comp_session) = + rustc_interface::create_and_enter_global_ctxt(compiler, krate, |tcx| { + let has_dep_info = render_options.dep_info().is_some(); + if render_options.emit.contains(&EmitType::HtmlNonStaticFiles) { + markdown::render_and_write(file, render_options, edition)?; } - rustc_interface::passes::write_dep_info(tcx); - } - Ok(()) - }) + if has_dep_info { + // Register the loaded external files in the source map so they show up in depinfo. + // We can't load them via the source map because it gets created after we process the options. + for external_path in &loaded_paths { + let _ = + compiler.sess.source_map().load_binary_file(external_path); + } + rustc_interface::passes::write_dep_info(tcx); + } + Ok(()) + }); + res }), ); } @@ -1005,7 +1008,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) { unreachable!() } } - }) + }); }) } diff --git a/tests/ui-fulldeps/codegen-backend/auxiliary/the_backend.rs b/tests/ui-fulldeps/codegen-backend/auxiliary/the_backend.rs index 610a4990a5a4b..5ddaed75aa323 100644 --- a/tests/ui-fulldeps/codegen-backend/auxiliary/the_backend.rs +++ b/tests/ui-fulldeps/codegen-backend/auxiliary/the_backend.rs @@ -16,8 +16,8 @@ use rustc_codegen_ssa::{CompiledModules, CrateInfo}; use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::WorkProductMap; use rustc_middle::ty::TyCtxt; -use rustc_session::Session; use rustc_session::config::OutputFilenames; +use rustc_session::{IncrCompSession, Session}; struct TheBackend; @@ -38,6 +38,7 @@ impl CodegenBackend for TheBackend { &self, ongoing_codegen: Box, _sess: &Session, + _incr_comp_session: Option<&IncrCompSession>, _outputs: &OutputFilenames, _crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap) { diff --git a/tests/ui-fulldeps/run-compiler-twice.rs b/tests/ui-fulldeps/run-compiler-twice.rs index d99d9c42d547f..ae0f41a205bf4 100644 --- a/tests/ui-fulldeps/run-compiler-twice.rs +++ b/tests/ui-fulldeps/run-compiler-twice.rs @@ -76,10 +76,11 @@ fn compile(code: String, output: PathBuf, sysroot: Sysroot, linker: Option<&Path interface::run_compiler(config, |compiler| { let krate = rustc_interface::passes::parse(&compiler.sess); - let linker = rustc_interface::create_and_enter_global_ctxt(&compiler, krate, |tcx| { - let _ = tcx.analysis(()); - Linker::codegen_and_build_linker(tcx, &*compiler.codegen_backend) - }); - linker.link(&compiler.sess, &*compiler.codegen_backend); + let (linker, incr_comp_session) = + rustc_interface::create_and_enter_global_ctxt(&compiler, krate, |tcx| { + let _ = tcx.analysis(()); + Linker::codegen_and_build_linker(tcx, &*compiler.codegen_backend) + }); + linker.link(&compiler.sess, incr_comp_session, &*compiler.codegen_backend); }); } From 7232830d10b6af772e0e4670a2ff61dd23830ed8 Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Fri, 3 Jul 2026 11:45:09 +0300 Subject: [PATCH 02/19] std: move futex implementations into sys::sync::futex Pure file moves; the module path repointing and platform gating follow in the next commit. Recorded in .git-blame-ignore-revs so blame skips the rename. --- library/std/src/sys/{pal/hermit/futex.rs => sync/futex/hermit.rs} | 0 library/std/src/sys/{pal/unix/futex.rs => sync/futex/unix.rs} | 0 .../sys/{pal/wasi/wasilibc_futex.rs => sync/futex/wasilibc.rs} | 0 .../std/src/sys/{pal/wasm/atomics/futex.rs => sync/futex/wasm.rs} | 0 .../std/src/sys/{pal/windows/futex.rs => sync/futex/windows.rs} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename library/std/src/sys/{pal/hermit/futex.rs => sync/futex/hermit.rs} (100%) rename library/std/src/sys/{pal/unix/futex.rs => sync/futex/unix.rs} (100%) rename library/std/src/sys/{pal/wasi/wasilibc_futex.rs => sync/futex/wasilibc.rs} (100%) rename library/std/src/sys/{pal/wasm/atomics/futex.rs => sync/futex/wasm.rs} (100%) rename library/std/src/sys/{pal/windows/futex.rs => sync/futex/windows.rs} (100%) diff --git a/library/std/src/sys/pal/hermit/futex.rs b/library/std/src/sys/sync/futex/hermit.rs similarity index 100% rename from library/std/src/sys/pal/hermit/futex.rs rename to library/std/src/sys/sync/futex/hermit.rs diff --git a/library/std/src/sys/pal/unix/futex.rs b/library/std/src/sys/sync/futex/unix.rs similarity index 100% rename from library/std/src/sys/pal/unix/futex.rs rename to library/std/src/sys/sync/futex/unix.rs diff --git a/library/std/src/sys/pal/wasi/wasilibc_futex.rs b/library/std/src/sys/sync/futex/wasilibc.rs similarity index 100% rename from library/std/src/sys/pal/wasi/wasilibc_futex.rs rename to library/std/src/sys/sync/futex/wasilibc.rs diff --git a/library/std/src/sys/pal/wasm/atomics/futex.rs b/library/std/src/sys/sync/futex/wasm.rs similarity index 100% rename from library/std/src/sys/pal/wasm/atomics/futex.rs rename to library/std/src/sys/sync/futex/wasm.rs diff --git a/library/std/src/sys/pal/windows/futex.rs b/library/std/src/sys/sync/futex/windows.rs similarity index 100% rename from library/std/src/sys/pal/windows/futex.rs rename to library/std/src/sys/sync/futex/windows.rs From 5b40f3d400ed3ebb3794b1c9911d3640267635fc Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Fri, 3 Jul 2026 11:59:56 +0300 Subject: [PATCH 03/19] std: connect sys::sync::futex and drop the pal declarations Select the platform implementation with a cfg_select! in sys::sync::futex, repoint each one at the pal primitives it uses (time, fuchsia, the windows api module, hermit_abi), and remove the now-unused futex declarations from the pal modules. The sync primitives import crate::sys::sync::futex rather than the crate::sys::futex glob re-export. --- .git-blame-ignore-revs | 3 ++ library/std/src/sys/pal/hermit/mod.rs | 1 - library/std/src/sys/pal/motor/mod.rs | 2 - library/std/src/sys/pal/unix/mod.rs | 1 - library/std/src/sys/pal/wasi/mod.rs | 18 --------- library/std/src/sys/pal/wasm/mod.rs | 4 -- library/std/src/sys/pal/windows/mod.rs | 2 - library/std/src/sys/sync/condvar/futex.rs | 2 +- library/std/src/sys/sync/futex/hermit.rs | 2 +- library/std/src/sys/sync/futex/mod.rs | 39 +++++++++++++++++++ library/std/src/sys/sync/futex/unix.rs | 20 +++------- library/std/src/sys/sync/futex/windows.rs | 2 +- library/std/src/sys/sync/mod.rs | 1 + library/std/src/sys/sync/mutex/futex.rs | 2 +- library/std/src/sys/sync/once/futex.rs | 2 +- library/std/src/sys/sync/rwlock/futex.rs | 2 +- .../std/src/sys/sync/thread_parking/futex.rs | 2 +- 17 files changed, 55 insertions(+), 50 deletions(-) create mode 100644 library/std/src/sys/sync/futex/mod.rs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index af071c706856e..4e2bef94982cc 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -33,3 +33,6 @@ c682aa162b0d41e21cc6748f4fecfe01efb69d1f 1fcae03369abb4c2cc180cd5a49e1f4440a81300 # Breaking up of compiletest runtest.rs 60600a6fa403216bfd66e04f948b1822f6450af7 + +# std: move futex implementations into sys::sync::futex +7232830d10b6af772e0e4670a2ff61dd23830ed8 diff --git a/library/std/src/sys/pal/hermit/mod.rs b/library/std/src/sys/pal/hermit/mod.rs index 53f6ddd7065d7..e8c9bf70b99df 100644 --- a/library/std/src/sys/pal/hermit/mod.rs +++ b/library/std/src/sys/pal/hermit/mod.rs @@ -21,7 +21,6 @@ use crate::os::hermit::hermit_abi; use crate::os::raw::c_char; use crate::sys::env; -pub mod futex; #[path = "../unix/time.rs"] pub mod time; diff --git a/library/std/src/sys/pal/motor/mod.rs b/library/std/src/sys/pal/motor/mod.rs index ac10d81ecfb89..5bf217db9013a 100644 --- a/library/std/src/sys/pal/motor/mod.rs +++ b/library/std/src/sys/pal/motor/mod.rs @@ -1,7 +1,5 @@ #![allow(unsafe_op_in_unsafe_fn)] -pub use moto_rt::futex; - use crate::io; pub(crate) fn map_motor_error(err: moto_rt::Error) -> io::Error { diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 2bd28ba498370..8fca169d93119 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -5,7 +5,6 @@ use crate::io; pub mod conf; #[cfg(target_os = "fuchsia")] pub mod fuchsia; -pub mod futex; pub mod stack_overflow; pub mod sync; pub mod thread_parking; diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs index 9069d0f0064a7..056f632ae0be2 100644 --- a/library/std/src/sys/pal/wasi/mod.rs +++ b/library/std/src/sys/pal/wasi/mod.rs @@ -11,24 +11,6 @@ pub mod stack_overflow; #[path = "../unix/time.rs"] pub mod time; -// The wasi-libc based futex is new enough that it's not present in older -// wasi-libc builds. For now that means it's only required on wasip3 (which -// requires a newer wasi-libc anyway). In the future this'll probably switch to -// unconditionally using `wasilibc_futex` as the implementation for all WASI -// targets (and switching all synchronization primitives to the futex version). -cfg_select! { - target_env = "p3" => { - pub mod wasilibc_futex; - pub use wasilibc_futex as futex; - } - target_feature = "atomics" => { - #[allow(unused)] - #[path = "../wasm/atomics/futex.rs"] - pub mod futex; - } - _ => {} -} - #[cfg(not(target_env = "p1"))] mod cabi_realloc; diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs index 24a2ab8eca30f..72e5982fc0732 100644 --- a/library/std/src/sys/pal/wasm/mod.rs +++ b/library/std/src/sys/pal/wasm/mod.rs @@ -16,10 +16,6 @@ #![deny(unsafe_op_in_unsafe_fn)] -#[cfg(target_feature = "atomics")] -#[path = "atomics/futex.rs"] -pub mod futex; - #[path = "../unsupported/common.rs"] #[deny(unsafe_op_in_unsafe_fn)] mod common; diff --git a/library/std/src/sys/pal/windows/mod.rs b/library/std/src/sys/pal/windows/mod.rs index b67ba37749789..4fa8c1b9a1323 100644 --- a/library/std/src/sys/pal/windows/mod.rs +++ b/library/std/src/sys/pal/windows/mod.rs @@ -15,8 +15,6 @@ pub mod compat; pub mod api; pub mod c; -#[cfg(not(target_vendor = "win7"))] -pub mod futex; pub mod handle; pub mod time; cfg_select! { diff --git a/library/std/src/sys/sync/condvar/futex.rs b/library/std/src/sys/sync/condvar/futex.rs index 0d0c5f0dbe701..b5b82e4c38257 100644 --- a/library/std/src/sys/sync/condvar/futex.rs +++ b/library/std/src/sys/sync/condvar/futex.rs @@ -1,6 +1,6 @@ use crate::sync::atomic::Ordering::Relaxed; -use crate::sys::futex::{Futex, futex_wait, futex_wake, futex_wake_all}; use crate::sys::sync::Mutex; +use crate::sys::sync::futex::{Futex, futex_wait, futex_wake, futex_wake_all}; use crate::time::Duration; pub struct Condvar { diff --git a/library/std/src/sys/sync/futex/hermit.rs b/library/std/src/sys/sync/futex/hermit.rs index 78c86071fdd53..783052526c525 100644 --- a/library/std/src/sys/sync/futex/hermit.rs +++ b/library/std/src/sys/sync/futex/hermit.rs @@ -1,4 +1,4 @@ -use super::hermit_abi; +use crate::os::hermit::hermit_abi; use crate::ptr::null; use crate::sync::atomic::Atomic; use crate::time::Duration; diff --git a/library/std/src/sys/sync/futex/mod.rs b/library/std/src/sys/sync/futex/mod.rs new file mode 100644 index 0000000000000..0edb46cc10f86 --- /dev/null +++ b/library/std/src/sys/sync/futex/mod.rs @@ -0,0 +1,39 @@ +cfg_select! { + any( + target_os = "linux", + target_os = "android", + all(target_os = "emscripten", target_feature = "atomics"), + target_os = "freebsd", + target_os = "openbsd", + target_os = "dragonfly", + target_os = "fuchsia", + ) => { + mod unix; + pub use unix::*; + } + all(target_os = "windows", not(target_vendor = "win7")) => { + mod windows; + pub use windows::*; + } + target_os = "hermit" => { + mod hermit; + pub use hermit::*; + } + // The wasi-libc based futex is new enough that it's not present in older + // wasi-libc builds. For now that means it's only required on wasip3 (which + // requires a newer wasi-libc anyway). In the future this'll probably switch to + // unconditionally using `wasilibc` as the implementation for all WASI + // targets (and switching all synchronization primitives to the futex version). + all(target_os = "wasi", target_env = "p3") => { + mod wasilibc; + pub use wasilibc::*; + } + all(target_family = "wasm", target_feature = "atomics") => { + mod wasm; + pub use wasm::*; + } + target_os = "motor" => { + pub use moto_rt::futex::*; + } + _ => {} +} diff --git a/library/std/src/sys/sync/futex/unix.rs b/library/std/src/sys/sync/futex/unix.rs index 2948d3d594eaa..16fda3ecbc7c3 100644 --- a/library/std/src/sys/sync/futex/unix.rs +++ b/library/std/src/sys/sync/futex/unix.rs @@ -1,13 +1,3 @@ -#![cfg(any( - target_os = "linux", - target_os = "android", - all(target_os = "emscripten", target_feature = "atomics"), - target_os = "freebsd", - target_os = "openbsd", - target_os = "dragonfly", - target_os = "fuchsia", -))] - use crate::sync::atomic::Atomic; use crate::time::Duration; @@ -28,9 +18,9 @@ pub type SmallPrimitive = u32; /// Returns false on timeout, and true in all other cases. #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))] pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { - use super::time::Timespec; use crate::ptr::null; use crate::sync::atomic::Ordering::Relaxed; + use crate::sys::pal::time::Timespec; // Calculate the timeout as an absolute timespec. // @@ -149,8 +139,8 @@ pub fn futex_wake_all(futex: &Atomic) { #[cfg(target_os = "openbsd")] pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { - use super::time::Timespec; use crate::ptr::{null, null_mut}; + use crate::sys::pal::time::Timespec; // Overflows are rounded up to an infinite timeout (None). let timespec = timeout @@ -258,7 +248,7 @@ pub fn futex_wake_all(futex: &Atomic) { #[cfg(target_os = "fuchsia")] pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) -> bool { - use super::fuchsia::*; + use crate::sys::pal::fuchsia::*; // Sleep forever if the timeout is longer than fits in a i64. let deadline = timeout @@ -274,11 +264,11 @@ pub fn futex_wait(futex: &Atomic, expected: u32, timeout: Option) // Fuchsia doesn't tell us how many threads are woken up, so this always returns false. #[cfg(target_os = "fuchsia")] pub fn futex_wake(futex: &Atomic) -> bool { - unsafe { super::fuchsia::zx_futex_wake(futex, 1) }; + unsafe { crate::sys::pal::fuchsia::zx_futex_wake(futex, 1) }; false } #[cfg(target_os = "fuchsia")] pub fn futex_wake_all(futex: &Atomic) { - unsafe { super::fuchsia::zx_futex_wake(futex, u32::MAX) }; + unsafe { crate::sys::pal::fuchsia::zx_futex_wake(futex, u32::MAX) }; } diff --git a/library/std/src/sys/sync/futex/windows.rs b/library/std/src/sys/sync/futex/windows.rs index cfa0a6b3815bd..eed0bb2548c1d 100644 --- a/library/std/src/sys/sync/futex/windows.rs +++ b/library/std/src/sys/sync/futex/windows.rs @@ -6,7 +6,7 @@ use core::sync::atomic::{ }; use core::time::Duration; -use super::api::{self, WinError}; +use crate::sys::pal::api::{self, WinError}; use crate::sys::{c, dur2timeout}; /// An atomic for use as a futex that is at least 32-bits but may be larger diff --git a/library/std/src/sys/sync/mod.rs b/library/std/src/sys/sync/mod.rs index 0691e96785198..8ee0b2649ed3d 100644 --- a/library/std/src/sys/sync/mod.rs +++ b/library/std/src/sys/sync/mod.rs @@ -1,4 +1,5 @@ mod condvar; +mod futex; mod mutex; mod once; mod once_box; diff --git a/library/std/src/sys/sync/mutex/futex.rs b/library/std/src/sys/sync/mutex/futex.rs index 70e2ea9f60586..015b5aacbc53b 100644 --- a/library/std/src/sys/sync/mutex/futex.rs +++ b/library/std/src/sys/sync/mutex/futex.rs @@ -1,5 +1,5 @@ use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; -use crate::sys::futex::{self, futex_wait, futex_wake}; +use crate::sys::sync::futex::{self, futex_wait, futex_wake}; type Futex = futex::SmallFutex; type State = futex::SmallPrimitive; diff --git a/library/std/src/sys/sync/once/futex.rs b/library/std/src/sys/sync/once/futex.rs index 236bc9ca4b7c7..8f17f065669a8 100644 --- a/library/std/src/sys/sync/once/futex.rs +++ b/library/std/src/sys/sync/once/futex.rs @@ -2,7 +2,7 @@ use crate::cell::Cell; use crate::sync as public; use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; use crate::sync::once::OnceExclusiveState; -use crate::sys::futex::{Futex, Primitive, futex_wait, futex_wake_all}; +use crate::sys::sync::futex::{Futex, Primitive, futex_wait, futex_wake_all}; // On some platforms, the OS is very nice and handles the waiter queue for us. // This means we only need one atomic value with 4 states: diff --git a/library/std/src/sys/sync/rwlock/futex.rs b/library/std/src/sys/sync/rwlock/futex.rs index 0e8e954de0758..c9389fe144b4d 100644 --- a/library/std/src/sys/sync/rwlock/futex.rs +++ b/library/std/src/sys/sync/rwlock/futex.rs @@ -1,5 +1,5 @@ use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; -use crate::sys::futex::{Futex, Primitive, futex_wait, futex_wake, futex_wake_all}; +use crate::sys::sync::futex::{Futex, Primitive, futex_wait, futex_wake, futex_wake_all}; pub struct RwLock { // The state consists of a 30-bit reader counter, a 'readers waiting' flag, and a 'writers waiting' flag. diff --git a/library/std/src/sys/sync/thread_parking/futex.rs b/library/std/src/sys/sync/thread_parking/futex.rs index c8f7f26386a01..691d839c41e6d 100644 --- a/library/std/src/sys/sync/thread_parking/futex.rs +++ b/library/std/src/sys/sync/thread_parking/futex.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_op_in_unsafe_fn)] use crate::pin::Pin; use crate::sync::atomic::Ordering::{Acquire, Release}; -use crate::sys::futex::{self, futex_wait, futex_wake}; +use crate::sys::sync::futex::{self, futex_wait, futex_wake}; use crate::time::Duration; type Futex = futex::SmallFutex; From 012c35624ed80e409f2bbb301e2824d547f01ef1 Mon Sep 17 00:00:00 2001 From: Makro Date: Wed, 29 Jul 2026 09:00:38 +0000 Subject: [PATCH 04/19] Select cache values to verify by key fingerprint, not value fingerprint --- compiler/rustc_middle/src/dep_graph/graph.rs | 9 +++++++ compiler/rustc_query_impl/src/execution.rs | 26 +++++++++++++------- compiler/rustc_query_impl/src/plumbing.rs | 3 +-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index 7892404badef3..b59fc263eec9a 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -705,6 +705,15 @@ impl DepGraphData { self.previous.value_fingerprint_for_index(prev_index) } + /// The number of incremental sessions in this graph's lineage, from + /// [`SerializedDepGraph::session_count`]. Advances by one per successful + /// session; a failed session does not commit a graph, so a re-run sees + /// the same count. + #[inline] + pub fn session_count(&self) -> u64 { + self.previous.session_count() + } + #[inline] pub(crate) fn prev_node_of(&self, prev_index: SerializedDepNodeIndex) -> &DepNode { self.previous.index_to_node(prev_index) diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index a9192d0417712..a1d68fc0dc7ab 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -1,7 +1,7 @@ use std::hash::Hash; use std::mem::ManuallyDrop; -use rustc_data_structures::fingerprint::Fingerprint; +use rustc_data_structures::fingerprint::{Fingerprint, PackedFingerprint}; use rustc_data_structures::hash_table::{Entry, HashTable}; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::sync::{DynSend, DynSync}; @@ -490,12 +490,21 @@ fn execute_job_incr<'tcx, C: QueryCache>( /// specified, re-hash results from the cache and make sure that they have the /// expected fingerprint. /// -/// If not, we still seek to verify a subset of fingerprints loaded from disk. -/// Re-hashing results is fairly expensive, so we can't currently afford to -/// verify every hash. This subset should still give us some coverage of -/// potential bugs. -pub(crate) fn should_verify_loaded_value(tcx: TyCtxt<'_>, prev_fingerprint: Fingerprint) -> bool { - prev_fingerprint.split().1.as_u64().is_multiple_of(32) +/// If not, we still verify a subset: re-hashing is too expensive to do for +/// every value. The subset rotates with the session count, covering the whole +/// cache every 32 sessions, and is deterministic so that a verification +/// failure reproduces on retry. +/// +/// `to_smaller_hash` mixes both fingerprint halves because neither half is +/// evenly distributed on its own (`DefPathHash` keys share the +/// `StableCrateId`, `HirId` keys contain a sequential id). +pub(crate) fn should_verify_loaded_value( + tcx: TyCtxt<'_>, + dep_graph_data: &DepGraphData, + key_fingerprint: PackedFingerprint, +) -> bool { + let hash = Fingerprint::from(key_fingerprint).to_smaller_hash().as_u64(); + hash % 32 == dep_graph_data.session_count() % 32 || tcx.sess.opts.unstable_opts.incremental_verify_ich } @@ -532,8 +541,7 @@ fn load_from_disk_or_invoke_provider_green<'tcx, C: QueryCache>( dep_graph_data.mark_debug_loaded_from_disk(*dep_node) } - let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_index); - let verify = should_verify_loaded_value(tcx, prev_fingerprint); + let verify = should_verify_loaded_value(tcx, dep_graph_data, dep_node.key_fingerprint); (value, verify) } diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index c53293447040b..83badcb269af6 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -179,8 +179,7 @@ pub(crate) fn promote_from_disk_inner<'tcx, C: QueryCache>( // Verify the fingerprints of the same subset of loaded values as // `load_from_disk_or_invoke_provider_green` does. - let prev_fingerprint = dep_graph_data.prev_value_fingerprint_of(prev_index); - if should_verify_loaded_value(tcx, prev_fingerprint) { + if should_verify_loaded_value(tcx, dep_graph_data, dep_node.key_fingerprint) { incremental_verify_ich( tcx, dep_graph_data, From 833ec34ae8f7b582ea4f9202fd19b01943f89cbe Mon Sep 17 00:00:00 2001 From: jyn Date: Thu, 18 Jun 2026 10:36:08 +0200 Subject: [PATCH 05/19] [blocked] Link to proposed LLM policy in CONTRIBUTING and pull request template --- .github/pull_request_template.md | 11 +++++++++++ CONTRIBUTING.md | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 93388ddd24075..872c8a0ade1ab 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,16 @@ + +- [ ] I did not use an LLM to create a change in this PR. +- [ ] I used an LLM to create a change in this PR, and I have explained below how it was used. + $DIR/macro-determinacy-non-module-issue-160195.rs:12:22 + | +LL | include!(concat!(env!())); + | ^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/resolve/macro-determinacy-non-module-issue-160195.env_second.stderr b/tests/ui/resolve/macro-determinacy-non-module-issue-160195.env_second.stderr new file mode 100644 index 0000000000000..cf8c7221c2367 --- /dev/null +++ b/tests/ui/resolve/macro-determinacy-non-module-issue-160195.env_second.stderr @@ -0,0 +1,8 @@ +error: `env!()` takes 1 or 2 arguments + --> $DIR/macro-determinacy-non-module-issue-160195.rs:12:22 + | +LL | include!(concat!(env!())); + | ^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/resolve/macro-determinacy-non-module-issue-160195.rs b/tests/ui/resolve/macro-determinacy-non-module-issue-160195.rs new file mode 100644 index 0000000000000..ca08a665e907b --- /dev/null +++ b/tests/ui/resolve/macro-determinacy-non-module-issue-160195.rs @@ -0,0 +1,21 @@ +//@ revisions: env_first env_second + +#[cfg(env_first)] +pub mod env { + #[derive(Default)] + pub struct BusinessData; +} + +pub mod interface { + use crate::env::{self}; + + include!(concat!(env!())); //~ ERROR `env!()` takes 1 or 2 arguments +} + +#[cfg(env_second)] +pub mod env { + #[derive(Default)] + pub struct BusinessData; +} + +fn main() {} From e0830fa2bec1da2e1ec8ba8c3d9eb4be332e8136 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Tue, 4 Aug 2026 16:01:41 +0200 Subject: [PATCH 10/19] implement unsafe speculative flag to be used by `CmRefCell::borrow`, which does tracked and untracked borrowing --- compiler/rustc_resolve/src/check_unused.rs | 2 +- .../rustc_resolve/src/diagnostics/impls.rs | 4 +- .../src/effective_visibilities.rs | 4 +- compiler/rustc_resolve/src/ident.rs | 25 +++--- compiler/rustc_resolve/src/imports.rs | 18 ++-- .../rustc_resolve/src/late/diagnostics.rs | 10 ++- compiler/rustc_resolve/src/lib.rs | 86 +++++++++++++++---- compiler/rustc_resolve/src/macros.rs | 2 +- 8 files changed, 107 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 41573749abbe7..dcbda2f96323e 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -559,7 +559,7 @@ impl Resolver<'_, '_> { let mut check_redundant_imports = FxIndexSet::default(); for module in &self.local_modules { for (_key, resolution) in self.resolutions(module.to_module()).iter() { - if let Some(decl) = resolution.borrow().best_decl() + if let Some(decl) = resolution.borrow(self).best_decl() && let DeclKind::Import { import, .. } = decl.kind && let ImportKind::Single { id, .. } = import.kind { diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index cc2c72ad59906..4e451665398e9 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1873,7 +1873,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.resolutions(parent_scope.module).iter().any(|(key, name_resolution)| { if key.ns == TypeNS && key.ident == *ident - && let Some(decl) = name_resolution.borrow().best_decl() + && let Some(decl) = name_resolution.borrow(self).best_decl() { match decl.res() { // No disambiguation needed if the identically named item we @@ -3603,7 +3603,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut res = false; let m = r.expect_module(parent_module); if m.is_local() { - for importer in m.glob_importers.borrow().iter() { + for importer in m.glob_importers.borrow(r).iter() { if let Some(next_parent_module) = importer.parent_scope.module.opt_def_id() { if next_parent_module == module diff --git a/compiler/rustc_resolve/src/effective_visibilities.rs b/compiler/rustc_resolve/src/effective_visibilities.rs index ff976b080d40d..840a8a8682538 100644 --- a/compiler/rustc_resolve/src/effective_visibilities.rs +++ b/compiler/rustc_resolve/src/effective_visibilities.rs @@ -126,7 +126,7 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { fn set_bindings_effective_visibilities(&mut self, module_id: LocalDefId) { let module = self.r.expect_module(module_id.to_def_id()); for (_, name_resolution) in self.r.resolutions(module).iter() { - let Some(decl) = name_resolution.borrow().best_decl() else { + let Some(decl) = name_resolution.borrow(self.r).best_decl() else { continue; }; self.update_decl_chain(decl, ParentId::Def(module_id)); @@ -310,7 +310,7 @@ impl<'a, 'ra, 'tcx> EffectiveVisibilitiesVisitor<'a, 'ra, 'tcx> { if self.macro_reachable.insert((module_def_id, defining_mod)) { let module = self.r.expect_module(module_def_id.to_def_id()); for (_, name_resolution) in self.r.resolutions(module).iter() { - let Some(decl) = name_resolution.borrow().best_decl() else { + let Some(decl) = name_resolution.borrow(self.r).best_decl() else { continue; }; diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 3f34af1d01d83..42fc5964f5aa1 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -714,7 +714,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } Scope::MacroUsePrelude => match self.macro_use_prelude.get(&ident.name).cloned() { Some(decl) => Ok(decl), - None => Err(Determinacy::determined(!self.graph_root.has_unexpanded_invocations())), + None => { + Err(Determinacy::determined(!self.graph_root.has_unexpanded_invocations(&self))) + } }, Scope::BuiltinAttrs => match self.builtin_attr_decls.get(&ident.name) { Some(decl) => Ok(*decl), @@ -727,9 +729,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { finalize.is_some(), ) { Some(decl) => Ok(decl), - None => { - Err(Determinacy::determined(!self.graph_root.has_unexpanded_invocations())) - } + None => Err(Determinacy::determined( + !self.graph_root.has_unexpanded_invocations(&self), + )), } } Scope::ExternPreludeFlags => { @@ -1158,7 +1160,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(finalize) = finalize { // finalize implies that the module is fully expanded - assert!(!module.has_unexpanded_invocations()); + assert!(!module.has_unexpanded_invocations(&self)); return self.get_mut().finalize_module_binding( ident, orig_ident_span, @@ -1195,7 +1197,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } // Check if one of unexpanded macros can still define the name. - if module.has_unexpanded_invocations() { + if module.has_unexpanded_invocations(&self) { return Err(ControlFlow::Continue(Undetermined)); } @@ -1224,7 +1226,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(finalize) = finalize { // finalize implies that the module is fully expanded - assert!(!module.has_unexpanded_invocations()); + assert!(!module.has_unexpanded_invocations(&self)); return self.get_mut().finalize_module_binding( ident, orig_ident_span, @@ -1268,7 +1270,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // and prohibit access to macro-expanded `macro_export` macros instead (unless restricted // shadowing is enabled, see `macro_expanded_macro_export_errors`). if let Some(binding) = binding { - return if binding.determined() || ns == MacroNS || shadowing == Shadowing::Restricted { + return if binding.determined(&self) + || ns == MacroNS + || shadowing == Shadowing::Restricted + { let accessible = self.is_accessible_from(binding.vis(), parent_scope.module); if accessible { Ok(binding) } else { Err(ControlFlow::Break(Determined)) } } else { @@ -1283,13 +1288,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // scopes we return `Undetermined` with `ControlFlow::Continue`. // Check if one of unexpanded macros can still define the name, // if it can then our "no resolution" result is not determined and can be invalidated. - if module.has_unexpanded_invocations() { + if module.has_unexpanded_invocations(&self) { return Err(ControlFlow::Continue(Undetermined)); } // Check if one of glob imports can still define the name, // if it can then our "no resolution" result is not determined and can be invalidated. - for glob_import in module.globs.borrow().iter() { + for glob_import in module.globs.borrow(&self).iter() { if ignore_import == Some(*glob_import) { continue; } diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 6e2ea9abf2de8..499f9ea297362 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -781,14 +781,22 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut imports_to_resolve = mem::take(&mut self.indeterminate_imports); - self.assert_speculative = true; + // SAFETY: This is a "top-level" function used by the macro expansion code, unless some + // weird thing is done, all `tracked` borrows done in the previous call of + // `resolve_imports` are dropped when that call ended. + unsafe { self.speculative_flag.set(true) }; rustc_data_structures::sync::par_for_each_slice( &mut imports_to_resolve, |(import, resolution, indeterminate_count)| { (*resolution, *indeterminate_count) = self.resolve_import(*import); }, ); - self.assert_speculative = false; + // SAFETY: All `untracked` borrows are dropped after the `par_for_each_slice` call, + // as they cannot escape since they are tied to the `CmRefCell` they borrowed from. + // + // Note: Some `CmRefCell`s are arena allocated and thus have the `'ra` lifetime, + // allowing these borrows to escape, but that does not and should not happen. + unsafe { self.speculative_flag.set(false) }; self.write_import_resolutions(&imports_to_resolve); @@ -1003,7 +1011,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { pub(crate) fn lint_reexports(&mut self, exported_ambiguities: FxHashSet>) { for module in &self.local_modules { for (key, resolution) in self.resolutions(module.to_module()).iter() { - let resolution = resolution.borrow(); + let resolution = resolution.borrow(self); let Some(binding) = resolution.best_decl() else { continue }; // Report "cannot reexport" errors for exotic cases involving macros 2.0 @@ -1490,7 +1498,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return None; } // `use _` is never valid - let resolution = resolution.borrow(); + let resolution = resolution.borrow(self); if let Some(name_binding) = resolution.best_decl() { match name_binding.kind { DeclKind::Import { source_decl, .. } => { @@ -1800,7 +1808,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .resolutions(module) .iter() .filter_map(|(key, resolution)| { - let res = resolution.borrow(); + let res = resolution.borrow(self); let decl = res.determined_decl()?; let mut key = *key; let scope = match key.ident.ctxt.update_unchecked(|ctxt| { diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 6350f79ed007f..b126272583692 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -194,7 +194,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if key.ident.name != assoc_name { return None; } - let resolution = resolution.borrow(); + let resolution = resolution.borrow(self.r); let binding = resolution.best_decl()?; match binding.res() { Res::Def(DefKind::AssocTy, def_id) => Some(def_id), @@ -1165,7 +1165,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let find_doc_alias_name = |r: &mut Resolver<'ra, '_>, m: Module<'ra>, item_name: Symbol| { for resolution in r.resolutions(m).values() { let Some(did) = - resolution.borrow().best_decl().and_then(|binding| binding.res().opt_def_id()) + resolution.borrow(r).best_decl().and_then(|binding| binding.res().opt_def_id()) else { continue; }; @@ -1905,7 +1905,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { .resolutions(module) .iter() .filter_map(|(key, resolution)| { - let resolution = resolution.borrow(); + let resolution = resolution.borrow(self.r); resolution.best_decl().map(|binding| binding.res()).and_then(|res| { if filter_fn(res) { Some((key.ident.name, resolution.orig_ident_span, res)) @@ -2766,7 +2766,9 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { .r .resolutions(*module) .iter() - .filter_map(|(key, res)| res.borrow().best_decl().map(|binding| (key, binding.res()))) + .filter_map(|(key, res)| { + res.borrow(self.r).best_decl().map(|binding| (key, binding.res())) + }) .filter(|(_, res)| match (kind, res) { (AssocItemKind::Const(..), Res::Def(DefKind::AssocConst { .. }, _)) => true, (AssocItemKind::Fn(_), Res::Def(DefKind::AssocFn, _)) => true, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index a3c804e56ee22..b7e57ad8ec37e 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -21,7 +21,7 @@ #![recursion_limit = "256"] // tidy-alphabetical-end -use std::cell::{Ref, RefMut}; +use std::cell::RefMut; use std::collections::BTreeSet; use std::ops::ControlFlow; use std::sync::{Arc, OnceLock}; @@ -81,6 +81,7 @@ use crate::diagnostics::impls::{ ImportSuggestion, LabelSuggestion, OnUnknownData, StructCtor, Suggestion, }; use crate::imports::{ImportResolution, NameResolutionRef}; +use crate::ref_mut::speculative::SpeculativeFlag; use crate::ref_mut::{CmCell, CmRef, CmRefCell}; mod build_reduced_graph; @@ -767,8 +768,8 @@ impl<'ra> ModuleData<'ra> { self.kind.is_local() } - fn has_unexpanded_invocations(&self) -> bool { - !self.unexpanded_invocations.borrow().is_empty() + fn has_unexpanded_invocations<'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> bool { + !self.unexpanded_invocations.borrow(r).is_empty() } fn res(&self) -> Option { @@ -793,7 +794,7 @@ impl<'ra> Module<'ra> { mut f: impl FnMut(&R, IdentKey, Span, Namespace, Decl<'ra>), ) { for (key, name_resolution) in resolver.as_ref().resolutions(self).iter() { - let name_resolution = name_resolution.borrow(); + let name_resolution = name_resolution.borrow(resolver.as_ref()); if let Some(decl) = name_resolution.best_decl() { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); } @@ -806,7 +807,7 @@ impl<'ra> Module<'ra> { mut f: impl FnMut(&mut R, IdentKey, Span, Namespace, Decl<'ra>), ) { for (key, name_resolution) in resolver.as_mut().resolutions(self).iter() { - let name_resolution = name_resolution.borrow(); + let name_resolution = name_resolution.borrow(resolver.as_mut()); if let Some(decl) = name_resolution.best_decl() { f(resolver, key.ident, name_resolution.orig_ident_span, key.ns, decl); } @@ -1252,10 +1253,11 @@ impl<'ra> DeclData<'ra> { /// the declaration may not be as "determined" as we think. /// FIXME: relationship between this function and similar `NameResolution::determined_decl` /// is unclear. - fn determined(&self) -> bool { + fn determined<'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> bool { match &self.kind { DeclKind::Import { source_decl, import, .. } if import.is_glob() => { - !import.parent_scope.module.has_unexpanded_invocations() && source_decl.determined() + !import.parent_scope.module.has_unexpanded_invocations(r) + && source_decl.determined(r) } _ => true, } @@ -1336,7 +1338,7 @@ pub struct Resolver<'ra, 'tcx> { graph_root: LocalModule<'ra>, /// Assert that we are in speculative resolution mode (unsafe field). - assert_speculative: bool, + speculative_flag: SpeculativeFlag, prelude: Option> = None, extern_prelude: FxIndexMap>, @@ -1810,7 +1812,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // The outermost module has def ID 0; this is not reflected in the // AST. graph_root, - assert_speculative: false, // Only set/cleared in Resolver::resolve_imports for now + // Only set/cleared in Resolver::resolve_imports for now + speculative_flag: SpeculativeFlag::default(), extern_prelude, empty_module, @@ -2011,7 +2014,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { /// Returns a conditionally mutable resolver that can be mutated. /// Will panic if the `assert_speculative` field is true. fn cm_mut(&mut self) -> CmResolver<'_, 'ra, 'tcx> { - assert!(!self.assert_speculative, "can't mutably borrow speculative resolver"); + assert!( + !self.speculative_flag.is_speculative(), + "can't mutably borrow speculative resolver" + ); CmResolver::Mut(self) } @@ -2127,7 +2133,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { found_traits: &mut Vec>, ) { module.ensure_traits(self); - let traits = module.traits.borrow(); + let traits = module.traits.borrow(self); for &(trait_name, trait_binding, trait_module, lint_ambiguous) in traits.as_ref().unwrap().iter() { @@ -2178,7 +2184,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { fn resolutions(&self, module: Module<'ra>) -> CmRef<'ra, ResolutionTable<'ra>> { match &module.0.0.lazy_resolutions { - Resolutions::Local(local_res) => CmRef::Tracked(local_res.borrow()), + Resolutions::Local(local_res) => local_res.borrow(self), Resolutions::Extern(extern_res) => { // It is fine to return a `CmRef::Untracked`, we never give out a `&mut` // to an external table. @@ -2206,8 +2212,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { &self, module: Module<'ra>, key: BindingKey, - ) -> Option>> { - self.resolutions(module).get(&key).map(|resolution| resolution.0.borrow()) + ) -> Option>> { + self.resolutions(module).get(&key).map(|resolution| resolution.0.borrow(self)) } #[track_caller] @@ -2917,7 +2923,7 @@ mod ref_mut { } pub(crate) fn set<'ra, 'tcx>(&self, val: T, r: &Resolver<'ra, 'tcx>) { - if r.assert_speculative { + if r.speculative_flag.is_speculative() { panic!("not allowed to mutate a `CmCell` during speculative resolution") } self.0.set(val); @@ -2946,6 +2952,27 @@ mod ref_mut { } } + pub(crate) mod speculative { + #[derive(Debug, Clone, Copy, Default)] + pub(crate) struct SpeculativeFlag(bool); + + impl SpeculativeFlag { + /// # SAFETY + /// + /// All borrows created by `CmRefCell::borrow` must be dropped before changing + /// the speculative flag: + /// - `tracked` borrows before setting it to `true`. + /// - `untracked` borrows before setting it to `false`. + pub(crate) unsafe fn set(&mut self, value: bool) { + self.0 = value; + } + + pub(crate) fn is_speculative(&self) -> bool { + self.0 + } + } + } + /// A wrapper around a [`RefCell`] that only allows writes (mutable borrows) based on a condition in the resolver. #[derive(Default)] pub(crate) struct CmRefCell(RefCell); @@ -2965,21 +2992,42 @@ mod ref_mut { &self, r: &Resolver<'ra, 'tcx>, ) -> Result, BorrowMutError> { - if r.assert_speculative { + if r.speculative_flag.is_speculative() { panic!("not allowed to mutably borrow a `CmRefCell` during speculative resolution"); } self.0.try_borrow_mut() } #[track_caller] - pub(crate) fn borrow(&self) -> Ref<'_, T> { - self.0.borrow() + pub(crate) fn borrow<'ra, 'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> CmRef<'_, T> { + if r.speculative_flag.is_speculative() { + // `try_borrow_unguarded` is unsafe because it returns a `&T` instead + // of `Ref<'_, T>`. It does provides an extra check to make sure no live + // `RefMut`s are still alive, but the other way can not be checked, so: + // + // SAFETY: This is only safe because we know that every `Untracked` borrow + // is only created during the import resolutions phase: + // + // ```rust + // // tracked borrows + // unsafe { resolver.speculative_flag.set_true() }; + // import_resolution(); // untracked borrows + // unsafe { resolver.speculative_flag.set_true() }; + // // tracked borrows + // ``` + // + // `speculative::Flag` requires all of the borrows that happened during a + // particular phase are dropped before being set to true/false. + CmRef::Untracked(unsafe { self.0.try_borrow_unguarded().unwrap() }) + } else { + CmRef::Tracked(self.0.borrow()) + } } } impl CmRefCell { pub(crate) fn take<'ra, 'tcx>(&self, r: &Resolver<'ra, 'tcx>) -> T { - if r.assert_speculative { + if r.speculative_flag.is_speculative() { panic!("not allowed to mutate a CmRefCell during speculative resolution"); } self.0.take() diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 1e9d60ca21551..6921d0ed595fe 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -562,7 +562,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { star_span: Span, ) -> Result)>, Indeterminate> { let target_trait = self.expect_module(trait_def_id); - if target_trait.has_unexpanded_invocations() { + if target_trait.has_unexpanded_invocations(self) { return Err(Indeterminate); } // FIXME: Instead of waiting try generating all trait methods, and pruning From 73f94b6b9f2f71532971f9fd1f0910d8a8953a16 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:53:41 +0200 Subject: [PATCH 11/19] Make the `rustc_unsafe_specialization_marker` attribute actually `unsafe` Also renames it to `rustc_specialization_ignore_lifetime_constraints` --- .../rustc_attr_parsing/src/attributes/traits.rs | 15 +++++++++++---- compiler/rustc_attr_parsing/src/context.rs | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 +- compiler/rustc_hir/src/attrs/data_structures.rs | 6 +++--- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 2 +- .../src/impl_wf_check/min_specialization.rs | 2 +- compiler/rustc_middle/src/ty/trait_def.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 3 ++- compiler/rustc_span/src/symbol.rs | 2 +- library/alloc/src/rc.rs | 2 +- library/alloc/src/vec/into_iter.rs | 2 +- library/core/src/array/iter.rs | 2 +- library/core/src/clone.rs | 2 +- library/core/src/iter/traits/marker.rs | 4 ++-- library/core/src/slice/sort/shared/mod.rs | 2 +- library/core/src/slice/sort/shared/smallsort.rs | 2 +- .../min_specialization/spec-marker-supertraits.rs | 2 +- .../min_specialization/specialization_marker.rs | 6 +++--- .../min_specialization/specialize_on_marker.rs | 4 ++-- .../unconstrained-var-specialization.rs | 2 +- 21 files changed, 38 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index 69bdccb85c5cd..1d0d26ea62cb3 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -3,6 +3,7 @@ use std::mem; use rustc_feature::AttributeStability; use super::prelude::*; +use crate::AttributeSafety; use crate::attributes::{NoArgsAttributeParser, SingleAttributeParser}; use crate::context::AcceptContext; use crate::parser::ArgParser; @@ -98,12 +99,18 @@ impl NoArgsAttributeParser for RustcSpecializationTraitParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcSpecializationTrait; } -pub(crate) struct RustcUnsafeSpecializationMarkerParser; -impl NoArgsAttributeParser for RustcUnsafeSpecializationMarkerParser { - const PATH: &[Symbol] = &[sym::rustc_unsafe_specialization_marker]; +pub(crate) struct RustcAllowLifetimeDependentSpecializationParser; +impl NoArgsAttributeParser for RustcAllowLifetimeDependentSpecializationParser { + const PATH: &[Symbol] = &[sym::rustc_allow_lifetime_dependent_specialization]; const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const STABILITY: AttributeStability = unstable!(rustc_attrs); - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcUnsafeSpecializationMarker; + const CREATE: fn(Span) -> AttributeKind = + |_| AttributeKind::RustcAllowLifetimeDependentSpecialization; + const SAFETY: AttributeSafety = AttributeSafety::Unsafe { + note: "this attribute requires `unsafe` because lifetime constraints from \ + the implementations of the trait are not considered when specializing", + unsafe_since: None, + }; } // Coherence diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 6254dd73f3263..55732edfbd166 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -295,6 +295,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, @@ -355,7 +356,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 1f6f97f1310ae..72b51ad204b9d 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -365,7 +365,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ sym::rustc_reservation_impl, sym::rustc_test_entrypoint_marker, sym::rustc_test_marker, - sym::rustc_unsafe_specialization_marker, + sym::rustc_allow_lifetime_dependent_specialization, sym::rustc_specialization_trait, sym::rustc_main, sym::rustc_skip_during_method_dispatch, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 78a15eeb923a5..530483e87329c 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1350,6 +1350,9 @@ pub enum AttributeKind { /// Represents `#[rustc_allow_incoherent_impl]`. RustcAllowIncoherentImpl(Span), + /// Represents `#[rustc_allow_lifetime_dependent_specialization]`. + RustcAllowLifetimeDependentSpecialization, + /// Represents `#[rustc_as_ptr]` (used by the `dangling_pointers_from_temporaries` lint). RustcAsPtr, @@ -1654,9 +1657,6 @@ pub enum AttributeKind { /// Represents `#[rustc_trivial_field_reads]` RustcTrivialFieldReads, - /// Represents `#[rustc_unsafe_specialization_marker]`. - RustcUnsafeSpecializationMarker, - /// Represents `#[sanitize]` /// /// the on set and off set are distjoint since there's a third option: unset. diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index a5a1fc2482b4e..455af47142446 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -106,6 +106,7 @@ impl AttributeKind { RustcAllocatorZeroedVariant { .. } => Yes, RustcAllowConstFnUnstable(..) => No, RustcAllowIncoherentImpl(..) => No, + RustcAllowLifetimeDependentSpecialization => No, RustcAsPtr => Yes, RustcAutodiff(..) => Yes, RustcBodyStability { .. } => No, @@ -196,7 +197,6 @@ impl AttributeKind { RustcTestMarker(..) => No, RustcThenThisWouldNeed(..) => No, RustcTrivialFieldReads => Yes, - RustcUnsafeSpecializationMarker => No, Sanitize { .. } => No, ShouldPanic { .. } => No, Splat(..) => Yes, diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index a1cc500a2f18f..19c06b720e825 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -963,7 +963,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { ) .unwrap_or([false; 2]); - let specialization_kind = if find_attr!(attrs, RustcUnsafeSpecializationMarker) { + let specialization_kind = if find_attr!(attrs, RustcAllowLifetimeDependentSpecialization) { ty::trait_def::TraitSpecializationKind::Marker } else if find_attr!(attrs, RustcSpecializationTrait) { ty::trait_def::TraitSpecializationKind::AlwaysApplicable diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index b8cb4c7f0e7c7..c146c3e62a301 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -55,7 +55,7 @@ //! `specialization` or `min_specialization` is enabled to implement these //! traits. //! -//! ### rustc_unsafe_specialization_marker +//! ### rustc_allow_lifetime_dependent_specialization //! //! There are also some specialization on traits with no methods, including the //! stable `FusedIterator` trait. We allow marking marker traits with an diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 3b0d78d34af76..da514036b20b9 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -91,7 +91,7 @@ pub enum TraitSpecializationKind { None, /// Specializing on this trait is allowed because it doesn't have any /// methods. For example `Sized` or `FusedIterator`. - /// Applies to traits with the `rustc_unsafe_specialization_marker` + /// Applies to traits with the `rustc_allow_lifetime_dependent_specialization` /// attribute. Marker, /// Specializing on this trait is allowed because all of the impls of this diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index e95b9b2ffdf01..54d17e4cd7ff7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -304,6 +304,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcAllocatorZeroed => (), AttributeKind::RustcAllocatorZeroedVariant { .. } => (), AttributeKind::RustcAllowIncoherentImpl(..) => (), + AttributeKind::RustcAllowLifetimeDependentSpecialization => (), AttributeKind::RustcAsPtr => (), AttributeKind::RustcAutodiff(..) => (), AttributeKind::RustcBodyStability { .. } => (), @@ -384,6 +385,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcShouldNotBeCalledOnConstItems => (), AttributeKind::RustcSimdMonomorphizeLaneLimit(..) => (), AttributeKind::RustcSkipDuringMethodDispatch { .. } => (), + AttributeKind::RustcSpecializationTrait => (), AttributeKind::RustcStdInternalSymbol => (), AttributeKind::RustcStrictCoherence(..) => (), @@ -391,7 +393,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcTestMarker(..) => (), AttributeKind::RustcThenThisWouldNeed(..) => (), AttributeKind::RustcTrivialFieldReads => (), - AttributeKind::RustcUnsafeSpecializationMarker => (), AttributeKind::Sanitize { .. } => {} AttributeKind::ShouldPanic { .. } => (), AttributeKind::Splat(..) => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fd555e6d97fd8..1e7527b68b80d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1762,6 +1762,7 @@ symbols! { rustc_allocator_zeroed_variant, rustc_allow_const_fn_unstable, rustc_allow_incoherent_impl, + rustc_allow_lifetime_dependent_specialization, rustc_allowed_through_unstable_modules, rustc_as_ptr, rustc_attrs, @@ -1869,7 +1870,6 @@ symbols! { rustc_test_marker, rustc_then_this_would_need, rustc_trivial_field_reads, - rustc_unsafe_specialization_marker, rustdoc, rustdoc_internals, rustdoc_missing_doc_code_examples, diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs index ffd02e2f4f6e5..f37c73f790755 100644 --- a/library/alloc/src/rc.rs +++ b/library/alloc/src/rc.rs @@ -2619,7 +2619,7 @@ impl RcEqIdent for Rc { } // Hack to allow specializing on `Eq` even though `Eq` has a method. -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] pub(crate) trait MarkerEq: PartialEq {} impl MarkerEq for T {} diff --git a/library/alloc/src/vec/into_iter.rs b/library/alloc/src/vec/into_iter.rs index ff3c9433ab58b..4b25634326e16 100644 --- a/library/alloc/src/vec/into_iter.rs +++ b/library/alloc/src/vec/into_iter.rs @@ -551,7 +551,7 @@ where #[doc(hidden)] #[unstable(issue = "none", feature = "std_internals")] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait NonDrop {} // T: Copy as approximation for !Drop since get_unchecked does not advance self.ptr diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index 0877b7bad9512..f10ed3edc0e6b 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -367,7 +367,7 @@ unsafe impl TrustedLen for IntoIter {} #[doc(hidden)] #[unstable(issue = "none", feature = "std_internals")] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait NonDrop {} // T: Copy as approximation for !Drop since get_unchecked does not advance self.alive diff --git a/library/core/src/clone.rs b/library/core/src/clone.rs index a67dc9d87499d..2996c753faea4 100644 --- a/library/core/src/clone.rs +++ b/library/core/src/clone.rs @@ -275,7 +275,7 @@ pub const trait Clone: Sized { // lifetime-dependent. Therefore, if `TrivialClone` is implemented for any lifetime, // its invariant holds whenever `Clone` is implemented, even if the actual // `TrivialClone` bound would not be satisfied because of lifetime bounds. -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] // If `#[derive(Clone, Clone, Copy)]` is written, there will be multiple // implementations of `TrivialClone`. To keep it from appearing in error // messages, make it a `#[marker]` trait. diff --git a/library/core/src/iter/traits/marker.rs b/library/core/src/iter/traits/marker.rs index 542d283fe95ab..1e6704fe524a9 100644 --- a/library/core/src/iter/traits/marker.rs +++ b/library/core/src/iter/traits/marker.rs @@ -25,7 +25,7 @@ pub unsafe trait TrustedFused {} /// /// [`Fuse`]: crate::iter::Fuse #[stable(feature = "fused", since = "1.26.0")] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] // FIXME: this should be a #[marker] and have another blanket impl for T: TrustedFused // but that ICEs iter::Fuse specializations. #[lang = "fused_iterator"] @@ -62,7 +62,7 @@ impl FusedIterator for &mut I {} /// This trait must only be implemented when the contract is upheld. Consumers /// of this trait must inspect [`Iterator::size_hint()`]’s upper bound. #[unstable(feature = "trusted_len", issue = "37572")] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] #[rustc_const_unstable(feature = "const_iter", issue = "92476")] pub const unsafe trait TrustedLen: [const] Iterator {} diff --git a/library/core/src/slice/sort/shared/mod.rs b/library/core/src/slice/sort/shared/mod.rs index e2cdcb3dd511d..e1977d79f3207 100644 --- a/library/core/src/slice/sort/shared/mod.rs +++ b/library/core/src/slice/sort/shared/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod smallsort; /// SAFETY: this is safety relevant, how does this interact with the soundness holes in /// specialization? -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] pub(crate) trait FreezeMarker {} impl FreezeMarker for T {} diff --git a/library/core/src/slice/sort/shared/smallsort.rs b/library/core/src/slice/sort/shared/smallsort.rs index 0017feb75b641..40939f922bcb6 100644 --- a/library/core/src/slice/sort/shared/smallsort.rs +++ b/library/core/src/slice/sort/shared/smallsort.rs @@ -134,7 +134,7 @@ impl UnstableSmallSortFreezeTypeImpl for T { } /// SAFETY: Only used for run-time optimization heuristic. -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait CopyMarker {} impl CopyMarker for T {} diff --git a/tests/ui/specialization/min_specialization/spec-marker-supertraits.rs b/tests/ui/specialization/min_specialization/spec-marker-supertraits.rs index 3bb2480e9e2be..57319e0e7bb21 100644 --- a/tests/ui/specialization/min_specialization/spec-marker-supertraits.rs +++ b/tests/ui/specialization/min_specialization/spec-marker-supertraits.rs @@ -8,7 +8,7 @@ trait HasMethod { fn method(&self); } -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait Marker: HasMethod {} trait Spec { diff --git a/tests/ui/specialization/min_specialization/specialization_marker.rs b/tests/ui/specialization/min_specialization/specialization_marker.rs index 93462d02ea578..55de99d7557f4 100644 --- a/tests/ui/specialization/min_specialization/specialization_marker.rs +++ b/tests/ui/specialization/min_specialization/specialization_marker.rs @@ -1,14 +1,14 @@ -// Test that `rustc_unsafe_specialization_marker` is only allowed on marker traits. +// Test that `rustc_allow_lifetime_dependent_specialization` is only allowed on marker traits. #![feature(rustc_attrs)] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait SpecMarker { fn f(); //~^ ERROR marker traits } -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait SpecMarker2 { type X; //~^ ERROR marker traits diff --git a/tests/ui/specialization/min_specialization/specialize_on_marker.rs b/tests/ui/specialization/min_specialization/specialize_on_marker.rs index f7bc057d3ba8a..8e1acf319f340 100644 --- a/tests/ui/specialization/min_specialization/specialize_on_marker.rs +++ b/tests/ui/specialization/min_specialization/specialize_on_marker.rs @@ -1,4 +1,4 @@ -// Test that specializing on a `rustc_unsafe_specialization_marker` trait is +// Test that specializing on a `rustc_allow_lifetime_dependent_specialization` trait is // allowed. //@ check-pass @@ -6,7 +6,7 @@ #![feature(min_specialization)] #![feature(rustc_attrs)] -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] trait SpecMarker {} trait X { diff --git a/tests/ui/traits/const-traits/unconstrained-var-specialization.rs b/tests/ui/traits/const-traits/unconstrained-var-specialization.rs index 4330e0aead1ac..d48880deefaf9 100644 --- a/tests/ui/traits/const-traits/unconstrained-var-specialization.rs +++ b/tests/ui/traits/const-traits/unconstrained-var-specialization.rs @@ -13,7 +13,7 @@ pub trait Iterator { type Item; } -#[rustc_unsafe_specialization_marker] +#[unsafe(rustc_allow_lifetime_dependent_specialization)] pub trait MoreSpecificThanIterator: Iterator {} pub trait Tr { From 180c6379b8ed8b8ff5d9545c716a17d2225c915c Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:21:15 +0200 Subject: [PATCH 12/19] Remove rustc_middle dependency on rustc_hir_pretty There is a `impl PpAnn for TyCtxt` that is unneeded. None of the big crates (middle, trait_selection) actually do any hir pretty printing so it can be removed and can either be implemented for local structs elsewhere or done by casting to `&dyn PpAnn` instead. --- Cargo.lock | 2 +- compiler/rustc_driver_impl/Cargo.toml | 1 + compiler/rustc_driver_impl/src/pretty.rs | 10 +- compiler/rustc_hir_typeck/src/_match.rs | 2 +- compiler/rustc_hir_typeck/src/callee.rs | 2 +- compiler/rustc_hir_typeck/src/expr.rs | 5 +- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 10 + .../src/fn_ctxt/suggestions.rs | 4 +- compiler/rustc_hir_typeck/src/lib.rs | 244 +++++++++--------- compiler/rustc_hir_typeck/src/pat.rs | 17 +- compiler/rustc_middle/Cargo.toml | 1 - compiler/rustc_middle/src/hir/map.rs | 7 - .../rustc_public_bridge/src/context/impls.rs | 14 +- src/librustdoc/json/conversions.rs | 8 +- .../src/matches/match_wild_err_arm.rs | 3 +- .../src/unnecessary_mut_passed.rs | 5 +- 16 files changed, 180 insertions(+), 155 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76b17e02c2359..2190fa22b77ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3954,6 +3954,7 @@ dependencies = [ "rustc_errors", "rustc_expand", "rustc_feature", + "rustc_hir", "rustc_hir_analysis", "rustc_hir_pretty", "rustc_index", @@ -4414,7 +4415,6 @@ dependencies = [ "rustc_graphviz", "rustc_hashes", "rustc_hir", - "rustc_hir_pretty", "rustc_index", "rustc_lint_defs", "rustc_macros", diff --git a/compiler/rustc_driver_impl/Cargo.toml b/compiler/rustc_driver_impl/Cargo.toml index c7d3e4fae3fc5..4871c7eb9e8b0 100644 --- a/compiler/rustc_driver_impl/Cargo.toml +++ b/compiler/rustc_driver_impl/Cargo.toml @@ -16,6 +16,7 @@ rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_expand = { path = "../rustc_expand" } rustc_feature = { path = "../rustc_feature" } +rustc_hir = { path = "../rustc_hir" } rustc_hir_analysis = { path = "../rustc_hir_analysis" } rustc_hir_pretty = { path = "../rustc_hir_pretty" } rustc_index = { path = "../rustc_index" } diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 3a0a6687dd812..4bf1a3d875866 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -7,7 +7,9 @@ use std::io; use rustc_ast as ast; use rustc_ast_pretty::pprust as pprust_ast; +use rustc_hir::intravisit; use rustc_hir_pretty as pprust_hir; +use rustc_hir_pretty::PpAnn; use rustc_middle::bug; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; @@ -71,7 +73,8 @@ struct HirIdentifiedAnn<'tcx> { impl<'tcx> pprust_hir::PpAnn for HirIdentifiedAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { - self.tcx.nested(state, nested) + let this = &self.tcx as &dyn intravisit::HirTyCtxt<'_>; + this.nested(state, nested) } fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { @@ -149,11 +152,12 @@ struct HirTypedAnn<'tcx> { impl<'tcx> pprust_hir::PpAnn for HirTypedAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { + let this = &self.tcx as &dyn intravisit::HirTyCtxt<'_>; let old_maybe_typeck_results = self.maybe_typeck_results.get(); if let pprust_hir::Nested::Body(id) = nested { self.maybe_typeck_results.set(Some(self.tcx.typeck_body(id))); } - self.tcx.nested(state, nested); + this.nested(state, nested); self.maybe_typeck_results.set(old_maybe_typeck_results); } @@ -281,7 +285,7 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { ) }; match s { - PpHirMode::Normal => f(&tcx), + PpHirMode::Normal => f(&(&tcx as &dyn intravisit::HirTyCtxt<'_>) as &dyn PpAnn), PpHirMode::Identified => { let annotation = HirIdentifiedAnn { tcx }; f(&annotation) diff --git a/compiler/rustc_hir_typeck/src/_match.rs b/compiler/rustc_hir_typeck/src/_match.rs index ebf9907e64e64..a1ff036574fdc 100644 --- a/compiler/rustc_hir_typeck/src/_match.rs +++ b/compiler/rustc_hir_typeck/src/_match.rs @@ -421,7 +421,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return self.get_fn_decl(hir_id).map(|(_, fn_decl)| { let (ty, span) = match fn_decl.output { hir::FnRetTy::DefaultReturn(span) => ("()".to_string(), span), - hir::FnRetTy::Return(ty) => (ty_to_string(&self.tcx, ty), ty.span), + hir::FnRetTy::Return(ty) => (ty_to_string(self, ty), ty.span), }; (span, format!("expected `{ty}` because of this return type")) }); diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 288a1903bf675..3074a5900773d 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -903,7 +903,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let removal_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi()); unit_variant = - Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(&self.tcx, qpath))); + Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(self, qpath))); } let callee_ty = self.resolve_vars_if_possible(callee_ty); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f89d67eced3fb..12e7f82cadd43 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -50,7 +50,7 @@ use crate::diagnostics::{ use crate::op::contains_let_in_chain; use crate::{ BreakableCtxt, CoroutineTypes, Diverges, FnCtxt, GatherLocalsVisitor, Needs, - TupleArgumentsFlag, cast, fatally_break_rust, report_unexpected_variant_res, type_error_struct, + TupleArgumentsFlag, cast, fatally_break_rust, type_error_struct, }; impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -589,8 +589,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ty::new_error(tcx, e) } Res::Def(DefKind::Variant, _) => { - let e = report_unexpected_variant_res( - tcx, + let e = self.report_unexpected_variant_res( res, Some(expr), &[], diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 287e3857087e7..7a5eeccb98260 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -220,6 +220,16 @@ impl<'a, 'tcx> Deref for FnCtxt<'a, 'tcx> { } } +impl<'tcx> rustc_hir_pretty::PpAnn for FnCtxt<'_, 'tcx> { + fn nested(&self, state: &mut rustc_hir_pretty::State<'_>, nested: rustc_hir_pretty::Nested) { + rustc_hir_pretty::PpAnn::nested( + &(&self.tcx as &dyn rustc_hir::intravisit::HirTyCtxt<'_>), + state, + nested, + ) + } +} + impl<'tcx> HirTyLowerer<'tcx> for FnCtxt<'_, 'tcx> { fn tcx(&self) -> TyCtxt<'tcx> { self.tcx diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index b28eb8ad940d9..fc99dd67289bb 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -723,12 +723,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let hir::FnDecl { inputs, output, .. } = fn_ptr_ty.decl; let inputs_str = - inputs.iter().map(|ty| rustc_hir_pretty::ty_to_string(&self.tcx, ty)).join(", "); + inputs.iter().map(|ty| rustc_hir_pretty::ty_to_string(self, ty)).join(", "); let output_str = match output { hir::FnRetTy::DefaultReturn(_) => String::new(), hir::FnRetTy::Return(ty) => { - format!(" -> {}", rustc_hir_pretty::ty_to_string(&self.tcx, ty)) + format!(" -> {}", rustc_hir_pretty::ty_to_string(self, ty)) } }; diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index c67b8f7cdaf5c..d20d8375fc228 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -478,134 +478,138 @@ impl<'tcx> EnclosingBreakables<'tcx> { } } } - -fn report_unexpected_variant_res( - tcx: TyCtxt<'_>, - res: Res, - expr: Option<&hir::Expr<'_>>, - sub_pats: &[hir::Pat<'_>], - qpath: &hir::QPath<'_>, - span: Span, - err_code: ErrCode, - expected: &str, -) -> ErrorGuaranteed { - let res_descr = match res { - Res::Def(DefKind::Variant, _) => "struct variant", - _ => res.descr(), - }; - let path_str = rustc_hir_pretty::qpath_to_string(&tcx, qpath); - let mut err = tcx - .dcx() - .struct_span_err(span, format!("expected {expected}, found {res_descr} `{path_str}`")) - .with_code(err_code); - match res { - Res::Def(DefKind::Fn | DefKind::AssocFn, _) if err_code == E0164 => { - let patterns_url = "https://doc.rust-lang.org/book/ch19-00-patterns.html"; - err.with_span_label(span, "`fn` calls are not allowed in patterns") - .with_help(format!("for more information, visit {patterns_url}")) - } - Res::Def(DefKind::Variant, _) if let Some(expr) = expr => { - err.span_label(span, format!("not a {expected}")); - let variant = tcx.expect_variant_res(res); - let sugg = if variant.fields.is_empty() { - " {}".to_string() - } else { - format!( - " {{ {} }}", - variant - .fields - .iter() - .map(|f| format!("{}: /* value */", f.name)) - .collect::>() - .join(", ") - ) - }; - let descr = "you might have meant to create a new value of the struct"; - let mut suggestion = vec![]; - match tcx.parent_hir_node(expr.hir_id) { - hir::Node::Expr(hir::Expr { - kind: hir::ExprKind::Call(..), - span: call_span, - .. - }) => { - suggestion.push((span.shrink_to_hi().with_hi(call_span.hi()), sugg)); - } - hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(..), hir_id, .. }) => { - suggestion.push((expr.span.shrink_to_lo(), "(".to_string())); - if let hir::Node::Expr(parent) = tcx.parent_hir_node(*hir_id) - && let hir::ExprKind::If(condition, block, None) = parent.kind - && condition.hir_id == *hir_id - && let hir::ExprKind::Block(block, _) = block.kind - && block.stmts.is_empty() - && let Some(expr) = block.expr - && let hir::ExprKind::Path(..) = expr.kind - { - // Special case: you can incorrectly write an equality condition: - // if foo == Struct { field } { /* if body */ } - // which should have been written - // if foo == (Struct { field }) { /* if body */ } - suggestion.push((block.span.shrink_to_hi(), ")".to_string())); - } else { - suggestion.push((span.shrink_to_hi().with_hi(expr.span.hi()), sugg)); +impl<'a, 'tcx> FnCtxt<'a, 'tcx> { + fn report_unexpected_variant_res( + &self, + res: Res, + expr: Option<&hir::Expr<'_>>, + sub_pats: &[hir::Pat<'_>], + qpath: &hir::QPath<'_>, + span: Span, + err_code: ErrCode, + expected: &str, + ) -> ErrorGuaranteed { + let tcx = self.tcx; + let res_descr = match res { + Res::Def(DefKind::Variant, _) => "struct variant", + _ => res.descr(), + }; + let path_str = rustc_hir_pretty::qpath_to_string(self, qpath); + let mut err = tcx + .dcx() + .struct_span_err(span, format!("expected {expected}, found {res_descr} `{path_str}`")) + .with_code(err_code); + match res { + Res::Def(DefKind::Fn | DefKind::AssocFn, _) if err_code == E0164 => { + let patterns_url = "https://doc.rust-lang.org/book/ch19-00-patterns.html"; + err.with_span_label(span, "`fn` calls are not allowed in patterns") + .with_help(format!("for more information, visit {patterns_url}")) + } + Res::Def(DefKind::Variant, _) if let Some(expr) = expr => { + err.span_label(span, format!("not a {expected}")); + let variant = tcx.expect_variant_res(res); + let sugg = if variant.fields.is_empty() { + " {}".to_string() + } else { + format!( + " {{ {} }}", + variant + .fields + .iter() + .map(|f| format!("{}: /* value */", f.name)) + .collect::>() + .join(", ") + ) + }; + let descr = "you might have meant to create a new value of the struct"; + let mut suggestion = vec![]; + match tcx.parent_hir_node(expr.hir_id) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Call(..), + span: call_span, + .. + }) => { + suggestion.push((span.shrink_to_hi().with_hi(call_span.hi()), sugg)); + } + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Binary(..), hir_id, .. + }) => { + suggestion.push((expr.span.shrink_to_lo(), "(".to_string())); + if let hir::Node::Expr(parent) = tcx.parent_hir_node(*hir_id) + && let hir::ExprKind::If(condition, block, None) = parent.kind + && condition.hir_id == *hir_id + && let hir::ExprKind::Block(block, _) = block.kind + && block.stmts.is_empty() + && let Some(expr) = block.expr + && let hir::ExprKind::Path(..) = expr.kind + { + // Special case: you can incorrectly write an equality condition: + // if foo == Struct { field } { /* if body */ } + // which should have been written + // if foo == (Struct { field }) { /* if body */ } + suggestion.push((block.span.shrink_to_hi(), ")".to_string())); + } else { + suggestion.push((span.shrink_to_hi().with_hi(expr.span.hi()), sugg)); + } + } + _ => { + suggestion.push((span.shrink_to_hi(), sugg)); } } - _ => { - suggestion.push((span.shrink_to_hi(), sugg)); - } + + err.multipart_suggestion(descr, suggestion, Applicability::HasPlaceholders); + err } + Res::Def(DefKind::Variant, _) if expr.is_none() => { + err.span_label(span, format!("not a {expected}")); - err.multipart_suggestion(descr, suggestion, Applicability::HasPlaceholders); - err - } - Res::Def(DefKind::Variant, _) if expr.is_none() => { - err.span_label(span, format!("not a {expected}")); - - let fields = &tcx.expect_variant_res(res).fields.raw; - let span = qpath.span().shrink_to_hi().to(span.shrink_to_hi()); - let (msg, sugg) = if fields.is_empty() { - ("use the struct variant pattern syntax", " {}".to_string()) - } else { - let msg = if fields.is_empty() { - "use struct variant pattern syntax" + let fields = &tcx.expect_variant_res(res).fields.raw; + let span = qpath.span().shrink_to_hi().to(span.shrink_to_hi()); + let (msg, sugg) = if fields.is_empty() { + ("use the struct variant pattern syntax", " {}".to_string()) } else { - "add the names to match a struct variant's fields" + let msg = if fields.is_empty() { + "use struct variant pattern syntax" + } else { + "add the names to match a struct variant's fields" + }; + let fields_sugg = fields + .iter() + .enumerate() + .map(|(i, field)| { + let field_name = field.ident(tcx).to_string(); + + let pat_snippet = sub_pats + .get(i) + .and_then(|sub_pat| { + tcx.sess.source_map().span_to_snippet(sub_pat.span).ok() + }) + .unwrap_or_else(|| "_".to_string()); + + if field_name == pat_snippet { + field_name + } else { + format!("{field_name}: {pat_snippet}") + } + }) + .collect::>() + .join(", "); + let sugg = format!(" {{ {} }}", fields_sugg); + (msg, sugg) }; - let fields_sugg = fields - .iter() - .enumerate() - .map(|(i, field)| { - let field_name = field.ident(tcx).to_string(); - - let pat_snippet = sub_pats - .get(i) - .and_then(|sub_pat| { - tcx.sess.source_map().span_to_snippet(sub_pat.span).ok() - }) - .unwrap_or_else(|| "_".to_string()); - - if field_name == pat_snippet { - field_name - } else { - format!("{field_name}: {pat_snippet}") - } - }) - .collect::>() - .join(", "); - let sugg = format!(" {{ {} }}", fields_sugg); - (msg, sugg) - }; - - err.span_suggestion_verbose( - qpath.span().shrink_to_hi().to(span.shrink_to_hi()), - msg, - sugg, - Applicability::HasPlaceholders, - ); - err + + err.span_suggestion_verbose( + qpath.span().shrink_to_hi().to(span.shrink_to_hi()), + msg, + sugg, + Applicability::HasPlaceholders, + ); + err + } + _ => err.with_span_label(span, format!("not a {expected}")), } - _ => err.with_span_label(span, format!("not a {expected}")), + .emit() } - .emit() } /// Controls whether all arguments are tupled. This is used for the call operator only. diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 52602b8041d66..01c48c0ae790c 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -32,7 +32,6 @@ use tracing::{debug, instrument, trace}; use ty::VariantDef; use ty::adjustment::{PatAdjust, PatAdjustment}; -use super::report_unexpected_variant_res; use crate::expectation::Expectation; use crate::gather_locals::DeclOrigin; use crate::{FnCtxt, diagnostics}; @@ -1585,8 +1584,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } Res::Def(DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::Variant, _) => { let expected = "unit struct, unit variant or constant"; - let e = report_unexpected_variant_res( - tcx, + let e = self.report_unexpected_variant_res( res, None, &[], @@ -1604,8 +1602,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { // Ok, we allow unit struct ctors in patterns only. } else { - let e = report_unexpected_variant_res( - tcx, + let e = self.report_unexpected_variant_res( res, None, &[], @@ -1775,8 +1772,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::PatKind::TupleStruct(_, sub_pats, _) => sub_pats, _ => &[], }; - let e = report_unexpected_variant_res( - tcx, res, None, sub_pats, qpath, pat.span, E0164, expected, + let e = self.report_unexpected_variant_res( + res, None, sub_pats, qpath, pat.span, E0164, expected, ); Err(e) }; @@ -2237,7 +2234,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { let has_shorthand_field_name = field_patterns.iter().any(|field| field.is_shorthand); if has_shorthand_field_name { - let path = rustc_hir_pretty::qpath_to_string(&self.tcx, qpath); + let path = rustc_hir_pretty::qpath_to_string(self, qpath); let mut err = struct_span_code_err!( self.dcx(), pat.span, @@ -2422,7 +2419,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // we don't care to report errors for a struct if the struct itself is tainted variant.has_errors()?; - let path = rustc_hir_pretty::qpath_to_string(&self.tcx, qpath); + let path = rustc_hir_pretty::qpath_to_string(self, qpath); let mut err = struct_span_code_err!( self.dcx(), pat.span, @@ -2472,7 +2469,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { f } } - Err(_) => rustc_hir_pretty::pat_to_string(&self.tcx, field.pat), + Err(_) => rustc_hir_pretty::pat_to_string(self, field.pat), } }) .collect::>() diff --git a/compiler/rustc_middle/Cargo.toml b/compiler/rustc_middle/Cargo.toml index f624fcee78f59..55608083d3751 100644 --- a/compiler/rustc_middle/Cargo.toml +++ b/compiler/rustc_middle/Cargo.toml @@ -22,7 +22,6 @@ rustc_feature = { path = "../rustc_feature" } rustc_graphviz = { path = "../rustc_graphviz" } rustc_hashes = { path = "../rustc_hashes" } rustc_hir = { path = "../rustc_hir" } -rustc_hir_pretty = { path = "../rustc_hir_pretty" } rustc_index = { path = "../rustc_index" } rustc_lint_defs = { path = "../rustc_lint_defs" } rustc_macros = { path = "../rustc_macros" } diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index c01d9e98e9b9c..8ec27921a5787 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -15,7 +15,6 @@ use rustc_hir::definitions::{DefKey, DefPath, DefPathHash}; use rustc_hir::intravisit::Visitor; use rustc_hir::lints::DelayedLints; use rustc_hir::*; -use rustc_hir_pretty as pprust_hir; use rustc_span::def_id::{CRATE_MOD_ID, StableCrateId}; use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, with_metavar_spans}; @@ -1156,12 +1155,6 @@ impl<'tcx> intravisit::HirTyCtxt<'tcx> for TyCtxt<'tcx> { } } -impl<'tcx> pprust_hir::PpAnn for TyCtxt<'tcx> { - fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { - pprust_hir::PpAnn::nested(&(self as &dyn intravisit::HirTyCtxt<'_>), state, nested) - } -} - pub(super) fn crate_hash(tcx: TyCtxt<'_>, _: LocalCrate) -> Svh { let krate = tcx.hir_crate_items(()); let upstream_crates = upstream_crates(tcx); diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 4a2fbb8f8b7af..f648a85249dfc 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -52,6 +52,16 @@ impl<'tcx, B: Bridge> AllocRangeHelpers<'tcx> for CompilerCtxt<'tcx, B> { } } +impl<'tcx, B: Bridge> rustc_hir_pretty::PpAnn for CompilerCtxt<'tcx, B> { + fn nested(&self, state: &mut rustc_hir_pretty::State<'_>, nested: rustc_hir_pretty::Nested) { + rustc_hir_pretty::PpAnn::nested( + &(&self.tcx as &dyn rustc_hir::intravisit::HirTyCtxt<'_>), + state, + nested, + ) + } +} + impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { pub fn lift>>(&self, value: T) -> T::Lifted { self.tcx.lift(value) @@ -295,7 +305,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { .get_attrs_by_path(def_id, &attr_name) .filter_map(|attribute| { if let Attribute::Unparsed(u) = attribute { - let attr_str = rustc_hir_pretty::attribute_to_string(&self.tcx, attribute); + let attr_str = rustc_hir_pretty::attribute_to_string(self, attribute); Some((attr_str, u.span)) } else { None @@ -314,7 +324,7 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { attrs_iter .filter_map(|attribute| { if let Attribute::Unparsed(u) = attribute { - let attr_str = rustc_hir_pretty::attribute_to_string(&self.tcx, attribute); + let attr_str = rustc_hir_pretty::attribute_to_string(self, attribute); Some((attr_str, u.span)) } else { None diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 7e46b2f593e49..eb382f368905f 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -12,7 +12,8 @@ use rustc_hir::attrs::{ }; use rustc_hir::def::{CtorKind, DefKind}; use rustc_hir::def_id::DefId; -use rustc_hir::{HeaderSafety, Safety, find_attr}; +use rustc_hir::{HeaderSafety, Safety, find_attr, intravisit}; +use rustc_hir_pretty::PpAnn; use rustc_metadata::rendered_const; use rustc_middle::ty::TyCtxt; use rustc_middle::{bug, ty}; @@ -1243,7 +1244,10 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) } fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute { - let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr); + let mut s = rustc_hir_pretty::attribute_to_string( + &(&tcx as &dyn intravisit::HirTyCtxt<'_>) as &dyn PpAnn, + attr, + ); assert_eq!(s.pop(), Some('\n')); Attribute::Other(s) } diff --git a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs index e38ba801c0bf7..9fc9f9944465c 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs @@ -6,6 +6,7 @@ use clippy_utils::{is_in_const_context, is_wild, peel_blocks_with_stmt}; use rustc_hir::{Arm, Expr, PatKind}; use rustc_lint::LateContext; use rustc_span::symbol::{kw, sym}; +use rustc_hir::intravisit; use super::MATCH_WILD_ERR_ARM; @@ -19,7 +20,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<' if ex_ty.is_diag_item(cx, sym::Result) { for arm in arms { if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { - let path_str = rustc_hir_pretty::qpath_to_string(&cx.tcx, path); + let path_str = rustc_hir_pretty::qpath_to_string(#[allow(trivial_casts)] &(&cx.tcx as &dyn intravisit::HirTyCtxt<'_>), path); if path_str == "Err" { let mut matching_wild = inner.iter().any(is_wild); let mut ident_bind_name = kw::Underscore; diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_mut_passed.rs b/src/tools/clippy/clippy_lints/src/unnecessary_mut_passed.rs index 60a6688927ab5..43721fa252837 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_mut_passed.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_mut_passed.rs @@ -6,6 +6,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; use std::iter; +use rustc_hir_pretty::PpAnn; +use rustc_hir::intravisit; declare_clippy_lint! { /// ### What it does @@ -51,7 +53,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { cx, &mut arguments.iter(), cx.typeck_results().expr_ty(fn_expr), - &rustc_hir_pretty::qpath_to_string(&cx.tcx, path), + #[allow(trivial_casts)] + &rustc_hir_pretty::qpath_to_string(&(&cx.tcx as &dyn intravisit::HirTyCtxt<'_>) as &dyn PpAnn, path), "function", ); } From 18e0dd9aa8993a19e332fd080904a72270d18a0d Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Tue, 4 Aug 2026 15:31:15 -0300 Subject: [PATCH 13/19] Document zero-sized autodiff slice handling Clarify that slice-tail layout checks apply to the sized prefix rather than the slice element, and cover zero-sized slice elements in the type-tree run-make test. --- compiler/rustc_middle/src/ty/typetree.rs | 6 ++++-- .../autodiff/type-trees/slice-dst-typetree/rmake.rs | 1 + .../type-trees/slice-dst-typetree/slice-dst.check | 5 +++++ .../autodiff/type-trees/slice-dst-typetree/test.rs | 13 +++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_middle/src/ty/typetree.rs b/compiler/rustc_middle/src/ty/typetree.rs index 90fb0316fe39b..100c3170e12a9 100644 --- a/compiler/rustc_middle/src/ty/typetree.rs +++ b/compiler/rustc_middle/src/ty/typetree.rs @@ -66,14 +66,16 @@ fn handle_indirection<'a>( // LLVM arguments, while its child describes the memory reached through `data`. let typing_env = ty::TypingEnv::fully_monomorphized(); if let ty::Slice(element_ty) = tcx.struct_tail_for_codegen(inner_ty, typing_env).kind() { + // `layout.size` here is the sized prefix of `inner_ty`, not the slice element size. + // Direct slices, transparent wrappers (`OsStr`), and ZST-prefixed DSTs have no byte + // offset to preserve. Nonzero prefixes (e.g. `Header<[f32]>`) keep field offsets. + // ZST elements still take this path and yield an empty child TypeTree (size 0). let child = if tcx .layout_of(typing_env.as_query_input(inner_ty)) .is_ok_and(|layout| layout.size.bytes() == 0) { - // Direct slices and transparent wrappers such as `OsStr` contain elements everywhere. typetree_from_ty_impl_inner(tcx, *element_ty, depth + 1, visited, false) } else { - // Preserve field offsets for a sized prefix before the slice tail. typetree_from_ty_impl_inner(tcx, inner_ty, depth + 1, visited, true) }; return TypeTree(vec![Type { diff --git a/tests/run-make/autodiff/type-trees/slice-dst-typetree/rmake.rs b/tests/run-make/autodiff/type-trees/slice-dst-typetree/rmake.rs index c19202fa41fe5..e0c8c87ca9e33 100644 --- a/tests/run-make/autodiff/type-trees/slice-dst-typetree/rmake.rs +++ b/tests/run-make/autodiff/type-trees/slice-dst-typetree/rmake.rs @@ -15,4 +15,5 @@ fn main() { let ir = rfs::read("test.ll"); llvm_filecheck().patterns("slice-dst.check").check_prefix("OSSTR").stdin_buf(&ir).run(); llvm_filecheck().patterns("slice-dst.check").check_prefix("HEADER").stdin_buf(&ir).run(); + llvm_filecheck().patterns("slice-dst.check").check_prefix("ZST").stdin_buf(&ir).run(); } diff --git a/tests/run-make/autodiff/type-trees/slice-dst-typetree/slice-dst.check b/tests/run-make/autodiff/type-trees/slice-dst-typetree/slice-dst.check index 6031b728213ae..4b149c9ee090c 100644 --- a/tests/run-make/autodiff/type-trees/slice-dst-typetree/slice-dst.check +++ b/tests/run-make/autodiff/type-trees/slice-dst-typetree/slice-dst.check @@ -7,3 +7,8 @@ OSSTR: call void @llvm.memcpy{{.*}}"enzyme_type"="{[0]:Pointer, [0,0]:Pointer, [ HEADER-LABEL: define{{.*}}@header_sum( HEADER-SAME: ptr{{.*}}"enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float, [-1,4]:Float@float}" HEADER-SAME: i64 "enzyme_type"="{[0]:Integer}" + +; ZST elements produce no child metadata under the slice data pointer. +ZST-LABEL: define{{.*}}@zst_slice_len( +ZST-SAME: ptr{{.*}}"enzyme_type"="{[-1]:Pointer}" +ZST-SAME: i64 "enzyme_type"="{[0]:Integer}" diff --git a/tests/run-make/autodiff/type-trees/slice-dst-typetree/test.rs b/tests/run-make/autodiff/type-trees/slice-dst-typetree/test.rs index 8cd4d7b57f270..e34a8c71f4cc5 100644 --- a/tests/run-make/autodiff/type-trees/slice-dst-typetree/test.rs +++ b/tests/run-make/autodiff/type-trees/slice-dst-typetree/test.rs @@ -36,3 +36,16 @@ pub fn header_sum(value: &Header<[f32]>) -> f32 { pub fn exercise_header_sum(value: &Header<[f32]>, derivative: &mut Header<[f32]>) -> f32 { d_header_sum(value, derivative, 1.0) } + +// ZST slice elements yield an empty child TypeTree; element size 0 is expected. +#[autodiff_reverse(d_zst_slice_len, Duplicated, Active)] +#[no_mangle] +#[inline(never)] +pub fn zst_slice_len(slice: &[()]) -> f32 { + slice.len() as f32 +} + +#[no_mangle] +pub fn exercise_zst_slice_len(slice: &[()], derivative: &mut [()]) -> f32 { + d_zst_slice_len(slice, derivative, 1.0) +} From 679481475548fc354e162809e4c64591423c66ef Mon Sep 17 00:00:00 2001 From: jackh726 Date: Mon, 3 Aug 2026 22:52:37 +0000 Subject: [PATCH 14/19] Add some tests for specialization. --- tests/crashes/{126268.rs => 102252-2.rs} | 5 +- tests/crashes/125014.rs | 17 --- ...associated-types-in-default-impl-bounds.rs | 18 +++ ...efault-assoc-type-recursion-issue-80700.rs | 35 +++++ ...ault-impl-coherence-overlap-issue-77026.rs | 27 ++++ ...-impl-coherence-overlap-issue-77026.stderr | 12 ++ ...efault-impl-not-a-candidate-issue-48515.rs | 64 +++++++++ ...lt-impl-not-a-candidate-issue-48515.stderr | 63 +++++++++ .../default-impl-not-an-impl.rs | 71 ++++++++++ .../default-impl-not-an-impl.stderr | 69 ++++++++++ .../default-impl-partial-and-inherits.rs | 111 +++++++++++++++ .../default-type-normalize-issue-50318.rs | 24 ++++ .../default-type-normalize-issue-50318.stderr | 18 +++ ...lf-projection-ice-issue-125014.next.stderr | 66 +++++++++ ...t-type-self-projection-ice-issue-125014.rs | 27 ++++ .../spec-influences-inference-issue-36262.rs | 128 ++++++++++++++++++ ...specialized-impl-projection-issue-32483.rs | 27 ++++ .../trait-alias-specialization-issue-74809.rs | 44 ++++++ 18 files changed, 808 insertions(+), 18 deletions(-) rename tests/crashes/{126268.rs => 102252-2.rs} (86%) delete mode 100644 tests/crashes/125014.rs create mode 100644 tests/ui/specialization/associated-types-in-default-impl-bounds.rs create mode 100644 tests/ui/specialization/default-assoc-type-recursion-issue-80700.rs create mode 100644 tests/ui/specialization/default-impl-coherence-overlap-issue-77026.rs create mode 100644 tests/ui/specialization/default-impl-coherence-overlap-issue-77026.stderr create mode 100644 tests/ui/specialization/default-impl-not-a-candidate-issue-48515.rs create mode 100644 tests/ui/specialization/default-impl-not-a-candidate-issue-48515.stderr create mode 100644 tests/ui/specialization/default-impl-not-an-impl.rs create mode 100644 tests/ui/specialization/default-impl-not-an-impl.stderr create mode 100644 tests/ui/specialization/default-impl-partial-and-inherits.rs create mode 100644 tests/ui/specialization/default-type-normalize-issue-50318.rs create mode 100644 tests/ui/specialization/default-type-normalize-issue-50318.stderr create mode 100644 tests/ui/specialization/default-type-self-projection-ice-issue-125014.next.stderr create mode 100644 tests/ui/specialization/default-type-self-projection-ice-issue-125014.rs create mode 100644 tests/ui/specialization/spec-influences-inference-issue-36262.rs create mode 100644 tests/ui/specialization/specialized-impl-projection-issue-32483.rs create mode 100644 tests/ui/specialization/trait-alias-specialization-issue-74809.rs diff --git a/tests/crashes/126268.rs b/tests/crashes/102252-2.rs similarity index 86% rename from tests/crashes/126268.rs rename to tests/crashes/102252-2.rs index 82e52fa115dc9..ccb15b82736e2 100644 --- a/tests/crashes/126268.rs +++ b/tests/crashes/102252-2.rs @@ -1,4 +1,5 @@ -//@ known-bug: #126268 +//@ known-bug: #102252 + #![feature(min_specialization)] trait Trait {} @@ -16,3 +17,5 @@ struct DatasetIter<'a, R: Data> { pub struct ArrayBase {} impl<'a> Trait for DatasetIter<'a, ArrayBase> {} + +fn main() {} diff --git a/tests/crashes/125014.rs b/tests/crashes/125014.rs deleted file mode 100644 index b29042ee5983a..0000000000000 --- a/tests/crashes/125014.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ known-bug: rust-lang/rust#125014 -//@ compile-flags: -Znext-solver=coherence -#![feature(specialization)] - -trait Foo {} - -impl Foo for ::Output {} - -impl Foo for u32 {} - -trait Assoc { - type Output; -} -impl Output for u32 {} -impl Assoc for ::Output { - default type Output = bool; -} diff --git a/tests/ui/specialization/associated-types-in-default-impl-bounds.rs b/tests/ui/specialization/associated-types-in-default-impl-bounds.rs new file mode 100644 index 0000000000000..ea7188810db07 --- /dev/null +++ b/tests/ui/specialization/associated-types-in-default-impl-bounds.rs @@ -0,0 +1,18 @@ +//@ check-pass + +#![allow(incomplete_features)] +#![feature(specialization)] + +// Tests that you can use a trait's associated types in the bounds of a default impl. +// Regression test for #52396. + +trait Foo { + type Baz; + fn bar(&self, _: Self::Baz); +} + +default impl> Foo for A { + fn bar(&self, _: isize) { } +} + +fn main() {} diff --git a/tests/ui/specialization/default-assoc-type-recursion-issue-80700.rs b/tests/ui/specialization/default-assoc-type-recursion-issue-80700.rs new file mode 100644 index 0000000000000..e013610121efe --- /dev/null +++ b/tests/ui/specialization/default-assoc-type-recursion-issue-80700.rs @@ -0,0 +1,35 @@ +//@ check-pass + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Tests that a blanket impl supplying a `default type` does not make a +// recursive trait requirement diverge. +// Regression test for #80700. + +use std::marker::PhantomData; + +struct Nil; +struct Cons(PhantomData<(Head, Tail)>); +struct Error; + +trait GetLast { + type Output; +} + +impl GetLast for T { + default type Output = Error; +} + +impl GetLast for Cons { + type Output = Nil; +} + +impl GetLast for Cons> +where + Cons: GetLast, +{ + type Output = as GetLast>::Output; +} + +fn main() {} diff --git a/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.rs b/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.rs new file mode 100644 index 0000000000000..6d610805608af --- /dev/null +++ b/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.rs @@ -0,0 +1,27 @@ +//@ check-fail + +#![feature(specialization)] +#![allow(incomplete_features)] + +// `default impl` still participates in coherence. However, we shouldn't get an overflow here. +// Regresion test for #77026. + +pub enum Either { + Left(L), + Right(R), +} + +default impl From for Either { + fn from(l: L) -> Self { + Either::Left(l) + } +} + +impl From for Either { + //~^ ERROR conflicting implementations of trait `From<_>` for type `Either<_, _>` + fn from(r: R) -> Self { + Either::Right(r) + } +} + +fn main() {} diff --git a/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.stderr b/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.stderr new file mode 100644 index 0000000000000..c9b30bf2f6495 --- /dev/null +++ b/tests/ui/specialization/default-impl-coherence-overlap-issue-77026.stderr @@ -0,0 +1,12 @@ +error[E0119]: conflicting implementations of trait `From<_>` for type `Either<_, _>` + --> $DIR/default-impl-coherence-overlap-issue-77026.rs:20:1 + | +LL | default impl From for Either { + | ------------------------------------------- first implementation here +... +LL | impl From for Either { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Either<_, _>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0119`. diff --git a/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.rs b/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.rs new file mode 100644 index 0000000000000..4e31dcf117daa --- /dev/null +++ b/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.rs @@ -0,0 +1,64 @@ +//@ check-fail + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Tests that we don't overflow when using `default impl`. +// Regression test for #48515, #98478, and #117909. + +// #48515 + +trait TypeString { + fn type_string() -> &'static str; +} + +default impl TypeString for T { + fn type_string() -> &'static str { + "unknown type" + } +} + +impl TypeString for () { + fn type_string() -> &'static str { + "()" + } +} + +// #98478 + +trait Spam {} + +trait SpamMore: Spam {} + +default impl Spam for T where T: SpamMore {} + +struct A; + +impl SpamMore for A {} +//~^ ERROR the trait bound `A: Spam` is not satisfied + +fn needs_spam() {} + +// #117909 + +trait Set { + fn contains(&self, bit: T); +} + +default impl Set<&T> for S +where + S: Set, +{ + fn contains(&self, _: &T) {} +} + +fn main() { + let _ = ::type_string(); + //~^ ERROR the trait bound `usize: TypeString` is not satisfied + + needs_spam::(); + //~^ ERROR the trait bound `A: Spam` is not satisfied + + 0u32.contains(()); + //~^ ERROR no method named `contains` found for type `u32` in the current scope +} diff --git a/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.stderr b/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.stderr new file mode 100644 index 0000000000000..91df5005d7095 --- /dev/null +++ b/tests/ui/specialization/default-impl-not-a-candidate-issue-48515.stderr @@ -0,0 +1,63 @@ +error[E0277]: the trait bound `A: Spam` is not satisfied + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:37:19 + | +LL | impl SpamMore for A {} + | ^ unsatisfied trait bound + | +help: the trait `Spam` is not implemented for `A` + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:35:1 + | +LL | struct A; + | ^^^^^^^^ +note: required by a bound in `SpamMore` + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:31:17 + | +LL | trait SpamMore: Spam {} + | ^^^^ required by this bound in `SpamMore` + +error[E0277]: the trait bound `usize: TypeString` is not satisfied + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:56:14 + | +LL | let _ = ::type_string(); + | ^^^^^ the trait `TypeString` is not implemented for `usize` + | +help: the trait `TypeString` is implemented for `()` + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:21:1 + | +LL | impl TypeString for () { + | ^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `A: Spam` is not satisfied + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:59:18 + | +LL | needs_spam::(); + | ^ unsatisfied trait bound + | +help: the trait `Spam` is not implemented for `A` + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:35:1 + | +LL | struct A; + | ^^^^^^^^ +note: required by a bound in `needs_spam` + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:40:18 + | +LL | fn needs_spam() {} + | ^^^^ required by this bound in `needs_spam` + +error[E0599]: no method named `contains` found for type `u32` in the current scope + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:62:10 + | +LL | 0u32.contains(()); + | ^^^^^^^^ method not found in `u32` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `Set` defines an item `contains`, perhaps you need to implement it + --> $DIR/default-impl-not-a-candidate-issue-48515.rs:44:1 + | +LL | trait Set { + | ^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0277, E0599. +For more information about an error, try `rustc --explain E0277`. diff --git a/tests/ui/specialization/default-impl-not-an-impl.rs b/tests/ui/specialization/default-impl-not-an-impl.rs new file mode 100644 index 0000000000000..2b0902173158e --- /dev/null +++ b/tests/ui/specialization/default-impl-not-an-impl.rs @@ -0,0 +1,71 @@ +//@ check-fail + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Tests that a `default impl` does not count as an *actual* impl, so it cannot +// be used to satisfy trait bounds. + +// A `default impl` may omit trait items, but a real impl may not. + +trait Gapped { + fn a(&self) -> u32; + fn b(&self) -> u32; +} + +default impl Gapped for T { + fn a(&self) -> u32 { + 1 + } +} + +impl Gapped for u8 {} +//~^ ERROR not all trait items implemented, missing: `b` + +// A `default impl` that defines *every* trait item is still not an impl. + +trait Foo { + fn f(&self) -> u32; +} + +default impl Foo for T { + fn f(&self) -> u32 { + 1 + } +} + +fn need_foo(t: &T) -> u32 { + t.f() +} + +trait Bar { + fn b(&self) -> u32; +} + +impl Bar for T { + fn b(&self) -> u32 { + self.f() + } +} + +fn need_bar(t: &T) -> u32 { + t.b() +} + +fn main() { + // as a bound (UFCS `::f` is the same trait-selection path, omitted) + need_foo(&0u32); + //~^ ERROR the trait bound `u32: Foo` is not satisfied + + // as a method-probe candidate + 0u32.f(); + //~^ ERROR no method named `f` found for type `u32` in the current scope + + // when building a vtable + let _: &dyn Foo = &0u32; + //~^ ERROR the trait bound `u32: Foo` is not satisfied + + // transitively, as another impl's where-clause + need_bar(&0i64); + //~^ ERROR the trait bound `i64: Bar` is not satisfied +} diff --git a/tests/ui/specialization/default-impl-not-an-impl.stderr b/tests/ui/specialization/default-impl-not-an-impl.stderr new file mode 100644 index 0000000000000..cde757b3f98a7 --- /dev/null +++ b/tests/ui/specialization/default-impl-not-an-impl.stderr @@ -0,0 +1,69 @@ +error[E0046]: not all trait items implemented, missing: `b` + --> $DIR/default-impl-not-an-impl.rs:22:1 + | +LL | fn b(&self) -> u32; + | ------------------- `b` from trait +... +LL | impl Gapped for u8 {} + | ^^^^^^^^^^^^^^^^^^ missing `b` in implementation + +error[E0277]: the trait bound `u32: Foo` is not satisfied + --> $DIR/default-impl-not-an-impl.rs:57:14 + | +LL | need_foo(&0u32); + | -------- ^^^^^ the trait `Foo` is not implemented for `u32` + | | + | required by a bound introduced by this call + | +note: required by a bound in `need_foo` + --> $DIR/default-impl-not-an-impl.rs:37:16 + | +LL | fn need_foo(t: &T) -> u32 { + | ^^^ required by this bound in `need_foo` + +error[E0599]: no method named `f` found for type `u32` in the current scope + --> $DIR/default-impl-not-an-impl.rs:61:10 + | +LL | 0u32.f(); + | ^ method not found in `u32` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `Foo` defines an item `f`, perhaps you need to implement it + --> $DIR/default-impl-not-an-impl.rs:27:1 + | +LL | trait Foo { + | ^^^^^^^^^ + +error[E0277]: the trait bound `u32: Foo` is not satisfied + --> $DIR/default-impl-not-an-impl.rs:65:23 + | +LL | let _: &dyn Foo = &0u32; + | ^^^^^ the trait `Foo` is not implemented for `u32` + | + = note: required for the cast from `&u32` to `&dyn Foo` + +error[E0277]: the trait bound `i64: Bar` is not satisfied + --> $DIR/default-impl-not-an-impl.rs:69:14 + | +LL | need_bar(&0i64); + | -------- ^^^^^ the trait `Foo` is not implemented for `i64` + | | + | required by a bound introduced by this call + | +note: required for `i64` to implement `Bar` + --> $DIR/default-impl-not-an-impl.rs:45:14 + | +LL | impl Bar for T { + | --- ^^^ ^ + | | + | unsatisfied trait bound introduced here +note: required by a bound in `need_bar` + --> $DIR/default-impl-not-an-impl.rs:51:16 + | +LL | fn need_bar(t: &T) -> u32 { + | ^^^ required by this bound in `need_bar` + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0046, E0277, E0599. +For more information about an error, try `rustc --explain E0046`. diff --git a/tests/ui/specialization/default-impl-partial-and-inherits.rs b/tests/ui/specialization/default-impl-partial-and-inherits.rs new file mode 100644 index 0000000000000..a2c622daafb08 --- /dev/null +++ b/tests/ui/specialization/default-impl-partial-and-inherits.rs @@ -0,0 +1,111 @@ +//@ run-pass + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Tests that a `default impl` does not need all items, but does contribute to +// the chain of specialization. + +// A partial `default impl` at each level of a 3-level chain. + +trait Foo { + type Assoc; + const N: u32; + fn from_root(&self) -> &'static str; + fn from_mid(&self) -> &'static str; + fn from_leaf(&self) -> &'static str; + fn from_trait(&self) -> &'static str { + "trait body" + } +} + +// root: assoc type, assoc const, one method +default impl Foo for T { + type Assoc = u8; + const N: u32 = 1; + fn from_root(&self) -> &'static str { + "root" + } +} + +// middle: one method +default impl Foo for T { + fn from_mid(&self) -> &'static str { + "mid" + } +} + +// leaf: one method. Everything else must come from the two ancestors, except +// `from_trait`, which no impl in the chain defines. +impl Foo for u32 { + fn from_leaf(&self) -> &'static str { + "leaf" + } +} + +// sibling leaf: overrides every inherited item, including assoc type and const +impl Foo for i8 { + type Assoc = bool; + const N: u32 = 2; + fn from_root(&self) -> &'static str { + "i8 root" + } + fn from_mid(&self) -> &'static str { + "i8 mid" + } + fn from_leaf(&self) -> &'static str { + "i8 leaf" + } + fn from_trait(&self) -> &'static str { + "i8 trait" + } +} + +fn generic(t: &T) -> [&'static str; 4] { + [t.from_root(), t.from_mid(), t.from_leaf(), t.from_trait()] +} + +// An empty `default impl`, and an empty real impl that inherits every item. + +trait Marker { + type A; + fn m(&self) -> &'static str; +} + +// Contributes nothing at all, and is still accepted. +default impl Marker for T {} + +// Covers every item of the trait. +default impl Marker for T { + type A = u8; + fn m(&self) -> &'static str { + "from default impl" + } +} + +// Declaration of intent and nothing else. This is what the `default impl` above +// is missing, and the only thing it is missing. +impl Marker for u32 {} + +fn main() { + // inherited across the chain, via a concrete receiver... + assert_eq!(0u32.from_root(), "root"); + assert_eq!(0u32.from_mid(), "mid"); + assert_eq!(0u32.from_leaf(), "leaf"); + assert_eq!(0u32.from_trait(), "trait body"); + assert_eq!(::N, 1); + // The omitting impl finalizes the ancestor's definition, so this normalizes. + let _: ::Assoc = 0u8; + + // ...and through a generic bound + assert_eq!(generic(&0u32), ["root", "mid", "leaf", "trait body"]); + assert_eq!(generic(&0i8), ["i8 root", "i8 mid", "i8 leaf", "i8 trait"]); + assert_eq!(::N, 2); + let _: ::Assoc = true; + + // empty impl really does implement: method, projection, and vtable + assert_eq!(0u32.m(), "from default impl"); + let _: ::A = 0u8; + let _: &dyn Marker = &0u32; + +} diff --git a/tests/ui/specialization/default-type-normalize-issue-50318.rs b/tests/ui/specialization/default-type-normalize-issue-50318.rs new file mode 100644 index 0000000000000..b69acfe47a929 --- /dev/null +++ b/tests/ui/specialization/default-type-normalize-issue-50318.rs @@ -0,0 +1,24 @@ +//@ check-fail +//@ known-bug: #50318 + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Tests that we can normalize a `default type`. + +trait Trait { + type AssocType; +} + +struct Struct {} + +impl Trait for Struct { + default type AssocType = i32; +} + +type AssocType = ::AssocType; + +fn main() { + assert_eq!(std::any::type_name::(), "i32"); + let x: AssocType = 0; +} diff --git a/tests/ui/specialization/default-type-normalize-issue-50318.stderr b/tests/ui/specialization/default-type-normalize-issue-50318.stderr new file mode 100644 index 0000000000000..b0d69287adac2 --- /dev/null +++ b/tests/ui/specialization/default-type-normalize-issue-50318.stderr @@ -0,0 +1,18 @@ +error[E0308]: mismatched types + --> $DIR/default-type-normalize-issue-50318.rs:23:24 + | +LL | let x: AssocType = 0; + | --------- ^ expected associated type, found integer + | | + | expected due to this + | + = note: expected associated type `::AssocType` + found type `{integer}` + = help: consider constraining the associated type `::AssocType` to `{integer}` or calling a method that returns `::AssocType` + = note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html + = note: the associated type `::AssocType` is defined as `{integer}` in the implementation, but the where-bound `Struct` shadows this definition + see issue #152409 for more information + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/specialization/default-type-self-projection-ice-issue-125014.next.stderr b/tests/ui/specialization/default-type-self-projection-ice-issue-125014.next.stderr new file mode 100644 index 0000000000000..7280c8213e5dd --- /dev/null +++ b/tests/ui/specialization/default-type-self-projection-ice-issue-125014.next.stderr @@ -0,0 +1,66 @@ +error[E0277]: the trait bound `u16: A` is not satisfied + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:12 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^ the trait `A` is not implemented for `u16` + | +help: the trait `A` is implemented for `::B` + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:1 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `u16: A` is not satisfied + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:12 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^ the trait `A` is not implemented for `u16` + | +help: the trait `A` is implemented for `::B` + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:1 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0277]: the trait bound `u16: A` is not satisfied + --> $DIR/default-type-self-projection-ice-issue-125014.rs:23:22 + | +LL | default type B = (); + | ^^ the trait `A` is not implemented for `u16` + | +help: the trait `A` is implemented for `::B` + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:1 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: the trait bound `u16: A` is not satisfied + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:12 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^ the trait `A` is not implemented for `u16` + | +help: the trait `A` is implemented for `::B` + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:1 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0277]: the trait bound `u16: A` is not satisfied + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:12 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^ the trait `A` is not implemented for `u16` + | +help: the trait `A` is implemented for `::B` + --> $DIR/default-type-self-projection-ice-issue-125014.rs:18:1 + | +LL | impl A for ::B { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/specialization/default-type-self-projection-ice-issue-125014.rs b/tests/ui/specialization/default-type-self-projection-ice-issue-125014.rs new file mode 100644 index 0000000000000..9b4e3eade03b6 --- /dev/null +++ b/tests/ui/specialization/default-type-self-projection-ice-issue-125014.rs @@ -0,0 +1,27 @@ +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@[current] known-bug: #125014 +//@[current] failure-status: 101 +//@[current] dont-check-compiler-stderr + +// Tests that we don't ICE when a `default type` is potentially used as a self-type in an impl. +// Regression for #125014. + +#![feature(specialization)] +#![allow(incomplete_features)] + +trait A { + type B; +} + +impl A for ::B { + //[next]~^ ERROR the trait bound `u16: A` is not satisfied + //[next]~^^ ERROR the trait bound `u16: A` is not satisfied + //[next]~^^^ ERROR the trait bound `u16: A` is not satisfied + //[next]~^^^^ ERROR the trait bound `u16: A` is not satisfied + default type B = (); + //[next]~^ ERROR the trait bound `u16: A` is not satisfied +} + +fn main() {} diff --git a/tests/ui/specialization/spec-influences-inference-issue-36262.rs b/tests/ui/specialization/spec-influences-inference-issue-36262.rs new file mode 100644 index 0000000000000..1e96fd5065097 --- /dev/null +++ b/tests/ui/specialization/spec-influences-inference-issue-36262.rs @@ -0,0 +1,128 @@ +//@ edition: 2021 +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver +//@[next] check-pass +//@[current] known-bug: #36262 +//@[current] dont-check-compiler-stderr + +// Tests that specialization does not leak into type inference. +// Regression for #36262 and duplicate issues. + +#![feature(specialization)] +#![allow(incomplete_features)] + +// Site 1: the receiver's type parameter, observed through a return position (#36262). +mod receiver_return { + struct My(T); + + trait Conv { + fn conv(self) -> T; + } + + impl Conv for My { + default fn conv(self) -> T { + self.0 + } + } + + impl Conv for My { + fn conv(self) -> u32 { + self.0 + } + } + + fn use_it() { + // Should infer `i32`; the sole `My` impl steers it to `u32`. + let x = My(0); + let _ = x.conv() + 0i32; + } +} + +// Site 2: a method argument's trait type parameter (#91973, #38516, #67918). +mod method_arg { + struct Foo; + + trait Bar { + fn bar(&self, _: T); + } + + impl Bar for Foo { + default fn bar(&self, _: T) {} + } + + impl Bar for Foo { + fn bar(&self, _: bool) {} + } + + fn use_it() { + // Should infer `{integer}`; the sole `Bar` impl steers it to `bool`. + Foo.bar(42); + } +} + +// Site 3: an explicit `_` in a UFCS trait reference (#40718). +mod ufcs_infer { + use std::vec; + + struct Foo(T); + + impl Foo { + fn build>(it: I) -> Foo { + // The second argument should infer to `I::IntoIter`; the sole + // `vec::IntoIter` impl steers it there. + >::from_iter(it.into_iter()) + } + } + + trait SpecExtend { + fn from_iter(iter: I) -> Self; + } + + impl SpecExtend for Foo + where + I: Iterator, + { + default fn from_iter(_: I) -> Self { + panic!() + } + } + + impl SpecExtend> for Foo { + fn from_iter(_: vec::IntoIter) -> Self { + panic!() + } + } +} + +// Site 4: an operator, where the sole specialization is derive-generated (#55243). +mod derived_specializer { + use std::borrow::Borrow; + + #[derive(PartialEq)] + struct MyString(String); + + impl Borrow for MyString { + fn borrow(&self) -> &str { + &self.0 + } + } + + impl PartialEq for MyString + where + Rhs: ?Sized + Borrow, + { + default fn eq(&self, rhs: &Rhs) -> bool { + self.0 == rhs.borrow() + } + } + + fn use_it() { + // Should select `PartialEq`; the derived `PartialEq` is the + // sole specialization and inference commits `Rhs = MyString`. + let s = MyString(String::from("Hello, world!")); + let _ = s == "Hello, world!"; + } +} + +fn main() {} diff --git a/tests/ui/specialization/specialized-impl-projection-issue-32483.rs b/tests/ui/specialization/specialized-impl-projection-issue-32483.rs new file mode 100644 index 0000000000000..5b26679422a40 --- /dev/null +++ b/tests/ui/specialization/specialized-impl-projection-issue-32483.rs @@ -0,0 +1,27 @@ +//@ check-pass + +#![allow(incomplete_features)] +#![feature(specialization)] + +// Tests that we allow some projections in specialized impls. +// Regression test for issue #32483. + +pub trait Foo { + type TypeA; + type TypeB: Bar; +} + +pub trait Bar { +} + +pub struct ImplsBar; +impl Bar for ImplsBar { +} + +impl Foo for T { + type TypeA = u8; + // WF checking `TypeB` here requires us to project `Self::TypeA` + default type TypeB = ImplsBar; +} + +fn main() {} diff --git a/tests/ui/specialization/trait-alias-specialization-issue-74809.rs b/tests/ui/specialization/trait-alias-specialization-issue-74809.rs new file mode 100644 index 0000000000000..e62532e8ab033 --- /dev/null +++ b/tests/ui/specialization/trait-alias-specialization-issue-74809.rs @@ -0,0 +1,44 @@ +//@ check-pass + +#![feature(specialization)] +#![feature(trait_alias)] +#![allow(incomplete_features)] + +// Tests that we can specialize on a trait alias. +// Regression test for #74809. + +pub trait Marker1 {} +pub trait Marker2 {} + +pub trait CombinedMarker = Marker1 + Marker2; + +pub struct Container { + p: std::marker::PhantomData<(T, U)>, +} + +pub struct Struct; +impl Marker1 for Struct {} + +pub trait Trait { + fn do_thing(&self); +} + +impl> Trait for Container { + default fn do_thing(&self) { + println!("default behavior"); + } +} + +impl> Trait for Container { + default fn do_thing(&self) { + println!("partially specialized behavior"); + } +} + +impl Trait for Container { + fn do_thing(&self) { + println!("fully specialized behavior") + } +} + +fn main() {} From 228bbb36ad155d5fbd2d1f783742a4c7ecd2e0c5 Mon Sep 17 00:00:00 2001 From: Jamie Hill-Daniel Date: Tue, 4 Aug 2026 20:11:32 +0100 Subject: [PATCH 15/19] fix(bootstrap): Normalize the names of proc macro dependency crates --- src/bootstrap/src/utils/proc_macro_deps.rs | 56 +++++++++++----------- src/tools/tidy/src/deps.rs | 11 ++++- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/bootstrap/src/utils/proc_macro_deps.rs b/src/bootstrap/src/utils/proc_macro_deps.rs index 8e91f95e18f3f..02d8fe41f5869 100644 --- a/src/bootstrap/src/utils/proc_macro_deps.rs +++ b/src/bootstrap/src/utils/proc_macro_deps.rs @@ -6,46 +6,46 @@ pub static CRATES: &[&str] = &[ "anyhow", "askama_derive", "askama_parser", - "basic-toml", + "basic_toml", "bitflags", - "block-buffer", + "block_buffer", "bumpalo", - "cfg-if", + "cfg_if", "cpufeatures", - "crypto-common", + "crypto_common", "darling", "darling_core", "derive_builder_core", "digest", "equivalent", - "fluent-bundle", - "fluent-langneg", - "fluent-syntax", + "fluent_bundle", + "fluent_langneg", + "fluent_syntax", "fnv", "foldhash", - "generic-array", + "generic_array", "glob", "hashbrown", "heck", - "id-arena", + "id_arena", "ident_case", "indexmap", - "intl-memoizer", + "intl_memoizer", "intl_pluralrules", "itoa", "leb128fmt", "libc", "log", "memchr", - "minimal-lexical", + "minimal_lexical", "nom", "pest", "pest_generator", "pest_meta", "prettyplease", - "proc-macro2", + "proc_macro2", "quote", - "rustc-hash", + "rustc_hash", "ryu", "self_cell", "semver", @@ -61,25 +61,25 @@ pub static CRATES: &[&str] = &[ "synstructure", "thiserror", "tinystr", - "type-map", + "type_map", "typenum", - "ucd-trie", - "unic-langid", - "unic-langid-impl", - "unic-langid-macros", - "unicode-ident", - "unicode-xid", + "ucd_trie", + "unic_langid", + "unic_langid_impl", + "unic_langid_macros", + "unicode_ident", + "unicode_xid", "version_check", - "wasm-bindgen-macro-support", - "wasm-bindgen-shared", - "wasm-encoder", - "wasm-metadata", + "wasm_bindgen_macro_support", + "wasm_bindgen_shared", + "wasm_encoder", + "wasm_metadata", "wasmparser", "winnow", - "wit-bindgen-core", - "wit-bindgen-rust", - "wit-component", - "wit-parser", + "wit_bindgen_core", + "wit_bindgen_rust", + "wit_component", + "wit_parser", "yoke", "zerofrom", "zerovec", diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index 479199414d7ec..734ca79518090 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -719,8 +719,15 @@ fn check_proc_macro_dep_list(root: &Path, cargo: &Path, bless: bool, check: &mut // Remove the proc-macro crates themselves proc_macro_deps.retain(|pkg| !is_proc_macro_pkg(&metadata[pkg])); // Sort and deduplicate the crate names. - let proc_macro_deps = - proc_macro_deps.into_iter().map(|dep| metadata[dep].name.as_ref()).collect::>(); + // Cargo package names may contain `-`, but will normalize these to `_` before passing to rustc. + // As bootstrap parses the `--crate-name` flag, use the name of the actual lib target which has + // been normalized. + let proc_macro_deps = proc_macro_deps + .into_iter() + .filter_map(|dep| { + metadata[dep].targets.iter().find_map(|target| target.is_lib().then_some(&target.name)) + }) + .collect::>(); let expected = { use std::fmt::Write; From a6dfd0cc18614a4232d0e533539bad9981a49efd Mon Sep 17 00:00:00 2001 From: derek-homel Date: Tue, 4 Aug 2026 19:02:23 -0400 Subject: [PATCH 16/19] docs: fix typo in AllowExprMetavar comment Fixes a small typo in the documentation comment for AllowExprMetavar. Changes decrarative to `declarative`. Change in compiler/rustc_attr_parsing/src/parser.rs: Line 492 --- compiler/rustc_attr_parsing/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index c4b2a5b509051..76587ba9f0ead 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -489,7 +489,7 @@ fn expr_to_lit<'sess>( } } -/// Whether expansions of `expr` metavariables from decrarative macros +/// Whether expansions of `expr` metavariables from declarative macros /// are permitted. Used when parsing meta items; currently, only `cfg` predicates /// enable this option #[derive(Clone, Copy, PartialEq, Eq)] From b5687751ea4f06cd14b48dd159d283e22af1a778 Mon Sep 17 00:00:00 2001 From: CacinieP Date: Wed, 5 Aug 2026 11:22:20 +0800 Subject: [PATCH 17/19] Update expect messages in tcp.rs doc examples to follow the style guide Reword the `.expect(...)` messages in the TcpStream/TcpListener doc examples in library/std/src/net/tcp.rs to follow the 'expect as precondition' style from the std library guidance (describe why the operation is expected to succeed, rather than restating the failure). Examples: "set_nodelay call failed" -> "set_nodelay should succeed" "could not set TTL" -> "set_ttl should succeed" "Cannot set non-blocking" -> "set_nonblocking should succeed" Doc-only change, no behavior change. --- library/std/src/net/tcp.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index b673abdff7ba1..d9090320bd5a6 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -239,7 +239,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.shutdown(Shutdown::Both).expect("shutdown call failed"); + /// stream.shutdown(Shutdown::Both).expect("shutdown should succeed"); /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { @@ -260,7 +260,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// let stream_clone = stream.try_clone().expect("clone failed..."); + /// let stream_clone = stream.try_clone().expect("clone should succeed"); /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn try_clone(&self) -> io::Result { @@ -290,7 +290,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_read_timeout(None).expect("set_read_timeout call failed"); + /// stream.set_read_timeout(None).expect("set_read_timeout should succeed"); /// ``` /// /// An [`Err`] is returned if the zero [`Duration`] is passed to this @@ -334,7 +334,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_write_timeout(None).expect("set_write_timeout call failed"); + /// stream.set_write_timeout(None).expect("set_write_timeout should succeed"); /// ``` /// /// An [`Err`] is returned if the zero [`Duration`] is passed to this @@ -372,7 +372,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_read_timeout(None).expect("set_read_timeout call failed"); + /// stream.set_read_timeout(None).expect("set_read_timeout should succeed"); /// assert_eq!(stream.read_timeout().unwrap(), None); /// ``` #[stable(feature = "socket_timeout", since = "1.4.0")] @@ -397,7 +397,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_write_timeout(None).expect("set_write_timeout call failed"); + /// stream.set_write_timeout(None).expect("set_write_timeout should succeed"); /// assert_eq!(stream.write_timeout().unwrap(), None); /// ``` #[stable(feature = "socket_timeout", since = "1.4.0")] @@ -420,7 +420,7 @@ impl TcpStream { /// let stream = TcpStream::connect("127.0.0.1:8000") /// .expect("Couldn't connect to the server..."); /// let mut buf = [0; 10]; - /// let len = stream.peek(&mut buf).expect("peek failed"); + /// let len = stream.peek(&mut buf).expect("peek should succeed"); /// ``` #[stable(feature = "peek", since = "1.18.0")] pub fn peek(&self, buf: &mut [u8]) -> io::Result { @@ -445,7 +445,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger call failed"); + /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger should succeed"); /// ``` #[unstable(feature = "tcp_linger", issue = "88494")] pub fn set_linger(&self, linger: Option) -> io::Result<()> { @@ -466,7 +466,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger call failed"); + /// stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger should succeed"); /// assert_eq!(stream.linger().unwrap(), Some(Duration::from_secs(0))); /// ``` #[unstable(feature = "tcp_linger", issue = "88494")] @@ -498,7 +498,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_keepalive(true).expect("set_keepalive call failed"); + /// stream.set_keepalive(true).expect("set_keepalive should succeed"); #[unstable(feature = "tcp_keepalive", issue = "155889")] pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> { self.0.set_keepalive(keepalive) @@ -517,7 +517,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_keepalive(true).expect("set_keepalive call failed"); + /// stream.set_keepalive(true).expect("set_keepalive should succeed"); /// assert_eq!(stream.keepalive().unwrap_or(false), true); /// ``` #[unstable(feature = "tcp_keepalive", issue = "155889")] @@ -540,7 +540,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_nodelay(true).expect("set_nodelay call failed"); + /// stream.set_nodelay(true).expect("set_nodelay should succeed"); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] pub fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { @@ -558,7 +558,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_nodelay(true).expect("set_nodelay call failed"); + /// stream.set_nodelay(true).expect("set_nodelay should succeed"); /// assert_eq!(stream.nodelay().unwrap_or(false), true); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] @@ -578,7 +578,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_ttl(100).expect("set_ttl call failed"); + /// stream.set_ttl(100).expect("set_ttl should succeed"); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { @@ -596,7 +596,7 @@ impl TcpStream { /// /// let stream = TcpStream::connect("127.0.0.1:8080") /// .expect("Couldn't connect to the server..."); - /// stream.set_ttl(100).expect("set_ttl call failed"); + /// stream.set_ttl(100).expect("set_ttl should succeed"); /// assert_eq!(stream.ttl().unwrap_or(0), 100); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] @@ -647,7 +647,7 @@ impl TcpStream { /// /// let mut stream = TcpStream::connect("127.0.0.1:7878") /// .expect("Couldn't connect to the server..."); - /// stream.set_nonblocking(true).expect("set_nonblocking call failed"); + /// stream.set_nonblocking(true).expect("set_nonblocking should succeed"); /// /// # fn wait_for_fd() { unimplemented!() } /// let mut buf = vec![]; @@ -1006,7 +1006,7 @@ impl TcpListener { /// use std::net::TcpListener; /// /// let listener = TcpListener::bind("127.0.0.1:80").unwrap(); - /// listener.set_ttl(100).expect("could not set TTL"); + /// listener.set_ttl(100).expect("set_ttl should succeed"); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] pub fn set_ttl(&self, ttl: u32) -> io::Result<()> { @@ -1023,7 +1023,7 @@ impl TcpListener { /// use std::net::TcpListener; /// /// let listener = TcpListener::bind("127.0.0.1:80").unwrap(); - /// listener.set_ttl(100).expect("could not set TTL"); + /// listener.set_ttl(100).expect("set_ttl should succeed"); /// assert_eq!(listener.ttl().unwrap_or(0), 100); /// ``` #[stable(feature = "net2_mutators", since = "1.9.0")] @@ -1086,7 +1086,7 @@ impl TcpListener { /// use std::net::TcpListener; /// /// let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); - /// listener.set_nonblocking(true).expect("Cannot set non-blocking"); + /// listener.set_nonblocking(true).expect("set_nonblocking should succeed"); /// /// # fn wait_for_fd() { unimplemented!() } /// # fn handle_connection(stream: std::net::TcpStream) { unimplemented!() } From eace512093ce4d96afcb7352f595bb72f1ba6bb8 Mon Sep 17 00:00:00 2001 From: YingqiDuan <141370165+YingqiDuan@users.noreply.github.com> Date: Wed, 5 Aug 2026 05:31:06 +0000 Subject: [PATCH 18/19] Suggest cast_signed for overflowing integer literals Co-authored-by: Roland Xu --- compiler/rustc_lint/src/lints.rs | 39 +++++++++++++------ compiler/rustc_lint/src/types/literal.rs | 26 +++++++++---- .../no-inline-literals-out-of-range.stderr | 9 +++-- tests/ui/lint/type-overflow.stderr | 14 ++++--- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index bff3b79df8655..07279a04b0c8c 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -2030,18 +2030,33 @@ pub(crate) enum OverflowingBinHexSub<'a> { } #[derive(Subdiagnostic)] -#[suggestion( - "to use as a negative number (decimal `{$negative_val}`), consider using the type `{$uint_ty}` for the literal and cast it to `{$int_ty}`", - code = "{lit_no_suffix}{uint_ty} as {int_ty}", - applicability = "maybe-incorrect" -)] -pub(crate) struct OverflowingBinHexSignBitSub<'a> { - #[primary_span] - pub span: Span, - pub lit_no_suffix: &'a str, - pub negative_val: String, - pub uint_ty: &'a str, - pub int_ty: &'a str, +pub(crate) enum OverflowingBinHexSignBitSub<'a> { + #[suggestion( + "to use as a negative number (decimal `{$negative_val}`), consider using the type `{$uint_ty}` for the literal and cast it to `{$int_ty}`", + code = "{lit_no_suffix}{uint_ty}.cast_signed()", + applicability = "maybe-incorrect" + )] + CastSigned { + #[primary_span] + span: Span, + lit_no_suffix: &'a str, + negative_val: String, + uint_ty: &'a str, + int_ty: &'a str, + }, + #[suggestion( + "to use as a negative number (decimal `{$negative_val}`), consider using the type `{$uint_ty}` for the literal and cast it to `{$int_ty}`", + code = "{lit_no_suffix}{uint_ty} as {int_ty}", + applicability = "maybe-incorrect" + )] + AsCast { + #[primary_span] + span: Span, + lit_no_suffix: &'a str, + negative_val: String, + uint_ty: &'a str, + int_ty: &'a str, + }, } #[derive(Diagnostic)] diff --git a/compiler/rustc_lint/src/types/literal.rs b/compiler/rustc_lint/src/types/literal.rs index bed26ee6f3d25..4759f087ed08b 100644 --- a/compiler/rustc_lint/src/types/literal.rs +++ b/compiler/rustc_lint/src/types/literal.rs @@ -205,13 +205,25 @@ fn report_bin_hex_error( &repr_str }; - Some(OverflowingBinHexSignBitSub { - span, - lit_no_suffix, - negative_val: actually, - int_ty: int_ty.name_str(), - uint_ty: Integer::fit_unsigned(val).uint_ty_str(), - }) + let uint_ty = Integer::fit_unsigned(val); + // `cast_signed` only supports equal-width integer casts. + if uint_ty.size() == size { + Some(OverflowingBinHexSignBitSub::CastSigned { + span, + lit_no_suffix, + negative_val: actually, + uint_ty: uint_ty.uint_ty_str(), + int_ty: int_ty.name_str(), + }) + } else { + Some(OverflowingBinHexSignBitSub::AsCast { + span, + lit_no_suffix, + negative_val: actually, + uint_ty: uint_ty.uint_ty_str(), + int_ty: int_ty.name_str(), + }) + } }) .flatten(); diff --git a/tests/ui/fmt/no-inline-literals-out-of-range.stderr b/tests/ui/fmt/no-inline-literals-out-of-range.stderr index 0800fb2497619..744a4e5625fef 100644 --- a/tests/ui/fmt/no-inline-literals-out-of-range.stderr +++ b/tests/ui/fmt/no-inline-literals-out-of-range.stderr @@ -13,8 +13,9 @@ LL + format_args!("{}", 0x8f_u8); // issue #115423 | help: to use as a negative number (decimal `-113`), consider using the type `u8` for the literal and cast it to `i8` | -LL | format_args!("{}", 0x8f_u8 as i8); // issue #115423 - | +++++ +LL - format_args!("{}", 0x8f_i8); // issue #115423 +LL + format_args!("{}", 0x8f_u8.cast_signed()); // issue #115423 + | error: literal out of range for `u8` --> $DIR/no-inline-literals-out-of-range.rs:6:24 @@ -50,8 +51,8 @@ LL | format_args!("{}", 0xffff_ffff); // treat unsuffixed literals as i32 = help: consider using the type `u32` instead help: to use as a negative number (decimal `-1`), consider using the type `u32` for the literal and cast it to `i32` | -LL | format_args!("{}", 0xffff_ffffu32 as i32); // treat unsuffixed literals as i32 - | ++++++++++ +LL | format_args!("{}", 0xffff_ffffu32.cast_signed()); // treat unsuffixed literals as i32 + | +++++++++++++++++ error: aborting due to 5 previous errors diff --git a/tests/ui/lint/type-overflow.stderr b/tests/ui/lint/type-overflow.stderr index 065c530adcf57..66d856dac3bdf 100644 --- a/tests/ui/lint/type-overflow.stderr +++ b/tests/ui/lint/type-overflow.stderr @@ -26,8 +26,9 @@ LL + let fail = 0b1000_0001u8; | help: to use as a negative number (decimal `-127`), consider using the type `u8` for the literal and cast it to `i8` | -LL | let fail = 0b1000_0001u8 as i8; - | +++++ +LL - let fail = 0b1000_0001i8; +LL + let fail = 0b1000_0001u8.cast_signed(); + | warning: literal out of range for `i64` --> $DIR/type-overflow.rs:15:16 @@ -43,8 +44,9 @@ LL + let fail = 0x8000_0000_0000_0000u64; | help: to use as a negative number (decimal `-9223372036854775808`), consider using the type `u64` for the literal and cast it to `i64` | -LL | let fail = 0x8000_0000_0000_0000u64 as i64; - | ++++++ +LL - let fail = 0x8000_0000_0000_0000i64; +LL + let fail = 0x8000_0000_0000_0000u64.cast_signed(); + | warning: literal out of range for `u32` --> $DIR/type-overflow.rs:19:16 @@ -64,8 +66,8 @@ LL | let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000; = help: consider using the type `u128` instead help: to use as a negative number (decimal `-170141183460469231731687303715884105728`), consider using the type `u128` for the literal and cast it to `i128` | -LL | let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000u128 as i128; - | ++++++++++++ +LL | let fail: i128 = 0x8000_0000_0000_0000_0000_0000_0000_0000u128.cast_signed(); + | ++++++++++++++++++ warning: literal out of range for `i32` --> $DIR/type-overflow.rs:27:16 From a91590b3f11005b2146066bd9a52e1fc1b16f71a Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 3 Aug 2026 10:43:34 +0200 Subject: [PATCH 19/19] move mir-opt miri tests to CI logic also refactor check-miri a bit to make it easier to read --- src/bootstrap/src/core/build_steps/test.rs | 24 ------------------ .../host-x86_64/x86_64-gnu-miri/check-miri.sh | 25 ++++++++++--------- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 3015d5a83db8d..f9846b7b41150 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -769,30 +769,6 @@ impl CommandLineStep for Miri { let _time = helpers::timeit(builder); cargo.run(builder); } - - // Run it again for mir-opt-level 4 to catch some miscompilations. - if builder.config.test_args().is_empty() { - cargo.env( - "MIRIFLAGS", - format!( - "{} -O -Zmir-opt-level=4 -Cdebug-assertions=yes", - env::var("MIRIFLAGS").unwrap_or_default() - ), - ); - // Optimizations can change backtraces - cargo.env("MIRI_SKIP_UI_CHECKS", "1"); - // `MIRI_SKIP_UI_CHECKS` and `RUSTC_BLESS` are incompatible - cargo.env_remove("RUSTC_BLESS"); - // Optimizations can change error locations and remove UB so don't run `fail` tests. - cargo.args(["tests/pass", "tests/panic"]); - - { - let _guard = - builder.msg_test("miri (mir-opt-level 4)", target, target_compiler.stage); - let _time = helpers::timeit(builder); - cargo.run(builder); - } - } } } diff --git a/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh b/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh index 8d7206d7391e2..9d4ec50e5f534 100755 --- a/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh +++ b/src/ci/docker/host-x86_64/x86_64-gnu-miri/check-miri.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # ignore-tidy-file-linelength set -eu @@ -15,6 +15,8 @@ if [ -z "${PR_CI_JOB:-}" ]; then else python3 "$X_PY" test --stage 2 miri cargo-miri fi +# Run the test suite again with mir optimizations, to catch some miscompilations. +MIRIFLAGS="-O -Zmir-opt-level=4 -Cdebug-assertions=yes" MIRI_SKIP_UI_CHECKS=1 python3 "$X_PY" test --stage 2 miri -- tests/{pass,panic} # We natively run this script on x86_64-unknown-linux-gnu and x86_64-pc-windows-msvc. # Also cover some other targets via cross-testing, in particular all tier 1 targets. case $HOST_TARGET in @@ -23,13 +25,12 @@ case $HOST_TARGET in # Fully test all main OSes, and all main architectures. python3 "$X_PY" test --stage 2 miri cargo-miri --target aarch64-apple-darwin python3 "$X_PY" test --stage 2 miri cargo-miri --target i686-pc-windows-msvc - # Only run "pass" tests for the remaining targets, which is quite a bit faster. - # We have to use `miri` instead of `src/tools/miri` here to avoid also running the cargo-miri - # tests. - python3 "$X_PY" test --stage 2 miri --target x86_64-pc-windows-gnu --test-args pass - python3 "$X_PY" test --stage 2 miri --target i686-unknown-linux-gnu --test-args pass - python3 "$X_PY" test --stage 2 miri --target aarch64-unknown-linux-gnu --test-args pass - python3 "$X_PY" test --stage 2 miri --target s390x-unknown-linux-gnu --test-args pass + # Only run "pass" tests for the remaining targets, which is a bit faster. We have to use `miri` + # instead of `src/tools/miri` here to avoid also running the cargo-miri tests. + python3 "$X_PY" test --stage 2 miri --target x86_64-pc-windows-gnu -- tests/pass + python3 "$X_PY" test --stage 2 miri --target i686-unknown-linux-gnu -- tests/pass + python3 "$X_PY" test --stage 2 miri --target aarch64-unknown-linux-gnu -- tests/pass + python3 "$X_PY" test --stage 2 miri --target s390x-unknown-linux-gnu -- tests/pass ;; x86_64-pc-windows-msvc) # Strangely, Linux targets do not work here. cargo always says @@ -38,7 +39,7 @@ case $HOST_TARGET in #FIXME: Re-enable this once CI issues are fixed # See # For now, these tests are moved to `x86_64-msvc-ext2` in `src/ci/github-actions/jobs.yml`. - #python3 "$X_PY" test --stage 2 miri --target x86_64-apple-darwin --test-args pass + #python3 "$X_PY" test --stage 2 miri --target x86_64-apple-darwin -- pass ;; *) echo "FATAL: unexpected host $HOST_TARGET" @@ -50,7 +51,7 @@ esac #FIXME: Re-enable this for msvc once CI issues are fixed if [ "$HOST_TARGET" != "x86_64-pc-windows-msvc" ]; then - python3 "$X_PY" miri --stage 2 library/core --test-args notest - python3 "$X_PY" miri --stage 2 library/alloc --test-args notest - python3 "$X_PY" miri --stage 2 library/std --test-args notest + python3 "$X_PY" miri --stage 2 library/core -- notest + python3 "$X_PY" miri --stage 2 library/alloc -- notest + python3 "$X_PY" miri --stage 2 library/std -- notest fi