From acd02b866bf58c99d56f45a4d7887f861b746f44 Mon Sep 17 00:00:00 2001 From: Autumn Dickinson Date: Wed, 9 Sep 2026 13:38:54 -0700 Subject: [PATCH 01/45] document that `!` does bitwise and logical negation --- library/core/src/ops/bit.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/library/core/src/ops/bit.rs b/library/core/src/ops/bit.rs index 7c008531c1f5a..5b80b4edc20de 100644 --- a/library/core/src/ops/bit.rs +++ b/library/core/src/ops/bit.rs @@ -1,4 +1,5 @@ -/// The unary logical negation operator `!`. +/// The unary negation operator `!`. It performs either logical or bitwise negation, +/// depending on the type. /// /// # Examples /// @@ -37,15 +38,23 @@ pub const trait Not { #[stable(feature = "rust1", since = "1.0.0")] type Output; - /// Performs the unary `!` operation. + /// Performs the unary `!` operation using logical or bitwise negation. /// /// # Examples /// /// ``` + /// // Logical negation changes between true and false: /// assert_eq!(!true, false); /// assert_eq!(!false, true); + /// + /// // Bitwise negation changes between 0 and 1 in the binary + /// // representation of integers: /// assert_eq!(!1u8, 254); /// assert_eq!(!0u8, 255); + /// + /// // The same examples, represented in binary: + /// assert_eq!(!0b0000_0001u8, 0b1111_1110); + /// assert_eq!(!0b0000_0000u8, 0b1111_1111); /// ``` #[must_use] #[stable(feature = "rust1", since = "1.0.0")] From 39a66e757920dfc94fe7dcb1b026ec18af0c6405 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:09:13 +0000 Subject: [PATCH 02/45] Remove unused method --- compiler/rustc_middle/src/dep_graph/graph.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/rustc_middle/src/dep_graph/graph.rs b/compiler/rustc_middle/src/dep_graph/graph.rs index dcb5775f20595..f93b80cc6c95c 100644 --- a/compiler/rustc_middle/src/dep_graph/graph.rs +++ b/compiler/rustc_middle/src/dep_graph/graph.rs @@ -861,12 +861,6 @@ impl DepGraph { self.data.as_ref().and_then(|data| data.previous_work_products.get(v).cloned()) } - /// Access the map of work-products created during the cached run. Only - /// used during saving of the dep-graph. - pub fn previous_work_products(&self) -> &WorkProductMap { - &self.data.as_ref().unwrap().previous_work_products - } - pub fn debug_was_loaded_from_disk(&self, dep_node: DepNode) -> bool { self.data.as_ref().unwrap().debug_loaded_from_disk.lock().contains(&dep_node) } From dc513a511a7c7569d5db790d538bdf0e8f6d8243 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 25 Sep 2026 09:47:37 +0000 Subject: [PATCH 03/45] Move some LLVM specific options from Linker::link to cg_llvm --- .../src/back/llvm_backend.rs | 39 +++++++++++++------ .../rustc_codegen_ssa/src/traits/backend.rs | 8 ---- compiler/rustc_interface/src/queries.rs | 24 ------------ 3 files changed, 27 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 8780af2f6d595..7fa7086d57456 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -377,21 +377,36 @@ impl CodegenBackend for LlvmCodegenBackend { }); } - (compiled_modules, work_products) - } + if sess.codegen_units().as_usize() == 1 && sess.opts.unstable_opts.time_llvm_passes { + let timings = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintPassTimings(s) }).unwrap(); + print!("{timings}"); + } - fn print_pass_timings(&self) { - let timings = llvm::build_string(|s| unsafe { llvm::LLVMRustPrintPassTimings(s) }).unwrap(); - print!("{timings}"); - } + if sess.print_llvm_stats() { + let stats = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatistics(s) }).unwrap(); + print!("{stats}"); + } - fn print_statistics(&self) { - let stats = llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatistics(s) }).unwrap(); - print!("{stats}"); - } + if let Some(out_path) = sess.print_llvm_stats_json() { + let llvm_stats_json = + llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatisticsJSON(s) }).unwrap(); - fn print_statistics_json(&self) -> String { - llvm::build_string(|s| unsafe { llvm::LLVMRustPrintStatisticsJSON(s) }).unwrap() + if !llvm_stats_json.is_empty() { + if let Err(e) = std::fs::write(&out_path, llvm_stats_json) { + sess.dcx().err(format!("failed to write stats to {}: {}", out_path, e)); + } + } else { + sess.dcx().warn(format!( + "requested to print LLVM statistics to JSON file {}, but the codegen backend \ + did not provide any statistics", + out_path, + )); + } + } + + (compiled_modules, work_products) } fn link( diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index e06ac36fd758a..bf5d67c213573 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -120,14 +120,6 @@ pub trait CodegenBackend { crate_info: &CrateInfo, ) -> (CompiledModules, WorkProductMap); - fn print_pass_timings(&self) {} - - fn print_statistics(&self) {} - - fn print_statistics_json(&self) -> String { - String::new() - } - /// This is called on the returned [`CompiledModules`] from [`join_codegen`](Self::join_codegen). fn link( &self, diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs index 759297bc69592..94cc1484586c8 100644 --- a/compiler/rustc_interface/src/queries.rs +++ b/compiler/rustc_interface/src/queries.rs @@ -67,30 +67,6 @@ impl Linker { } }); - if sess.codegen_units().as_usize() == 1 && sess.opts.unstable_opts.time_llvm_passes { - codegen_backend.print_pass_timings() - } - - if sess.print_llvm_stats() { - codegen_backend.print_statistics() - } - - if let Some(out_path) = sess.print_llvm_stats_json() { - let llvm_stats_json = codegen_backend.print_statistics_json(); - - if !llvm_stats_json.is_empty() { - if let Err(e) = std::fs::write(&out_path, llvm_stats_json) { - sess.dcx().err(format!("failed to write stats to {}: {}", out_path, e)); - } - } else { - sess.dcx().warn(format!( - "requested to print LLVM statistics to JSON file {}, but the codegen backend \ - did not provide any statistics", - out_path, - )); - } - } - sess.timings.end_section(sess.dcx(), TimingSection::Codegen); if sess.opts.incremental.is_some() From 2b7a3ab6b282c0fb66173e367f7d521e4126cc30 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 30 Sep 2026 13:47:13 +0200 Subject: [PATCH 04/45] Apply suggestion Co-authored-by: Bruno Kolenbrander <59372212+mejrs@users.noreply.github.com> --- compiler/rustc_codegen_llvm/src/back/llvm_backend.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index 7fa7086d57456..e8bf1cf9a6f45 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -395,13 +395,12 @@ impl CodegenBackend for LlvmCodegenBackend { if !llvm_stats_json.is_empty() { if let Err(e) = std::fs::write(&out_path, llvm_stats_json) { - sess.dcx().err(format!("failed to write stats to {}: {}", out_path, e)); + sess.dcx().err(format!("failed to write stats to {out_path}: {e}")); } } else { sess.dcx().warn(format!( - "requested to print LLVM statistics to JSON file {}, but the codegen backend \ + "requested to print LLVM statistics to JSON file {out_path}, but the codegen backend \ did not provide any statistics", - out_path, )); } } From e243b6db3ffc86c98b482d8a9266cddd2dae664c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:38:04 +0000 Subject: [PATCH 05/45] Move incr comp session dirs out of CodegenContext CodegenContext will in the future be serialized and deserialized into a different rustc instance when -Zno-link/-Zlink-only is used. A separate incr comp session has to be used for both rustc invocations. --- .../rustc_codegen_cranelift/src/driver/aot.rs | 3 +- compiler/rustc_codegen_gcc/src/lib.rs | 5 +- .../src/back/llvm_backend.rs | 6 +- compiler/rustc_codegen_llvm/src/back/lto.rs | 23 +++--- compiler/rustc_codegen_ssa/src/back/write.rs | 77 ++++++++++++------- .../rustc_codegen_ssa/src/traits/write.rs | 3 +- compiler/rustc_incremental/src/persist/fs.rs | 2 +- compiler/rustc_session/src/session.rs | 39 ++++++++++ 8 files changed, 115 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index a62dff641cfec..463b2d5cb2a37 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -22,8 +22,8 @@ use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::WorkProduct; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mono::{MonoItem, MonoItemData, Visibility}; -use rustc_session::Session; use rustc_session::config::{OptLevel, OutputFilenames, OutputType}; +use rustc_session::{BorrowedIncrCompSession, Session}; use rustc_span::Symbol; use crate::base::CodegenedFunction; @@ -337,6 +337,7 @@ impl WriteBackendMethods for AotDriver { fn run_thin_lto( _cgcx: &CodegenContext, _prof: &SelfProfilerRef, + _incr_comp_session: Option<&BorrowedIncrCompSession>, _dcx: rustc_errors::DiagCtxtHandle<'_>, _exported_symbols_for_lto: &[String], _each_linked_rlib_for_lto: &[PathBuf], diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index b82d05dc211aa..59585c520a6cd 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -93,7 +93,9 @@ use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{OptLevel, OutputFilenames}; -use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session}; +use rustc_session::{ + BorrowedIncrCompSession, CodegenBackendInit, EarlySession, IncrCompSession, Session, +}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{RelocModel, TargetTuple}; use tempfile::TempDir; @@ -404,6 +406,7 @@ impl WriteBackendMethods for GccCodegenBackend { fn run_thin_lto( _cgcx: &CodegenContext, _prof: &SelfProfilerRef, + _incr_comp_session: Option<&BorrowedIncrCompSession>, _dcx: DiagCtxtHandle<'_>, // FIXME(bjorn3): Limit LTO exports to these symbols _exported_symbols_for_lto: &[String], diff --git a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs index e8bf1cf9a6f45..14309aa345150 100644 --- a/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs +++ b/compiler/rustc_codegen_llvm/src/back/llvm_backend.rs @@ -20,7 +20,9 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest}; -use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session}; +use rustc_session::{ + BorrowedIncrCompSession, CodegenBackendInit, EarlySession, IncrCompSession, Session, +}; use rustc_span::{Symbol, sym}; use rustc_target::spec::{RelocModel, TlsModel}; @@ -120,6 +122,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { fn run_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -128,6 +131,7 @@ impl WriteBackendMethods for LlvmCodegenBackend { back::lto::run_thin( cgcx, prof, + incr_comp_session, dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index d52e34d30fb7d..29be4b970dfaf 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -19,7 +19,7 @@ use rustc_data_structures::memmap::Mmap; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_middle::dep_graph::WorkProduct; -use rustc_session::config; +use rustc_session::{BorrowedIncrCompSession, config}; use rustc_span::bug; use rustc_structures::SanitizerSet; use tracing::{debug, info}; @@ -184,6 +184,7 @@ pub(crate) fn run_fat( pub(crate) fn run_thin( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -199,7 +200,7 @@ pub(crate) fn run_thin( is deferred to the linker" ); } - thin_lto(cgcx, prof, dcx, modules, upstream_modules, &symbols_below_threshold) + thin_lto(prof, incr_comp_session, dcx, modules, upstream_modules, &symbols_below_threshold) } fn fat_lto( @@ -371,8 +372,8 @@ fn fat_lto( /// all of the `LtoModuleCodegen` units returned below and destroyed once /// they all go out of scope. fn thin_lto( - cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, modules: Vec>, serialized_modules: Vec<(SerializedModule, CString)>, @@ -463,13 +464,12 @@ fn thin_lto( info!("thin LTO data created"); - let new_key_map_path = cgcx - .new_incr_comp_session_dir - .as_ref() - .map(|dir| dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME)); + let new_key_map_path = incr_comp_session.as_ref().map(|incr_comp_session| { + incr_comp_session.new_session_directory.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME) + }); - let prev_key_map = if let Some(ref old_incr_comp_session_dir) = - cgcx.old_incr_comp_session_dir + let prev_key_map = if let Some(ref old_incr_comp_session_dir) = incr_comp_session + .and_then(|incr_comp_session| incr_comp_session.old_session_directory.as_deref()) { let old_path = old_incr_comp_session_dir.join(THIN_LTO_KEYS_INCR_COMP_FILE_NAME); @@ -481,7 +481,7 @@ fn thin_lto( assert!(green_modules.is_empty()); None }; - let curr_key_map = if cgcx.new_incr_comp_session_dir.is_some() { + let curr_key_map = if incr_comp_session.is_some() { ThinLTOKeysMap::from_thin_lto_modules(&data, &thin_modules, &module_names) } else { assert!(green_modules.is_empty()); @@ -506,8 +506,7 @@ fn thin_lto( if let (Some(prev_key_map), true) = (prev_key_map.as_ref(), green_modules.contains_key(module_name)) { - assert!(cgcx.old_incr_comp_session_dir.is_some()); - assert!(cgcx.new_incr_comp_session_dir.is_some()); + assert!(incr_comp_session.unwrap().old_session_directory.is_some()); // If a module exists in both the current and the previous session, // and has the same LTO cache key in both sessions, then we can re-use it diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 04be7956e8278..1b659ea9974d4 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -25,7 +25,7 @@ use rustc_middle::ty::TyCtxt; use rustc_session::config::{ self, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, SwitchWithOptPath, }; -use rustc_session::{IncrCompSession, Session}; +use rustc_session::{BorrowedIncrCompSession, IncrCompSession, Session}; use rustc_span::source_map::SourceMap; use rustc_span::{BytePos, FileName, InnerSpan, Span, SyntaxContext, bug}; use rustc_structures::CrateType; @@ -348,12 +348,6 @@ pub struct CodegenContext { /// Directory into which should the LLVM optimization remarks be written. /// If `None`, they will be written to stderr. pub remark_dir: Option, - /// The previous incremental compilation session directory, or None if we - /// are not compiling incrementally or there is no previous session. - pub old_incr_comp_session_dir: Option, - /// The incremental compilation session directory, or None if we are not - /// compiling incrementally - pub new_incr_comp_session_dir: Option, /// `Some(limit)` if the codegen should be run in parallel. /// /// Depends on [`WriteBackendMethods::supports_parallel()`] and `--jobs-backend`. @@ -363,6 +357,7 @@ pub struct CodegenContext { fn generate_thin_lto_work( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], @@ -373,6 +368,7 @@ fn generate_thin_lto_work( let (lto_modules, copy_jobs) = B::run_thin_lto( cgcx, prof, + incr_comp_session, dcx, exported_symbols_for_lto, each_linked_rlib_for_lto, @@ -819,6 +815,7 @@ pub(crate) fn compute_per_cgu_lto_type( fn execute_optimize_work_item( cgcx: &CodegenContext, prof: &SelfProfilerRef, + new_incr_comp_session_dir: Option<&Path>, shared_emitter: SharedEmitter, mut module: ModuleCodegen, ) -> WorkItemResult { @@ -838,7 +835,7 @@ fn execute_optimize_work_item( // save our module to disk first. let bitcode = if cgcx.module_config.emit_pre_lto_bc { let filename = pre_lto_bitcode_filename(&module.name); - cgcx.new_incr_comp_session_dir.as_ref().map(|path| path.join(&filename)) + new_incr_comp_session_dir.map(|path| path.join(&filename)) } else { None }; @@ -876,6 +873,7 @@ fn execute_optimize_work_item( fn execute_copy_from_cache_work_item( cgcx: &CodegenContext, prof: &SelfProfilerRef, + old_incr_comp_session_dir: &Path, shared_emitter: SharedEmitter, module: CachedModuleCodegen, ) -> CompiledModule { @@ -885,10 +883,8 @@ fn execute_copy_from_cache_work_item( let dcx = DiagCtxt::new(Box::new(shared_emitter)); let dcx = dcx.handle(); - let incr_comp_session_dir = cgcx.old_incr_comp_session_dir.as_ref().unwrap(); - let load_from_incr_comp_dir = |output_path: PathBuf, saved_path: &str| { - let source_file_in_incr_comp_dir = incr_comp_session_dir.join(saved_path); + let source_file_in_incr_comp_dir = old_incr_comp_session_dir.join(saved_path); debug!( "copying preexisting module `{}` from {:?} to {}", module.name, @@ -988,6 +984,7 @@ fn do_fat_lto( fn do_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option, shared_emitter: SharedEmitter, tm_factory: TargetMachineFactoryFn, exported_symbols_for_lto: &[String], @@ -1031,6 +1028,7 @@ fn do_thin_lto( for (i, (work, cost)) in generate_thin_lto_work::( cgcx, prof, + incr_comp_session.as_ref(), dcx, &exported_symbols_for_lto, &each_linked_rlib_for_lto, @@ -1080,6 +1078,12 @@ fn do_thin_lto( spawn_thin_lto_work( &cgcx, prof, + incr_comp_session + .as_ref() + .and_then(|incr_comp_session| { + incr_comp_session.old_session_directory.as_deref() + }) + .map(ToOwned::to_owned), shared_emitter.clone(), Arc::clone(&tm_factory), coordinator_send.clone(), @@ -1213,6 +1217,8 @@ fn start_executing_work( ) -> thread::JoinHandle, ()>> { let sess = tcx.sess; let prof = sess.prof.clone(); + let incr_comp_session = + tcx.incr_comp_session.map(|incr_comp_session| incr_comp_session.borrow()); // Compute the set of symbols we need to retain when doing thin local LTO (if we need to) let exported_symbols_for_lto = @@ -1265,15 +1271,6 @@ fn start_executing_work( time_trace: sess.opts.unstable_opts.llvm_time_trace, remark: sess.opts.cg.remark.clone(), remark_dir, - old_incr_comp_session_dir: tcx - .incr_comp_session - .as_ref() - .and_then(|incr_comp_session| incr_comp_session.old_session_directory.as_deref()) - .map(ToOwned::to_owned), - new_incr_comp_session_dir: tcx - .incr_comp_session - .as_ref() - .map(|incr_comp_session| (&*incr_comp_session.new_session_directory).to_owned()), output_filenames: Arc::clone(tcx.output_filenames(())), module_config: regular_config, opt_level, @@ -1512,6 +1509,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1538,6 +1536,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1582,6 +1581,7 @@ fn start_executing_work( spawn_work( &cgcx, &prof, + incr_comp_session.as_ref(), shared_emitter.clone(), coordinator_send.clone(), &mut llvm_start_time, @@ -1750,6 +1750,7 @@ fn start_executing_work( compiled_modules.extend(do_thin_lto::( &cgcx, &prof, + incr_comp_session, shared_emitter.clone(), tm_factory, &exported_symbols_for_lto, @@ -1847,6 +1848,7 @@ pub(crate) struct WorkerFatalError; fn spawn_work<'a, B: WriteBackendMethods>( cgcx: &CodegenContext, prof: &'a SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, shared_emitter: SharedEmitter, coordinator_send: Sender>, llvm_start_time: &mut Option>, @@ -1857,6 +1859,11 @@ fn spawn_work<'a, B: WriteBackendMethods>( *llvm_start_time = Some(prof.verbose_generic_activity("LLVM_passes")); } + let old_incr_comp_session_dir = incr_comp_session + .and_then(|incr_comp_session| incr_comp_session.old_session_directory.clone()); + let new_incr_comp_session_dir = + incr_comp_session.map(|incr_comp_session| incr_comp_session.new_session_directory.clone()); + let cgcx = cgcx.clone(); let prof = prof.clone(); @@ -1865,10 +1872,22 @@ fn spawn_work<'a, B: WriteBackendMethods>( let _profiler = if cgcx.time_trace { B::thread_profiler() } else { Box::new(()) }; let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - WorkItem::Optimize(m) => execute_optimize_work_item(&cgcx, &prof, shared_emitter, m), - WorkItem::CopyPostLtoArtifacts(m) => WorkItemResult::Finished( - execute_copy_from_cache_work_item(&cgcx, &prof, shared_emitter, m), + WorkItem::Optimize(m) => execute_optimize_work_item( + &cgcx, + &prof, + new_incr_comp_session_dir.as_deref(), + shared_emitter, + m, ), + WorkItem::CopyPostLtoArtifacts(m) => { + WorkItemResult::Finished(execute_copy_from_cache_work_item( + &cgcx, + &prof, + old_incr_comp_session_dir.as_deref().unwrap(), + shared_emitter, + m, + )) + } })); let msg = match result { @@ -1895,6 +1914,7 @@ fn spawn_work<'a, B: WriteBackendMethods>( fn spawn_thin_lto_work( cgcx: &CodegenContext, prof: &SelfProfilerRef, + old_incr_comp_session_dir: Option, shared_emitter: SharedEmitter, tm_factory: TargetMachineFactoryFn, coordinator_send: Sender, @@ -1909,9 +1929,13 @@ fn spawn_thin_lto_work( let _profiler = if cgcx.time_trace { B::thread_profiler() } else { Box::new(()) }; let result = std::panic::catch_unwind(AssertUnwindSafe(|| match work { - ThinLtoWorkItem::CopyPostLtoArtifacts(m) => { - execute_copy_from_cache_work_item(&cgcx, &prof, shared_emitter, m) - } + ThinLtoWorkItem::CopyPostLtoArtifacts(m) => execute_copy_from_cache_work_item( + &cgcx, + &prof, + old_incr_comp_session_dir.as_deref().unwrap(), + shared_emitter, + m, + ), ThinLtoWorkItem::ThinLto(m) => { let _timer = prof.generic_activity_with_arg("codegen_module_perform_lto", m.name()); B::optimize_and_codegen_thin(&cgcx, &prof, &shared_emitter, tm_factory, m) @@ -2139,6 +2163,7 @@ impl OngoingCodegen { modules: do_thin_lto::( &cgcx, &sess.prof, + incr_comp_session.map(|incr_comp_session| incr_comp_session.borrow()), shared_emitter, tm_factory, &crate_info.exported_symbols_for_lto, diff --git a/compiler/rustc_codegen_ssa/src/traits/write.rs b/compiler/rustc_codegen_ssa/src/traits/write.rs index bb63e189d20d3..4c0df97a41985 100644 --- a/compiler/rustc_codegen_ssa/src/traits/write.rs +++ b/compiler/rustc_codegen_ssa/src/traits/write.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::DiagCtxtHandle; use rustc_middle::dep_graph::WorkProduct; -use rustc_session::{Session, config}; +use rustc_session::{BorrowedIncrCompSession, Session, config}; use crate::back::lto::ThinModule; use crate::back::write::{ @@ -50,6 +50,7 @@ pub trait WriteBackendMethods: Clone + 'static { fn run_thin_lto( cgcx: &CodegenContext, prof: &SelfProfilerRef, + incr_comp_session: Option<&BorrowedIncrCompSession>, dcx: DiagCtxtHandle<'_>, exported_symbols_for_lto: &[String], each_linked_rlib_for_lto: &[PathBuf], diff --git a/compiler/rustc_incremental/src/persist/fs.rs b/compiler/rustc_incremental/src/persist/fs.rs index 3f896584eaef2..038bddaa021d2 100644 --- a/compiler/rustc_incremental/src/persist/fs.rs +++ b/compiler/rustc_incremental/src/persist/fs.rs @@ -268,7 +268,7 @@ pub(crate) fn prepare_session_directory( None }; - IncrCompSession { old_session_directory, new_session_directory } + IncrCompSession::new(old_session_directory, new_session_directory) } /// This function finalizes and thus 'publishes' the session directory by diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f068aaa584dce..d86ee5e5a4898 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1867,6 +1867,45 @@ pub struct IncrCompSession { /// The directory to which cached data for the current session can be /// written to. pub new_session_directory: flock::LockedDir, + borrows: Arc<()>, +} + +impl IncrCompSession { + pub fn new( + old_session_directory: Option, + new_session_directory: flock::LockedDir, + ) -> Self { + IncrCompSession { old_session_directory, new_session_directory, borrows: Arc::new(()) } + } + + pub fn borrow(&self) -> BorrowedIncrCompSession { + BorrowedIncrCompSession { + old_session_directory: self.old_session_directory.as_deref().map(ToOwned::to_owned), + new_session_directory: (&*self.new_session_directory).to_owned(), + _borrows: Arc::clone(&self.borrows), + } + } +} + +impl Drop for IncrCompSession { + fn drop(&mut self) { + // Check that there are no worker threads remaining that use the incr + // comp session before we unlock the old and new session dir. If there + // does exist a worker thread, there is not much we can do, but at + // least we will unconditionally complain rather than the worker thread + // sometimes crashing depending on what other rustc instances run. + assert!( + Arc::strong_count(&self.borrows) == 1, + "Incr comp session dropped while there are still references", + ); + } +} + +/// A runtime tracked borrow of the incr comp session. Can be sent to worker threads. +pub struct BorrowedIncrCompSession { + pub old_session_directory: Option, + pub new_session_directory: PathBuf, + _borrows: Arc<()>, } /// A wrapper around an [`DiagCtxt`] that is used for early error emissions. From 824062852772eefc601aaaee5e040ea522f6ff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 2 Oct 2026 14:25:10 +0200 Subject: [PATCH 06/45] Add `RUSTC_BOOTSTRAP=-2` as a way of simulating a stable rustc in UI tests --- compiler/rustc_driver_impl/src/lib.rs | 2 +- compiler/rustc_feature/src/lib.rs | 36 +++++++++++++++++-- compiler/rustc_feature/src/tests.rs | 7 ++++ compiler/rustc_session/src/config.rs | 30 +++++++++------- src/doc/rustc-dev-guide/src/tests/misc.md | 28 +++++++-------- src/librustdoc/config.rs | 2 +- ..._bootstrap.force_stable_allow_flags.stderr | 9 +++++ tests/ui/bootstrap/rustc_bootstrap.rs | 10 ++++-- 8 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 9b1f43156522e..ea62f1545f633 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -1304,7 +1304,7 @@ pub fn handle_help(matches: &getopts::Matches, args: &[String]) -> bool { let chelp_pos = opt_help_pos("C"); let print_help = || { // Only show unstable options in --help if we accept unstable options. - let unstable_enabled = nightly_options::is_unstable_enabled(&matches); + let unstable_enabled = nightly_options::unstable_options_enabled(&matches); let nightly_build = nightly_options::match_is_nightly_build(&matches); usage(matches.opt_present("verbose"), unstable_enabled, nightly_build); }; diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 83bf4f47a8ac9..e2eadaa66b0f0 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -36,15 +36,34 @@ pub struct Feature { #[derive(Clone, Copy, Debug, Hash)] pub enum UnstableFeatures { - /// Disallow use of unstable features, as on beta/stable channels. + /// Disallow use of unstable features and unstable compiler flags. + /// + /// Used by default by beta/stable channels. Disallow, - /// Allow use of unstable features, as on nightly. + /// Allow use of unstable features and unstable compiler flags. + /// + /// Used by default by the nightly channel. Allow, + /// Allow use of unstable features and unstable compiler flags, even if it is not the default + /// behavior. + /// + /// Can be used to make a beta/stable channel act like a nightly channel. + /// /// Errors are bypassed for bootstrapping. This is required any time /// during the build that feature-related lints are set to warn or above /// because the build turns on warnings-as-errors and uses lots of unstable /// features. As a result, this is always required for building Rust itself. Cheat, + /// Allow the use of *unstable compiler flags*, but disallow the use of + /// *unstable language features*. + /// And in general, try to treat everything except for compiler flags as if we were on stable. + /// + /// **This mode is designed only for internal testing of rustc.** + /// + /// Specifically, we need it when we want to test how rustc behaves on the stable channel, + /// but at the same time we need to be able to pass it some unstable `-Z` compiler flags + /// required for the testing infrastructure itself to work. + ActAsStableForTesting, } impl UnstableFeatures { @@ -76,6 +95,8 @@ impl UnstableFeatures { // Hypnotize ourselves so that we think we are a stable compiler and thus don't // allow any unstable features. "-1" => return UnstableFeatures::Disallow, + // Act as stable, but still accept unstable compiler flags required by compiletest + "-2" => return UnstableFeatures::ActAsStableForTesting, _ => {} } } @@ -83,10 +104,19 @@ impl UnstableFeatures { if disable_unstable_features { UnstableFeatures::Disallow } else { UnstableFeatures::Allow } } + pub fn allow_unstable_compiler_flags(&self) -> bool { + match *self { + UnstableFeatures::Allow + | UnstableFeatures::Cheat + | UnstableFeatures::ActAsStableForTesting => true, + UnstableFeatures::Disallow => false, + } + } + pub fn is_nightly_build(&self) -> bool { match *self { UnstableFeatures::Allow | UnstableFeatures::Cheat => true, - UnstableFeatures::Disallow => false, + UnstableFeatures::Disallow | UnstableFeatures::ActAsStableForTesting => false, } } } diff --git a/compiler/rustc_feature/src/tests.rs b/compiler/rustc_feature/src/tests.rs index a5d589171d105..9280a9de7e506 100644 --- a/compiler/rustc_feature/src/tests.rs +++ b/compiler/rustc_feature/src/tests.rs @@ -34,4 +34,11 @@ fn rustc_bootstrap_parsing() { // Does not support specifying any crate. assert!(is_force_stable(Some("x"))); assert!(is_force_stable(Some("x,y,z"))); + + // `RUSTC_BOOTSTRAP=-2` simulates the stable toolchain, while allowing unstable compiler flags + // to be passed. + std::assert_matches!( + UnstableFeatures::from_environment_value(None, Ok("-2".to_string())), + UnstableFeatures::ActAsStableForTesting + ); } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 7e274c66f6f75..a4f8bcf2dab6c 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2411,7 +2411,7 @@ pub fn parse_crate_edition( } }; - if !edition.is_stable() && !nightly_options::is_unstable_enabled(matches) { + if !edition.is_stable() && !nightly_options::unstable_options_enabled(matches) { let msg = if !is_nightly { format!( "the crate requires edition {edition}, but the latest edition supported by this \ @@ -2429,10 +2429,10 @@ pub fn parse_crate_edition( fn check_error_format_stability( early_dcx: &EarlyDiagCtxt, unstable_opts: &UnstableOptions, - is_nightly_build: bool, + unstable_flags_enabled: bool, format: ErrorOutputType, ) { - if unstable_opts.unstable_options || is_nightly_build { + if unstable_opts.unstable_options || unstable_flags_enabled { return; } let format = match format { @@ -2792,7 +2792,7 @@ pub fn build_session_options( check_error_format_stability( early_dcx, &unstable_opts, - unstable_features.is_nightly_build(), + unstable_features.allow_unstable_compiler_flags(), error_format, ); @@ -2888,7 +2888,7 @@ pub fn build_session_options( } } - let unstable_options_enabled = nightly_options::is_unstable_enabled(matches); + let unstable_options_enabled = nightly_options::unstable_options_enabled(matches); if !unstable_options_enabled && cg.force_frame_pointers == FramePointer::NonLeaf { early_dcx.early_fatal( "`-Cforce-frame-pointers=non-leaf` or `always` also requires `-Zunstable-options` \ @@ -2896,7 +2896,7 @@ pub fn build_session_options( ) } - if !nightly_options::is_unstable_enabled(matches) && !unstable_opts.offload.is_empty() { + if !unstable_options_enabled && !unstable_opts.offload.is_empty() { early_dcx.early_fatal( "`-Zoffload=Enable` also requires `-Zunstable-options` and a nightly compiler", ) @@ -3199,17 +3199,21 @@ pub mod nightly_options { use super::{OptionStability, RustcOptGroup}; use crate::EarlyDiagCtxt; - pub fn is_unstable_enabled(matches: &getopts::Matches) -> bool { - match_is_nightly_build(matches) + /// Are we allowed to use -Z compiler flags *and* -Zunstable-options was passed? + pub fn unstable_options_enabled(matches: &getopts::Matches) -> bool { + unstable_compiler_flags_allowed(matches) && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") } - pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { - is_nightly_build(matches.opt_str("crate-name").as_deref()) + /// Are we allowed to use -Z compiler flags? + pub fn unstable_compiler_flags_allowed(matches: &getopts::Matches) -> bool { + UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()) + .allow_unstable_compiler_flags() } - fn is_nightly_build(krate: Option<&str>) -> bool { - UnstableFeatures::from_environment(krate).is_nightly_build() + pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { + let krate = matches.opt_str("crate-name"); + UnstableFeatures::from_environment(krate.as_deref()).is_nightly_build() } pub fn check_nightly_options( @@ -3218,7 +3222,7 @@ pub mod nightly_options { flags: &[RustcOptGroup], ) { let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); - let really_allows_unstable_options = match_is_nightly_build(matches); + let really_allows_unstable_options = unstable_compiler_flags_allowed(matches); let mut nightly_options_on_stable = 0; for opt in flags.iter() { diff --git a/src/doc/rustc-dev-guide/src/tests/misc.md b/src/doc/rustc-dev-guide/src/tests/misc.md index e8671257a4144..167e57f757a3d 100644 --- a/src/doc/rustc-dev-guide/src/tests/misc.md +++ b/src/doc/rustc-dev-guide/src/tests/misc.md @@ -11,23 +11,19 @@ This is a bootstrap/compiler implementation detail, but it can also be useful fo even if it's actually a nightly `rustc`. This is useful because some behaviors of the compiler (e.g. diagnostics) can differ depending on whether the compiler is nightly or not. - -Note that setting `RUSTC_BOOTSTRAP` in ui tests (e.g. via `//@ rustc-env`) is pointless since ui +- `RUSTC_BOOTSTRAP=-2` will force a given `rustc` to pretend it is a stable + compiler, even if it's actually a nightly `rustc`. + However, the compiler will still accept unstable compiler flags. + This is useful for internal testing of the compiler, where we need to pass + unstable flags required by compiletest, but we also want to test the compiler's + behavior on the stable channel. + +Note that setting `RUSTC_BOOTSTRAP=-1` in ui tests (e.g. via `//@ rustc-env`) is pointless since ui tests inherit the bootstrap env var from bootstrap (so it's set to `1` by default), and since compiletest itself passes `-Z` flags (so `-1` breaks the test). -To test stable-only behavior, you need to write a `run-make` test instead. +To test stable-only behavior, use +`RUSTC_BOOTSTRAP=-2` instead. For `run-make`/`run-make-cargo` tests, `//@ rustc-env` is not supported. -You can do something like the following for individual `rustc` invocations. - -```rust,ignore -use run_make_support::rustc; - -fn main() { - rustc() - // Pretend that I am very stable - .env("RUSTC_BOOTSTRAP", "-1") - //... - .run(); -} -``` +You can do use the `run_make_support::stable_bare_rustc` function to create an instance of the compiler +that acts as the stable channel (this internally uses `RUSTC_BOOTSTRAP=-1`). diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 7604973739f08..6e988728858a8 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -452,7 +452,7 @@ impl Options { match ( output_format_s.as_ref().map(|_| output_format), show_coverage, - nightly_options::is_unstable_enabled(matches), + nightly_options::unstable_options_enabled(matches), ) { (None | Some(OutputFormat::CoverageJson), true, _) => {} (_, true, _) => { diff --git a/tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr b/tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr new file mode 100644 index 0000000000000..c4436ca5a1815 --- /dev/null +++ b/tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr @@ -0,0 +1,9 @@ +error[E0554]: `#![feature]` may not be used on the nightly release channel + --> $DIR/rustc_bootstrap.rs:51:1 + | +LL | #![feature(rustc_attrs)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0554`. diff --git a/tests/ui/bootstrap/rustc_bootstrap.rs b/tests/ui/bootstrap/rustc_bootstrap.rs index fb72bba95a158..96a464e8e4dbe 100644 --- a/tests/ui/bootstrap/rustc_bootstrap.rs +++ b/tests/ui/bootstrap/rustc_bootstrap.rs @@ -6,11 +6,13 @@ //! - `1`: cheat, allow usage of unstable features even if rustc thinks it is a stable compiler. //! - `x,y,z`: comma-delimited list of crates. //! - `-1`: force rustc to think it is a stable compiler. +//! - `-2`: force rustc to think it is a stable compiler, while still allowing -Z flags. // ignore-tidy-linelength -//@ revisions: default_nightly cheat cheat_single_crate cheat_multi_crate force_stable invalid_zero invalid_junk +//@ revisions: default_nightly cheat cheat_single_crate cheat_multi_crate force_stable force_stable_allow_flags invalid_zero invalid_junk //@ only-nightly +//@ normalize-stderr: "dev release channel" -> "nightly release channel" //@[default_nightly] unset-rustc-env:RUSTC_BOOTSTRAP //@[default_nightly] check-pass @@ -39,10 +41,14 @@ //@[force_stable] rustc-env:RUSTC_BOOTSTRAP=-1 //@[force_stable] compile-flags: -Z unstable-options -#![crate_type = "lib"] +// Here rustc shouldn't complain about compiletest's -Z flags being passed. +//@[force_stable_allow_flags] rustc-env:RUSTC_BOOTSTRAP=-2 +//@[force_stable_allow_flags] compile-flags: -Z unstable-options +#![crate_type = "lib"] // Note: `rustc_attrs` is a perma-unstable internal feature that is unlikely to change, which is // used as a proxy to check `RUSTC_BOOTSTRAP` versus stability checking logic. #![feature(rustc_attrs)] +//[force_stable_allow_flags]~^ ERROR: `#![feature]` may not be used on the //[force_stable]~? RAW the option `Z` is only accepted on the nightly compiler From 94a6b12557e9af7c09eec7d292c00adcb03fb228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 2 Oct 2026 16:12:41 +0200 Subject: [PATCH 07/45] Implement `act-as-stable` compiletest directive --- src/doc/rustc-dev-guide/src/tests/misc.md | 17 +++++++++++------ src/tools/compiletest/src/directives.rs | 1 + .../src/directives/directive_names.rs | 1 + .../compiletest/src/directives/handlers.rs | 6 ++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/tests/misc.md b/src/doc/rustc-dev-guide/src/tests/misc.md index 167e57f757a3d..688be7e97fdca 100644 --- a/src/doc/rustc-dev-guide/src/tests/misc.md +++ b/src/doc/rustc-dev-guide/src/tests/misc.md @@ -18,12 +18,17 @@ This is a bootstrap/compiler implementation detail, but it can also be useful fo unstable flags required by compiletest, but we also want to test the compiler's behavior on the stable channel. +### UI tests + Note that setting `RUSTC_BOOTSTRAP=-1` in ui tests (e.g. via `//@ rustc-env`) is pointless since ui -tests inherit the bootstrap env var from bootstrap (so it's set to `1` by default), -and since compiletest itself passes `-Z` flags (so `-1` breaks the test). -To test stable-only behavior, use -`RUSTC_BOOTSTRAP=-2` instead. +tests inherit the bootstrap env var from bootstrap (so it's set to `1` by default), and since +compiletest itself passes `-Z` flags (so `-1` breaks the test). + +To test stable-only behavior, use the `act-as-stable` directive, which will set `RUSTC_BOOTSTRAP=-2` +for you. + +### Run-make tests -For `run-make`/`run-make-cargo` tests, `//@ rustc-env` is not supported. -You can do use the `run_make_support::stable_bare_rustc` function to create an instance of the compiler +For `run-make`/`run-make-cargo` tests, `//@ act-as-stable` is not supported. +You can use the `run_make_support::stable_bare_rustc` function to create an instance of the compiler that acts as the stable channel (this internally uses `RUSTC_BOOTSTRAP=-1`). diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 1037b2c6375b7..4ef2b5fd46d65 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -222,6 +222,7 @@ pub(crate) struct TestProps { } mod directives { + pub(crate) const ACT_AS_STABLE: &str = "act-as-stable"; pub(crate) const ERROR_PATTERN: &str = "error-pattern"; pub(crate) const REGEX_ERROR_PATTERN: &str = "regex-error-pattern"; pub(crate) const COMPILE_FLAGS: &str = "compile-flags"; diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index 732f4ab275dd7..07ac09944379b 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -9,6 +9,7 @@ pub(crate) static KNOWN_DIRECTIVE_NAMES_SET: LazyLock> = /// a best-effort approximation for diagnostics. Add new directives to this list when needed. pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ // tidy-alphabetical-start + "act-as-stable", "add-minicore", "assembly-output", "aux-bin", diff --git a/src/tools/compiletest/src/directives/handlers.rs b/src/tools/compiletest/src/directives/handlers.rs index 4e86dcb308f0d..f33688180419f 100644 --- a/src/tools/compiletest/src/directives/handlers.rs +++ b/src/tools/compiletest/src/directives/handlers.rs @@ -64,6 +64,12 @@ fn make_directive_handlers_map() -> HashMap<&'static str, Handler> { // these handlers, e.g. by getting rid of now-redundant name checks. let handlers: Vec = vec![ + handler(ACT_AS_STABLE, |_config, ln, props| { + if ln.name == ACT_AS_STABLE { + // Simulate the stable toolchain, but allow unstable -Z flags passed by compiletest + props.rustc_env.push(("RUSTC_BOOTSTRAP".to_string(), "-2".to_string())); + } + }), handler(ERROR_PATTERN, |config, ln, props| { config.push_name_value_directive(ln, ERROR_PATTERN, &mut props.error_patterns, |r| r); }), From 522200b2e4a67362f10c9b4af13b88fe676c44d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 2 Oct 2026 16:12:54 +0200 Subject: [PATCH 08/45] Use `act-as-stable` in the `feature-gate-autodiff-use` test --- ... feature-gate-autodiff-use.nightly.stderr} | 4 ++-- .../feature-gate-autodiff-use.rs | 22 ++++++++++++------- .../feature-gate-autodiff-use.stable.stderr | 19 ++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) rename tests/ui/feature-gates/{feature-gate-autodiff-use.stderr => feature-gate-autodiff-use.nightly.stderr} (91%) create mode 100644 tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.stderr b/tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr similarity index 91% rename from tests/ui/feature-gates/feature-gate-autodiff-use.stderr rename to tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr index 94b9a065e491e..ca724632063d4 100644 --- a/tests/ui/feature-gates/feature-gate-autodiff-use.stderr +++ b/tests/ui/feature-gates/feature-gate-autodiff-use.nightly.stderr @@ -1,5 +1,5 @@ error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:11:3 + --> $DIR/feature-gate-autodiff-use.rs:16:3 | LL | #[autodiff_reverse(dfoo)] | ^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | #[autodiff_reverse(dfoo)] = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: use of unstable library feature `autodiff` - --> $DIR/feature-gate-autodiff-use.rs:5:5 + --> $DIR/feature-gate-autodiff-use.rs:9:5 | LL | use std::autodiff::autodiff_reverse; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.rs b/tests/ui/feature-gates/feature-gate-autodiff-use.rs index 85608be4ac04a..d7336e50df1f3 100644 --- a/tests/ui/feature-gates/feature-gate-autodiff-use.rs +++ b/tests/ui/feature-gates/feature-gate-autodiff-use.rs @@ -1,16 +1,22 @@ +//@ revisions: nightly stable +//@[nightly] only-nightly +//@[stable] act-as-stable + // This checks that without enabling the autodiff feature, we can't import std::autodiff::autodiff; #![crate_type = "lib"] use std::autodiff::autodiff_reverse; -//~^ ERROR use of unstable library feature `autodiff` -//| NOTE see issue #124509 for more information -//| HELP add `#![feature(autodiff)]` to the crate attributes to enable -//| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +//[stable]~^ ERROR use of unstable library feature `autodiff` +//[nightly]~^^ ERROR use of unstable library feature `autodiff` +//[nightly]~| NOTE see issue #124509 for more information +//[nightly]~| HELP add `#![feature(autodiff)]` to the crate attributes to enable +//[nightly]~| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date #[autodiff_reverse(dfoo)] -//~^ ERROR use of unstable library feature `autodiff` [E0658] -//| NOTE see issue #124509 for more information -//| HELP add `#![feature(autodiff)]` to the crate attributes to enable -//| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +//[stable]~^ ERROR use of unstable library feature `autodiff` [E0658] +//[nightly]~^^ ERROR use of unstable library feature `autodiff` [E0658] +//[nightly]~| NOTE see issue #124509 for more information +//[nightly]~| HELP add `#![feature(autodiff)]` to the crate attributes to enable +//[nightly]~| NOTE this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date fn foo() {} diff --git a/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr b/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr new file mode 100644 index 0000000000000..2ac3c82b644ef --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-autodiff-use.stable.stderr @@ -0,0 +1,19 @@ +error[E0658]: use of unstable library feature `autodiff` + --> $DIR/feature-gate-autodiff-use.rs:16:3 + | +LL | #[autodiff_reverse(dfoo)] + | ^^^^^^^^^^^^^^^^ + | + = note: see issue #124509 for more information + +error[E0658]: use of unstable library feature `autodiff` + --> $DIR/feature-gate-autodiff-use.rs:9:5 + | +LL | use std::autodiff::autodiff_reverse; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #124509 for more information + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. From aa97b1bfb6a761c2de78e04d580f6753d7d0e944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 2 Oct 2026 16:20:04 +0200 Subject: [PATCH 09/45] Migrate `const-destruct-stable-toolchain` from a run-make test to a UI test --- .../const-drop-stable.stderr | 11 -------- .../const-drop.rs | 3 -- .../const-destruct-stable-toolchain/rmake.rs | 28 ------------------- ...-destruct-stable-toolchain.nightly.stderr} | 2 +- .../consts/const-destruct-stable-toolchain.rs | 15 ++++++++++ ...st-destruct-stable-toolchain.stable.stderr | 11 ++++++++ 6 files changed, 27 insertions(+), 43 deletions(-) delete mode 100644 tests/run-make/const-destruct-stable-toolchain/const-drop-stable.stderr delete mode 100644 tests/run-make/const-destruct-stable-toolchain/const-drop.rs delete mode 100644 tests/run-make/const-destruct-stable-toolchain/rmake.rs rename tests/{run-make/const-destruct-stable-toolchain/const-drop-nightly.stderr => ui/consts/const-destruct-stable-toolchain.nightly.stderr} (91%) create mode 100644 tests/ui/consts/const-destruct-stable-toolchain.rs create mode 100644 tests/ui/consts/const-destruct-stable-toolchain.stable.stderr diff --git a/tests/run-make/const-destruct-stable-toolchain/const-drop-stable.stderr b/tests/run-make/const-destruct-stable-toolchain/const-drop-stable.stderr deleted file mode 100644 index dc37d840658fd..0000000000000 --- a/tests/run-make/const-destruct-stable-toolchain/const-drop-stable.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0493]: destructor of `T` cannot be evaluated at compile-time - --> const-drop.rs:1:24 - | -1 | const fn const_drop(_: T) {} - | ^ - value is dropped here - | | - | the destructor for this type cannot be evaluated in constant functions - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0493`. diff --git a/tests/run-make/const-destruct-stable-toolchain/const-drop.rs b/tests/run-make/const-destruct-stable-toolchain/const-drop.rs deleted file mode 100644 index 6479498281fe5..0000000000000 --- a/tests/run-make/const-destruct-stable-toolchain/const-drop.rs +++ /dev/null @@ -1,3 +0,0 @@ -const fn const_drop(_: T) {} - -fn main() {} diff --git a/tests/run-make/const-destruct-stable-toolchain/rmake.rs b/tests/run-make/const-destruct-stable-toolchain/rmake.rs deleted file mode 100644 index 47d66cf267ab9..0000000000000 --- a/tests/run-make/const-destruct-stable-toolchain/rmake.rs +++ /dev/null @@ -1,28 +0,0 @@ -//@ needs-target-std -//@ ignore-backends: gcc - -// Test that the suggestion to constrain a type parameter that is dropped in a const -// function with a `[const] Destruct` bound is only offered on nightly, since the bound -// requires an unstable feature. - -use run_make_support::{diff, rustc, stable_bare_rustc}; - -fn main() { - let out = stable_bare_rustc() - .input("const-drop.rs") - .edition("2015") - .run_fail() - .assert_stderr_not_contains("consider restricting type parameter `T`") - .stderr_utf8(); - diff().expected_file("const-drop-stable.stderr").actual_text("(rustc)", &out).run(); - let out = rustc() - .input("const-drop.rs") - .edition("2015") - .ui_testing() - .run_fail() - .assert_stderr_contains( - "consider restricting type parameter `T` with unstable trait `Destruct`", - ) - .stderr_utf8(); - diff().expected_file("const-drop-nightly.stderr").actual_text("(rustc)", &out).run(); -} diff --git a/tests/run-make/const-destruct-stable-toolchain/const-drop-nightly.stderr b/tests/ui/consts/const-destruct-stable-toolchain.nightly.stderr similarity index 91% rename from tests/run-make/const-destruct-stable-toolchain/const-drop-nightly.stderr rename to tests/ui/consts/const-destruct-stable-toolchain.nightly.stderr index 52f68743b6275..2d05a300165aa 100644 --- a/tests/run-make/const-destruct-stable-toolchain/const-drop-nightly.stderr +++ b/tests/ui/consts/const-destruct-stable-toolchain.nightly.stderr @@ -1,5 +1,5 @@ error[E0493]: destructor of `T` cannot be evaluated at compile-time - --> const-drop.rs:1:24 + --> $DIR/const-destruct-stable-toolchain.rs:12:24 | LL | const fn const_drop(_: T) {} | ^ - value is dropped here diff --git a/tests/ui/consts/const-destruct-stable-toolchain.rs b/tests/ui/consts/const-destruct-stable-toolchain.rs new file mode 100644 index 0000000000000..ad16a74508e4c --- /dev/null +++ b/tests/ui/consts/const-destruct-stable-toolchain.rs @@ -0,0 +1,15 @@ +// Test that the suggestion to constrain a type parameter that is dropped in a const +// function with a `[const] Destruct` bound is only offered on nightly, since the bound +// requires an unstable feature. +// +//@ needs-target-std +//@ ignore-backends: gcc +// +//@ revisions: stable nightly +// +//@[stable] act-as-stable +//@[nightly] only-nightly +const fn const_drop(_: T) {} +//~^ ERROR: destructor of `T` cannot be evaluated at compile-time [E0493] + +fn main() {} diff --git a/tests/ui/consts/const-destruct-stable-toolchain.stable.stderr b/tests/ui/consts/const-destruct-stable-toolchain.stable.stderr new file mode 100644 index 0000000000000..4755afcd3f1e3 --- /dev/null +++ b/tests/ui/consts/const-destruct-stable-toolchain.stable.stderr @@ -0,0 +1,11 @@ +error[E0493]: destructor of `T` cannot be evaluated at compile-time + --> $DIR/const-destruct-stable-toolchain.rs:12:24 + | +LL | const fn const_drop(_: T) {} + | ^ - value is dropped here + | | + | the destructor for this type cannot be evaluated in constant functions + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0493`. From 0c3b3bee4e044fbaf28ddbabadf646b8688e6f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 2 Oct 2026 16:25:27 +0200 Subject: [PATCH 10/45] Migrate `print-request-help-stable-unstable` from a run-make test to a UI test --- .../help-diff.diff | 7 ---- .../rmake.rs | 33 ------------------- ...quest-help-stable-unstable.nightly.stderr} | 0 .../print-request-help-stable-unstable.rs | 12 +++++++ ...equest-help-stable-unstable.stable.stderr} | 0 5 files changed, 12 insertions(+), 40 deletions(-) delete mode 100644 tests/run-make/print-request-help-stable-unstable/help-diff.diff delete mode 100644 tests/run-make/print-request-help-stable-unstable/rmake.rs rename tests/{run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err => ui/compile-flags/print-request-help-stable-unstable.nightly.stderr} (100%) create mode 100644 tests/ui/compile-flags/print-request-help-stable-unstable.rs rename tests/{run-make/print-request-help-stable-unstable/stable-invalid-print-request-help.err => ui/compile-flags/print-request-help-stable-unstable.stable.stderr} (100%) diff --git a/tests/run-make/print-request-help-stable-unstable/help-diff.diff b/tests/run-make/print-request-help-stable-unstable/help-diff.diff deleted file mode 100644 index 8a9c3d0cf46c1..0000000000000 --- a/tests/run-make/print-request-help-stable-unstable/help-diff.diff +++ /dev/null @@ -1,7 +0,0 @@ -@@ -1,5 +1,5 @@ - error: unknown print request: `xxx` - | -- = help: valid print requests are: `calling-conventions`, `cfg`, `code-models`, `crate-name`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `tls-models` -+ = help: valid print requests are: `all-target-specs-json`, `backend-has-mnemonic`, `backend-has-zstd`, `calling-conventions`, `cfg`, `check-cfg`, `code-models`, `crate-name`, `crate-root-lint-levels`, `deployment-target`, `file-names`, `host-tuple`, `link-args`, `native-static-libs`, `relocation-models`, `split-debuginfo`, `stack-protector-strategies`, `supported-crate-types`, `sysroot`, `target-cpus`, `target-features`, `target-libdir`, `target-list`, `target-spec-json`, `target-spec-json-schema`, `tls-models`, `wasm-proc-macro-tuple` - = help: for more information, see the rustc book: https://doc.rust-lang.org/rustc/command-line-arguments.html#--print-print-compiler-information - diff --git a/tests/run-make/print-request-help-stable-unstable/rmake.rs b/tests/run-make/print-request-help-stable-unstable/rmake.rs deleted file mode 100644 index 49df84b353cd4..0000000000000 --- a/tests/run-make/print-request-help-stable-unstable/rmake.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Check that unstable print requests are omitted from help if compiler is in stable channel. -//! -//! Issue: - -//@ ignore-backends: gcc - -use run_make_support::{diff, rustc, similar, stable_bare_rustc}; - -fn main() { - let stable_invalid_print_request_help = - stable_bare_rustc().edition("2015").print("xxx").run_fail().stderr_utf8(); - assert!(!stable_invalid_print_request_help.contains("all-target-specs-json")); - diff() - .expected_file("stable-invalid-print-request-help.err") - .actual_text("stable_invalid_print_request_help", &stable_invalid_print_request_help) - .run(); - - let unstable_invalid_print_request_help = - rustc().edition("2015").print("xxx").run_fail().stderr_utf8(); - assert!(unstable_invalid_print_request_help.contains("all-target-specs-json")); - diff() - .expected_file("unstable-invalid-print-request-help.err") - .actual_text("unstable_invalid_print_request_help", &unstable_invalid_print_request_help) - .run(); - - let help_diff = similar::TextDiff::from_lines( - &stable_invalid_print_request_help, - &unstable_invalid_print_request_help, - ) - .unified_diff() - .to_string(); - diff().expected_file("help-diff.diff").actual_text("help_diff", help_diff).run(); -} diff --git a/tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err b/tests/ui/compile-flags/print-request-help-stable-unstable.nightly.stderr similarity index 100% rename from tests/run-make/print-request-help-stable-unstable/unstable-invalid-print-request-help.err rename to tests/ui/compile-flags/print-request-help-stable-unstable.nightly.stderr diff --git a/tests/ui/compile-flags/print-request-help-stable-unstable.rs b/tests/ui/compile-flags/print-request-help-stable-unstable.rs new file mode 100644 index 0000000000000..f75996f28e6bd --- /dev/null +++ b/tests/ui/compile-flags/print-request-help-stable-unstable.rs @@ -0,0 +1,12 @@ +//! Check that unstable print requests are omitted from help if compiler is in stable channel. +//! +//! Issue: + +//@ ignore-backends: gcc +//@ compile-flags: --print xxx +// +//@ revisions: stable nightly +//@[stable] act-as-stable +//@[nightly] only-nightly + +//~? ERROR unknown print request: `xxx` diff --git a/tests/run-make/print-request-help-stable-unstable/stable-invalid-print-request-help.err b/tests/ui/compile-flags/print-request-help-stable-unstable.stable.stderr similarity index 100% rename from tests/run-make/print-request-help-stable-unstable/stable-invalid-print-request-help.err rename to tests/ui/compile-flags/print-request-help-stable-unstable.stable.stderr From 6fc1dc1bbfd90a36ba80662c73378054a632a59c Mon Sep 17 00:00:00 2001 From: Yu Date: Tue, 13 Oct 2026 21:03:02 +0800 Subject: [PATCH 11/45] lint: suggest fXX::is_nan for unsuffixed NaN comparisons --- compiler/rustc_lint/src/diagnostics.rs | 16 ++ compiler/rustc_lint/src/types.rs | 163 ++++++++++++++---- .../invalid-nan-comparison-suggestion.fixed | 42 +++++ .../lint/invalid-nan-comparison-suggestion.rs | 42 +++++ .../invalid-nan-comparison-suggestion.stderr | 114 +++++++++++- 5 files changed, 336 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index 601f1213650e1..cdb962a12d111 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -2334,6 +2334,22 @@ pub(crate) enum InvalidNanComparisonsSuggestion { #[suggestion_part(code = "")] nan_plus_binop: Span, }, + #[multipart_suggestion( + "use `f32::is_nan()` or `f64::is_nan()` instead", + style = "verbose", + applicability = "machine-applicable" + )] + Ufcs { + prefix: String, + #[suggestion_part(code = "{prefix}")] + prefix_span: Span, + #[suggestion_part(code = ")")] + close_span: Span, + #[suggestion_part(code = "")] + remove_nan: Span, + #[suggestion_part(code = "")] + remove_borrow: Option, + }, #[help("use `f32::is_nan()` or `f64::is_nan()` instead")] Spanless, } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 9897e9cbb4f07..7549014dab97a 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -229,58 +229,145 @@ fn lint_nan<'tcx>( l: &'tcx hir::Expr<'tcx>, r: &'tcx hir::Expr<'tcx>, ) { - fn is_nan(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + fn nan_ty(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { let expr = expr.peel_blocks().peel_borrows(); - match expr.kind { - ExprKind::Path(qpath) => { - let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() - else { - return false; - }; + let ExprKind::Path(qpath) = expr.kind else { return None }; + let def_id = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id()?; + if !matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::f16_nan | sym::f32_nan | sym::f64_nan | sym::f128_nan) + ) { + return None; + } + match cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() { + ty::Float(fty) => Some(*fty), + _ => None, + } + } - matches!( - cx.tcx.get_diagnostic_name(def_id), - Some(sym::f16_nan | sym::f32_nan | sym::f64_nan | sym::f128_nan) - ) - } - _ => false, + fn is_primitive_float(mut ty: &hir::Ty<'_>) -> bool { + while let hir::TyKind::Ref(_, inner, _) = ty.kind { + ty = inner; } + matches!( + ty.kind, + hir::TyKind::Path(hir::QPath::Resolved(_, path)) + if matches!(path.res, hir::def::Res::PrimTy(hir::PrimTy::Float(_))) + ) } - fn eq_ne( - e: &hir::Expr<'_>, - l: &hir::Expr<'_>, - r: &hir::Expr<'_>, - f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion, - ) -> InvalidNanComparisons { - let suggestion = if let Some(l_span) = l.span.find_ancestor_inside(e.span) - && let Some(r_span) = r.span.find_ancestor_inside(e.span) + // `true` when `expr.is_nan()` still type-checks after this comparison is removed. + // `let [mut] b = &3.3; b != &f64::NAN` is `&f64` only because of that comparison, so + // `b.is_nan()` leaves `{float}` and does not compile. `let b: f64 = 3.3` does. + fn can_suggest_is_nan_method<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) -> bool { + let mut cursor = expr.peel_blocks().peel_borrows(); + while let ExprKind::Path(qpath) = &cursor.kind + && let hir::def::Res::Local(hir_id) = cx.qpath_res(qpath, cursor.hir_id) + && let hir::Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id) + { + if local.ty.is_some_and(is_primitive_float) { + return true; + } + if let hir::PatKind::Binding(..) = local.pat.kind + && let Some(init) = local.init + { + cursor = init.peel_blocks().peel_borrows(); + } else { + break; + } + } + if let ExprKind::Lit(lit) = cursor.kind + && let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node { - f(l_span, r_span) + return false; + } + cx.typeck_results().expr_ty_opt(cursor).is_some_and(|ty| ty.peel_refs().is_floating_point()) + } + + fn eq_ne_suggestion<'a>( + cx: &LateContext<'a>, + cmp: &'a hir::Expr<'a>, + value: &'a hir::Expr<'a>, + nan: &'a hir::Expr<'a>, + float_ty: ty::FloatTy, + is_ne: bool, + ) -> InvalidNanComparisonsSuggestion { + let (Some(value_span), Some(nan_span)) = + (value.span.find_ancestor_inside(cmp.span), nan.span.find_ancestor_inside(cmp.span)) + else { + return InvalidNanComparisonsSuggestion::Spanless; + }; + let remove_nan = if nan_span.lo() < value_span.lo() { + nan_span.until(value_span) } else { - InvalidNanComparisonsSuggestion::Spanless + value_span.shrink_to_hi().to(nan_span) }; - InvalidNanComparisons::EqNe { suggestion } + if can_suggest_is_nan_method(cx, value) { + return InvalidNanComparisonsSuggestion::Spanful { + neg: is_ne.then(|| value_span.shrink_to_lo()), + float: value_span.shrink_to_hi(), + nan_plus_binop: remove_nan, + }; + } + + let Some(mut ty) = cx.typeck_results().expr_ty_opt(value) else { + return InvalidNanComparisonsSuggestion::Spanless; + }; + let mut operand = value.peel_blocks(); + let Some(mut operand_span) = operand.span.find_ancestor_inside(value_span) else { + return InvalidNanComparisonsSuggestion::Spanless; + }; + let outer_span = operand_span; + let mut deref_count = 0; + while let ty::Ref(_, inner, _) = *ty.kind() { + if let ExprKind::AddrOf(_, _, inner_expr) = operand.kind + && let peeled = inner_expr.peel_blocks() + && let Some(peeled_span) = peeled.span.find_ancestor_inside(operand_span) + { + operand = peeled; + operand_span = peeled_span; + } else { + deref_count += 1; + } + ty = inner; + } + let matches_float = match *ty.kind() { + ty::Float(fty) => fty == float_ty, + ty::Infer(ty::FloatVar(_)) => true, + _ => false, + }; + if !matches_float { + return InvalidNanComparisonsSuggestion::Spanless; + } + let written_borrow = outer_span.until(operand_span); + let neg = if is_ne { "!" } else { "" }; + InvalidNanComparisonsSuggestion::Ufcs { + prefix: format!("{neg}{}::is_nan({}", float_ty.name_str(), "*".repeat(deref_count)), + prefix_span: operand_span.shrink_to_lo(), + close_span: operand_span.shrink_to_hi(), + remove_nan, + remove_borrow: (!written_borrow.is_empty()).then_some(written_borrow), + } } + let l_nan = nan_ty(cx, l); + let r_nan = nan_ty(cx, r); + let is_ne = binop == hir::BinOpKind::Ne; let lint = match binop { - hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => { - eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { - nan_plus_binop: l_span.until(r_span), - float: r_span.shrink_to_hi(), - neg: (binop == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()), - }) - } - hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => { - eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { - nan_plus_binop: l_span.shrink_to_hi().to(r_span), - float: l_span.shrink_to_hi(), - neg: (binop == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()), - }) + hir::BinOpKind::Eq | hir::BinOpKind::Ne => { + let Some((value, nan, float_ty)) = l_nan + .map(|float_ty| (r, l, float_ty)) + .or_else(|| r_nan.map(|float_ty| (l, r, float_ty))) + else { + return; + }; + InvalidNanComparisons::EqNe { + suggestion: eq_ne_suggestion(cx, e, value, nan, float_ty, is_ne), + } } hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge - if is_nan(cx, l) || is_nan(cx, r) => + if l_nan.is_some() || r_nan.is_some() => { InvalidNanComparisons::LtLeGtGe } diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed index 2d88c274080b6..39d2ba3e93788 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed @@ -39,6 +39,38 @@ fn main() { let _ = !b.is_nan(); + // Issue #164143: an unsuffixed float is constrained by the `NAN` operand. + // Suggesting `.is_nan()` drops that constraint and does not compile. + let b = &3.3; + if !f64::is_nan(*b) {} + //~^ WARN incorrect NaN comparison + + let mut m = &3.3; + if !f64::is_nan(*m) { + //~^ WARN incorrect NaN comparison + m = &1.0; + } + let _ = m; + + let _ = f64::is_nan(3.3); + //~^ WARN incorrect NaN comparison + + let _ = !f64::is_nan(3.3); + //~^ WARN incorrect NaN comparison + + let suffixed = 3.3f64; + let copied = suffixed; + let _ = !copied.is_nan(); + //~^ WARN incorrect NaN comparison + + let ascribed: f64 = 3.3; + let _ = !ascribed.is_nan(); + //~^ WARN incorrect NaN comparison + + parameter(1.0); + parameter_ref(&1.0); + let _ = |x: f64| !x.is_nan(); //~ WARN incorrect NaN comparison + #[allow(unused_macros)] macro_rules! nan { () => { f32::NAN }; } macro_rules! number { () => { 5f32 }; } @@ -48,3 +80,13 @@ fn main() { let _ = !number!().is_nan(); //~^ WARN incorrect NaN comparison } + +fn parameter(x: f64) { + let _ = x.is_nan(); + //~^ WARN incorrect NaN comparison +} + +fn parameter_ref(x: &f32) { + if !x.is_nan() {} + //~^ WARN incorrect NaN comparison +} diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.rs b/tests/ui/lint/invalid-nan-comparison-suggestion.rs index 9175344786948..18e4a4093d10f 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.rs +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.rs @@ -42,6 +42,38 @@ fn main() { &f32::NAN }; + // Issue #164143: an unsuffixed float is constrained by the `NAN` operand. + // Suggesting `.is_nan()` drops that constraint and does not compile. + let b = &3.3; + if b != &f64::NAN {} + //~^ WARN incorrect NaN comparison + + let mut m = &3.3; + if m != &f64::NAN { + //~^ WARN incorrect NaN comparison + m = &1.0; + } + let _ = m; + + let _ = 3.3 == f64::NAN; + //~^ WARN incorrect NaN comparison + + let _ = &3.3 != &f64::NAN; + //~^ WARN incorrect NaN comparison + + let suffixed = 3.3f64; + let copied = suffixed; + let _ = copied != f64::NAN; + //~^ WARN incorrect NaN comparison + + let ascribed: f64 = 3.3; + let _ = ascribed != f64::NAN; + //~^ WARN incorrect NaN comparison + + parameter(1.0); + parameter_ref(&1.0); + let _ = |x: f64| x != f64::NAN; //~ WARN incorrect NaN comparison + #[allow(unused_macros)] macro_rules! nan { () => { f32::NAN }; } macro_rules! number { () => { 5f32 }; } @@ -51,3 +83,13 @@ fn main() { let _ = number!() != nan!(); //~^ WARN incorrect NaN comparison } + +fn parameter(x: f64) { + let _ = x == f64::NAN; + //~^ WARN incorrect NaN comparison +} + +fn parameter_ref(x: &f32) { + if x != &f32::NAN {} + //~^ WARN incorrect NaN comparison +} diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr index f9b1428de464c..5252977148e09 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr @@ -138,7 +138,91 @@ LL + !b.is_nan(); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:49:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:48:8 + | +LL | if b != &f64::NAN {} + | ^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if b != &f64::NAN {} +LL + if !f64::is_nan(*b) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:52:8 + | +LL | if m != &f64::NAN { + | ^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if m != &f64::NAN { +LL + if !f64::is_nan(*m) { + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:58:13 + | +LL | let _ = 3.3 == f64::NAN; + | ^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = 3.3 == f64::NAN; +LL + let _ = f64::is_nan(3.3); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:61:13 + | +LL | let _ = &3.3 != &f64::NAN; + | ^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = &3.3 != &f64::NAN; +LL + let _ = !f64::is_nan(3.3); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:66:13 + | +LL | let _ = copied != f64::NAN; + | ^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = copied != f64::NAN; +LL + let _ = !copied.is_nan(); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:70:13 + | +LL | let _ = ascribed != f64::NAN; + | ^^^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = ascribed != f64::NAN; +LL + let _ = !ascribed.is_nan(); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:75:22 + | +LL | let _ = |x: f64| x != f64::NAN; + | ^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = |x: f64| x != f64::NAN; +LL + let _ = |x: f64| !x.is_nan(); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:81:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ @@ -150,7 +234,7 @@ LL + let _ = number!().is_nan(); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:51:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:83:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ @@ -161,5 +245,29 @@ LL - let _ = number!() != nan!(); LL + let _ = !number!().is_nan(); | -warning: 13 warnings emitted +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:88:13 + | +LL | let _ = x == f64::NAN; + | ^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = x == f64::NAN; +LL + let _ = x.is_nan(); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:93:8 + | +LL | if x != &f32::NAN {} + | ^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if x != &f32::NAN {} +LL + if !x.is_nan() {} + | + +warning: 22 warnings emitted From 4f9916a7b7dba2794fa0825f778a6228586ba787 Mon Sep 17 00:00:00 2001 From: Yu Date: Tue, 13 Oct 2026 23:49:26 +0800 Subject: [PATCH 12/45] lint: use UFCS for NaN comparison suggestions --- compiler/rustc_lint/src/diagnostics.rs | 13 --- compiler/rustc_lint/src/types.rs | 47 -------- .../invalid-nan-comparison-suggestion.fixed | 52 ++++++--- .../lint/invalid-nan-comparison-suggestion.rs | 16 +++ .../invalid-nan-comparison-suggestion.stderr | 104 +++++++++++++----- tests/ui/lint/invalid-nan-comparison.stderr | 34 +++--- 6 files changed, 143 insertions(+), 123 deletions(-) diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index cdb962a12d111..ea36a485fc3ac 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -2321,19 +2321,6 @@ pub(crate) enum InvalidNanComparisons { #[derive(Subdiagnostic)] pub(crate) enum InvalidNanComparisonsSuggestion { - #[multipart_suggestion( - "use `f32::is_nan()` or `f64::is_nan()` instead", - style = "verbose", - applicability = "machine-applicable" - )] - Spanful { - #[suggestion_part(code = "!")] - neg: Option, - #[suggestion_part(code = ".is_nan()")] - float: Span, - #[suggestion_part(code = "")] - nan_plus_binop: Span, - }, #[multipart_suggestion( "use `f32::is_nan()` or `f64::is_nan()` instead", style = "verbose", diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 7549014dab97a..4769cb7bf485c 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -245,45 +245,6 @@ fn lint_nan<'tcx>( } } - fn is_primitive_float(mut ty: &hir::Ty<'_>) -> bool { - while let hir::TyKind::Ref(_, inner, _) = ty.kind { - ty = inner; - } - matches!( - ty.kind, - hir::TyKind::Path(hir::QPath::Resolved(_, path)) - if matches!(path.res, hir::def::Res::PrimTy(hir::PrimTy::Float(_))) - ) - } - - // `true` when `expr.is_nan()` still type-checks after this comparison is removed. - // `let [mut] b = &3.3; b != &f64::NAN` is `&f64` only because of that comparison, so - // `b.is_nan()` leaves `{float}` and does not compile. `let b: f64 = 3.3` does. - fn can_suggest_is_nan_method<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) -> bool { - let mut cursor = expr.peel_blocks().peel_borrows(); - while let ExprKind::Path(qpath) = &cursor.kind - && let hir::def::Res::Local(hir_id) = cx.qpath_res(qpath, cursor.hir_id) - && let hir::Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id) - { - if local.ty.is_some_and(is_primitive_float) { - return true; - } - if let hir::PatKind::Binding(..) = local.pat.kind - && let Some(init) = local.init - { - cursor = init.peel_blocks().peel_borrows(); - } else { - break; - } - } - if let ExprKind::Lit(lit) = cursor.kind - && let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node - { - return false; - } - cx.typeck_results().expr_ty_opt(cursor).is_some_and(|ty| ty.peel_refs().is_floating_point()) - } - fn eq_ne_suggestion<'a>( cx: &LateContext<'a>, cmp: &'a hir::Expr<'a>, @@ -303,14 +264,6 @@ fn lint_nan<'tcx>( value_span.shrink_to_hi().to(nan_span) }; - if can_suggest_is_nan_method(cx, value) { - return InvalidNanComparisonsSuggestion::Spanful { - neg: is_ne.then(|| value_span.shrink_to_lo()), - float: value_span.shrink_to_hi(), - nan_plus_binop: remove_nan, - }; - } - let Some(mut ty) = cx.typeck_results().expr_ty_opt(value) else { return InvalidNanComparisonsSuggestion::Spanless; }; diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed index 39d2ba3e93788..857ccf152fb4e 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed @@ -5,39 +5,39 @@ fn main() { let x = 5f16; - let _ = x.is_nan(); + let _ = f16::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !x.is_nan(); + let _ = !f16::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f32; - let _ = x.is_nan(); + let _ = f32::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !x.is_nan(); + let _ = !f32::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f64; - let _ = x.is_nan(); + let _ = f64::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !x.is_nan(); + let _ = !f64::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f128; - let _ = x.is_nan(); + let _ = f128::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !x.is_nan(); + let _ = !f128::is_nan(x); //~^ WARN incorrect NaN comparison let b = &2.3f32; - if !b.is_nan() {} + if !f32::is_nan(*b) {} //~^ WARN incorrect NaN comparison let b = &2.3f32; - if !b.is_nan() {} + if !f32::is_nan(*b) {} //~^ WARN incorrect NaN comparison let _ = - !b.is_nan(); + !f32::is_nan(*b); // Issue #164143: an unsuffixed float is constrained by the `NAN` operand. // Suggesting `.is_nan()` drops that constraint and does not compile. @@ -52,6 +52,22 @@ fn main() { } let _ = m; + let unary = &(-3.3); + if !f64::is_nan(*unary) {} + //~^ WARN incorrect NaN comparison + + let block = &{ 3.3 }; + if !f64::is_nan(*block) {} + //~^ WARN incorrect NaN comparison + + let (tuple,) = (&3.3,); + if !f64::is_nan(*tuple) {} + //~^ WARN incorrect NaN comparison + + let arithmetic = &(3.3 + 0.0); + if !f64::is_nan(*arithmetic) {} + //~^ WARN incorrect NaN comparison + let _ = f64::is_nan(3.3); //~^ WARN incorrect NaN comparison @@ -60,33 +76,33 @@ fn main() { let suffixed = 3.3f64; let copied = suffixed; - let _ = !copied.is_nan(); + let _ = !f64::is_nan(copied); //~^ WARN incorrect NaN comparison let ascribed: f64 = 3.3; - let _ = !ascribed.is_nan(); + let _ = !f64::is_nan(ascribed); //~^ WARN incorrect NaN comparison parameter(1.0); parameter_ref(&1.0); - let _ = |x: f64| !x.is_nan(); //~ WARN incorrect NaN comparison + let _ = |x: f64| !f64::is_nan(x); //~ WARN incorrect NaN comparison #[allow(unused_macros)] macro_rules! nan { () => { f32::NAN }; } macro_rules! number { () => { 5f32 }; } - let _ = number!().is_nan(); + let _ = f32::is_nan(number!()); //~^ WARN incorrect NaN comparison - let _ = !number!().is_nan(); + let _ = !f32::is_nan(number!()); //~^ WARN incorrect NaN comparison } fn parameter(x: f64) { - let _ = x.is_nan(); + let _ = f64::is_nan(x); //~^ WARN incorrect NaN comparison } fn parameter_ref(x: &f32) { - if !x.is_nan() {} + if !f32::is_nan(*x) {} //~^ WARN incorrect NaN comparison } diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.rs b/tests/ui/lint/invalid-nan-comparison-suggestion.rs index 18e4a4093d10f..44979955c444e 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.rs +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.rs @@ -55,6 +55,22 @@ fn main() { } let _ = m; + let unary = &(-3.3); + if unary != &f64::NAN {} + //~^ WARN incorrect NaN comparison + + let block = &{ 3.3 }; + if block != &f64::NAN {} + //~^ WARN incorrect NaN comparison + + let (tuple,) = (&3.3,); + if tuple != &f64::NAN {} + //~^ WARN incorrect NaN comparison + + let arithmetic = &(3.3 + 0.0); + if arithmetic != &f64::NAN {} + //~^ WARN incorrect NaN comparison + let _ = 3.3 == f64::NAN; //~^ WARN incorrect NaN comparison diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr index 5252977148e09..1b58a4e97a97a 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr @@ -8,7 +8,7 @@ LL | let _ = x == f16::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x == f16::NAN; -LL + let _ = x.is_nan(); +LL + let _ = f16::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -20,7 +20,7 @@ LL | let _ = x != f16::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x != f16::NAN; -LL + let _ = !x.is_nan(); +LL + let _ = !f16::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -32,7 +32,7 @@ LL | let _ = x == f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x == f32::NAN; -LL + let _ = x.is_nan(); +LL + let _ = f32::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -44,7 +44,7 @@ LL | let _ = x != f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x != f32::NAN; -LL + let _ = !x.is_nan(); +LL + let _ = !f32::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -56,7 +56,7 @@ LL | let _ = x == f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x == f64::NAN; -LL + let _ = x.is_nan(); +LL + let _ = f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -68,7 +68,7 @@ LL | let _ = x != f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x != f64::NAN; -LL + let _ = !x.is_nan(); +LL + let _ = !f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -80,7 +80,7 @@ LL | let _ = x == f128::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x == f128::NAN; -LL + let _ = x.is_nan(); +LL + let _ = f128::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -92,7 +92,7 @@ LL | let _ = x != f128::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x != f128::NAN; -LL + let _ = !x.is_nan(); +LL + let _ = !f128::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -104,7 +104,7 @@ LL | if b != &f32::NAN {} help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - if b != &f32::NAN {} -LL + if !b.is_nan() {} +LL + if !f32::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -116,7 +116,7 @@ LL | if b != { &f32::NAN } {} help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - if b != { &f32::NAN } {} -LL + if !b.is_nan() {} +LL + if !f32::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -134,7 +134,7 @@ LL - b != { LL - LL - &f32::NAN LL - }; -LL + !b.is_nan(); +LL + !f32::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -162,7 +162,55 @@ LL + if !f64::is_nan(*m) { | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:58:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:59:8 + | +LL | if unary != &f64::NAN {} + | ^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if unary != &f64::NAN {} +LL + if !f64::is_nan(*unary) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:63:8 + | +LL | if block != &f64::NAN {} + | ^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if block != &f64::NAN {} +LL + if !f64::is_nan(*block) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:67:8 + | +LL | if tuple != &f64::NAN {} + | ^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if tuple != &f64::NAN {} +LL + if !f64::is_nan(*tuple) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:71:8 + | +LL | if arithmetic != &f64::NAN {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if arithmetic != &f64::NAN {} +LL + if !f64::is_nan(*arithmetic) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:74:13 | LL | let _ = 3.3 == f64::NAN; | ^^^^^^^^^^^^^^^ @@ -174,7 +222,7 @@ LL + let _ = f64::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:61:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:77:13 | LL | let _ = &3.3 != &f64::NAN; | ^^^^^^^^^^^^^^^^^ @@ -186,7 +234,7 @@ LL + let _ = !f64::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:66:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:82:13 | LL | let _ = copied != f64::NAN; | ^^^^^^^^^^^^^^^^^^ @@ -194,11 +242,11 @@ LL | let _ = copied != f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = copied != f64::NAN; -LL + let _ = !copied.is_nan(); +LL + let _ = !f64::is_nan(copied); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:70:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:86:13 | LL | let _ = ascribed != f64::NAN; | ^^^^^^^^^^^^^^^^^^^^ @@ -206,11 +254,11 @@ LL | let _ = ascribed != f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = ascribed != f64::NAN; -LL + let _ = !ascribed.is_nan(); +LL + let _ = !f64::is_nan(ascribed); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:75:22 + --> $DIR/invalid-nan-comparison-suggestion.rs:91:22 | LL | let _ = |x: f64| x != f64::NAN; | ^^^^^^^^^^^^^ @@ -218,11 +266,11 @@ LL | let _ = |x: f64| x != f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = |x: f64| x != f64::NAN; -LL + let _ = |x: f64| !x.is_nan(); +LL + let _ = |x: f64| !f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:81:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:97:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ @@ -230,11 +278,11 @@ LL | let _ = nan!() == number!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = nan!() == number!(); -LL + let _ = number!().is_nan(); +LL + let _ = f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:83:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:99:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ @@ -242,11 +290,11 @@ LL | let _ = number!() != nan!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = number!() != nan!(); -LL + let _ = !number!().is_nan(); +LL + let _ = !f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:88:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:104:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -254,11 +302,11 @@ LL | let _ = x == f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - let _ = x == f64::NAN; -LL + let _ = x.is_nan(); +LL + let _ = f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:93:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:109:8 | LL | if x != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -266,8 +314,8 @@ LL | if x != &f32::NAN {} help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - if x != &f32::NAN {} -LL + if !x.is_nan() {} +LL + if !f32::is_nan(*x) {} | -warning: 22 warnings emitted +warning: 26 warnings emitted diff --git a/tests/ui/lint/invalid-nan-comparison.stderr b/tests/ui/lint/invalid-nan-comparison.stderr index 486d2a9636c95..4b31abf84ff54 100644 --- a/tests/ui/lint/invalid-nan-comparison.stderr +++ b/tests/ui/lint/invalid-nan-comparison.stderr @@ -8,7 +8,7 @@ LL | const TEST: bool = 5f32 == f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - const TEST: bool = 5f32 == f32::NAN; -LL + const TEST: bool = 5f32.is_nan(); +LL + const TEST: bool = f32::is_nan(5f32); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -20,7 +20,7 @@ LL | x == f16::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x == f16::NAN; -LL + x.is_nan(); +LL + f16::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -32,7 +32,7 @@ LL | x != f16::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x != f16::NAN; -LL + !x.is_nan(); +LL + !f16::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -68,7 +68,7 @@ LL | number!() == f16::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - number!() == f16::NAN; -LL + number!().is_nan(); +LL + f16::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -80,7 +80,7 @@ LL | f16::NAN != number!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - f16::NAN != number!(); -LL + !number!().is_nan(); +LL + !f16::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -92,7 +92,7 @@ LL | x == f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x == f32::NAN; -LL + x.is_nan(); +LL + f32::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -104,7 +104,7 @@ LL | x != f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x != f32::NAN; -LL + !x.is_nan(); +LL + !f32::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -140,7 +140,7 @@ LL | number!() == f32::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - number!() == f32::NAN; -LL + number!().is_nan(); +LL + f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -152,7 +152,7 @@ LL | f32::NAN != number!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - f32::NAN != number!(); -LL + !number!().is_nan(); +LL + !f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -164,7 +164,7 @@ LL | x == f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x == f64::NAN; -LL + x.is_nan(); +LL + f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -176,7 +176,7 @@ LL | x != f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x != f64::NAN; -LL + !x.is_nan(); +LL + !f64::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -212,7 +212,7 @@ LL | number!() == f64::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - number!() == f64::NAN; -LL + number!().is_nan(); +LL + f64::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -224,7 +224,7 @@ LL | f64::NAN != number!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - f64::NAN != number!(); -LL + !number!().is_nan(); +LL + !f64::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -236,7 +236,7 @@ LL | x == f128::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x == f128::NAN; -LL + x.is_nan(); +LL + f128::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -248,7 +248,7 @@ LL | x != f128::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - x != f128::NAN; -LL + !x.is_nan(); +LL + !f128::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -284,7 +284,7 @@ LL | number!() == f128::NAN; help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - number!() == f128::NAN; -LL + number!().is_nan(); +LL + f128::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -296,7 +296,7 @@ LL | f128::NAN != number!(); help: use `f32::is_nan()` or `f64::is_nan()` instead | LL - f128::NAN != number!(); -LL + !number!().is_nan(); +LL + !f128::is_nan(number!()); | warning: 33 warnings emitted From ed4718fea5784a076c587b7467737d4e9bf8d133 Mon Sep 17 00:00:00 2001 From: Yu Date: Wed, 14 Oct 2026 01:19:39 +0800 Subject: [PATCH 13/45] lint: preserve qualified paths in NaN suggestions --- compiler/rustc_lint/src/types.rs | 13 +- .../invalid-nan-comparison-suggestion.fixed | 28 +++++ .../lint/invalid-nan-comparison-suggestion.rs | 28 +++++ .../invalid-nan-comparison-suggestion.stderr | 114 +++++++++++++----- 4 files changed, 155 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 4769cb7bf485c..78cf3429116ec 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -245,6 +245,14 @@ fn lint_nan<'tcx>( } } + fn nan_method_path(cx: &LateContext<'_>, nan: &hir::Expr<'_>) -> Option { + let expr = nan.peel_blocks().peel_borrows(); + let path = cx.sess().source_map().span_to_snippet(expr.span).ok()?; + path.strip_suffix("NAN") + .filter(|path| path.contains("::")) + .map(|path| format!("{path}is_nan")) + } + fn eq_ne_suggestion<'a>( cx: &LateContext<'a>, cmp: &'a hir::Expr<'a>, @@ -295,8 +303,11 @@ fn lint_nan<'tcx>( } let written_borrow = outer_span.until(operand_span); let neg = if is_ne { "!" } else { "" }; + let Some(method_path) = nan_method_path(cx, nan) else { + return InvalidNanComparisonsSuggestion::Spanless; + }; InvalidNanComparisonsSuggestion::Ufcs { - prefix: format!("{neg}{}::is_nan({}", float_ty.name_str(), "*".repeat(deref_count)), + prefix: format!("{neg}{method_path}({}", "*".repeat(deref_count)), prefix_span: operand_span.shrink_to_lo(), close_span: operand_span.shrink_to_hi(), remove_nan, diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed index 857ccf152fb4e..8fbdd079053c2 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed @@ -3,6 +3,8 @@ #![feature(f16, f128)] +extern crate core; + fn main() { let x = 5f16; let _ = f16::is_nan(x); @@ -68,6 +70,17 @@ fn main() { if !f64::is_nan(*arithmetic) {} //~^ WARN incorrect NaN comparison + let x = 1.0f64; + if !f64::is_nan(x) {} + //~^ WARN incorrect NaN comparison + let p = &x; + let _ = f64::is_nan(*p); + //~^ WARN incorrect NaN comparison + let _ = f64::is_nan(-x); + //~^ WARN incorrect NaN comparison + + shadowed::check(); + let _ = f64::is_nan(3.3); //~^ WARN incorrect NaN comparison @@ -106,3 +119,18 @@ fn parameter_ref(x: &f32) { if !f32::is_nan(*x) {} //~^ WARN incorrect NaN comparison } + +mod shadowed { + #[allow(dead_code, non_camel_case_types)] + struct f64; + use ::core::primitive::f64 as Float; + + pub fn check() { + let b = &3.3; + if !::core::primitive::f64::is_nan(*b) {} + //~^ WARN incorrect NaN comparison + let c = &4.4; + if !Float::is_nan(*c) {} + //~^ WARN incorrect NaN comparison + } +} diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.rs b/tests/ui/lint/invalid-nan-comparison-suggestion.rs index 44979955c444e..fb23208dfcfc9 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.rs +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.rs @@ -3,6 +3,8 @@ #![feature(f16, f128)] +extern crate core; + fn main() { let x = 5f16; let _ = x == f16::NAN; @@ -71,6 +73,17 @@ fn main() { if arithmetic != &f64::NAN {} //~^ WARN incorrect NaN comparison + let x = 1.0f64; + if &x != &f64::NAN {} + //~^ WARN incorrect NaN comparison + let p = &x; + let _ = *p == f64::NAN; + //~^ WARN incorrect NaN comparison + let _ = -x == f64::NAN; + //~^ WARN incorrect NaN comparison + + shadowed::check(); + let _ = 3.3 == f64::NAN; //~^ WARN incorrect NaN comparison @@ -109,3 +122,18 @@ fn parameter_ref(x: &f32) { if x != &f32::NAN {} //~^ WARN incorrect NaN comparison } + +mod shadowed { + #[allow(dead_code, non_camel_case_types)] + struct f64; + use ::core::primitive::f64 as Float; + + pub fn check() { + let b = &3.3; + if b != &::core::primitive::f64::NAN {} + //~^ WARN incorrect NaN comparison + let c = &4.4; + if c != &Float::NAN {} + //~^ WARN incorrect NaN comparison + } +} diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr index 1b58a4e97a97a..c9f3b6bd95052 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:8:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:10:13 | LL | let _ = x == f16::NAN; | ^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL + let _ = f16::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:10:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:12:13 | LL | let _ = x != f16::NAN; | ^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL + let _ = !f16::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:14:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:16:13 | LL | let _ = x == f32::NAN; | ^^^^^^^^^^^^^ @@ -36,7 +36,7 @@ LL + let _ = f32::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:16:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:18:13 | LL | let _ = x != f32::NAN; | ^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL + let _ = !f32::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:20:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:22:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL + let _ = f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:22:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:24:13 | LL | let _ = x != f64::NAN; | ^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL + let _ = !f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:26:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:28:13 | LL | let _ = x == f128::NAN; | ^^^^^^^^^^^^^^ @@ -84,7 +84,7 @@ LL + let _ = f128::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:28:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:30:13 | LL | let _ = x != f128::NAN; | ^^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + let _ = !f128::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:32:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:34:8 | LL | if b != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -108,7 +108,7 @@ LL + if !f32::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:36:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:38:8 | LL | if b != { &f32::NAN } {} | ^^^^^^^^^^^^^^^^^^ @@ -120,7 +120,7 @@ LL + if !f32::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:40:9 + --> $DIR/invalid-nan-comparison-suggestion.rs:42:9 | LL | / b != { LL | | @@ -138,7 +138,7 @@ LL + !f32::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:48:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:50:8 | LL | if b != &f64::NAN {} | ^^^^^^^^^^^^^^ @@ -150,7 +150,7 @@ LL + if !f64::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:52:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:54:8 | LL | if m != &f64::NAN { | ^^^^^^^^^^^^^^ @@ -162,7 +162,7 @@ LL + if !f64::is_nan(*m) { | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:59:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:61:8 | LL | if unary != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -174,7 +174,7 @@ LL + if !f64::is_nan(*unary) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:63:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:65:8 | LL | if block != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -186,7 +186,7 @@ LL + if !f64::is_nan(*block) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:67:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:69:8 | LL | if tuple != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -198,7 +198,7 @@ LL + if !f64::is_nan(*tuple) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:71:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:73:8 | LL | if arithmetic != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -210,7 +210,43 @@ LL + if !f64::is_nan(*arithmetic) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:74:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:77:8 + | +LL | if &x != &f64::NAN {} + | ^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if &x != &f64::NAN {} +LL + if !f64::is_nan(x) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:80:13 + | +LL | let _ = *p == f64::NAN; + | ^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = *p == f64::NAN; +LL + let _ = f64::is_nan(*p); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:82:13 + | +LL | let _ = -x == f64::NAN; + | ^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - let _ = -x == f64::NAN; +LL + let _ = f64::is_nan(-x); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:87:13 | LL | let _ = 3.3 == f64::NAN; | ^^^^^^^^^^^^^^^ @@ -222,7 +258,7 @@ LL + let _ = f64::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:77:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:90:13 | LL | let _ = &3.3 != &f64::NAN; | ^^^^^^^^^^^^^^^^^ @@ -234,7 +270,7 @@ LL + let _ = !f64::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:82:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:95:13 | LL | let _ = copied != f64::NAN; | ^^^^^^^^^^^^^^^^^^ @@ -246,7 +282,7 @@ LL + let _ = !f64::is_nan(copied); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:86:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:99:13 | LL | let _ = ascribed != f64::NAN; | ^^^^^^^^^^^^^^^^^^^^ @@ -258,7 +294,7 @@ LL + let _ = !f64::is_nan(ascribed); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:91:22 + --> $DIR/invalid-nan-comparison-suggestion.rs:104:22 | LL | let _ = |x: f64| x != f64::NAN; | ^^^^^^^^^^^^^ @@ -270,7 +306,7 @@ LL + let _ = |x: f64| !f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:97:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:110:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ @@ -282,7 +318,7 @@ LL + let _ = f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:99:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:112:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ @@ -294,7 +330,7 @@ LL + let _ = !f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:104:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:117:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -306,7 +342,7 @@ LL + let _ = f64::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:109:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:122:8 | LL | if x != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -317,5 +353,29 @@ LL - if x != &f32::NAN {} LL + if !f32::is_nan(*x) {} | -warning: 26 warnings emitted +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:133:12 + | +LL | if b != &::core::primitive::f64::NAN {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if b != &::core::primitive::f64::NAN {} +LL + if !::core::primitive::f64::is_nan(*b) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:136:12 + | +LL | if c != &Float::NAN {} + | ^^^^^^^^^^^^^^^^ + | +help: use `f32::is_nan()` or `f64::is_nan()` instead + | +LL - if c != &Float::NAN {} +LL + if !Float::is_nan(*c) {} + | + +warning: 31 warnings emitted From 62cf356902c44a2c2362b02b901831d4c8dd9b40 Mon Sep 17 00:00:00 2001 From: Yu Date: Wed, 14 Oct 2026 14:05:14 +0800 Subject: [PATCH 14/45] lint: fix NaN suggestions for nested operands and macros --- compiler/rustc_lint/src/diagnostics.rs | 9 +- compiler/rustc_lint/src/types.rs | 118 ++++--- ...nvalid-nan-comparison-custom-partial-eq.rs | 25 ++ ...id-nan-comparison-custom-partial-eq.stderr | 27 ++ .../lint/invalid-nan-comparison-no-std.fixed | 18 ++ .../ui/lint/invalid-nan-comparison-no-std.rs | 18 ++ .../lint/invalid-nan-comparison-no-std.stderr | 15 + .../invalid-nan-comparison-no-suggestion.rs | 28 ++ ...nvalid-nan-comparison-no-suggestion.stderr | 50 +++ .../invalid-nan-comparison-suggestion.fixed | 113 +++++-- .../lint/invalid-nan-comparison-suggestion.rs | 51 +++ .../invalid-nan-comparison-suggestion.stderr | 304 +++++++++++++----- tests/ui/lint/invalid-nan-comparison.stderr | 68 ++-- 13 files changed, 649 insertions(+), 195 deletions(-) create mode 100644 tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs create mode 100644 tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr create mode 100644 tests/ui/lint/invalid-nan-comparison-no-std.fixed create mode 100644 tests/ui/lint/invalid-nan-comparison-no-std.rs create mode 100644 tests/ui/lint/invalid-nan-comparison-no-std.stderr create mode 100644 tests/ui/lint/invalid-nan-comparison-no-suggestion.rs create mode 100644 tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr diff --git a/compiler/rustc_lint/src/diagnostics.rs b/compiler/rustc_lint/src/diagnostics.rs index ea36a485fc3ac..d66a3c1ca13f3 100644 --- a/compiler/rustc_lint/src/diagnostics.rs +++ b/compiler/rustc_lint/src/diagnostics.rs @@ -2322,7 +2322,7 @@ pub(crate) enum InvalidNanComparisons { #[derive(Subdiagnostic)] pub(crate) enum InvalidNanComparisonsSuggestion { #[multipart_suggestion( - "use `f32::is_nan()` or `f64::is_nan()` instead", + "use the `is_nan` method instead", style = "verbose", applicability = "machine-applicable" )] @@ -2330,14 +2330,13 @@ pub(crate) enum InvalidNanComparisonsSuggestion { prefix: String, #[suggestion_part(code = "{prefix}")] prefix_span: Span, - #[suggestion_part(code = ")")] + suffix: &'static str, + #[suggestion_part(code = "{suffix}")] close_span: Span, #[suggestion_part(code = "")] remove_nan: Span, - #[suggestion_part(code = "")] - remove_borrow: Option, }, - #[help("use `f32::is_nan()` or `f64::is_nan()` instead")] + #[help("use the `is_nan` method instead")] Spanless, } diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 78cf3429116ec..c38e77c5fe6c7 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -2,6 +2,7 @@ use std::iter; use rustc_abi::{BackendRepr, TagEncoding, Variants, WrappingRange}; use rustc_ast as ast; +use rustc_ast::util::parser::ExprPrecedence; use rustc_attr_ir::find_attr; use rustc_attr_ir::lang_items::LangItem; use rustc_hir as hir; @@ -113,8 +114,8 @@ declare_lint! { } declare_lint! { - /// The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN` - /// as one of the operand. + /// The `invalid_nan_comparisons` lint checks comparisons with the `NAN` constants + /// of `f16`, `f32`, `f64`, and `f128`. /// /// ### Example /// @@ -127,8 +128,8 @@ declare_lint! { /// /// ### Explanation /// - /// NaN does not compare meaningfully to anything – not - /// even itself – so those comparisons are always false. + /// NaN compares unequal to every floating-point value, including itself. + /// Use the `is_nan` method to check whether a value is NaN. INVALID_NAN_COMPARISONS, Warn, "detects invalid floating point NaN comparisons" @@ -229,28 +230,50 @@ fn lint_nan<'tcx>( l: &'tcx hir::Expr<'tcx>, r: &'tcx hir::Expr<'tcx>, ) { - fn nan_ty(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { - let expr = expr.peel_blocks().peel_borrows(); + fn nan_ty(cx: &LateContext<'_>, mut expr: &hir::Expr<'_>) -> Option { + loop { + expr = expr.peel_blocks(); + match expr.kind { + ExprKind::AddrOf(.., inner) => expr = inner, + _ => break, + } + } let ExprKind::Path(qpath) = expr.kind else { return None }; let def_id = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id()?; - if !matches!( - cx.tcx.get_diagnostic_name(def_id), - Some(sym::f16_nan | sym::f32_nan | sym::f64_nan | sym::f128_nan) - ) { - return None; - } - match cx.tcx.type_of(def_id).instantiate_identity().skip_norm_wip().kind() { - ty::Float(fty) => Some(*fty), - _ => None, - } + Some(match cx.tcx.get_diagnostic_name(def_id)? { + sym::f16_nan => ty::FloatTy::F16, + sym::f32_nan => ty::FloatTy::F32, + sym::f64_nan => ty::FloatTy::F64, + sym::f128_nan => ty::FloatTy::F128, + _ => return None, + }) } - fn nan_method_path(cx: &LateContext<'_>, nan: &hir::Expr<'_>) -> Option { - let expr = nan.peel_blocks().peel_borrows(); - let path = cx.sess().source_map().span_to_snippet(expr.span).ok()?; - path.strip_suffix("NAN") - .filter(|path| path.contains("::")) - .map(|path| format!("{path}is_nan")) + fn nan_method_path( + cx: &LateContext<'_>, + mut nan: &hir::Expr<'_>, + float_ty: ty::FloatTy, + cmp_span: Span, + ) -> Option { + loop { + match nan.kind { + ExprKind::AddrOf(.., inner) => nan = inner, + ExprKind::Block(block, _) if block.stmts.is_empty() => nan = block.expr?, + _ => break, + } + } + let ExprKind::Path(hir::QPath::TypeRelative(ty, _)) = nan.kind else { + return None; + }; + if !ty.span.from_expansion() && cmp_span.contains(ty.span) { + let ty = cx.sess().source_map().span_to_snippet(ty.span).ok()?; + Some(format!("<{ty}>::is_nan")) + } else if matches!(float_ty, ty::FloatTy::F32 | ty::FloatTy::F64) { + let krate = std_or_core(cx)?; + Some(format!("::{krate}::primitive::{}::is_nan", float_ty.name_str())) + } else { + None + } } fn eq_ne_suggestion<'a>( @@ -261,11 +284,18 @@ fn lint_nan<'tcx>( float_ty: ty::FloatTy, is_ne: bool, ) -> InvalidNanComparisonsSuggestion { - let (Some(value_span), Some(nan_span)) = - (value.span.find_ancestor_inside(cmp.span), nan.span.find_ancestor_inside(cmp.span)) - else { + if cmp.span.from_expansion() { + return InvalidNanComparisonsSuggestion::Spanless; + } + let (Some(value_span), Some(nan_span)) = ( + value.span.find_ancestor_inside_same_ctxt(cmp.span), + nan.span.find_ancestor_inside_same_ctxt(cmp.span), + ) else { return InvalidNanComparisonsSuggestion::Spanless; }; + if value_span.overlaps(nan_span) { + return InvalidNanComparisonsSuggestion::Spanless; + } let remove_nan = if nan_span.lo() < value_span.lo() { nan_span.until(value_span) } else { @@ -275,43 +305,29 @@ fn lint_nan<'tcx>( let Some(mut ty) = cx.typeck_results().expr_ty_opt(value) else { return InvalidNanComparisonsSuggestion::Spanless; }; - let mut operand = value.peel_blocks(); - let Some(mut operand_span) = operand.span.find_ancestor_inside(value_span) else { - return InvalidNanComparisonsSuggestion::Spanless; - }; - let outer_span = operand_span; let mut deref_count = 0; while let ty::Ref(_, inner, _) = *ty.kind() { - if let ExprKind::AddrOf(_, _, inner_expr) = operand.kind - && let peeled = inner_expr.peel_blocks() - && let Some(peeled_span) = peeled.span.find_ancestor_inside(operand_span) - { - operand = peeled; - operand_span = peeled_span; - } else { - deref_count += 1; - } + deref_count += 1; ty = inner; } - let matches_float = match *ty.kind() { - ty::Float(fty) => fty == float_ty, - ty::Infer(ty::FloatVar(_)) => true, - _ => false, - }; - if !matches_float { + if !matches!(*ty.kind(), ty::Float(fty) if fty == float_ty) { return InvalidNanComparisonsSuggestion::Spanless; } - let written_borrow = outer_span.until(operand_span); let neg = if is_ne { "!" } else { "" }; - let Some(method_path) = nan_method_path(cx, nan) else { + let Some(method_path) = nan_method_path(cx, nan, float_ty, cmp.span) else { return InvalidNanComparisonsSuggestion::Spanless; }; + let (open, suffix) = if deref_count != 0 && cx.precedence(value) < ExprPrecedence::Prefix { + ("(", "))") + } else { + ("", ")") + }; InvalidNanComparisonsSuggestion::Ufcs { - prefix: format!("{neg}{method_path}({}", "*".repeat(deref_count)), - prefix_span: operand_span.shrink_to_lo(), - close_span: operand_span.shrink_to_hi(), + prefix: format!("{neg}{method_path}({}{open}", "*".repeat(deref_count)), + prefix_span: value_span.shrink_to_lo(), + suffix, + close_span: value_span.shrink_to_hi(), remove_nan, - remove_borrow: (!written_borrow.is_empty()).then_some(written_borrow), } } diff --git a/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs b/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs new file mode 100644 index 0000000000000..be3351916ddc4 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs @@ -0,0 +1,25 @@ +//@ check-pass + +struct NotFloat; + +impl PartialEq for NotFloat { + fn eq(&self, _: &f64) -> bool { + true + } +} + +impl PartialEq for f64 { + fn eq(&self, _: &NotFloat) -> bool { + true + } +} + +fn main() { + let value = NotFloat; + let _ = value == f64::NAN; + //~^ WARN incorrect NaN comparison + let _ = f64::NAN != value; + //~^ WARN incorrect NaN comparison + let _ = &value != &f64::NAN; + //~^ WARN incorrect NaN comparison +} diff --git a/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr b/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr new file mode 100644 index 0000000000000..daf91b092a3bb --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr @@ -0,0 +1,27 @@ +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:19:13 + | +LL | let _ = value == f64::NAN; + | ^^^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + = note: `#[warn(invalid_nan_comparisons)]` on by default + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:21:13 + | +LL | let _ = f64::NAN != value; + | ^^^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:23:13 + | +LL | let _ = &value != &f64::NAN; + | ^^^^^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + +warning: 3 warnings emitted + diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.fixed b/tests/ui/lint/invalid-nan-comparison-no-std.fixed new file mode 100644 index 0000000000000..3e35d14d223d7 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-no-std.fixed @@ -0,0 +1,18 @@ +//@ check-pass +//@ run-rustfix +//@ compile-flags: --crate-type=lib + +#![no_std] + +pub type MacroFloat = ::core::primitive::f64; + +#[allow(non_camel_case_types)] +pub struct f64; + +pub fn check() { + #[allow(unused_macros)] + macro_rules! nan { () => { $crate::MacroFloat::NAN }; } + let b = &3.3; + let _ = !::core::primitive::f64::is_nan(*b); + //~^ WARN incorrect NaN comparison +} diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.rs b/tests/ui/lint/invalid-nan-comparison-no-std.rs new file mode 100644 index 0000000000000..7ace5b976c890 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-no-std.rs @@ -0,0 +1,18 @@ +//@ check-pass +//@ run-rustfix +//@ compile-flags: --crate-type=lib + +#![no_std] + +pub type MacroFloat = ::core::primitive::f64; + +#[allow(non_camel_case_types)] +pub struct f64; + +pub fn check() { + #[allow(unused_macros)] + macro_rules! nan { () => { $crate::MacroFloat::NAN }; } + let b = &3.3; + let _ = b != &nan!(); + //~^ WARN incorrect NaN comparison +} diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.stderr b/tests/ui/lint/invalid-nan-comparison-no-std.stderr new file mode 100644 index 0000000000000..143736b352ad9 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-no-std.stderr @@ -0,0 +1,15 @@ +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-std.rs:16:13 + | +LL | let _ = b != &nan!(); + | ^^^^^^^^^^^^ + | + = note: `#[warn(invalid_nan_comparisons)]` on by default +help: use the `is_nan` method instead + | +LL - let _ = b != &nan!(); +LL + let _ = !::core::primitive::f64::is_nan(*b); + | + +warning: 1 warning emitted + diff --git a/tests/ui/lint/invalid-nan-comparison-no-suggestion.rs b/tests/ui/lint/invalid-nan-comparison-no-suggestion.rs new file mode 100644 index 0000000000000..afe3cb7cfd259 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-no-suggestion.rs @@ -0,0 +1,28 @@ +//@ check-pass + +#![feature(f16, f128)] + +fn main() { + let mut effects = 0; + let _ = 3.3 == { + effects += 1; + f64::NAN + }; + //~^^^^ WARN incorrect NaN comparison + let _ = effects; + + macro_rules! scoped_nan { () => {{ type Float = f64; Float::NAN }}; } + let _ = 3.3 == scoped_nan!(); + //~^ WARN incorrect NaN comparison + + macro_rules! nan16 { () => { f16::NAN }; } + let _ = 3.3 == nan16!(); + //~^ WARN incorrect NaN comparison + macro_rules! nan128 { () => { f128::NAN }; } + let _ = 3.3 != nan128!(); + //~^ WARN incorrect NaN comparison + + macro_rules! compare { () => { 3.3 != f64::NAN }; } + //~^ WARN incorrect NaN comparison + let _ = compare!(); +} diff --git a/tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr b/tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr new file mode 100644 index 0000000000000..659deb868b027 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr @@ -0,0 +1,50 @@ +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-suggestion.rs:7:13 + | +LL | let _ = 3.3 == { + | _____________^ +LL | | effects += 1; +LL | | f64::NAN +LL | | }; + | |_____^ + | + = help: use the `is_nan` method instead + = note: `#[warn(invalid_nan_comparisons)]` on by default + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-suggestion.rs:15:13 + | +LL | let _ = 3.3 == scoped_nan!(); + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-suggestion.rs:19:13 + | +LL | let _ = 3.3 == nan16!(); + | ^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-suggestion.rs:22:13 + | +LL | let _ = 3.3 != nan128!(); + | ^^^^^^^^^^^^^^^^ + | + = help: use the `is_nan` method instead + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-no-suggestion.rs:25:36 + | +LL | macro_rules! compare { () => { 3.3 != f64::NAN }; } + | ^^^^^^^^^^^^^^^ +LL | +LL | let _ = compare!(); + | ---------- in this macro invocation + | + = help: use the `is_nan` method instead + +warning: 5 warnings emitted + diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed index 8fbdd079053c2..d3043e3f72516 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.fixed @@ -7,116 +7,164 @@ extern crate core; fn main() { let x = 5f16; - let _ = f16::is_nan(x); + let _ = ::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !f16::is_nan(x); + let _ = !::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f32; - let _ = f32::is_nan(x); + let _ = ::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !f32::is_nan(x); + let _ = !::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f64; - let _ = f64::is_nan(x); + let _ = ::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !f64::is_nan(x); + let _ = !::is_nan(x); //~^ WARN incorrect NaN comparison let x = 5f128; - let _ = f128::is_nan(x); + let _ = ::is_nan(x); //~^ WARN incorrect NaN comparison - let _ = !f128::is_nan(x); + let _ = !::is_nan(x); //~^ WARN incorrect NaN comparison let b = &2.3f32; - if !f32::is_nan(*b) {} + if !::is_nan(*b) {} //~^ WARN incorrect NaN comparison let b = &2.3f32; - if !f32::is_nan(*b) {} + if !::is_nan(*b) {} //~^ WARN incorrect NaN comparison let _ = - !f32::is_nan(*b); + !::is_nan(*b); // Issue #164143: an unsuffixed float is constrained by the `NAN` operand. // Suggesting `.is_nan()` drops that constraint and does not compile. let b = &3.3; - if !f64::is_nan(*b) {} + if !::is_nan(*b) {} //~^ WARN incorrect NaN comparison let mut m = &3.3; - if !f64::is_nan(*m) { + if !::is_nan(*m) { //~^ WARN incorrect NaN comparison m = &1.0; } let _ = m; let unary = &(-3.3); - if !f64::is_nan(*unary) {} + if !::is_nan(*unary) {} //~^ WARN incorrect NaN comparison let block = &{ 3.3 }; - if !f64::is_nan(*block) {} + if !::is_nan(*block) {} //~^ WARN incorrect NaN comparison let (tuple,) = (&3.3,); - if !f64::is_nan(*tuple) {} + if !::is_nan(*tuple) {} //~^ WARN incorrect NaN comparison let arithmetic = &(3.3 + 0.0); - if !f64::is_nan(*arithmetic) {} + if !::is_nan(*arithmetic) {} //~^ WARN incorrect NaN comparison let x = 1.0f64; - if !f64::is_nan(x) {} + if !::is_nan(*&x) {} //~^ WARN incorrect NaN comparison let p = &x; - let _ = f64::is_nan(*p); + let _ = ::is_nan(*p); //~^ WARN incorrect NaN comparison - let _ = f64::is_nan(-x); + let _ = ::is_nan(-x); //~^ WARN incorrect NaN comparison shadowed::check(); + nested_operands(); + macro_paths(); - let _ = f64::is_nan(3.3); + let _ = ::is_nan(3.3); //~^ WARN incorrect NaN comparison - let _ = !f64::is_nan(3.3); + let _ = !::is_nan(*&3.3); //~^ WARN incorrect NaN comparison let suffixed = 3.3f64; let copied = suffixed; - let _ = !f64::is_nan(copied); + let _ = !::is_nan(copied); //~^ WARN incorrect NaN comparison let ascribed: f64 = 3.3; - let _ = !f64::is_nan(ascribed); + let _ = !::is_nan(ascribed); //~^ WARN incorrect NaN comparison parameter(1.0); parameter_ref(&1.0); - let _ = |x: f64| !f64::is_nan(x); //~ WARN incorrect NaN comparison + let _ = |x: f64| !::is_nan(x); //~ WARN incorrect NaN comparison #[allow(unused_macros)] macro_rules! nan { () => { f32::NAN }; } macro_rules! number { () => { 5f32 }; } - let _ = f32::is_nan(number!()); + let _ = ::std::primitive::f32::is_nan(number!()); //~^ WARN incorrect NaN comparison - let _ = !f32::is_nan(number!()); + let _ = !::std::primitive::f32::is_nan(number!()); //~^ WARN incorrect NaN comparison } fn parameter(x: f64) { - let _ = f64::is_nan(x); + let _ = ::is_nan(x); //~^ WARN incorrect NaN comparison } fn parameter_ref(x: &f32) { - if !f32::is_nan(*x) {} + if !::is_nan(*x) {} + //~^ WARN incorrect NaN comparison +} + +fn nested_operands() { + let x = 1.0f64; + let _ = ::is_nan(*(&x as &f64)); + //~^ WARN incorrect NaN comparison + let _ = !::is_nan(*{ &{ 3.3 } }); + //~^ WARN incorrect NaN comparison + let _ = ::is_nan(***&&&3.3); + //~^ WARN incorrect NaN comparison + let b = &&&3.3; + let _ = !::is_nan(***b); + //~^ WARN incorrect NaN comparison + let b = &mut 3.3; + let _ = !::is_nan(*b); + //~^ WARN incorrect NaN comparison + let b = &3.3; + let _ = ::is_nan(*b); + //~^ WARN incorrect NaN comparison + let b = &3.3; + let _ = !::is_nan(*b); + //~^ WARN incorrect NaN comparison + let b = &&3.3; + let _ = ::is_nan(**b); + //~^ WARN incorrect NaN comparison + + let b = &(-3.3); + let _ = !::is_nan(*b); + //~^ WARN incorrect NaN comparison + let b = &(3.3 + 0.0); + let _ = ::is_nan(*b); + //~^ WARN incorrect NaN comparison + + macro_rules! borrowed { () => { &3.3 }; } + let _ = !::is_nan(*borrowed!()); + //~^ WARN incorrect NaN comparison +} + +pub type MacroFloat = f64; + +fn macro_paths() { + #[allow(unused_macros)] + macro_rules! nan { () => { $crate::MacroFloat::NAN }; } + let b = &3.3; + let _ = !::std::primitive::f64::is_nan(*b); //~^ WARN incorrect NaN comparison } @@ -127,10 +175,13 @@ mod shadowed { pub fn check() { let b = &3.3; - if !::core::primitive::f64::is_nan(*b) {} + if !<::core::primitive::f64>::is_nan(*b) {} //~^ WARN incorrect NaN comparison let c = &4.4; - if !Float::is_nan(*c) {} + if !::is_nan(*c) {} + //~^ WARN incorrect NaN comparison + let d = &5.5; + if !::is_nan(*d) {} //~^ WARN incorrect NaN comparison } } diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.rs b/tests/ui/lint/invalid-nan-comparison-suggestion.rs index fb23208dfcfc9..d5376db904453 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.rs +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.rs @@ -83,6 +83,8 @@ fn main() { //~^ WARN incorrect NaN comparison shadowed::check(); + nested_operands(); + macro_paths(); let _ = 3.3 == f64::NAN; //~^ WARN incorrect NaN comparison @@ -123,6 +125,52 @@ fn parameter_ref(x: &f32) { //~^ WARN incorrect NaN comparison } +fn nested_operands() { + let x = 1.0f64; + let _ = &x as &f64 == &f64::NAN; + //~^ WARN incorrect NaN comparison + let _ = { &{ 3.3 } } != &f64::NAN; + //~^ WARN incorrect NaN comparison + let _ = &&&3.3 == &&&f64::NAN; + //~^ WARN incorrect NaN comparison + let b = &&&3.3; + let _ = &&&f64::NAN != b; + //~^ WARN incorrect NaN comparison + let b = &mut 3.3; + let _ = b != &f64::NAN; + //~^ WARN incorrect NaN comparison + let b = &3.3; + let _ = &(::NAN) == b; + //~^ WARN incorrect NaN comparison + let b = &3.3; + let _ = b != &{ f64::NAN }; + //~^ WARN incorrect NaN comparison + let b = &&3.3; + let _ = &{ &{ f64::NAN } } == b; + //~^ WARN incorrect NaN comparison + + let b = &(-3.3); + let _ = b != &f16::NAN; + //~^ WARN incorrect NaN comparison + let b = &(3.3 + 0.0); + let _ = b == &f128::NAN; + //~^ WARN incorrect NaN comparison + + macro_rules! borrowed { () => { &3.3 }; } + let _ = borrowed!() != &f64::NAN; + //~^ WARN incorrect NaN comparison +} + +pub type MacroFloat = f64; + +fn macro_paths() { + #[allow(unused_macros)] + macro_rules! nan { () => { $crate::MacroFloat::NAN }; } + let b = &3.3; + let _ = b != &nan!(); + //~^ WARN incorrect NaN comparison +} + mod shadowed { #[allow(dead_code, non_camel_case_types)] struct f64; @@ -135,5 +183,8 @@ mod shadowed { let c = &4.4; if c != &Float::NAN {} //~^ WARN incorrect NaN comparison + let d = &5.5; + if d != &Float::r#NAN {} + //~^ WARN incorrect NaN comparison } } diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr index c9f3b6bd95052..9c920496c3f54 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparison-suggestion.stderr @@ -5,10 +5,10 @@ LL | let _ = x == f16::NAN; | ^^^^^^^^^^^^^ | = note: `#[warn(invalid_nan_comparisons)]` on by default -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x == f16::NAN; -LL + let _ = f16::is_nan(x); +LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -17,10 +17,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x != f16::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x != f16::NAN; -LL + let _ = !f16::is_nan(x); +LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -29,10 +29,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x == f32::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x == f32::NAN; -LL + let _ = f32::is_nan(x); +LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -41,10 +41,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x != f32::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x != f32::NAN; -LL + let _ = !f32::is_nan(x); +LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -53,10 +53,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x == f64::NAN; -LL + let _ = f64::is_nan(x); +LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -65,10 +65,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x != f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x != f64::NAN; -LL + let _ = !f64::is_nan(x); +LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -77,10 +77,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x == f128::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x == f128::NAN; -LL + let _ = f128::is_nan(x); +LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -89,10 +89,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = x != f128::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x != f128::NAN; -LL + let _ = !f128::is_nan(x); +LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -101,10 +101,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if b != &f32::NAN {} | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if b != &f32::NAN {} -LL + if !f32::is_nan(*b) {} +LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -113,10 +113,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if b != { &f32::NAN } {} | ^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if b != { &f32::NAN } {} -LL + if !f32::is_nan(*b) {} +LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -128,13 +128,13 @@ LL | | &f32::NAN LL | | }; | |_________^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - b != { LL - LL - &f32::NAN LL - }; -LL + !f32::is_nan(*b); +LL + !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -143,10 +143,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if b != &f64::NAN {} | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if b != &f64::NAN {} -LL + if !f64::is_nan(*b) {} +LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -155,10 +155,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if m != &f64::NAN { | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if m != &f64::NAN { -LL + if !f64::is_nan(*m) { +LL + if !::is_nan(*m) { | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -167,10 +167,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if unary != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if unary != &f64::NAN {} -LL + if !f64::is_nan(*unary) {} +LL + if !::is_nan(*unary) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -179,10 +179,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if block != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if block != &f64::NAN {} -LL + if !f64::is_nan(*block) {} +LL + if !::is_nan(*block) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -191,10 +191,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if tuple != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if tuple != &f64::NAN {} -LL + if !f64::is_nan(*tuple) {} +LL + if !::is_nan(*tuple) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -203,10 +203,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if arithmetic != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if arithmetic != &f64::NAN {} -LL + if !f64::is_nan(*arithmetic) {} +LL + if !::is_nan(*arithmetic) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -215,10 +215,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | if &x != &f64::NAN {} | ^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if &x != &f64::NAN {} -LL + if !f64::is_nan(x) {} +LL + if !::is_nan(*&x) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -227,10 +227,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = *p == f64::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = *p == f64::NAN; -LL + let _ = f64::is_nan(*p); +LL + let _ = ::is_nan(*p); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -239,143 +239,299 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | let _ = -x == f64::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = -x == f64::NAN; -LL + let _ = f64::is_nan(-x); +LL + let _ = ::is_nan(-x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:87:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:89:13 | LL | let _ = 3.3 == f64::NAN; | ^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = 3.3 == f64::NAN; -LL + let _ = f64::is_nan(3.3); +LL + let _ = ::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:90:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:92:13 | LL | let _ = &3.3 != &f64::NAN; | ^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = &3.3 != &f64::NAN; -LL + let _ = !f64::is_nan(3.3); +LL + let _ = !::is_nan(*&3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:95:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:97:13 | LL | let _ = copied != f64::NAN; | ^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = copied != f64::NAN; -LL + let _ = !f64::is_nan(copied); +LL + let _ = !::is_nan(copied); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:99:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:101:13 | LL | let _ = ascribed != f64::NAN; | ^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = ascribed != f64::NAN; -LL + let _ = !f64::is_nan(ascribed); +LL + let _ = !::is_nan(ascribed); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:104:22 + --> $DIR/invalid-nan-comparison-suggestion.rs:106:22 | LL | let _ = |x: f64| x != f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = |x: f64| x != f64::NAN; -LL + let _ = |x: f64| !f64::is_nan(x); +LL + let _ = |x: f64| !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:110:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:112:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = nan!() == number!(); -LL + let _ = f32::is_nan(number!()); +LL + let _ = ::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:112:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:114:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = number!() != nan!(); -LL + let _ = !f32::is_nan(number!()); +LL + let _ = !::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:117:13 + --> $DIR/invalid-nan-comparison-suggestion.rs:119:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - let _ = x == f64::NAN; -LL + let _ = f64::is_nan(x); +LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:122:8 + --> $DIR/invalid-nan-comparison-suggestion.rs:124:8 | LL | if x != &f32::NAN {} | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if x != &f32::NAN {} -LL + if !f32::is_nan(*x) {} +LL + if !::is_nan(*x) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:130:13 + | +LL | let _ = &x as &f64 == &f64::NAN; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = &x as &f64 == &f64::NAN; +LL + let _ = ::is_nan(*(&x as &f64)); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:132:13 + | +LL | let _ = { &{ 3.3 } } != &f64::NAN; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = { &{ 3.3 } } != &f64::NAN; +LL + let _ = !::is_nan(*{ &{ 3.3 } }); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:133:12 + --> $DIR/invalid-nan-comparison-suggestion.rs:134:13 + | +LL | let _ = &&&3.3 == &&&f64::NAN; + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = &&&3.3 == &&&f64::NAN; +LL + let _ = ::is_nan(***&&&3.3); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:137:13 + | +LL | let _ = &&&f64::NAN != b; + | ^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = &&&f64::NAN != b; +LL + let _ = !::is_nan(***b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:140:13 + | +LL | let _ = b != &f64::NAN; + | ^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = b != &f64::NAN; +LL + let _ = !::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:143:13 + | +LL | let _ = &(::NAN) == b; + | ^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = &(::NAN) == b; +LL + let _ = ::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:146:13 + | +LL | let _ = b != &{ f64::NAN }; + | ^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = b != &{ f64::NAN }; +LL + let _ = !::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:149:13 + | +LL | let _ = &{ &{ f64::NAN } } == b; + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = &{ &{ f64::NAN } } == b; +LL + let _ = ::is_nan(**b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:153:13 + | +LL | let _ = b != &f16::NAN; + | ^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = b != &f16::NAN; +LL + let _ = !::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:156:13 + | +LL | let _ = b == &f128::NAN; + | ^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = b == &f128::NAN; +LL + let _ = ::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:160:13 + | +LL | let _ = borrowed!() != &f64::NAN; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = borrowed!() != &f64::NAN; +LL + let _ = !::is_nan(*borrowed!()); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:170:13 + | +LL | let _ = b != &nan!(); + | ^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _ = b != &nan!(); +LL + let _ = !::std::primitive::f64::is_nan(*b); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:181:12 | LL | if b != &::core::primitive::f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if b != &::core::primitive::f64::NAN {} -LL + if !::core::primitive::f64::is_nan(*b) {} +LL + if !<::core::primitive::f64>::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:136:12 + --> $DIR/invalid-nan-comparison-suggestion.rs:184:12 | LL | if c != &Float::NAN {} | ^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - if c != &Float::NAN {} -LL + if !Float::is_nan(*c) {} +LL + if !::is_nan(*c) {} + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/invalid-nan-comparison-suggestion.rs:187:12 + | +LL | if d != &Float::r#NAN {} + | ^^^^^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - if d != &Float::r#NAN {} +LL + if !::is_nan(*d) {} | -warning: 31 warnings emitted +warning: 44 warnings emitted diff --git a/tests/ui/lint/invalid-nan-comparison.stderr b/tests/ui/lint/invalid-nan-comparison.stderr index 4b31abf84ff54..3ee29a9fc4570 100644 --- a/tests/ui/lint/invalid-nan-comparison.stderr +++ b/tests/ui/lint/invalid-nan-comparison.stderr @@ -5,10 +5,10 @@ LL | const TEST: bool = 5f32 == f32::NAN; | ^^^^^^^^^^^^^^^^ | = note: `#[warn(invalid_nan_comparisons)]` on by default -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - const TEST: bool = 5f32 == f32::NAN; -LL + const TEST: bool = f32::is_nan(5f32); +LL + const TEST: bool = ::is_nan(5f32); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -17,10 +17,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x == f16::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x == f16::NAN; -LL + f16::is_nan(x); +LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -29,10 +29,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x != f16::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x != f16::NAN; -LL + !f16::is_nan(x); +LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -65,10 +65,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | number!() == f16::NAN; | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - number!() == f16::NAN; -LL + f16::is_nan(number!()); +LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -77,10 +77,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | f16::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - f16::NAN != number!(); -LL + !f16::is_nan(number!()); +LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -89,10 +89,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x == f32::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x == f32::NAN; -LL + f32::is_nan(x); +LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -101,10 +101,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x != f32::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x != f32::NAN; -LL + !f32::is_nan(x); +LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -137,10 +137,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | number!() == f32::NAN; | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - number!() == f32::NAN; -LL + f32::is_nan(number!()); +LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -149,10 +149,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | f32::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - f32::NAN != number!(); -LL + !f32::is_nan(number!()); +LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -161,10 +161,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x == f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x == f64::NAN; -LL + f64::is_nan(x); +LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -173,10 +173,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x != f64::NAN; | ^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x != f64::NAN; -LL + !f64::is_nan(x); +LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -209,10 +209,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | number!() == f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - number!() == f64::NAN; -LL + f64::is_nan(number!()); +LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -221,10 +221,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | f64::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - f64::NAN != number!(); -LL + !f64::is_nan(number!()); +LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -233,10 +233,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x == f128::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x == f128::NAN; -LL + f128::is_nan(x); +LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -245,10 +245,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | x != f128::NAN; | ^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - x != f128::NAN; -LL + !f128::is_nan(x); +LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable @@ -281,10 +281,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | number!() == f128::NAN; | ^^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - number!() == f128::NAN; -LL + f128::is_nan(number!()); +LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself @@ -293,10 +293,10 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself LL | f128::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^^ | -help: use `f32::is_nan()` or `f64::is_nan()` instead +help: use the `is_nan` method instead | LL - f128::NAN != number!(); -LL + !f128::is_nan(number!()); +LL + !::is_nan(number!()); | warning: 33 warnings emitted From f6c3d62706c688007bd978034473e6328cf3fdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Oct 2026 13:42:34 +0200 Subject: [PATCH 15/45] Replace `RUSTC_BOOTSTRAP=-2` with `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags=` --- compiler/rustc_feature/src/lib.rs | 40 +++++++++----- compiler/rustc_feature/src/tests.rs | 15 ++++-- compiler/rustc_session/src/config.rs | 52 +++++++++++++++---- .../rustc_session/src/config/native_libs.rs | 2 +- src/doc/rustc-dev-guide/src/tests/misc.md | 15 ++++-- src/librustdoc/config.rs | 2 +- src/librustdoc/doctest.rs | 2 +- 7 files changed, 91 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index e2eadaa66b0f0..890142718fe92 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -34,7 +34,7 @@ pub struct Feature { issue: Option>, } -#[derive(Clone, Copy, Debug, Hash)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum UnstableFeatures { /// Disallow use of unstable features and unstable compiler flags. /// @@ -54,16 +54,18 @@ pub enum UnstableFeatures { /// because the build turns on warnings-as-errors and uses lots of unstable /// features. As a result, this is always required for building Rust itself. Cheat, - /// Allow the use of *unstable compiler flags*, but disallow the use of + /// Allow the use of selected *unstable compiler flags*, but disallow the use of /// *unstable language features*. - /// And in general, try to treat everything except for compiler flags as if we were on stable. /// /// **This mode is designed only for internal testing of rustc.** /// /// Specifically, we need it when we want to test how rustc behaves on the stable channel, /// but at the same time we need to be able to pass it some unstable `-Z` compiler flags /// required for the testing infrastructure itself to work. - ActAsStableForTesting, + AllowUnstableCompilerFlagsOnly { + /// Unstable compiler flags that we are allowed to use, without their `-Z` prefix. + allowed_flags: Vec, + }, } impl UnstableFeatures { @@ -89,14 +91,19 @@ impl UnstableFeatures { let is_unstable_crate = |var: &str| krate.is_some_and(|name| var.split(',').any(|new_krate| new_krate == name)); + let allow_unstable_flags_prefix = "-allow-unstable-compiler-flags="; if let Ok(val) = env_var_rustc_bootstrap.as_deref() { match val { + // Act as stable, but still accept unstable compiler flags required by compiletest + val if let Some(flags) = val.strip_prefix(allow_unstable_flags_prefix) => { + return UnstableFeatures::AllowUnstableCompilerFlagsOnly { + allowed_flags: flags.split(",").map(|s| s.trim().to_string()).collect(), + }; + } val if val == "1" || is_unstable_crate(val) => return UnstableFeatures::Cheat, // Hypnotize ourselves so that we think we are a stable compiler and thus don't // allow any unstable features. "-1" => return UnstableFeatures::Disallow, - // Act as stable, but still accept unstable compiler flags required by compiletest - "-2" => return UnstableFeatures::ActAsStableForTesting, _ => {} } } @@ -104,19 +111,26 @@ impl UnstableFeatures { if disable_unstable_features { UnstableFeatures::Disallow } else { UnstableFeatures::Allow } } - pub fn allow_unstable_compiler_flags(&self) -> bool { - match *self { - UnstableFeatures::Allow - | UnstableFeatures::Cheat - | UnstableFeatures::ActAsStableForTesting => true, - UnstableFeatures::Disallow => false, + /// Are we allowed to use the given unstable `flag`? + /// If it is a `-Z`, it should be passed without the `-Z` prefix. + pub fn allow_unstable_compiler_flag(&self, flag: &str) -> bool { + match self { + UnstableFeatures::Allow | UnstableFeatures::Cheat => true, + UnstableFeatures::AllowUnstableCompilerFlagsOnly { allowed_flags } + if allowed_flags.iter().any(|allowed_flag| allowed_flag == flag) => + { + true + } + UnstableFeatures::Disallow + | UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } => false, } } pub fn is_nightly_build(&self) -> bool { match *self { UnstableFeatures::Allow | UnstableFeatures::Cheat => true, - UnstableFeatures::Disallow | UnstableFeatures::ActAsStableForTesting => false, + UnstableFeatures::Disallow + | UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } => false, } } } diff --git a/compiler/rustc_feature/src/tests.rs b/compiler/rustc_feature/src/tests.rs index 9280a9de7e506..46e44f0ffc877 100644 --- a/compiler/rustc_feature/src/tests.rs +++ b/compiler/rustc_feature/src/tests.rs @@ -35,10 +35,15 @@ fn rustc_bootstrap_parsing() { assert!(is_force_stable(Some("x"))); assert!(is_force_stable(Some("x,y,z"))); - // `RUSTC_BOOTSTRAP=-2` simulates the stable toolchain, while allowing unstable compiler flags - // to be passed. - std::assert_matches!( - UnstableFeatures::from_environment_value(None, Ok("-2".to_string())), - UnstableFeatures::ActAsStableForTesting + // `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags` simulates the stable toolchain, while + // allowing unstable compiler flags to be passed. + std::assert_eq!( + UnstableFeatures::from_environment_value( + None, + Ok("-allow-unstable-compiler-flags=foo,bar".to_string()) + ), + UnstableFeatures::AllowUnstableCompilerFlagsOnly { + allowed_flags: vec!["foo".to_string(), "bar".to_string()] + } ); } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index a4f8bcf2dab6c..0ae95a822e70d 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2792,7 +2792,7 @@ pub fn build_session_options( check_error_format_stability( early_dcx, &unstable_opts, - unstable_features.allow_unstable_compiler_flags(), + unstable_features.allow_unstable_compiler_flag("error-format"), error_format, ); @@ -2974,7 +2974,7 @@ pub fn build_session_options( } // Parse any `-l` flags, which link to native libraries. - let libs = parse_native_libs(early_dcx, &unstable_opts, unstable_features, matches); + let libs = parse_native_libs(early_dcx, &unstable_opts, &unstable_features, matches); let test = matches.opt_present("test"); @@ -3201,16 +3201,12 @@ pub mod nightly_options { /// Are we allowed to use -Z compiler flags *and* -Zunstable-options was passed? pub fn unstable_options_enabled(matches: &getopts::Matches) -> bool { - unstable_compiler_flags_allowed(matches) + let unstable_features = + UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()); + unstable_features.allow_unstable_compiler_flag("unstable-options") && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") } - /// Are we allowed to use -Z compiler flags? - pub fn unstable_compiler_flags_allowed(matches: &getopts::Matches) -> bool { - UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()) - .allow_unstable_compiler_flags() - } - pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { let krate = matches.opt_str("crate-name"); UnstableFeatures::from_environment(krate.as_deref()).is_nightly_build() @@ -3222,7 +3218,8 @@ pub mod nightly_options { flags: &[RustcOptGroup], ) { let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); - let really_allows_unstable_options = unstable_compiler_flags_allowed(matches); + let unstable_features = + UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()); let mut nightly_options_on_stable = 0; for opt in flags.iter() { @@ -3239,7 +3236,40 @@ pub mod nightly_options { opt.name )); } - if really_allows_unstable_options { + + // Check if the matched values are enabled + // If we're dealing with -Z, then check all the matched values (i.e. -Zfoo, -Zbar, etc.) + // If we're dealing with other flags, check their name directly + if opt.name == "Z" { + // For `-Zui-testing -Zfoo=bar -Zfoo=baz`, this returns + // ["ui-testing", "foo=bar", "foo=baz"]. + // Check that all of them are allowed + let values = matches.opt_strs(opt.name); + let disallowed_flags = values + .iter() + .map(|flag| flag.split_once("=").map(|s| s.0).unwrap_or(flag)) + .filter(|flag| !unstable_features.allow_unstable_compiler_flag(flag)) + .collect::>(); + if disallowed_flags.is_empty() { + continue; + } + + // At this point, check if we're using the "allow unstable flags only" mode. + // In this case, if the user allowed only foo and bar, but then -Zbaz was also + // passed, rustc would only print that `-Z` is not allowed, which doesn't tell the + // user the actual -Z flag value that was disallowed. So we try to provide better + // diagnostics here (solely for the use-case of debugging compiletest itself). + let unstable_features = + UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()); + if matches!( + unstable_features, + UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } + ) { + for flag in disallowed_flags { + let _ = early_dcx.early_err(format!("the -Z{flag} option was not allowed through `-allow-unstable-compiler-flags`")); + } + } + } else if unstable_features.allow_unstable_compiler_flag(opt.name) { continue; } diff --git a/compiler/rustc_session/src/config/native_libs.rs b/compiler/rustc_session/src/config/native_libs.rs index 0733bd3ca4b78..1f159f8071323 100644 --- a/compiler/rustc_session/src/config/native_libs.rs +++ b/compiler/rustc_session/src/config/native_libs.rs @@ -18,7 +18,7 @@ mod tests; pub(crate) fn parse_native_libs( early_dcx: &EarlyDiagCtxt, unstable_opts: &UnstableOptions, - unstable_features: UnstableFeatures, + unstable_features: &UnstableFeatures, matches: &getopts::Matches, ) -> Vec { let cx = ParseNativeLibCx { diff --git a/src/doc/rustc-dev-guide/src/tests/misc.md b/src/doc/rustc-dev-guide/src/tests/misc.md index 688be7e97fdca..4f496860f2d3e 100644 --- a/src/doc/rustc-dev-guide/src/tests/misc.md +++ b/src/doc/rustc-dev-guide/src/tests/misc.md @@ -11,9 +11,12 @@ This is a bootstrap/compiler implementation detail, but it can also be useful fo even if it's actually a nightly `rustc`. This is useful because some behaviors of the compiler (e.g. diagnostics) can differ depending on whether the compiler is nightly or not. -- `RUSTC_BOOTSTRAP=-2` will force a given `rustc` to pretend it is a stable - compiler, even if it's actually a nightly `rustc`. - However, the compiler will still accept unstable compiler flags. +- `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags=` will force a given `rustc` to pretend it + is a stable compiler, even if it's actually a nightly `rustc`. + However, the compiler will still accept the passed unstable compiler flags `` (the allowed flag + should be separated by commas). + For example, if you use `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags=foo,bar`, it will be possible to + pass `-Zfoo` and `-Zbar` to the compiler. This is useful for internal testing of the compiler, where we need to pass unstable flags required by compiletest, but we also want to test the compiler's behavior on the stable channel. @@ -24,8 +27,10 @@ Note that setting `RUSTC_BOOTSTRAP=-1` in ui tests (e.g. via `//@ rustc-env`) is tests inherit the bootstrap env var from bootstrap (so it's set to `1` by default), and since compiletest itself passes `-Z` flags (so `-1` breaks the test). -To test stable-only behavior, use the `act-as-stable` directive, which will set `RUSTC_BOOTSTRAP=-2` -for you. +To test stable-only behavior, use the `act-as-stable` directive, which will set +`RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags` with the flags required by compiletest for you. With this +directive, the compiler will not allow using unstable language features or unstable flags that are not set by +compiletest. ### Run-make tests diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 6e988728858a8..597c648c1106a 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -944,7 +944,7 @@ impl Options { output_format, json_unused_externs, scrape_examples_options, - unstable_features, + unstable_features: unstable_features.clone(), doctest_build_args, target_modifiers: collected_options.target_modifiers, }; diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 35b2af5370fff..ab55bcf239623 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -173,7 +173,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions lint_cap: Some(options.lint_cap.unwrap_or(lint::Forbid)), cg: options.codegen_options.clone(), externs: options.externs.clone(), - unstable_features: options.unstable_features, + unstable_features: options.unstable_features.clone(), actually_rustdoc: true, edition: options.edition, target_triple: options.target.clone(), From adbf2a534930e077fe2b7e334b80938d06d6c05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 14 Oct 2026 13:43:12 +0200 Subject: [PATCH 16/45] Use `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags` in compiletest --- .../compiletest/src/directives/handlers.rs | 18 +++++++++++--- src/tools/compiletest/src/runtest.rs | 24 +++++++++++++++++++ ...ce_stable_disallow_additional_flags.stderr | 10 ++++++++ ..._stable_disallow_unstable_features.stderr} | 2 +- tests/ui/bootstrap/rustc_bootstrap.rs | 14 +++++++---- 5 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_additional_flags.stderr rename tests/ui/bootstrap/{rustc_bootstrap.force_stable_allow_flags.stderr => rustc_bootstrap.force_stable_disallow_unstable_features.stderr} (87%) diff --git a/src/tools/compiletest/src/directives/handlers.rs b/src/tools/compiletest/src/directives/handlers.rs index f33688180419f..0e80d6d865f22 100644 --- a/src/tools/compiletest/src/directives/handlers.rs +++ b/src/tools/compiletest/src/directives/handlers.rs @@ -1,12 +1,13 @@ use std::collections::HashMap; use std::sync::{Arc, LazyLock}; -use crate::common::{Config, PassFailMode, TestMode}; +use crate::common::{Config, PassFailMode, TestMode, TestSuite}; use crate::directives::{ DirectiveLine, NormalizeKind, NormalizeRule, TestProps, parse_and_update_aux, parse_edition_range, split_flags, }; use crate::errors::ErrorKind; +use crate::runtest::COMPILETEST_UI_SUITE_UNSTABLE_FLAGS; pub(crate) static DIRECTIVE_HANDLERS_MAP: LazyLock> = LazyLock::new(make_directive_handlers_map); @@ -64,10 +65,21 @@ fn make_directive_handlers_map() -> HashMap<&'static str, Handler> { // these handlers, e.g. by getting rid of now-redundant name checks. let handlers: Vec = vec![ - handler(ACT_AS_STABLE, |_config, ln, props| { + handler(ACT_AS_STABLE, |config, ln, props| { if ln.name == ACT_AS_STABLE { + if ![TestSuite::Ui, TestSuite::UiFullDeps].contains(&config.suite) { + panic!( + "The {ACT_AS_STABLE} directive is only usable in the ui and ui-fulldeps test suites" + ); + } // Simulate the stable toolchain, but allow unstable -Z flags passed by compiletest - props.rustc_env.push(("RUSTC_BOOTSTRAP".to_string(), "-2".to_string())); + props.rustc_env.push(( + "RUSTC_BOOTSTRAP".to_string(), + format!( + "-allow-unstable-compiler-flags={}", + COMPILETEST_UI_SUITE_UNSTABLE_FLAGS.join(",") + ), + )); } }), handler(ERROR_PATTERN, |config, ln, props| { diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 263c8d5aded54..860a69fca66b2 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -260,6 +260,27 @@ enum CompilerKind { Rustdoc, } +/// Unstable compiler flags that compiletest passes to UI tests on its own, i.e. without specific +/// tests opting into them explicitly. +/// This should include both flags that are always passed unconditionally (e.g. `-Zui-testing`) and +/// flags that are enabled across the whole test suite when compiletest is configured in a +/// certain way (e.g. a specific `-Zcodegen-backend` being used for all tests). +/// +/// Note: try to keep this up-to-date with [`TestCx::make_compile_args`]. +pub(crate) const COMPILETEST_UI_SUITE_UNSTABLE_FLAGS: &[&str] = &[ + "simulate-remapped-rust-src-base", + "translate-remapped-path-to-local-path", + "ignore-directory-in-diagnostics-source-blocks", + "deduplicate-diagnostics", + "write-long-types-to-disk", + "codegen-backend", + "next-solver", + "polonius", + "wasm-proc-macros", + "threads", + "ui-testing", +]; + impl<'test> TestCx<'test> { /// Code executed for each revision in turn (or, if there are no /// revisions, exactly once, with revision == None). @@ -1660,6 +1681,9 @@ impl<'test> TestCx<'test> { compiler.arg("-Zunstable-options").arg("--disable-minification"); } + // Note: when adding new unstable compile flags here, also update + // `COMPILETEST_UI_SUITE_UNSTABLE_FLAGS` + // Hide libstd sources from ui tests to make sure we generate the stderr // output that users will see. // Without this, we may be producing good diagnostics in-tree but users diff --git a/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_additional_flags.stderr b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_additional_flags.stderr new file mode 100644 index 0000000000000..13d619cfc388f --- /dev/null +++ b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_additional_flags.stderr @@ -0,0 +1,10 @@ +error: the -Zmir-opt-level option was not allowed through `-allow-unstable-compiler-flags` + +error: the option `Z` is only accepted on the nightly compiler + +error: 1 nightly option was parsed + | + = help: consider switching to a nightly toolchain: `rustup default nightly` + = note: selecting a toolchain with `+toolchain` arguments require a rustup proxy; see + = note: for more information about Rust's stability policy, see + diff --git a/tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr similarity index 87% rename from tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr rename to tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr index c4436ca5a1815..13627ce02eee3 100644 --- a/tests/ui/bootstrap/rustc_bootstrap.force_stable_allow_flags.stderr +++ b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr @@ -1,5 +1,5 @@ error[E0554]: `#![feature]` may not be used on the nightly release channel - --> $DIR/rustc_bootstrap.rs:51:1 + --> $DIR/rustc_bootstrap.rs:55:1 | LL | #![feature(rustc_attrs)] | ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/bootstrap/rustc_bootstrap.rs b/tests/ui/bootstrap/rustc_bootstrap.rs index 96a464e8e4dbe..6395a750718e8 100644 --- a/tests/ui/bootstrap/rustc_bootstrap.rs +++ b/tests/ui/bootstrap/rustc_bootstrap.rs @@ -10,7 +10,7 @@ // ignore-tidy-linelength -//@ revisions: default_nightly cheat cheat_single_crate cheat_multi_crate force_stable force_stable_allow_flags invalid_zero invalid_junk +//@ revisions: default_nightly cheat cheat_single_crate cheat_multi_crate force_stable force_stable_disallow_additional_flags force_stable_disallow_unstable_features invalid_zero invalid_junk //@ only-nightly //@ normalize-stderr: "dev release channel" -> "nightly release channel" @@ -41,14 +41,18 @@ //@[force_stable] rustc-env:RUSTC_BOOTSTRAP=-1 //@[force_stable] compile-flags: -Z unstable-options -// Here rustc shouldn't complain about compiletest's -Z flags being passed. -//@[force_stable_allow_flags] rustc-env:RUSTC_BOOTSTRAP=-2 -//@[force_stable_allow_flags] compile-flags: -Z unstable-options +// Here rustc shouldn't complain about the allowed compiletest -Z flags being passed, but should +// still complain about other unstable flags. +//@[force_stable_disallow_additional_flags] act-as-stable +//@[force_stable_disallow_additional_flags] compile-flags: -Z mir-opt-level=3 + +// Here rustc should not allow using unstable language features. +//@[force_stable_disallow_unstable_features] act-as-stable #![crate_type = "lib"] // Note: `rustc_attrs` is a perma-unstable internal feature that is unlikely to change, which is // used as a proxy to check `RUSTC_BOOTSTRAP` versus stability checking logic. #![feature(rustc_attrs)] -//[force_stable_allow_flags]~^ ERROR: `#![feature]` may not be used on the +//[force_stable_disallow_unstable_features]~^ ERROR: `#![feature]` may not be used on the //[force_stable]~? RAW the option `Z` is only accepted on the nightly compiler From 142d509100ec3daf546ee77fe0c96b2ee9db10d7 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Sat, 17 Oct 2026 12:43:04 -0700 Subject: [PATCH 17/45] Don't try to skip anything in `Skip::advance_by(0)` If we try to eagerly process our skip count, we might see the end of the inner iterator. `Iterator::advance_by` is supposed to return a non-zero `Err` count when exhausted, but there's no room for `Skip` to report anything in this case. So, it's better that we don't even try. --- library/core/src/iter/adapters/skip.rs | 8 ++++++ library/coretests/tests/iter/adapters/skip.rs | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/library/core/src/iter/adapters/skip.rs b/library/core/src/iter/adapters/skip.rs index f7a6beb2b8c63..600e897b13bbd 100644 --- a/library/core/src/iter/adapters/skip.rs +++ b/library/core/src/iter/adapters/skip.rs @@ -135,6 +135,14 @@ where #[inline] #[rustc_inherit_overflow_checks] fn advance_by(&mut self, mut n: usize) -> Result<(), NonZero> { + if self.n > 0 && n == 0 { + // If we try to process our skips now, we might witness the end of the inner iterator, + // but there's no valid `Err(k)` that we could return. This is especially a problem for + // unfused iterators that might end up called again by `Chain`, for example, so it's + // better that we do nothing at all. + return Ok(()); + } + let skip_inner = self.n; let skip_and_advance = skip_inner.saturating_add(n); diff --git a/library/coretests/tests/iter/adapters/skip.rs b/library/coretests/tests/iter/adapters/skip.rs index 8d5d06ad9fb3a..cfd00ac609715 100644 --- a/library/coretests/tests/iter/adapters/skip.rs +++ b/library/coretests/tests/iter/adapters/skip.rs @@ -86,6 +86,31 @@ fn test_skip_advance_by() { assert_eq!((0..0).skip(1).advance_back_by(0), Ok(())); } +#[test] +fn test_skip_unfused_advance_by() { + /// A legal non-fused iterator: `None` once, then `Some(1)`, `Some(2)`, ... + struct Gap(u32); + + impl Iterator for Gap { + type Item = u32; + fn next(&mut self) -> Option { + self.0 += 1; + if self.0 == 1 { None } else { Some(self.0 - 1) } + } + } + + assert_eq!(Gap(0).skip(1).next(), None); + + let mut iter = Gap(0).skip(1); + assert_eq!(iter.advance_by(0), Ok(())); + // `Ok` means we didn't see the (unfused) end yet... + assert_eq!(iter.next(), None); + + // Chain also depends on this behavior internally + assert_eq!(Gap(0).skip(1).chain(empty()).next(), None); + assert_eq!(Gap(0).skip(1).chain(empty()).nth(0), None); +} + #[test] fn test_iterator_skip_count() { let xs = [0, 1, 2, 3, 5, 13, 15, 16, 17, 19, 20, 30]; From 20a7d95cf566d27738df9f5bbf21bf9844a4a149 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 18 Oct 2026 11:55:12 +0200 Subject: [PATCH 18/45] layout invariant: turn delayed bug back into proper assertion --- .../rustc_ty_utils/src/layout/invariant.rs | 14 ++---- .../invalid-niche-discriminant.rs | 1 + .../invalid-niche-discriminant.normal.stderr | 35 -------------- ...lid-niche-discriminant.with_delayed.stderr | 47 ------------------- 4 files changed, 5 insertions(+), 92 deletions(-) rename tests/{ui/enum-discriminant => crashes}/invalid-niche-discriminant.rs (96%) delete mode 100644 tests/ui/enum-discriminant/invalid-niche-discriminant.normal.stderr delete mode 100644 tests/ui/enum-discriminant/invalid-niche-discriminant.with_delayed.stderr diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index 562a3ef387d9a..a244c597eef3d 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -310,16 +310,10 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou } // Ensure that for niche encoded tags the discriminant coincides with the variant index. - let val = layout.ty.discriminant_for_variant(tcx, idx).unwrap().val; - if val != u128::from(idx.as_u32()) { - let adt_def = layout.ty.ty_adt_def().unwrap(); - cx.tcx().dcx().span_delayed_bug( - cx.tcx().def_span(adt_def.did()), - format!( - "variant {idx:?} has discriminant {val:?} in niche-encoded type" - ), - ); - } + assert_eq!( + layout.ty.discriminant_for_variant(tcx, idx).unwrap().val, + u128::from(idx.as_u32()), + ); } } for variant in variants.iter() { diff --git a/tests/ui/enum-discriminant/invalid-niche-discriminant.rs b/tests/crashes/invalid-niche-discriminant.rs similarity index 96% rename from tests/ui/enum-discriminant/invalid-niche-discriminant.rs rename to tests/crashes/invalid-niche-discriminant.rs index f70f7d1736bae..b5a355d5b9a41 100644 --- a/tests/ui/enum-discriminant/invalid-niche-discriminant.rs +++ b/tests/crashes/invalid-niche-discriminant.rs @@ -1,3 +1,4 @@ +//@ known-bug: #144501 //@ needs-rustc-debug-assertions //@ revisions: normal with_delayed //@ [with_delayed] compile-flags: -Z eagerly-emit-delayed-bugs diff --git a/tests/ui/enum-discriminant/invalid-niche-discriminant.normal.stderr b/tests/ui/enum-discriminant/invalid-niche-discriminant.normal.stderr deleted file mode 100644 index 9c66c1782e4dc..0000000000000 --- a/tests/ui/enum-discriminant/invalid-niche-discriminant.normal.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error[E0732]: `#[repr(inttype)]` must be specified for enums with explicit discriminants and non-unit variants - --> $DIR/invalid-niche-discriminant.rs:11:1 - | -LL | enum E { - | ^^^^^^ -... -LL | S0 { - | -- non-unit discriminant declared here -... -LL | Bar = { - | ___________- -LL | | let x = 1; -LL | | 3 -LL | | }, - | |_____- explicit discriminant specified here - -error[E0599]: no variant named `S1` found for enum `E` - --> $DIR/invalid-niche-discriminant.rs:23:18 - | -LL | enum E { - | ------ variant `S1` not found here -... -LL | static C: E = E::S1 { u: 23 }; - | ^^ - | -help: there is a variant with a similar name - | -LL - static C: E = E::S1 { u: 23 }; -LL + static C: E = E::S0 { u: 23 }; - | - -error: aborting due to 2 previous errors - -Some errors have detailed explanations: E0599, E0732. -For more information about an error, try `rustc --explain E0599`. diff --git a/tests/ui/enum-discriminant/invalid-niche-discriminant.with_delayed.stderr b/tests/ui/enum-discriminant/invalid-niche-discriminant.with_delayed.stderr deleted file mode 100644 index 20f118655a17a..0000000000000 --- a/tests/ui/enum-discriminant/invalid-niche-discriminant.with_delayed.stderr +++ /dev/null @@ -1,47 +0,0 @@ -error[E0732]: `#[repr(inttype)]` must be specified for enums with explicit discriminants and non-unit variants - --> $DIR/invalid-niche-discriminant.rs:11:1 - | -LL | enum E { - | ^^^^^^ -... -LL | S0 { - | -- non-unit discriminant declared here -... -LL | Bar = { - | ___________- -LL | | let x = 1; -LL | | 3 -LL | | }, - | |_____- explicit discriminant specified here - -error: variant 1 has discriminant 3 in niche-encoded type - --> $DIR/invalid-niche-discriminant.rs:11:1 - | -LL | enum E { - | ^^^^^^ - -error[E0599]: no variant named `S1` found for enum `E` - --> $DIR/invalid-niche-discriminant.rs:23:18 - | -LL | enum E { - | ------ variant `S1` not found here -... -LL | static C: E = E::S1 { u: 23 }; - | ^^ - | -help: there is a variant with a similar name - | -LL - static C: E = E::S1 { u: 23 }; -LL + static C: E = E::S0 { u: 23 }; - | - -error: `Res::Err` but no error emitted - --> $DIR/invalid-niche-discriminant.rs:23:15 - | -LL | static C: E = E::S1 { u: 23 }; - | ^^^^^ - -error: aborting due to 4 previous errors - -Some errors have detailed explanations: E0599, E0732. -For more information about an error, try `rustc --explain E0599`. From f5705f8da4aa217243600395529342c19f6e89e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 18 Oct 2026 19:45:20 +0000 Subject: [PATCH 19/45] Add test for issue 142887 Invalid lifetime suggested: ``` error[E0311]: the parameter type `S` may not live long enough --> $DIR/do-not-suggest-anon-lifetime.rs:16:10 | LL | ) -> impl Future> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `impl Future>::Response, >::Error>>` will meet its required lifetime bounds... | ::: $DIR/auxiliary/anon_lifetime.rs:14:9 | LL | &self, | - the parameter type `S` must be valid for the anonymous lifetime as defined here... | note: ...that is required by this bound --> $DIR/auxiliary/anon_lifetime.rs:17:77 | LL | ) -> impl Future> + Send + '_; | ^^ help: consider adding an explicit lifetime bound | LL | ) -> impl Future> where S: anon { | +++++++++++++ ``` --- tests/ui/lifetimes/auxiliary/anon_lifetime.rs | 22 +++++++++++++++++ .../lifetimes/do-not-suggest-anon-lifetime.rs | 22 +++++++++++++++++ .../do-not-suggest-anon-lifetime.stderr | 24 +++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 tests/ui/lifetimes/auxiliary/anon_lifetime.rs create mode 100644 tests/ui/lifetimes/do-not-suggest-anon-lifetime.rs create mode 100644 tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr diff --git a/tests/ui/lifetimes/auxiliary/anon_lifetime.rs b/tests/ui/lifetimes/auxiliary/anon_lifetime.rs new file mode 100644 index 0000000000000..34cdd7c40fe12 --- /dev/null +++ b/tests/ui/lifetimes/auxiliary/anon_lifetime.rs @@ -0,0 +1,22 @@ +use std::pin::Pin; +use std::future::Future; + +pub trait Service: Sized + Send + Sync + 'static { + /// The type of response returned by the service. + type Response: Send + 'static; + + /// The type of error returned by the service. + type Error: Send + 'static; + + /// Serve a response or error for the given request, + /// using the given context. + fn serve( + &self, + ctx: Context, + req: Request, + ) -> impl Future> + Send + '_; +} + +pub struct Context { + state: S, +} diff --git a/tests/ui/lifetimes/do-not-suggest-anon-lifetime.rs b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.rs new file mode 100644 index 0000000000000..5e4f5f191f850 --- /dev/null +++ b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.rs @@ -0,0 +1,22 @@ +//@ aux-build:anon_lifetime.rs +//@ edition:2024 +extern crate anon_lifetime; +use anon_lifetime::{Service, Context}; + +struct Foo; + +impl Service for Foo { + type Response = (); + type Error = (); + + fn serve( + &self, + _ctx: Context, + _req: Request, + ) -> impl Future> { + //~^ ERROR: the parameter type `S` may not live long enough + async { todo!() } + } +} + +fn main() {} diff --git a/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr new file mode 100644 index 0000000000000..e8a1453891df4 --- /dev/null +++ b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr @@ -0,0 +1,24 @@ +error[E0311]: the parameter type `S` may not live long enough + --> $DIR/do-not-suggest-anon-lifetime.rs:16:10 + | +LL | ) -> impl Future> { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `impl Future>::Response, >::Error>>` will meet its required lifetime bounds... + | + ::: $DIR/auxiliary/anon_lifetime.rs:14:9 + | +LL | &self, + | - the parameter type `S` must be valid for the anonymous lifetime as defined here... + | +note: ...that is required by this bound + --> $DIR/auxiliary/anon_lifetime.rs:17:77 + | +LL | ) -> impl Future> + Send + '_; + | ^^ +help: consider adding an explicit lifetime bound + | +LL | ) -> impl Future> where S: anon { + | +++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0311`. From 0a5b7612e83e1894ff89e2cfb2194c7fee1af8b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 11 Nov 2025 22:39:55 +0000 Subject: [PATCH 20/45] Avoid incorrect lifetime suggestion involving `sym::anon` Given ```rust --- packag.edition = "2024" [dependencies] rama = "0.2.0" --- fn main() {} struct Foo; impl rama::Service for Foo { type Response = (); type Error = (); fn serve( &self, _ctx: rama::Context, _req: Request, ) -> impl Future> { async { todo!() } } } ``` emit ``` error[E0311]: the parameter type `S` may not live long enough --> f20.rs:20:10 | 20 | ) -> impl Future> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `impl Future>::Response, >::Error>>` will meet its required lifetime bounds... | ::: /home/gh-estebank/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rama-core-0.2.0/src/service/svc.rs:19:9 | 19 | &self, | - the parameter type `S` must be valid for the anonymous lifetime as defined here... | note: ...that is required by this bound --> /home/gh-estebank/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rama-core-0.2.0/src/service/svc.rs:22:77 | 22 | ) -> impl Future> + Send + '_; | ^^ ``` without the incorrect suggestion ``` help: consider adding an explicit lifetime bound | 22 | ) -> impl Future> where S: anon { | +++++++++++++ ``` --- .../src/error_reporting/infer/region.rs | 66 +++++++++---------- .../do-not-suggest-anon-lifetime.stderr | 4 -- 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs index d356a62200cfd..20a3f56ef5e48 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/region.rs @@ -845,39 +845,39 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; let mut suggs = vec![]; - let lt_name = self.suggest_name_region(generic_param_scope, sub, &mut suggs); - - if let Some((sp, suggestion_type)) = type_param_sugg_span - && suggestion_scope == type_scope - { - match suggestion_type { - LifetimeSuggestion::NeedsPlus(open_paren_sp) => { - let suggestion = format!(" + {lt_name}"); - if let Some(open_paren_sp) = open_paren_sp { - suggs.push((open_paren_sp, "(".to_string())); - suggs.push((sp, format!("){suggestion}"))); - } else { - suggs.push((sp, suggestion)); + if let Some(lt_name) = self.suggest_name_region(generic_param_scope, sub, &mut suggs) { + if let Some((sp, suggestion_type)) = type_param_sugg_span + && suggestion_scope == type_scope + { + match suggestion_type { + LifetimeSuggestion::NeedsPlus(open_paren_sp) => { + let suggestion = format!(" + {lt_name}"); + if let Some(open_paren_sp) = open_paren_sp { + suggs.push((open_paren_sp, "(".to_string())); + suggs.push((sp, format!("){suggestion}"))); + } else { + suggs.push((sp, suggestion)); + } } + LifetimeSuggestion::NeedsColon => suggs.push((sp, format!(": {lt_name}"))), + LifetimeSuggestion::HasColon => suggs.push((sp, format!(" {lt_name}"))), } - LifetimeSuggestion::NeedsColon => suggs.push((sp, format!(": {lt_name}"))), - LifetimeSuggestion::HasColon => suggs.push((sp, format!(" {lt_name}"))), + } else if let GenericKind::Alias(ref p) = bound_kind + && let ty::Projection { def_id } = p.kind(self.tcx) + && let DefKind::AssocTy = self.tcx.def_kind(def_id) + && let Some(ty::ImplTraitInTraitData::Trait { .. }) = + self.tcx.opt_rpitit_info(def_id) + { + // The lifetime found in the `impl` is longer than the one on the RPITIT. + // Do not suggest `::{opaque}: 'static`. + } else if let Some(generics) = self.tcx.hir_get_generics(suggestion_scope) { + let pred = format!("{bound_kind}: {lt_name}"); + let suggestion = format!("{} {}", generics.add_where_or_trailing_comma(), pred); + suggs.push((generics.tail_span_for_predicate_suggestion(), suggestion)) + } else { + let consider = format!("{msg} `{bound_kind}: {sub}`..."); + err.help(consider); } - } else if let GenericKind::Alias(ref p) = bound_kind - && let ty::Projection { def_id } = p.kind - && let DefKind::AssocTy = self.tcx.def_kind(def_id) - && let Some(ty::ImplTraitInTraitData::Trait { .. }) = - self.tcx.opt_rpitit_info(def_id) - { - // The lifetime found in the `impl` is longer than the one on the RPITIT. - // Do not suggest `::{opaque}: 'static`. - } else if let Some(generics) = self.tcx.hir_get_generics(suggestion_scope) { - let pred = format!("{bound_kind}: {lt_name}"); - let suggestion = format!("{} {}", generics.add_where_or_trailing_comma(), pred); - suggs.push((generics.tail_span_for_predicate_suggestion(), suggestion)) - } else { - let consider = format!("{msg} `{bound_kind}: {sub}`..."); - err.help(consider); } if !suggs.is_empty() { @@ -933,7 +933,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { generic_param_scope: LocalDefId, lifetime: Region<'tcx>, add_lt_suggs: &mut Vec<(Span, String)>, - ) -> String { + ) -> Option { struct LifetimeReplaceVisitor<'a> { needle: hir::LifetimeKind, new_lt: &'a str, @@ -953,7 +953,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { Some(info) if !lifetime.is_named(self.tcx) => { (info.region_def_id.expect_local(), info.scope) } - _ => return lifetime.get_name_or_anon(self.tcx).to_string(), + _ => return Some(lifetime.get_name(self.tcx)?.to_string()), }; let new_lt = { @@ -999,7 +999,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .unwrap_or_else(|| (ast_generics.span, format!("<{new_lt}>"))); add_lt_suggs.push(sugg); - new_lt + Some(new_lt) } fn report_sub_sup_conflict( diff --git a/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr index e8a1453891df4..b23ffd83ce877 100644 --- a/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr +++ b/tests/ui/lifetimes/do-not-suggest-anon-lifetime.stderr @@ -14,10 +14,6 @@ note: ...that is required by this bound | LL | ) -> impl Future> + Send + '_; | ^^ -help: consider adding an explicit lifetime bound - | -LL | ) -> impl Future> where S: anon { - | +++++++++++++ error: aborting due to 1 previous error From 0b7a2d93f5dbee90e498642ec50c7ef2b472382d Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Sun, 18 Oct 2026 23:03:36 +0200 Subject: [PATCH 21/45] Remove `MatcherLoc::Delimited` It does not have any use, and it is a no-op we have to process on the hot path of macro parsing. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index 4505bcf394ace..b39dbbb289409 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -100,7 +100,6 @@ pub(crate) enum MatcherLoc { Token { token: Token, }, - Delimited, Sequence { op: KleeneOp, num_metavar_decls: usize, @@ -132,7 +131,6 @@ impl MatcherLoc { pub(super) fn span(&self) -> Option { match self { MatcherLoc::Token { token } => Some(token.span), - MatcherLoc::Delimited => None, MatcherLoc::Sequence { .. } => None, MatcherLoc::SequenceKleeneOpNoSep { .. } => None, MatcherLoc::SequenceSep { .. } => None, @@ -155,7 +153,6 @@ impl Display for MatcherLoc { MatcherLoc::Eof => f.write_str("end of macro"), // These are not printed in the diagnostic - MatcherLoc::Delimited => f.write_str("delimiter"), MatcherLoc::Sequence { .. } => f.write_str("sequence start"), MatcherLoc::SequenceKleeneOpNoSep { .. } => f.write_str("sequence end"), MatcherLoc::SequenceKleeneOpAfterSep { .. } => f.write_str("sequence end"), @@ -179,7 +176,6 @@ pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec { let open_token = Token::new(delimited.delim.as_open_token_kind(), span.open); let close_token = Token::new(delimited.delim.as_close_token_kind(), span.close); - locs.push(MatcherLoc::Delimited); locs.push(MatcherLoc::Token { token: open_token }); inner(&delimited.tts, locs, next_metavar, seq_depth); locs.push(MatcherLoc::Token { token: close_token }); @@ -525,11 +521,6 @@ impl TtParser { self.next_mps.push(mp); } } - MatcherLoc::Delimited => { - // Entering the delimiter is trivial. - mp.idx += 1; - self.cur_mps.push(mp); - } &MatcherLoc::Sequence { op, num_metavar_decls, From b012329b5eaa28bc8f825f63c1dc0ee56c7e60c6 Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Sun, 18 Oct 2026 23:14:24 +0200 Subject: [PATCH 22/45] Strip doc-comment tokens in `compute_locs()` The parsing code is much hotter than `compute_locs()`, so perform the match over there. This might mildly change the debug representation of computed `[MatcherLoc]`s, but it's probably worthwhile for perf. --- compiler/rustc_expand/src/mbe/macro_parser.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index b39dbbb289409..e67f25105ed26 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -170,7 +170,12 @@ pub(super) fn compute_locs(matcher: &[TokenTree]) -> Vec { for tt in tts { match tt { TokenTree::Token(token) => { - locs.push(MatcherLoc::Token { token: *token }); + // We ignore doc comments. This is a bug, but #95267 showed that existing + // programs rely on this behaviour, and changing it would require some care and + // a transition period. + if !matches!(token, Token { kind: DocComment(..), .. }) { + locs.push(MatcherLoc::Token { token: *token }); + } } TokenTree::Delimited(span, _, delimited) => { let open_token = Token::new(delimited.delim.as_open_token_kind(), span.open); @@ -504,18 +509,11 @@ impl TtParser { match matcher_loc { MatcherLoc::Token { token: t } => { - // If it's a doc comment, we just ignore it and move on to the next tt in the - // matcher. This is a bug, but #95267 showed that existing programs rely on this - // behaviour, and changing it would require some care and a transition period. - // // If the token matches, we can just advance the parser. // // Otherwise, this match has failed, there is nothing to do, and hopefully another // mp in `cur_mps` will match. - if matches!(t, Token { kind: DocComment(..), .. }) { - mp.idx += 1; - self.cur_mps.push(mp); - } else if token_name_eq(t, token) { + if token_name_eq(t, token) { track.matched_one(parser, mp.idx); mp.idx += 1; self.next_mps.push(mp); From 5fc2428a483c40ad3c6234e04bfe30af1bec54ac Mon Sep 17 00:00:00 2001 From: arya dradjica Date: Sun, 18 Oct 2026 23:55:16 +0200 Subject: [PATCH 23/45] Remove redundant return value in `try_match_*()` --- compiler/rustc_expand/src/mbe/macro_rules.rs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 12f308fc546c3..1ded75caaa48d 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -245,7 +245,8 @@ impl MacroRulesMacroExpander { } match try_match_macro_derive(psess, body, rules, &mut NoopTracker) { - Ok((rule_index, rule, named_matches)) => { + Ok((rule_index, named_matches)) => { + let rule = &rules[rule_index]; let MacroRule::Derive { rhs, .. } = rule else { panic!("try_match_macro_derive returned non-derive rule"); }; @@ -450,7 +451,8 @@ fn expand_macro<'cx, 'a: 'cx>( let try_success_result = try_match_macro(psess, &arg, rules, &mut NoopTracker); match try_success_result { - Ok((rule_index, rule, named_matches)) => { + Ok((rule_index, named_matches)) => { + let rule = &rules[rule_index]; let MacroRule::Func { lhs, rhs, .. } = rule else { panic!("try_match_macro returned non-func rule"); }; @@ -539,7 +541,8 @@ fn expand_macro_attr( // Track nothing for the best performance. match try_match_macro_attr(psess, &args, &body, rules, &mut NoopTracker) { - Ok((i, rule, named_matches)) => { + Ok((i, named_matches)) => { + let rule = &rules[i]; let MacroRule::Attr { rhs, unsafe_rule, .. } = rule else { panic!("try_macro_match_attr returned non-attr rule"); }; @@ -609,7 +612,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( arg: &TokenStream, rules: &'matcher [MacroRule], track: &mut T, -) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { +) -> Result<(usize, NamedMatches), CanRetry> { // We create a base parser that can be used for the "black box" parts. // Every iteration needs a fresh copy of that parser. However, the parser // is not mutated on many of the iterations, particularly when dealing with @@ -653,7 +656,7 @@ pub(super) fn try_match_macro<'matcher, T: Tracker<'matcher>>( // Merge the gated spans from parsing the matcher with the preexisting ones. psess.gated_spans.merge(gated_spans_snapshot); - return Ok((i, rule, named_matches)); + return Ok((i, named_matches)); } Failure => { trace!("Failed to match arm, trying the next one"); @@ -689,7 +692,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( attr_body: &TokenStream, rules: &'matcher [MacroRule], track: &mut T, -) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { +) -> Result<(usize, NamedMatches), CanRetry> { // This uses the same strategy as `try_match_macro` let args_parser = parser_from_cx(psess, attr_args.clone(), T::recovery()); let body_parser = parser_from_cx(psess, attr_body.clone(), T::recovery()); @@ -722,7 +725,7 @@ pub(super) fn try_match_macro_attr<'matcher, T: Tracker<'matcher>>( psess.gated_spans.merge(gated_spans_snapshot); #[allow(rustc::potential_query_instability)] named_matches.extend(body_named_matches); - return Ok((i, rule, named_matches)); + return Ok((i, named_matches)); } Failure => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) @@ -744,7 +747,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( body: &TokenStream, rules: &'matcher [MacroRule], track: &mut T, -) -> Result<(usize, &'matcher MacroRule, NamedMatches), CanRetry> { +) -> Result<(usize, NamedMatches), CanRetry> { // This uses the same strategy as `try_match_macro` let body_parser = parser_from_cx(psess, body.clone(), T::recovery()); let mut tt_parser = TtParser::new(); @@ -760,7 +763,7 @@ pub(super) fn try_match_macro_derive<'matcher, T: Tracker<'matcher>>( match result { Success(named_matches) => { psess.gated_spans.merge(gated_spans_snapshot); - return Ok((i, rule, named_matches)); + return Ok((i, named_matches)); } Failure => { mem::swap(&mut gated_spans_snapshot, &mut psess.gated_spans.spans.borrow_mut()) From 2c2a5e9c24e5078ec5d338e8087bdfdf963c9b59 Mon Sep 17 00:00:00 2001 From: Yu Date: Mon, 19 Oct 2026 22:30:17 +0800 Subject: [PATCH 24/45] test: re-organize ui test case --- .../basic.rs} | 0 .../basic.stderr} | 66 +++++++------- .../custom-partial-eq.rs} | 0 .../custom-partial-eq.stderr} | 6 +- .../no-std.fixed} | 0 .../no-std.rs} | 0 .../no-std.stderr} | 2 +- .../no-suggestion.rs} | 0 .../no-suggestion.stderr} | 10 +-- .../suggestion.fixed} | 0 .../suggestion.rs} | 0 .../suggestion.stderr} | 88 +++++++++---------- 12 files changed, 86 insertions(+), 86 deletions(-) rename tests/ui/lint/{invalid-nan-comparison.rs => invalid-nan-comparisons/basic.rs} (100%) rename tests/ui/lint/{invalid-nan-comparison.stderr => invalid-nan-comparisons/basic.stderr} (81%) rename tests/ui/lint/{invalid-nan-comparison-custom-partial-eq.rs => invalid-nan-comparisons/custom-partial-eq.rs} (100%) rename tests/ui/lint/{invalid-nan-comparison-custom-partial-eq.stderr => invalid-nan-comparisons/custom-partial-eq.stderr} (79%) rename tests/ui/lint/{invalid-nan-comparison-no-std.fixed => invalid-nan-comparisons/no-std.fixed} (100%) rename tests/ui/lint/{invalid-nan-comparison-no-std.rs => invalid-nan-comparisons/no-std.rs} (100%) rename tests/ui/lint/{invalid-nan-comparison-no-std.stderr => invalid-nan-comparisons/no-std.stderr} (88%) rename tests/ui/lint/{invalid-nan-comparison-no-suggestion.rs => invalid-nan-comparisons/no-suggestion.rs} (100%) rename tests/ui/lint/{invalid-nan-comparison-no-suggestion.stderr => invalid-nan-comparisons/no-suggestion.stderr} (81%) rename tests/ui/lint/{invalid-nan-comparison-suggestion.fixed => invalid-nan-comparisons/suggestion.fixed} (100%) rename tests/ui/lint/{invalid-nan-comparison-suggestion.rs => invalid-nan-comparisons/suggestion.rs} (100%) rename tests/ui/lint/{invalid-nan-comparison-suggestion.stderr => invalid-nan-comparisons/suggestion.stderr} (83%) diff --git a/tests/ui/lint/invalid-nan-comparison.rs b/tests/ui/lint/invalid-nan-comparisons/basic.rs similarity index 100% rename from tests/ui/lint/invalid-nan-comparison.rs rename to tests/ui/lint/invalid-nan-comparisons/basic.rs diff --git a/tests/ui/lint/invalid-nan-comparison.stderr b/tests/ui/lint/invalid-nan-comparisons/basic.stderr similarity index 81% rename from tests/ui/lint/invalid-nan-comparison.stderr rename to tests/ui/lint/invalid-nan-comparisons/basic.stderr index 3ee29a9fc4570..77cd6fc111e75 100644 --- a/tests/ui/lint/invalid-nan-comparison.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/basic.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:12:20 + --> $DIR/basic.rs:12:20 | LL | const TEST: bool = 5f32 == f32::NAN; | ^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL + const TEST: bool = ::is_nan(5f32); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:18:5 + --> $DIR/basic.rs:18:5 | LL | x == f16::NAN; | ^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:20:5 + --> $DIR/basic.rs:20:5 | LL | x != f16::NAN; | ^^^^^^^^^^^^^ @@ -36,31 +36,31 @@ LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:22:5 + --> $DIR/basic.rs:22:5 | LL | x < f16::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:24:5 + --> $DIR/basic.rs:24:5 | LL | x > f16::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:26:5 + --> $DIR/basic.rs:26:5 | LL | x <= f16::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:28:5 + --> $DIR/basic.rs:28:5 | LL | x >= f16::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:30:5 + --> $DIR/basic.rs:30:5 | LL | number!() == f16::NAN; | ^^^^^^^^^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:32:5 + --> $DIR/basic.rs:32:5 | LL | f16::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -84,7 +84,7 @@ LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:39:5 + --> $DIR/basic.rs:39:5 | LL | x == f32::NAN; | ^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:41:5 + --> $DIR/basic.rs:41:5 | LL | x != f32::NAN; | ^^^^^^^^^^^^^ @@ -108,31 +108,31 @@ LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:43:5 + --> $DIR/basic.rs:43:5 | LL | x < f32::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:45:5 + --> $DIR/basic.rs:45:5 | LL | x > f32::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:47:5 + --> $DIR/basic.rs:47:5 | LL | x <= f32::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:49:5 + --> $DIR/basic.rs:49:5 | LL | x >= f32::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:51:5 + --> $DIR/basic.rs:51:5 | LL | number!() == f32::NAN; | ^^^^^^^^^^^^^^^^^^^^^ @@ -144,7 +144,7 @@ LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:53:5 + --> $DIR/basic.rs:53:5 | LL | f32::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -156,7 +156,7 @@ LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:60:5 + --> $DIR/basic.rs:60:5 | LL | x == f64::NAN; | ^^^^^^^^^^^^^ @@ -168,7 +168,7 @@ LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:62:5 + --> $DIR/basic.rs:62:5 | LL | x != f64::NAN; | ^^^^^^^^^^^^^ @@ -180,31 +180,31 @@ LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:64:5 + --> $DIR/basic.rs:64:5 | LL | x < f64::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:66:5 + --> $DIR/basic.rs:66:5 | LL | x > f64::NAN; | ^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:68:5 + --> $DIR/basic.rs:68:5 | LL | x <= f64::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:70:5 + --> $DIR/basic.rs:70:5 | LL | x >= f64::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:72:5 + --> $DIR/basic.rs:72:5 | LL | number!() == f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^ @@ -216,7 +216,7 @@ LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:74:5 + --> $DIR/basic.rs:74:5 | LL | f64::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -228,7 +228,7 @@ LL + !::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:81:5 + --> $DIR/basic.rs:81:5 | LL | x == f128::NAN; | ^^^^^^^^^^^^^^ @@ -240,7 +240,7 @@ LL + ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:83:5 + --> $DIR/basic.rs:83:5 | LL | x != f128::NAN; | ^^^^^^^^^^^^^^ @@ -252,31 +252,31 @@ LL + !::is_nan(x); | warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:85:5 + --> $DIR/basic.rs:85:5 | LL | x < f128::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:87:5 + --> $DIR/basic.rs:87:5 | LL | x > f128::NAN; | ^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:89:5 + --> $DIR/basic.rs:89:5 | LL | x <= f128::NAN; | ^^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN is not orderable - --> $DIR/invalid-nan-comparison.rs:91:5 + --> $DIR/basic.rs:91:5 | LL | x >= f128::NAN; | ^^^^^^^^^^^^^^ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:93:5 + --> $DIR/basic.rs:93:5 | LL | number!() == f128::NAN; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -288,7 +288,7 @@ LL + ::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison.rs:95:5 + --> $DIR/basic.rs:95:5 | LL | f128::NAN != number!(); | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs b/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-custom-partial-eq.rs rename to tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs diff --git a/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr b/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr similarity index 79% rename from tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr rename to tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr index daf91b092a3bb..f0c914dccdce6 100644 --- a/tests/ui/lint/invalid-nan-comparison-custom-partial-eq.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:19:13 + --> $DIR/custom-partial-eq.rs:19:13 | LL | let _ = value == f64::NAN; | ^^^^^^^^^^^^^^^^^ @@ -8,7 +8,7 @@ LL | let _ = value == f64::NAN; = note: `#[warn(invalid_nan_comparisons)]` on by default warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:21:13 + --> $DIR/custom-partial-eq.rs:21:13 | LL | let _ = f64::NAN != value; | ^^^^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ LL | let _ = f64::NAN != value; = help: use the `is_nan` method instead warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-custom-partial-eq.rs:23:13 + --> $DIR/custom-partial-eq.rs:23:13 | LL | let _ = &value != &f64::NAN; | ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.fixed b/tests/ui/lint/invalid-nan-comparisons/no-std.fixed similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-no-std.fixed rename to tests/ui/lint/invalid-nan-comparisons/no-std.fixed diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.rs b/tests/ui/lint/invalid-nan-comparisons/no-std.rs similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-no-std.rs rename to tests/ui/lint/invalid-nan-comparisons/no-std.rs diff --git a/tests/ui/lint/invalid-nan-comparison-no-std.stderr b/tests/ui/lint/invalid-nan-comparisons/no-std.stderr similarity index 88% rename from tests/ui/lint/invalid-nan-comparison-no-std.stderr rename to tests/ui/lint/invalid-nan-comparisons/no-std.stderr index 143736b352ad9..b10909e157020 100644 --- a/tests/ui/lint/invalid-nan-comparison-no-std.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/no-std.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-std.rs:16:13 + --> $DIR/no-std.rs:16:13 | LL | let _ = b != &nan!(); | ^^^^^^^^^^^^ diff --git a/tests/ui/lint/invalid-nan-comparison-no-suggestion.rs b/tests/ui/lint/invalid-nan-comparisons/no-suggestion.rs similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-no-suggestion.rs rename to tests/ui/lint/invalid-nan-comparisons/no-suggestion.rs diff --git a/tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr b/tests/ui/lint/invalid-nan-comparisons/no-suggestion.stderr similarity index 81% rename from tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr rename to tests/ui/lint/invalid-nan-comparisons/no-suggestion.stderr index 659deb868b027..278f4d225aec4 100644 --- a/tests/ui/lint/invalid-nan-comparison-no-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/no-suggestion.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-suggestion.rs:7:13 + --> $DIR/no-suggestion.rs:7:13 | LL | let _ = 3.3 == { | _____________^ @@ -12,7 +12,7 @@ LL | | }; = note: `#[warn(invalid_nan_comparisons)]` on by default warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-suggestion.rs:15:13 + --> $DIR/no-suggestion.rs:15:13 | LL | let _ = 3.3 == scoped_nan!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | let _ = 3.3 == scoped_nan!(); = help: use the `is_nan` method instead warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-suggestion.rs:19:13 + --> $DIR/no-suggestion.rs:19:13 | LL | let _ = 3.3 == nan16!(); | ^^^^^^^^^^^^^^^ @@ -28,7 +28,7 @@ LL | let _ = 3.3 == nan16!(); = help: use the `is_nan` method instead warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-suggestion.rs:22:13 + --> $DIR/no-suggestion.rs:22:13 | LL | let _ = 3.3 != nan128!(); | ^^^^^^^^^^^^^^^^ @@ -36,7 +36,7 @@ LL | let _ = 3.3 != nan128!(); = help: use the `is_nan` method instead warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-no-suggestion.rs:25:36 + --> $DIR/no-suggestion.rs:25:36 | LL | macro_rules! compare { () => { 3.3 != f64::NAN }; } | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.fixed b/tests/ui/lint/invalid-nan-comparisons/suggestion.fixed similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-suggestion.fixed rename to tests/ui/lint/invalid-nan-comparisons/suggestion.fixed diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.rs b/tests/ui/lint/invalid-nan-comparisons/suggestion.rs similarity index 100% rename from tests/ui/lint/invalid-nan-comparison-suggestion.rs rename to tests/ui/lint/invalid-nan-comparisons/suggestion.rs diff --git a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr b/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr similarity index 83% rename from tests/ui/lint/invalid-nan-comparison-suggestion.stderr rename to tests/ui/lint/invalid-nan-comparisons/suggestion.stderr index 9c920496c3f54..467e97be03f57 100644 --- a/tests/ui/lint/invalid-nan-comparison-suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr @@ -1,5 +1,5 @@ warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:10:13 + --> $DIR/suggestion.rs:10:13 | LL | let _ = x == f16::NAN; | ^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:12:13 + --> $DIR/suggestion.rs:12:13 | LL | let _ = x != f16::NAN; | ^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:16:13 + --> $DIR/suggestion.rs:16:13 | LL | let _ = x == f32::NAN; | ^^^^^^^^^^^^^ @@ -36,7 +36,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:18:13 + --> $DIR/suggestion.rs:18:13 | LL | let _ = x != f32::NAN; | ^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:22:13 + --> $DIR/suggestion.rs:22:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -60,7 +60,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:24:13 + --> $DIR/suggestion.rs:24:13 | LL | let _ = x != f64::NAN; | ^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:28:13 + --> $DIR/suggestion.rs:28:13 | LL | let _ = x == f128::NAN; | ^^^^^^^^^^^^^^ @@ -84,7 +84,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:30:13 + --> $DIR/suggestion.rs:30:13 | LL | let _ = x != f128::NAN; | ^^^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + let _ = !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:34:8 + --> $DIR/suggestion.rs:34:8 | LL | if b != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -108,7 +108,7 @@ LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:38:8 + --> $DIR/suggestion.rs:38:8 | LL | if b != { &f32::NAN } {} | ^^^^^^^^^^^^^^^^^^ @@ -120,7 +120,7 @@ LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:42:9 + --> $DIR/suggestion.rs:42:9 | LL | / b != { LL | | @@ -138,7 +138,7 @@ LL + !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:50:8 + --> $DIR/suggestion.rs:50:8 | LL | if b != &f64::NAN {} | ^^^^^^^^^^^^^^ @@ -150,7 +150,7 @@ LL + if !::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:54:8 + --> $DIR/suggestion.rs:54:8 | LL | if m != &f64::NAN { | ^^^^^^^^^^^^^^ @@ -162,7 +162,7 @@ LL + if !::is_nan(*m) { | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:61:8 + --> $DIR/suggestion.rs:61:8 | LL | if unary != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -174,7 +174,7 @@ LL + if !::is_nan(*unary) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:65:8 + --> $DIR/suggestion.rs:65:8 | LL | if block != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -186,7 +186,7 @@ LL + if !::is_nan(*block) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:69:8 + --> $DIR/suggestion.rs:69:8 | LL | if tuple != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -198,7 +198,7 @@ LL + if !::is_nan(*tuple) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:73:8 + --> $DIR/suggestion.rs:73:8 | LL | if arithmetic != &f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -210,7 +210,7 @@ LL + if !::is_nan(*arithmetic) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:77:8 + --> $DIR/suggestion.rs:77:8 | LL | if &x != &f64::NAN {} | ^^^^^^^^^^^^^^^ @@ -222,7 +222,7 @@ LL + if !::is_nan(*&x) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:80:13 + --> $DIR/suggestion.rs:80:13 | LL | let _ = *p == f64::NAN; | ^^^^^^^^^^^^^^ @@ -234,7 +234,7 @@ LL + let _ = ::is_nan(*p); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:82:13 + --> $DIR/suggestion.rs:82:13 | LL | let _ = -x == f64::NAN; | ^^^^^^^^^^^^^^ @@ -246,7 +246,7 @@ LL + let _ = ::is_nan(-x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:89:13 + --> $DIR/suggestion.rs:89:13 | LL | let _ = 3.3 == f64::NAN; | ^^^^^^^^^^^^^^^ @@ -258,7 +258,7 @@ LL + let _ = ::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:92:13 + --> $DIR/suggestion.rs:92:13 | LL | let _ = &3.3 != &f64::NAN; | ^^^^^^^^^^^^^^^^^ @@ -270,7 +270,7 @@ LL + let _ = !::is_nan(*&3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:97:13 + --> $DIR/suggestion.rs:97:13 | LL | let _ = copied != f64::NAN; | ^^^^^^^^^^^^^^^^^^ @@ -282,7 +282,7 @@ LL + let _ = !::is_nan(copied); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:101:13 + --> $DIR/suggestion.rs:101:13 | LL | let _ = ascribed != f64::NAN; | ^^^^^^^^^^^^^^^^^^^^ @@ -294,7 +294,7 @@ LL + let _ = !::is_nan(ascribed); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:106:22 + --> $DIR/suggestion.rs:106:22 | LL | let _ = |x: f64| x != f64::NAN; | ^^^^^^^^^^^^^ @@ -306,7 +306,7 @@ LL + let _ = |x: f64| !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:112:13 + --> $DIR/suggestion.rs:112:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ @@ -318,7 +318,7 @@ LL + let _ = ::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:114:13 + --> $DIR/suggestion.rs:114:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ @@ -330,7 +330,7 @@ LL + let _ = !::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:119:13 + --> $DIR/suggestion.rs:119:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -342,7 +342,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:124:8 + --> $DIR/suggestion.rs:124:8 | LL | if x != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -354,7 +354,7 @@ LL + if !::is_nan(*x) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:130:13 + --> $DIR/suggestion.rs:130:13 | LL | let _ = &x as &f64 == &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -366,7 +366,7 @@ LL + let _ = ::is_nan(*(&x as &f64)); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:132:13 + --> $DIR/suggestion.rs:132:13 | LL | let _ = { &{ 3.3 } } != &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -378,7 +378,7 @@ LL + let _ = !::is_nan(*{ &{ 3.3 } }); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:134:13 + --> $DIR/suggestion.rs:134:13 | LL | let _ = &&&3.3 == &&&f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^ @@ -390,7 +390,7 @@ LL + let _ = ::is_nan(***&&&3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:137:13 + --> $DIR/suggestion.rs:137:13 | LL | let _ = &&&f64::NAN != b; | ^^^^^^^^^^^^^^^^ @@ -402,7 +402,7 @@ LL + let _ = !::is_nan(***b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:140:13 + --> $DIR/suggestion.rs:140:13 | LL | let _ = b != &f64::NAN; | ^^^^^^^^^^^^^^ @@ -414,7 +414,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:143:13 + --> $DIR/suggestion.rs:143:13 | LL | let _ = &(::NAN) == b; | ^^^^^^^^^^^^^^^^^^ @@ -426,7 +426,7 @@ LL + let _ = ::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:146:13 + --> $DIR/suggestion.rs:146:13 | LL | let _ = b != &{ f64::NAN }; | ^^^^^^^^^^^^^^^^^^ @@ -438,7 +438,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:149:13 + --> $DIR/suggestion.rs:149:13 | LL | let _ = &{ &{ f64::NAN } } == b; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -450,7 +450,7 @@ LL + let _ = ::is_nan(**b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:153:13 + --> $DIR/suggestion.rs:153:13 | LL | let _ = b != &f16::NAN; | ^^^^^^^^^^^^^^ @@ -462,7 +462,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:156:13 + --> $DIR/suggestion.rs:156:13 | LL | let _ = b == &f128::NAN; | ^^^^^^^^^^^^^^^ @@ -474,7 +474,7 @@ LL + let _ = ::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:160:13 + --> $DIR/suggestion.rs:160:13 | LL | let _ = borrowed!() != &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -486,7 +486,7 @@ LL + let _ = !::is_nan(*borrowed!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:170:13 + --> $DIR/suggestion.rs:170:13 | LL | let _ = b != &nan!(); | ^^^^^^^^^^^^ @@ -498,7 +498,7 @@ LL + let _ = !::std::primitive::f64::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:181:12 + --> $DIR/suggestion.rs:181:12 | LL | if b != &::core::primitive::f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -510,7 +510,7 @@ LL + if !<::core::primitive::f64>::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:184:12 + --> $DIR/suggestion.rs:184:12 | LL | if c != &Float::NAN {} | ^^^^^^^^^^^^^^^^ @@ -522,7 +522,7 @@ LL + if !::is_nan(*c) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/invalid-nan-comparison-suggestion.rs:187:12 + --> $DIR/suggestion.rs:187:12 | LL | if d != &Float::r#NAN {} | ^^^^^^^^^^^^^^^^^^ From d201932dcbf01ba8217d4fd99860dab878a7887c Mon Sep 17 00:00:00 2001 From: Yu Date: Mon, 19 Oct 2026 22:38:19 +0800 Subject: [PATCH 25/45] test: cover shadowing in qualified NaN suggestions --- .../invalid-nan-comparisons/suggestion.fixed | 28 +++++++ .../invalid-nan-comparisons/suggestion.rs | 28 +++++++ .../invalid-nan-comparisons/suggestion.stderr | 74 ++++++++++++------- 3 files changed, 105 insertions(+), 25 deletions(-) diff --git a/tests/ui/lint/invalid-nan-comparisons/suggestion.fixed b/tests/ui/lint/invalid-nan-comparisons/suggestion.fixed index d3043e3f72516..be5f477e67992 100644 --- a/tests/ui/lint/invalid-nan-comparisons/suggestion.fixed +++ b/tests/ui/lint/invalid-nan-comparisons/suggestion.fixed @@ -82,6 +82,7 @@ fn main() { shadowed::check(); nested_operands(); macro_paths(); + method_resolution::check(); let _ = ::is_nan(3.3); //~^ WARN incorrect NaN comparison @@ -185,3 +186,30 @@ mod shadowed { //~^ WARN incorrect NaN comparison } } + +mod method_resolution { + mod f64 { + pub fn is_nan(_: ::core::primitive::f64) {} + } + + trait IsNan { + fn is_nan(self); + } + + impl IsNan for &f64 { + fn is_nan(self) {} + } + + pub fn check() { + let _: () = f64::is_nan(5.0); + let _: () = (&5f64).is_nan(); + + let x = 5f64; + let _: bool = ::is_nan(x); + //~^ WARN incorrect NaN comparison + + let x = &5f64; + let _: bool = ::is_nan(*x); + //~^ WARN incorrect NaN comparison + } +} diff --git a/tests/ui/lint/invalid-nan-comparisons/suggestion.rs b/tests/ui/lint/invalid-nan-comparisons/suggestion.rs index d5376db904453..96b7618e921e9 100644 --- a/tests/ui/lint/invalid-nan-comparisons/suggestion.rs +++ b/tests/ui/lint/invalid-nan-comparisons/suggestion.rs @@ -85,6 +85,7 @@ fn main() { shadowed::check(); nested_operands(); macro_paths(); + method_resolution::check(); let _ = 3.3 == f64::NAN; //~^ WARN incorrect NaN comparison @@ -188,3 +189,30 @@ mod shadowed { //~^ WARN incorrect NaN comparison } } + +mod method_resolution { + mod f64 { + pub fn is_nan(_: ::core::primitive::f64) {} + } + + trait IsNan { + fn is_nan(self); + } + + impl IsNan for &f64 { + fn is_nan(self) {} + } + + pub fn check() { + let _: () = f64::is_nan(5.0); + let _: () = (&5f64).is_nan(); + + let x = 5f64; + let _: bool = x == f64::NAN; + //~^ WARN incorrect NaN comparison + + let x = &5f64; + let _: bool = x == &f64::NAN; + //~^ WARN incorrect NaN comparison + } +} diff --git a/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr b/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr index 467e97be03f57..50d5684dbb2c1 100644 --- a/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr +++ b/tests/ui/lint/invalid-nan-comparisons/suggestion.stderr @@ -246,7 +246,7 @@ LL + let _ = ::is_nan(-x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:89:13 + --> $DIR/suggestion.rs:90:13 | LL | let _ = 3.3 == f64::NAN; | ^^^^^^^^^^^^^^^ @@ -258,7 +258,7 @@ LL + let _ = ::is_nan(3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:92:13 + --> $DIR/suggestion.rs:93:13 | LL | let _ = &3.3 != &f64::NAN; | ^^^^^^^^^^^^^^^^^ @@ -270,7 +270,7 @@ LL + let _ = !::is_nan(*&3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:97:13 + --> $DIR/suggestion.rs:98:13 | LL | let _ = copied != f64::NAN; | ^^^^^^^^^^^^^^^^^^ @@ -282,7 +282,7 @@ LL + let _ = !::is_nan(copied); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:101:13 + --> $DIR/suggestion.rs:102:13 | LL | let _ = ascribed != f64::NAN; | ^^^^^^^^^^^^^^^^^^^^ @@ -294,7 +294,7 @@ LL + let _ = !::is_nan(ascribed); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:106:22 + --> $DIR/suggestion.rs:107:22 | LL | let _ = |x: f64| x != f64::NAN; | ^^^^^^^^^^^^^ @@ -306,7 +306,7 @@ LL + let _ = |x: f64| !::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:112:13 + --> $DIR/suggestion.rs:113:13 | LL | let _ = nan!() == number!(); | ^^^^^^^^^^^^^^^^^^^ @@ -318,7 +318,7 @@ LL + let _ = ::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:114:13 + --> $DIR/suggestion.rs:115:13 | LL | let _ = number!() != nan!(); | ^^^^^^^^^^^^^^^^^^^ @@ -330,7 +330,7 @@ LL + let _ = !::std::primitive::f32::is_nan(number!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:119:13 + --> $DIR/suggestion.rs:120:13 | LL | let _ = x == f64::NAN; | ^^^^^^^^^^^^^ @@ -342,7 +342,7 @@ LL + let _ = ::is_nan(x); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:124:8 + --> $DIR/suggestion.rs:125:8 | LL | if x != &f32::NAN {} | ^^^^^^^^^^^^^^ @@ -354,7 +354,7 @@ LL + if !::is_nan(*x) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:130:13 + --> $DIR/suggestion.rs:131:13 | LL | let _ = &x as &f64 == &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -366,7 +366,7 @@ LL + let _ = ::is_nan(*(&x as &f64)); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:132:13 + --> $DIR/suggestion.rs:133:13 | LL | let _ = { &{ 3.3 } } != &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -378,7 +378,7 @@ LL + let _ = !::is_nan(*{ &{ 3.3 } }); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:134:13 + --> $DIR/suggestion.rs:135:13 | LL | let _ = &&&3.3 == &&&f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^ @@ -390,7 +390,7 @@ LL + let _ = ::is_nan(***&&&3.3); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:137:13 + --> $DIR/suggestion.rs:138:13 | LL | let _ = &&&f64::NAN != b; | ^^^^^^^^^^^^^^^^ @@ -402,7 +402,7 @@ LL + let _ = !::is_nan(***b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:140:13 + --> $DIR/suggestion.rs:141:13 | LL | let _ = b != &f64::NAN; | ^^^^^^^^^^^^^^ @@ -414,7 +414,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:143:13 + --> $DIR/suggestion.rs:144:13 | LL | let _ = &(::NAN) == b; | ^^^^^^^^^^^^^^^^^^ @@ -426,7 +426,7 @@ LL + let _ = ::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:146:13 + --> $DIR/suggestion.rs:147:13 | LL | let _ = b != &{ f64::NAN }; | ^^^^^^^^^^^^^^^^^^ @@ -438,7 +438,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:149:13 + --> $DIR/suggestion.rs:150:13 | LL | let _ = &{ &{ f64::NAN } } == b; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -450,7 +450,7 @@ LL + let _ = ::is_nan(**b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:153:13 + --> $DIR/suggestion.rs:154:13 | LL | let _ = b != &f16::NAN; | ^^^^^^^^^^^^^^ @@ -462,7 +462,7 @@ LL + let _ = !::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:156:13 + --> $DIR/suggestion.rs:157:13 | LL | let _ = b == &f128::NAN; | ^^^^^^^^^^^^^^^ @@ -474,7 +474,7 @@ LL + let _ = ::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:160:13 + --> $DIR/suggestion.rs:161:13 | LL | let _ = borrowed!() != &f64::NAN; | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -486,7 +486,7 @@ LL + let _ = !::is_nan(*borrowed!()); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:170:13 + --> $DIR/suggestion.rs:171:13 | LL | let _ = b != &nan!(); | ^^^^^^^^^^^^ @@ -498,7 +498,7 @@ LL + let _ = !::std::primitive::f64::is_nan(*b); | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:181:12 + --> $DIR/suggestion.rs:182:12 | LL | if b != &::core::primitive::f64::NAN {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -510,7 +510,7 @@ LL + if !<::core::primitive::f64>::is_nan(*b) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:184:12 + --> $DIR/suggestion.rs:185:12 | LL | if c != &Float::NAN {} | ^^^^^^^^^^^^^^^^ @@ -522,7 +522,7 @@ LL + if !::is_nan(*c) {} | warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/suggestion.rs:187:12 + --> $DIR/suggestion.rs:188:12 | LL | if d != &Float::r#NAN {} | ^^^^^^^^^^^^^^^^^^ @@ -533,5 +533,29 @@ LL - if d != &Float::r#NAN {} LL + if !::is_nan(*d) {} | -warning: 44 warnings emitted +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/suggestion.rs:211:23 + | +LL | let _: bool = x == f64::NAN; + | ^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _: bool = x == f64::NAN; +LL + let _: bool = ::is_nan(x); + | + +warning: incorrect NaN comparison, NaN cannot be directly compared to itself + --> $DIR/suggestion.rs:215:23 + | +LL | let _: bool = x == &f64::NAN; + | ^^^^^^^^^^^^^^ + | +help: use the `is_nan` method instead + | +LL - let _: bool = x == &f64::NAN; +LL + let _: bool = ::is_nan(*x); + | + +warning: 46 warnings emitted From 3b9350aa2ca884c952656a084dd686b62483170c Mon Sep 17 00:00:00 2001 From: Yu Date: Mon, 19 Oct 2026 23:05:31 +0800 Subject: [PATCH 26/45] lint: skip NaN diagnostics for custom comparisons --- compiler/rustc_lint/src/types.rs | 39 +++++----- .../custom-comparisons.rs | 73 +++++++++++++++++++ .../custom-partial-eq.rs | 25 ------- .../custom-partial-eq.stderr | 27 ------- 4 files changed, 92 insertions(+), 72 deletions(-) create mode 100644 tests/ui/lint/invalid-nan-comparisons/custom-comparisons.rs delete mode 100644 tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs delete mode 100644 tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index c38e77c5fe6c7..1b38860aef443 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -310,9 +310,6 @@ fn lint_nan<'tcx>( deref_count += 1; ty = inner; } - if !matches!(*ty.kind(), ty::Float(fty) if fty == float_ty) { - return InvalidNanComparisonsSuggestion::Spanless; - } let neg = if is_ne { "!" } else { "" }; let Some(method_path) = nan_method_path(cx, nan, float_ty, cmp.span) else { return InvalidNanComparisonsSuggestion::Spanless; @@ -331,24 +328,26 @@ fn lint_nan<'tcx>( } } - let l_nan = nan_ty(cx, l); - let r_nan = nan_ty(cx, r); - let is_ne = binop == hir::BinOpKind::Ne; + let Some((value, nan, float_ty)) = nan_ty(cx, l) + .map(|float_ty| (r, l, float_ty)) + .or_else(|| nan_ty(cx, r).map(|float_ty| (l, r, float_ty))) + else { + return; + }; + let typeck_results = cx.typeck_results(); + // Custom comparison implementations need not follow primitive float NaN semantics. + if ![l, r].into_iter().all(|operand| { + typeck_results + .expr_ty_opt(operand) + .is_some_and(|ty| matches!(*ty.peel_refs().kind(), ty::Float(fty) if fty == float_ty)) + }) { + return; + } let lint = match binop { - hir::BinOpKind::Eq | hir::BinOpKind::Ne => { - let Some((value, nan, float_ty)) = l_nan - .map(|float_ty| (r, l, float_ty)) - .or_else(|| r_nan.map(|float_ty| (l, r, float_ty))) - else { - return; - }; - InvalidNanComparisons::EqNe { - suggestion: eq_ne_suggestion(cx, e, value, nan, float_ty, is_ne), - } - } - hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge - if l_nan.is_some() || r_nan.is_some() => - { + hir::BinOpKind::Eq | hir::BinOpKind::Ne => InvalidNanComparisons::EqNe { + suggestion: eq_ne_suggestion(cx, e, value, nan, float_ty, binop == hir::BinOpKind::Ne), + }, + hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge => { InvalidNanComparisons::LtLeGtGe } _ => return, diff --git a/tests/ui/lint/invalid-nan-comparisons/custom-comparisons.rs b/tests/ui/lint/invalid-nan-comparisons/custom-comparisons.rs new file mode 100644 index 0000000000000..ccdb7e0d31859 --- /dev/null +++ b/tests/ui/lint/invalid-nan-comparisons/custom-comparisons.rs @@ -0,0 +1,73 @@ +//@ check-pass + +#![deny(invalid_nan_comparisons)] + +use std::cmp::Ordering; + +struct NotFloat; + +impl PartialEq for NotFloat { + fn eq(&self, other: &f64) -> bool { + other.is_nan() + } +} + +impl PartialEq for f64 { + fn eq(&self, other: &NotFloat) -> bool { + other == self + } +} + +impl PartialOrd for NotFloat { + fn partial_cmp(&self, other: &f64) -> Option { + other.is_nan().then_some(Ordering::Equal) + } +} + +impl PartialOrd for f64 { + fn partial_cmp(&self, other: &NotFloat) -> Option { + other.partial_cmp(self).map(Ordering::reverse) + } +} + +fn main() { + let value = NotFloat; + let _ = value == f64::NAN; + let _ = value != f64::NAN; + let _ = value < f64::NAN; + let _ = value <= f64::NAN; + let _ = value > f64::NAN; + let _ = value >= f64::NAN; + + let _ = f64::NAN == value; + let _ = f64::NAN != value; + let _ = f64::NAN < value; + let _ = f64::NAN <= value; + let _ = f64::NAN > value; + let _ = f64::NAN >= value; + + let _ = &value == &f64::NAN; + let _ = &value != &f64::NAN; + let _ = &value < &f64::NAN; + let _ = &value <= &f64::NAN; + let _ = &value > &f64::NAN; + let _ = &value >= &f64::NAN; + + let _ = &f64::NAN == &value; + let _ = &f64::NAN != &value; + let _ = &f64::NAN < &value; + let _ = &f64::NAN <= &value; + let _ = &f64::NAN > &value; + let _ = &f64::NAN >= &value; + + generic(value); +} + +fn generic>(value: T) { + let _ = value == f64::NAN; + let _ = value != f64::NAN; + let _ = value < f64::NAN; + let _ = value <= f64::NAN; + let _ = value > f64::NAN; + let _ = value >= f64::NAN; +} diff --git a/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs b/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs deleted file mode 100644 index be3351916ddc4..0000000000000 --- a/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.rs +++ /dev/null @@ -1,25 +0,0 @@ -//@ check-pass - -struct NotFloat; - -impl PartialEq for NotFloat { - fn eq(&self, _: &f64) -> bool { - true - } -} - -impl PartialEq for f64 { - fn eq(&self, _: &NotFloat) -> bool { - true - } -} - -fn main() { - let value = NotFloat; - let _ = value == f64::NAN; - //~^ WARN incorrect NaN comparison - let _ = f64::NAN != value; - //~^ WARN incorrect NaN comparison - let _ = &value != &f64::NAN; - //~^ WARN incorrect NaN comparison -} diff --git a/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr b/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr deleted file mode 100644 index f0c914dccdce6..0000000000000 --- a/tests/ui/lint/invalid-nan-comparisons/custom-partial-eq.stderr +++ /dev/null @@ -1,27 +0,0 @@ -warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/custom-partial-eq.rs:19:13 - | -LL | let _ = value == f64::NAN; - | ^^^^^^^^^^^^^^^^^ - | - = help: use the `is_nan` method instead - = note: `#[warn(invalid_nan_comparisons)]` on by default - -warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/custom-partial-eq.rs:21:13 - | -LL | let _ = f64::NAN != value; - | ^^^^^^^^^^^^^^^^^ - | - = help: use the `is_nan` method instead - -warning: incorrect NaN comparison, NaN cannot be directly compared to itself - --> $DIR/custom-partial-eq.rs:23:13 - | -LL | let _ = &value != &f64::NAN; - | ^^^^^^^^^^^^^^^^^^^ - | - = help: use the `is_nan` method instead - -warning: 3 warnings emitted - From 8aa94f124cf40efe3e7e79cdadbc7a27f5f86b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 20:01:41 +0200 Subject: [PATCH 27/45] Rename the unstable compiler flag mode to `AllowUnstableCompilerFlagsOnly` and fix docs --- compiler/rustc_feature/src/lib.rs | 10 +++++----- compiler/rustc_feature/src/tests.rs | 6 +++--- compiler/rustc_session/src/config.rs | 10 ++++------ tests/ui/bootstrap/rustc_bootstrap.rs | 3 ++- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 890142718fe92..d0d0f9283f98d 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -62,7 +62,7 @@ pub enum UnstableFeatures { /// Specifically, we need it when we want to test how rustc behaves on the stable channel, /// but at the same time we need to be able to pass it some unstable `-Z` compiler flags /// required for the testing infrastructure itself to work. - AllowUnstableCompilerFlagsOnly { + AllowSelectedUnstableCompilerFlagsOnly { /// Unstable compiler flags that we are allowed to use, without their `-Z` prefix. allowed_flags: Vec, }, @@ -96,7 +96,7 @@ impl UnstableFeatures { match val { // Act as stable, but still accept unstable compiler flags required by compiletest val if let Some(flags) = val.strip_prefix(allow_unstable_flags_prefix) => { - return UnstableFeatures::AllowUnstableCompilerFlagsOnly { + return UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { allowed_flags: flags.split(",").map(|s| s.trim().to_string()).collect(), }; } @@ -116,13 +116,13 @@ impl UnstableFeatures { pub fn allow_unstable_compiler_flag(&self, flag: &str) -> bool { match self { UnstableFeatures::Allow | UnstableFeatures::Cheat => true, - UnstableFeatures::AllowUnstableCompilerFlagsOnly { allowed_flags } + UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { allowed_flags } if allowed_flags.iter().any(|allowed_flag| allowed_flag == flag) => { true } UnstableFeatures::Disallow - | UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } => false, + | UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { .. } => false, } } @@ -130,7 +130,7 @@ impl UnstableFeatures { match *self { UnstableFeatures::Allow | UnstableFeatures::Cheat => true, UnstableFeatures::Disallow - | UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } => false, + | UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { .. } => false, } } } diff --git a/compiler/rustc_feature/src/tests.rs b/compiler/rustc_feature/src/tests.rs index 46e44f0ffc877..32e7cc5ddc9fd 100644 --- a/compiler/rustc_feature/src/tests.rs +++ b/compiler/rustc_feature/src/tests.rs @@ -35,14 +35,14 @@ fn rustc_bootstrap_parsing() { assert!(is_force_stable(Some("x"))); assert!(is_force_stable(Some("x,y,z"))); - // `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags` simulates the stable toolchain, while - // allowing unstable compiler flags to be passed. + // `RUSTC_BOOTSTRAP=-allow-unstable-compiler-flags` simulates the stable toolchain, only + // allowing the specified unstable compiler flags to be passed. std::assert_eq!( UnstableFeatures::from_environment_value( None, Ok("-allow-unstable-compiler-flags=foo,bar".to_string()) ), - UnstableFeatures::AllowUnstableCompilerFlagsOnly { + UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { allowed_flags: vec!["foo".to_string(), "bar".to_string()] } ); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 0ae95a822e70d..6ed5bd18d2290 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -513,7 +513,7 @@ impl LinkerFeaturesCli { /// When *not* using `-Z unstable-options` on the CLI, ensure only stable linker features are /// used, for the given `TargetTuple`. Returns `Ok` if no unstable variants are used. - /// The caller should ensure that e.g. `nightly_options::is_unstable_enabled()` + /// The caller should ensure that e.g. [`nightly_options::unstable_options_enabled`] /// returns false. pub(crate) fn check_unstable_variants(&self, target_tuple: &TargetTuple) -> Result<(), String> { // `-C linker-features=-lld` is only stable on x64 linux. @@ -3247,23 +3247,21 @@ pub mod nightly_options { let values = matches.opt_strs(opt.name); let disallowed_flags = values .iter() - .map(|flag| flag.split_once("=").map(|s| s.0).unwrap_or(flag)) + .map(|flag| flag.split_once("=").map(|(k, _)| k).unwrap_or(flag)) .filter(|flag| !unstable_features.allow_unstable_compiler_flag(flag)) .collect::>(); if disallowed_flags.is_empty() { continue; } - // At this point, check if we're using the "allow unstable flags only" mode. + // Check if we're using the "allow specified unstable flags only" mode. // In this case, if the user allowed only foo and bar, but then -Zbaz was also // passed, rustc would only print that `-Z` is not allowed, which doesn't tell the // user the actual -Z flag value that was disallowed. So we try to provide better // diagnostics here (solely for the use-case of debugging compiletest itself). - let unstable_features = - UnstableFeatures::from_environment(matches.opt_str("crate-name").as_deref()); if matches!( unstable_features, - UnstableFeatures::AllowUnstableCompilerFlagsOnly { .. } + UnstableFeatures::AllowSelectedUnstableCompilerFlagsOnly { .. } ) { for flag in disallowed_flags { let _ = early_dcx.early_err(format!("the -Z{flag} option was not allowed through `-allow-unstable-compiler-flags`")); diff --git a/tests/ui/bootstrap/rustc_bootstrap.rs b/tests/ui/bootstrap/rustc_bootstrap.rs index 6395a750718e8..a9d77214add14 100644 --- a/tests/ui/bootstrap/rustc_bootstrap.rs +++ b/tests/ui/bootstrap/rustc_bootstrap.rs @@ -6,7 +6,8 @@ //! - `1`: cheat, allow usage of unstable features even if rustc thinks it is a stable compiler. //! - `x,y,z`: comma-delimited list of crates. //! - `-1`: force rustc to think it is a stable compiler. -//! - `-2`: force rustc to think it is a stable compiler, while still allowing -Z flags. +//! - `-allow-unstable-compiler-flags=`: force rustc to think it is a stable compiler, +//! while still allowing the specified -Z flags. // ignore-tidy-linelength From 61ab2cd4a37fbc4727aafc2ef86982622b26da5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 20:03:23 +0200 Subject: [PATCH 28/45] Update Cranelift test script --- compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh b/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh index 648f68588e04b..5d4011b659335 100755 --- a/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh +++ b/compiler/rustc_codegen_cranelift/scripts/test_rustc_tests.sh @@ -161,9 +161,7 @@ rm -r tests/run-make-cargo/panic-immediate-abort-codegen # same rm -r tests/run-make-cargo/panic-strategies # same rm -r tests/run-make/missing-unstable-trait-bound # This disables support for unstable features, but running cg_clif needs some unstable features rm -r tests/run-make/const-trait-stable-toolchain # same -rm -r tests/run-make/print-request-help-stable-unstable # same rm -r tests/run-make/issue-149402-suggest-unresolve # same -rm -r tests/run-make/const-destruct-stable-toolchain # same rm -r tests/run-make/unstable-target-feature # same rm -r tests/run-make/incr-add-rust-src-component rm tests/ui/errors/remap-path-prefix-sysroot.rs # different sysroot source path From e39618ce3569c31d5a0623128397e60d18755c3d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 19 Oct 2026 18:39:44 +0200 Subject: [PATCH 29/45] Correctly handle inner `deprecated` attribute on modules --- compiler/rustc_attr_ir/src/data_structures.rs | 2 ++ .../src/attributes/deprecation.rs | 1 + compiler/rustc_passes/src/stability.rs | 2 +- .../passes/collect_intra_doc_links.rs | 14 ++++++++++--- tests/pretty/delegation/inherit-attributes.pp | 9 ++++++--- tests/pretty/delegation/inline-attribute.pp | 12 +++++++---- .../intra-doc/link-in-deprecation-note.rs | 12 +++++++++++ .../intra-doc/link-in-deprecation-note.stderr | 20 +++++++++++++++++++ tests/ui/eii/cfg_on_eii.stdout | 5 +++-- tests/ui/unpretty/deprecated-attr.stdout | 14 +++++++------ 10 files changed, 72 insertions(+), 19 deletions(-) create mode 100644 tests/rustdoc-ui/intra-doc/link-in-deprecation-note.rs create mode 100644 tests/rustdoc-ui/intra-doc/link-in-deprecation-note.stderr diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 6ae4eb1d4b388..4a88cd6190c96 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -859,6 +859,8 @@ pub enum AttributeKind { Deprecated { deprecation: Deprecation, span: Span, + /// `true` if this an inner attribute (ie `#![]`). + is_inner: bool, }, /// Represents `#[diagnostic::do_not_recommend]`. diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index b68042c39670c..084a20b74347d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -210,6 +210,7 @@ impl SingleAttributeParser for DeprecatedParser { Some(AttributeKind::Deprecated { deprecation: Deprecation { since, note, suggestion }, span: cx.attr_span, + is_inner: matches!(cx.attr_style, rustc_ast::AttrStyle::Inner), }) } } diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 162914c95c0e5..451c3a39dd480 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -101,7 +101,7 @@ fn annotation_kind(tcx: TyCtxt<'_>, def_id: LocalDefId) -> AnnotationKind { fn lookup_deprecation_entry(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option { let depr = find_attr!(tcx, def_id, - Deprecated { deprecation, span: _ } => *deprecation + Deprecated { deprecation, .. } => *deprecation ); let Some(depr) = depr else { diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index ea131574b17e3..4caaca9c776b2 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1152,8 +1152,11 @@ impl LinkCollector<'_, '_> { // Also resolve links in the note text of `#[deprecated]`. for attr in &item.attrs.other_attrs { - let Attribute::Parsed(AttributeKind::Deprecated { span: depr_span, deprecation }) = - attr + let Attribute::Parsed(AttributeKind::Deprecated { + span: depr_span, + deprecation, + is_inner, + }) = attr else { continue; }; @@ -1185,7 +1188,12 @@ impl LinkCollector<'_, '_> { } else { item.item_id.expect_def_id() }; - try_insert_links(item_id, &DocStrings { outer: note.to_owned(), inner: String::new() }); + let attr = if *is_inner { + DocStrings { outer: String::new(), inner: note.to_owned() } + } else { + DocStrings { outer: note.to_owned(), inner: String::new() } + }; + try_insert_links(item_id, &attr); } } diff --git a/tests/pretty/delegation/inherit-attributes.pp b/tests/pretty/delegation/inherit-attributes.pp index 01aa7bd2fa5eb..575e309c392bb 100644 --- a/tests/pretty/delegation/inherit-attributes.pp +++ b/tests/pretty/delegation/inherit-attributes.pp @@ -23,11 +23,13 @@ fn foo_no_reason(x: usize) -> usize { x } #[attr = Cold] - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] fn bar(x: usize) -> usize { x } } -#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, +is_inner: false}] #[attr = MustUse {reason: "foo: some reason"}] #[attr = Inline(Hint)] fn foo1(arg0: _) -> _ { to_reuse::foo(self + 1) } @@ -36,7 +38,8 @@ #[attr = Inline(Hint)] fn foo_no_reason(arg0: _) -> _ { to_reuse::foo_no_reason(self + 1) } -#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, +is_inner: false}] #[attr = MustUse {reason: "some reason"}] #[attr = Inline(Hint)] fn foo2(arg0: _) -> _ { to_reuse::foo(self + 1) } diff --git a/tests/pretty/delegation/inline-attribute.pp b/tests/pretty/delegation/inline-attribute.pp index bf55d76ad3fb8..dfe1097035674 100644 --- a/tests/pretty/delegation/inline-attribute.pp +++ b/tests/pretty/delegation/inline-attribute.pp @@ -46,7 +46,8 @@ // Check that #[inline(hint)] is added when other attributes present in inner reuse #[attr = Cold] #[attr = MustUse] - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] #[attr = Inline(Hint)] fn foo1(arg0: _) -> _ { to_reuse::foo(self / 2) } @@ -62,7 +63,8 @@ #[attr = Cold] #[attr = MustUse] #[attr = Inline(Never)] - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] fn foo4(arg0: _) -> _ { to_reuse::foo(self / 2) } Trait::foo(self) } @@ -70,7 +72,8 @@ // Check that #[inline(hint)] is added when there are other attributes present in trait reuse #[attr = Cold] #[attr = MustUse] - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] #[attr = Inline(Hint)] fn foo1(self: _) -> _ { Trait::foo1(self.0) } @@ -86,7 +89,8 @@ #[attr = Cold] #[attr = MustUse] #[attr = Inline(Never)] - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] fn foo4(self: _) -> _ { Trait::foo4(self.0) } } diff --git a/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.rs b/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.rs new file mode 100644 index 0000000000000..4546c257bee37 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.rs @@ -0,0 +1,12 @@ +// Because inner and outer attributes don't have the same scope for a module, we need +// to also apply the logic to the other module attributes which can contain intra-doc +// links. +// Regression test for . + +#![deny(rustdoc::broken_intra_doc_links)] + +pub mod a { + //! + #![deprecated = "[](TypeAlias::x)"] + //~^^ ERROR broken_intra_doc_links +} diff --git a/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.stderr b/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.stderr new file mode 100644 index 0000000000000..9b7df6f02c533 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/link-in-deprecation-note.stderr @@ -0,0 +1,20 @@ +error: unresolved link to `TypeAlias::x` + --> $DIR/link-in-deprecation-note.rs:9:5 + | +LL | / //! +LL | | #![deprecated = "[](TypeAlias::x)"] + | |______________________________________^ + | + = note: the link appears in this line: + + [](TypeAlias::x) + ^^^^^^^^^^^^ + = note: no item named `TypeAlias` in scope +note: the lint level is defined here + --> $DIR/link-in-deprecation-note.rs:6:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/eii/cfg_on_eii.stdout b/tests/ui/eii/cfg_on_eii.stdout index f6ed2aa90ed97..e49f3cdbb8c45 100644 --- a/tests/ui/eii/cfg_on_eii.stdout +++ b/tests/ui/eii/cfg_on_eii.stdout @@ -52,13 +52,14 @@ const _: () = extern "Rust" { #[attr = CfgAttrTrace([Bool(true, $DIR/cfg_on_eii.rs:13:12: 13:16 (#0))])] #[attr = Deprecated {deprecation: Deprecation {since: Unspecified, - note: bar#0}}] + note: bar#0}, + is_inner: false}] #[attr = RustcEiiForeignItem] fn cfg_attr_on_eii(); } #[attr = CfgAttrTrace([Bool(true, $DIR/cfg_on_eii.rs:13:12: 13:16 (#0))])] #[attr = Deprecated {deprecation: Deprecation {since: Unspecified, -note: bar#0}}] +note: bar#0}, is_inner: false}] #[attr = RustcMacroTransparency(SemiOpaque)] #[attr = RustcBuiltinMacro {builtin_name: "eii_shared_macro", helper_attrs: []}] diff --git a/tests/ui/unpretty/deprecated-attr.stdout b/tests/ui/unpretty/deprecated-attr.stdout index 4ba5b0b366df9..c7d825f1f29c2 100644 --- a/tests/ui/unpretty/deprecated-attr.stdout +++ b/tests/ui/unpretty/deprecated-attr.stdout @@ -5,28 +5,30 @@ use ::std::prelude::rust_2015::*; //@ check-pass //@ edition: 2015 -#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, +is_inner: false}] struct PlainDeprecated; #[attr = Deprecated {deprecation: Deprecation {since: Unspecified, -note: here's why this is deprecated#0}}] +note: here's why this is deprecated#0}, is_inner: false}] struct DirectNote; #[attr = Deprecated {deprecation: Deprecation {since: Unspecified, -note: here's why this is deprecated#0}}] +note: here's why this is deprecated#0}, is_inner: false}] struct ExplicitNote; #[attr = Deprecated {deprecation: Deprecation {since: NonStandard("1.2.3"), -note: here's why this is deprecated#0}}] +note: here's why this is deprecated#0}, is_inner: false}] struct SinceAndNote; #[attr = Deprecated {deprecation: Deprecation {since: NonStandard("1.2.3"), -note: here's why this is deprecated#0}}] +note: here's why this is deprecated#0}, is_inner: false}] struct FlippedOrder; fn f() { // Attribute is ignored here (with a warning), but still preserved in HIR - #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}, + is_inner: false}] 0 } From 8735b14a347b534fea24bbababd5107fd0cd76cc Mon Sep 17 00:00:00 2001 From: ywxt Date: Tue, 20 Oct 2026 09:59:59 +0800 Subject: [PATCH 30/45] Improve bit permutation codegen --- library/core/src/num/imp/int_bits.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/library/core/src/num/imp/int_bits.rs b/library/core/src/num/imp/int_bits.rs index 2bc95e66e7ef6..dbb1a54fc59f6 100644 --- a/library/core/src/num/imp/int_bits.rs +++ b/library/core/src/num/imp/int_bits.rs @@ -115,7 +115,7 @@ macro_rules! uint_impl { // [0; n], [X; a], [0; n], [0; b] // q // [0; n], [0; a + n], [Y; b] // x ^= q // [0; n + n], [X; a], [0; b] // q >> n - // [0; n], [0; n], [X; a], [Y; b] // x ^= q << n + // [0; n], [0; n], [X; a], [Y; b] // x ^= q >> n // ``` // Only zeros are shifted out, satisfying the assumption // for the next group. @@ -123,9 +123,18 @@ macro_rules! uint_impl { // In effect, the upper run of data is swapped with the // group of `n` zeros below it. let q = x & masks[stage]; - x ^= q; - x ^= q >> n; + if cfg!(all(target_arch = "aarch64", target_feature = "neon")) && $U::BITS <= 64 + { + // `q >> n` has no set bits in common with `x ^ q`, so addition + // is equivalent to XOR here. For widths up to 64 bits this + // allows AArch64 to use `usra`. For `u128`, this add may introduce + // a slower cross-limb carry chain. + x = (x & !masks[stage]).wrapping_add(q >> n); + } else { + x ^= q; + x ^= q >> n; + } stage += 1; } x From 37bc46f797428b9526874e4daac2ac2b41800d6b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 3 Oct 2026 15:14:08 +0200 Subject: [PATCH 31/45] add ReadDir::dir to get the directory handle of the underlying directory --- library/std/src/fs.rs | 14 ++++++++++++ library/std/src/fs/tests.rs | 13 +++++++++++ library/std/src/sys/fs/hermit.rs | 8 +++++++ library/std/src/sys/fs/motor.rs | 8 +++++++ library/std/src/sys/fs/solid.rs | 8 +++++++ library/std/src/sys/fs/uefi.rs | 8 +++++++ library/std/src/sys/fs/unix.rs | 33 +++++++++++++++++++++++++++ library/std/src/sys/fs/unix/dir.rs | 2 +- library/std/src/sys/fs/unsupported.rs | 6 +++++ library/std/src/sys/fs/vexos.rs | 6 +++++ library/std/src/sys/fs/windows.rs | 9 ++++++++ 11 files changed, 114 insertions(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f922696298fff..eadf23ec96e89 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -2795,6 +2795,20 @@ impl AsInner for Permissions { } } +impl ReadDir { + /// Returns a handle for the directory this `ReadDir` is reading. + /// + /// Whenever possible, the new handle will be created in a race-free manner from the underlying + /// handle used by `ReadDir`. However, this may fall back to opening a new handle based on the + /// path, which is subject to race conditions if the file system has changed since the `ReadDir` + /// was created. + // FIXME: before stabilization, ensure that we have a race-free implementation on Windows! + #[unstable(feature = "dirfd", issue = "120426")] + pub fn dir(&self) -> io::Result { + self.0.dir().map(|d| Dir { inner: d }) + } +} + #[stable(feature = "rust1", since = "1.0.0")] impl Iterator for ReadDir { type Item = io::Result; diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 605ad25270a63..758a10b6644ae 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -3341,6 +3341,19 @@ fn test_dir_metadata() { assert!(metadata.is_symlink()); } +#[test] +fn test_read_dir_dir() { + let tmpdir = tmpdir(); + check!(fs::write(tmpdir.path().join("file.txt"), b"hello")); + + let read_dir = check!(fs::read_dir(tmpdir.path())); + let dir = check!(read_dir.dir()); + let mut f = check!(dir.open_file("file.txt")); + let mut data = vec![]; + check!(f.read_to_end(&mut data)); + assert_eq!(data, b"hello"); +} + fn root_test_dir(what: &str) -> PathBuf { crate::env::current_dir().unwrap().ancestors().last().unwrap().join(what) } diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 650f7e884c41d..32ed0bd76214f 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -221,6 +221,14 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.inner.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 06d7356b2a519..269ece5666494 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -410,6 +410,14 @@ pub fn readdir(path: &Path) -> io::Result { }) } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(Path::new(&self.path), &options) + } +} + impl Iterator for ReadDir { type Item = io::Result; diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index 849a1657b761d..c43a4794e620e 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -159,6 +159,14 @@ pub fn readdir(p: &Path) -> io::Result { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.inner.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index ef902931533d3..945eaeaff1d7d 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -147,6 +147,14 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + Dir::open(&self.0.path(), &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut b = f.debug_struct("ReadDir"); diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index d0cbabbdd4955..28ef1f7877970 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -796,6 +796,39 @@ impl fmt::Debug for FilePermissions { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + + cfg_select! { + // Some targets don't have `dirfd`. Open `Dir` based on path. + any( + target_os = "redox", + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nto", + target_os = "qnx", + target_os = "vxworks", + target_os = "l4re", + ) => Dir::open(&self.inner.root, &options), + // Use `dirfd` where possible. + _ => { + let fd = unsafe { libc::dirfd(self.inner.dirp.0) }; + // Make this FD into a directory handle. We don't actually drop it, + // so having an `OwnedFd` is fine. + let dir_handle = + mem::ManuallyDrop::new(dir::Dir(unsafe { OwnedFd::from_raw_fd(fd) })); + // We don't want to expose `fd` or even the underlying file description + // as the directory stream has state attached to it. So we open a completely + // new file description based on this one. + dir_handle.open_dir(Path::new("."), &options) + } + } + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index dd01db58c600d..2ac5695e3d72a 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -60,7 +60,7 @@ impl Dir { pub fn self_metadata(&self) -> io::Result { // Reuse the implementation for files, which should work for all FDs. let fd = self.0.as_raw_fd(); - let f = core::mem::ManuallyDrop::new(File( + let f = mem::ManuallyDrop::new(File( // SAFETY: we borrowed `self` so the FD will not be closed while this function runs. unsafe { FileDesc::from_raw_fd(fd) }, )); diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 1421dad97272f..78b4a1a698a33 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -157,6 +157,12 @@ impl fmt::Debug for FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + self.0 + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 6529159eb49fc..5dbcdfef7c80d 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -127,6 +127,12 @@ impl FileType { } } +impl ReadDir { + pub fn dir(&self) -> io::Result { + self.0 + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 548b2d4b6f00f..c7dbc815b3434 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -113,6 +113,15 @@ impl fmt::Debug for c::FILETIME { #[derive(Debug)] pub struct DirBuilder; +impl ReadDir { + pub fn dir(&self) -> io::Result { + let mut options = OpenOptions::new(); + options.read(true); + // FIXME: use race-free handle-based approach instead. + Dir::open(&self.root, &options) + } +} + impl fmt::Debug for ReadDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // This will only be called from std::fs::ReadDir, which will add a "ReadDir()" frame. From 730a53d17171e4113ca481f6459a0aa6eca6a8ea Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 5 Oct 2026 15:48:36 +0200 Subject: [PATCH 32/45] miri: use and test new ReadDir::dir support --- src/tools/miri/src/shims/unix/fs.rs | 15 +++++++-------- src/tools/miri/tests/pass-dep/libc/fs.rs | 2 ++ src/tools/miri/tests/pass/shims/fs.rs | 8 +++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index bee60af61f0fa..4622114dcacd8 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1367,14 +1367,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match result { Ok(read_dir) => { - // Also open the same directory as a directory handle, so we have - // an underlying FD. - // FIXME(https://github.com/rust-lang/miri/issues/5326): This is racy! We can't even - // verify whether there was a race. We just trust that the directory did not change - // in between above and here. One day, the standard library will support converting - // between `Dir` and `ReadDir` (one of the two directions would suffice for our - // needs), then we'll use that. - let Ok(dir) = fs::Dir::open(&name) else { + // Create an underlying FD as well, in case someone calls `dirfd` later. + // (We could do this lazily but that does not seem worth it.) + #[cfg(not(bootstrap))] + let dir = read_dir.dir(); + #[cfg(bootstrap)] + let dir = fs::Dir::open(&name); + let Ok(dir) = dir else { throw_unsup_format!( "cannot `opendir` this directory: failed to create directory handle" ); diff --git a/src/tools/miri/tests/pass-dep/libc/fs.rs b/src/tools/miri/tests/pass-dep/libc/fs.rs index 90053d36fd1b1..a5a60de75f6b0 100644 --- a/src/tools/miri/tests/pass-dep/libc/fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/fs.rs @@ -1659,6 +1659,8 @@ fn test_dirfd() { assert!(!dir.is_null()); let dirfd = unsafe { libc::dirfd(dir) }; + let dirfd2 = unsafe { libc::dirfd(dir) }; + assert_eq!(dirfd, dirfd2); // make sure we always return the same one let mut stat = MaybeUninit::::uninit(); errno_check(unsafe { libc::fstat(dirfd, stat.as_mut_ptr()) }); diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs index 683b0f5605b0e..cd95ed21e5dc6 100644 --- a/src/tools/miri/tests/pass/shims/fs.rs +++ b/src/tools/miri/tests/pass/shims/fs.rs @@ -501,22 +501,24 @@ fn test_directory() { let dir_1 = dir_path.join("test_dir_1"); create_dir(&dir_1).unwrap(); // Test that read_dir metadata calls succeed + let mut dir_iter = read_dir(&dir_path).unwrap(); assert_eq!( BTreeMap::from([ (OsString::from("test_file_1"), true), (OsString::from("test_file_2"), true), (OsString::from("test_dir_1"), false) ]), - read_dir(&dir_path) - .unwrap() + (&mut dir_iter) .map(|e| { let e = e.unwrap(); (e.file_name(), e.metadata().unwrap().is_file()) }) .collect::>() ); + // We can access the directory handle from this iterator. + let dir_handle = dir_iter.dir().unwrap(); + drop(dir_handle.open_file("test_file_1").unwrap()); // Deleting the directory should fail, since it is not empty. - // Solaris/Illumos `rmdir` call set errno to EEXIST if directory contains // other entries than `.` and `..`. // https://docs.oracle.com/cd/E86824_01/html/E54765/rmdir-2.html From cccc5be4488e9c62aaa71e1cbc57ac7955cba1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 19 Oct 2026 20:07:00 +0200 Subject: [PATCH 33/45] Also allow `-Zunstable-options` in `act-as-stable` --- src/tools/compiletest/src/runtest.rs | 1 + ...stc_bootstrap.force_stable_disallow_unstable_features.stderr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 860a69fca66b2..7243fa3a4fe99 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -279,6 +279,7 @@ pub(crate) const COMPILETEST_UI_SUITE_UNSTABLE_FLAGS: &[&str] = &[ "wasm-proc-macros", "threads", "ui-testing", + "unstable-options", ]; impl<'test> TestCx<'test> { diff --git a/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr index 13627ce02eee3..0a0772ff7453e 100644 --- a/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr +++ b/tests/ui/bootstrap/rustc_bootstrap.force_stable_disallow_unstable_features.stderr @@ -1,5 +1,5 @@ error[E0554]: `#![feature]` may not be used on the nightly release channel - --> $DIR/rustc_bootstrap.rs:55:1 + --> $DIR/rustc_bootstrap.rs:56:1 | LL | #![feature(rustc_attrs)] | ^^^^^^^^^^^^^^^^^^^^^^^^ From 03704177f51ea14794de54a991a702413a5b3b47 Mon Sep 17 00:00:00 2001 From: Walnut <39544927+Walnut356@users.noreply.github.com> Date: Tue, 20 Oct 2026 04:01:22 -0500 Subject: [PATCH 34/45] emit usize and isize as typedefs --- .../src/debuginfo/metadata.rs | 46 ++++++++++++++++++- tests/codegen-llvm/debug-vtable.rs | 2 +- .../basic-types/lldb_input/non_windows.json | 4 +- .../basic-types/lldb_input/windows_gnu.json | 6 +-- .../pretty-std/lldb_input/non_windows.json | 15 ++++-- .../pretty-std/lldb_input/windows_gnu.json | 4 +- tests/debuginfo/rc_arc.rs | 10 ++++ 7 files changed, 72 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 60a29e5c73b16..87e0612bc102d 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -6,6 +6,7 @@ use std::{assert_matches, iter, ptr}; use libc::{c_longlong, c_uint}; use rustc_abi::{Align, Endian, Layout, NumScalableVectors, Size}; +use rustc_ast::{IntTy, UintTy}; use rustc_codegen_ssa::debuginfo::type_names::{VTableNameKind, cpp_like_debuginfo}; use rustc_codegen_ssa::traits::*; use rustc_hir::def::{CtorKind, DefKind}; @@ -794,9 +795,50 @@ fn build_basic_type_di_node<'ll, 'tcx>( _ => bug!("debuginfo::build_basic_type_di_node - `t` is invalid type"), }; - let ty_di_node = create_basic_type(cx, name, cx.size_of(t), encoding); + // Around LLDB 21 or 22, LLDB started using prebuilt Clang type nodes for primitives rather than + // reading the full DWARF data. These nodes are based on the size and signedness of the + // primitive, and ignore pretty much all other fields. This causes 2 issues: + // 1. type names get normalized to C-style names which isn't ideal. The normalization occurs + // even in generic args. + // 2. if you have a generic type `S`, and 2 instantiations are `S` + // and `S` (on a 64-bit target), the use of prebuilt nodes causes a name collision. + // Whichever instantiation is interacted with first will work fine, all others will break a + // bunch of stuff in the LLDB pipeline. To avoid this, we explicitly treat `usize` and `isize` + // as typedefs of their equivalent types. For more info see: llvm/llvm-project#196812 + + let mut needs_typedef = cpp_like_debuginfo; + + let ty_di_node = match t.kind() { + ty::Uint(UintTy::Usize) => { + needs_typedef = true; + + let equivalent = match t.primitive_size(cx.tcx).bits() { + 8 => cx.tcx.types.u8, + 16 => cx.tcx.types.u16, + 32 => cx.tcx.types.u32, + 64 => cx.tcx.types.u64, + 128 => cx.tcx.types.u128, + x => bug!("Unexpected signed int size {x}"), + }; + type_di_node(cx, equivalent) + } + ty::Int(IntTy::Isize) => { + needs_typedef = true; + + let equivalent = match t.primitive_size(cx.tcx).bits() { + 8 => cx.tcx.types.i8, + 16 => cx.tcx.types.i16, + 32 => cx.tcx.types.i32, + 64 => cx.tcx.types.i64, + 128 => cx.tcx.types.i128, + x => bug!("Unexpected signed int size: {x}"), + }; + type_di_node(cx, equivalent) + } + _ => create_basic_type(cx, name, cx.size_of(t), encoding), + }; - if !cpp_like_debuginfo { + if !needs_typedef { return DINodeCreationResult::new(ty_di_node, false); } diff --git a/tests/codegen-llvm/debug-vtable.rs b/tests/codegen-llvm/debug-vtable.rs index 8a7b1cc3c4bcc..031153832629c 100644 --- a/tests/codegen-llvm/debug-vtable.rs +++ b/tests/codegen-llvm/debug-vtable.rs @@ -19,7 +19,7 @@ // CHECK: @vtable.3 = private constant <{ // CHECK: @vtable.4 = private constant <{ -// NONMSVC: ![[USIZE:[0-9]+]] = !DIBasicType(name: "usize" +// NONMSVC: ![[USIZE:[0-9]+]] = !DIDerivedType(tag: DW_TAG_typedef, name: "usize" // MSVC: ![[USIZE:[0-9]+]] = !DIDerivedType(tag: DW_TAG_typedef, name: "usize" // NONMSVC: ![[PTR:[0-9]+]] = !DIDerivedType(tag: DW_TAG_pointer_type, name: "*const ()" // MSVC: ![[PTR:[0-9]+]] = !DIDerivedType(tag: DW_TAG_pointer_type, name: "ptr_const$ >" diff --git a/tests/debuginfo/basic-types/lldb_input/non_windows.json b/tests/debuginfo/basic-types/lldb_input/non_windows.json index af225246e6109..67c45674981a2 100644 --- a/tests/debuginfo/basic-types/lldb_input/non_windows.json +++ b/tests/debuginfo/basic-types/lldb_input/non_windows.json @@ -12,7 +12,7 @@ "value": false }, "i": { - "type": "long", + "type": "isize", "pretty_print": "-1", "value": -1 }, @@ -43,7 +43,7 @@ "value": -64 }, "u": { - "type": "unsigned long", + "type": "usize", "pretty_print": "1", "value": 1 }, diff --git a/tests/debuginfo/basic-types/lldb_input/windows_gnu.json b/tests/debuginfo/basic-types/lldb_input/windows_gnu.json index 80c35ffcbea60..06b21d7cb0b35 100644 --- a/tests/debuginfo/basic-types/lldb_input/windows_gnu.json +++ b/tests/debuginfo/basic-types/lldb_input/windows_gnu.json @@ -1,7 +1,7 @@ { "bless_metadata": { "python_version": "3.11.9 (tags/v3.11.9:de54cf5, Apr 2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]", - "debugger_version": "lldb version 22.1.2 (https://github.com/llvm/llvm-project revision 1ab49a973e210e97d61e5db6557180dcb92c3e98)\n clang revision 1ab49a973e210e97d61e5db6557180dcb92c3e98\n llvm revision 1ab49a973e210e97d61e5db6557180dcb92c3e98", + "debugger_version": "lldb version 22.1.8 (https://github.com/llvm/llvm-project revision ca7933e47d3a3451d81e72ac174dcb5aa28b59d1)\n clang revision ca7933e47d3a3451d81e72ac174dcb5aa28b59d1\n llvm revision ca7933e47d3a3451d81e72ac174dcb5aa28b59d1", "feature_flags": "LLDBFeature.StaticFields|TypeRecognizers|Float128" }, "breakpoints": [ @@ -12,7 +12,7 @@ "value": false }, "i": { - "type": "long long", + "type": "isize", "pretty_print": "-1", "value": -1 }, @@ -43,7 +43,7 @@ "value": -64 }, "u": { - "type": "unsigned long long", + "type": "usize", "pretty_print": "1", "value": 1 }, diff --git a/tests/debuginfo/pretty-std/lldb_input/non_windows.json b/tests/debuginfo/pretty-std/lldb_input/non_windows.json index 737eb5004b2bf..f76ba1ded06a1 100644 --- a/tests/debuginfo/pretty-std/lldb_input/non_windows.json +++ b/tests/debuginfo/pretty-std/lldb_input/non_windows.json @@ -162,7 +162,7 @@ }, { "name": "length", - "type": "unsigned long", + "type": "usize", "offset": 8 } ] @@ -178,7 +178,7 @@ }, { "name": "length", - "type": "unsigned long", + "type": "usize", "offset": 8 } ] @@ -273,7 +273,7 @@ }, { "name": "len", - "type": "unsigned long", + "type": "usize", "offset": 16 } ], @@ -293,7 +293,7 @@ }, { "name": "len", - "type": "unsigned long", + "type": "usize", "offset": 16 } ], @@ -322,7 +322,7 @@ "fields": [ { "name": "__0", - "type": "unsigned long", + "type": "usize", "offset": 0 } ] @@ -560,6 +560,11 @@ "size": 2, "type_class": 4, "basic_type": 12 + }, + "usize": { + "size": 8, + "type_class": 32768, + "basic_type": 16 } } } diff --git a/tests/debuginfo/pretty-std/lldb_input/windows_gnu.json b/tests/debuginfo/pretty-std/lldb_input/windows_gnu.json index 7f7772bd4caa1..56bfb83deef32 100644 --- a/tests/debuginfo/pretty-std/lldb_input/windows_gnu.json +++ b/tests/debuginfo/pretty-std/lldb_input/windows_gnu.json @@ -173,7 +173,7 @@ }, { "name": "length", - "type": "unsigned long long", + "type": "usize", "offset": 8 } ] @@ -284,7 +284,7 @@ }, { "name": "len", - "type": "unsigned long long", + "type": "usize", "offset": 16 } ], diff --git a/tests/debuginfo/rc_arc.rs b/tests/debuginfo/rc_arc.rs index adeb96dba1b80..e1af1eba02193 100644 --- a/tests/debuginfo/rc_arc.rs +++ b/tests/debuginfo/rc_arc.rs @@ -21,6 +21,11 @@ //@ lldb-check:[...] strong=11, weak=2 { value = 111 } //@ lldb-command:v arc //@ lldb-check:[...] strong=21, weak=2 { data = 222 } +//@ lldb-command:continue +//@ lldb-command:v rc_u64 +//@ lldb-check:[...] rc_u64 = strong=1, weak=1 { value = 10 } +//@ lldb-command:v rc_usize +//@ lldb-check:[...] rc_usize = strong=1, weak=1 { value = 20 } // === CDB TESTS ================================================================================== @@ -132,6 +137,11 @@ fn main() { let slice_arc_weak = Arc::downgrade(&slice_arc); zzz(); // #break + + let rc_u64 = Rc::new(10u64); + let rc_usize = Rc::new(20usize); + + zzz(); // #break } fn inc_ref_count(rc: &T, count: usize) { From 1f4a76145751fc60ce240241b504d485d9bb5b95 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sun, 18 Oct 2026 18:34:57 -0500 Subject: [PATCH 35/45] std: Make the PPC portion of `std_detect` easier to follow Use the constants from --- .../src/detect/os/freebsd/powerpc.rs | 14 ++++++++--- .../std_detect/src/detect/os/linux/powerpc.rs | 24 +++++++++++++------ .../src/detect/os/openbsd/powerpc.rs | 17 ++++++++++--- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/library/std_detect/src/detect/os/freebsd/powerpc.rs b/library/std_detect/src/detect/os/freebsd/powerpc.rs index d03af68cd0815..e41c37f1a5f73 100644 --- a/library/std_detect/src/detect/os/freebsd/powerpc.rs +++ b/library/std_detect/src/detect/os/freebsd/powerpc.rs @@ -1,8 +1,16 @@ //! Run-time feature detection for PowerPC on FreeBSD. +//! +//! Constants for both 32- and 64-bit come from +//! . use super::auxvec; use crate::detect::{Feature, cache}; +const PPC_FEATURE_HAS_ALTIVEC: usize = 0x10000000; +const PPC_FEATURE_HAS_VSX: usize = 0x00000080; +/// ISA 2.07, typically power8 +const PPC_FEATURE2_ARCH_2_07: usize = 0x80000000; + pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); let enable_feature = |value: &mut cache::Initializer, f, enable| { @@ -12,9 +20,9 @@ pub(crate) fn detect_features() -> cache::Initializer { }; if let Ok(auxv) = auxvec::auxv() { - enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); - enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); - enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + enable_feature(&mut value, Feature::altivec, auxv.hwcap & PPC_FEATURE_HAS_ALTIVEC != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & PPC_FEATURE_HAS_VSX != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & PPC_FEATURE2_ARCH_2_07 != 0); return value; } value diff --git a/library/std_detect/src/detect/os/linux/powerpc.rs b/library/std_detect/src/detect/os/linux/powerpc.rs index 6a4f7e715d932..73d5f3d1f7347 100644 --- a/library/std_detect/src/detect/os/linux/powerpc.rs +++ b/library/std_detect/src/detect/os/linux/powerpc.rs @@ -1,8 +1,18 @@ //! Run-time feature detection for PowerPC on Linux. +//! +//! Constants for both 32- and 64-bit come from +//! . use super::auxvec; use crate::detect::{Feature, cache}; +const PPC_FEATURE_HAS_ALTIVEC: usize = 0x10000000; +const PPC_FEATURE_HAS_VSX: usize = 0x00000080; +/// ISA 2.07, typically power8 +const PPC_FEATURE2_ARCH_2_07: usize = 0x80000000; +/// ISA 3.00, typically power9 +const PPC_FEATURE2_ARCH_3_00: usize = 0x00800000; + /// Try to read the features from the auxiliary vector. pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); @@ -12,20 +22,20 @@ pub(crate) fn detect_features() -> cache::Initializer { } }; - // The values are part of the platform-specific [asm/cputable.h][cputable] - // - // [cputable]: https://github.com/torvalds/linux/blob/master/arch/powerpc/include/uapi/asm/cputable.h if let Ok(auxv) = auxvec::auxv() { // note: the PowerPC values are the mask to do the test (instead of the // index of the bit to test like in ARM and Aarch64) - enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); - enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); - let power8_features = auxv.hwcap2 & 0x80000000 != 0; + enable_feature(&mut value, Feature::altivec, auxv.hwcap & PPC_FEATURE_HAS_ALTIVEC != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & PPC_FEATURE_HAS_VSX != 0); + // FIXME(powerpc_target_feature): this code assumes the ISA hwcaps imply vsx/altivec, but + // this is not a correct assumption. See rust-lang/compiler-team#1046 for + // a possible resolution. + let power8_features = auxv.hwcap2 & PPC_FEATURE2_ARCH_2_07 != 0; enable_feature(&mut value, Feature::power8, power8_features); enable_feature(&mut value, Feature::power8_altivec, power8_features); enable_feature(&mut value, Feature::power8_crypto, power8_features); enable_feature(&mut value, Feature::power8_vector, power8_features); - let power9_features = auxv.hwcap2 & 0x00800000 != 0; + let power9_features = auxv.hwcap2 & PPC_FEATURE2_ARCH_3_00 != 0; enable_feature(&mut value, Feature::power9, power9_features); enable_feature(&mut value, Feature::power9_altivec, power9_features); enable_feature(&mut value, Feature::power9_vector, power9_features); diff --git a/library/std_detect/src/detect/os/openbsd/powerpc.rs b/library/std_detect/src/detect/os/openbsd/powerpc.rs index dd98ab2a3f76e..7a684cd273c64 100644 --- a/library/std_detect/src/detect/os/openbsd/powerpc.rs +++ b/library/std_detect/src/detect/os/openbsd/powerpc.rs @@ -1,8 +1,19 @@ //! Run-time feature detection for PowerPC on OpenBSD. +//! +//! Constants for 32-bit are located at +//! , and for 64-bit +//! . use super::auxvec; use crate::detect::{Feature, cache}; +/* Note that most constants are the same for 32- and 64-bit, but not all are */ + +const PPC_FEATURE_HAS_ALTIVEC: usize = 0x10000000; +const PPC_FEATURE_HAS_VSX: usize = 0x00000080; +/// ISA 2.07, typically power8 +const PPC_FEATURE2_ARCH_2_07: usize = 0x80000000; + pub(crate) fn detect_features() -> cache::Initializer { let mut value = cache::Initializer::default(); let enable_feature = |value: &mut cache::Initializer, f, enable| { @@ -12,9 +23,9 @@ pub(crate) fn detect_features() -> cache::Initializer { }; if let Ok(auxv) = auxvec::auxv() { - enable_feature(&mut value, Feature::altivec, auxv.hwcap & 0x10000000 != 0); - enable_feature(&mut value, Feature::vsx, auxv.hwcap & 0x00000080 != 0); - enable_feature(&mut value, Feature::power8, auxv.hwcap2 & 0x80000000 != 0); + enable_feature(&mut value, Feature::altivec, auxv.hwcap & PPC_FEATURE_HAS_ALTIVEC != 0); + enable_feature(&mut value, Feature::vsx, auxv.hwcap & PPC_FEATURE_HAS_VSX != 0); + enable_feature(&mut value, Feature::power8, auxv.hwcap2 & PPC_FEATURE2_ARCH_2_07 != 0); return value; } value From 17c92451e20bd8f2f29a47037ce822fc70a80f5f Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 19 Oct 2026 21:28:42 -0500 Subject: [PATCH 36/45] std: Combine `is_powerpc64_feature_detected` into `is_powerpc_feature_detected` The available target feature set is the same for these architectures, so combine to a single macro. This matches what is done for x86, loongarch, and RISC-V. --- library/std/src/lib.rs | 4 +-- library/std/tests/run-time-detect.rs | 21 +++++------- library/std_detect/src/detect/arch/mod.rs | 8 +---- library/std_detect/src/detect/arch/powerpc.rs | 2 +- .../std_detect/src/detect/arch/powerpc64.rs | 33 ------------------- library/std_detect/src/lib.rs | 1 - library/std_detect/tests/cpu-detection.rs | 20 ++++------- .../std_detect/tests/macro_trailing_commas.rs | 9 +---- .../stdarch/crates/simd-test-macro/src/lib.rs | 3 +- 9 files changed, 20 insertions(+), 81 deletions(-) delete mode 100644 library/std_detect/src/detect/arch/powerpc64.rs diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 0d0d9e0759d82..a6eb67611fe1f 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -711,6 +711,8 @@ pub mod arch { pub use std_detect::is_arm_feature_detected; #[stable(feature = "stdarch_loongarch_feature", since = "1.89.0")] pub use std_detect::is_loongarch_feature_detected; + #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] + pub use std_detect::is_powerpc_feature_detected; #[stable(feature = "riscv_ratified", since = "1.78.0")] pub use std_detect::is_riscv_feature_detected; #[stable(feature = "stdarch_s390x_feature_detection", since = "1.93.0")] @@ -719,8 +721,6 @@ pub mod arch { pub use std_detect::is_x86_feature_detected; #[unstable(feature = "stdarch_mips_feature_detection", issue = "111188")] pub use std_detect::{is_mips_feature_detected, is_mips64_feature_detected}; - #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] - pub use std_detect::{is_powerpc_feature_detected, is_powerpc64_feature_detected}; } // This was stabilized in the crate root so we have to keep it there. diff --git a/library/std/tests/run-time-detect.rs b/library/std/tests/run-time-detect.rs index 5506fd469732d..d202a2e1bb800 100644 --- a/library/std/tests/run-time-detect.rs +++ b/library/std/tests/run-time-detect.rs @@ -9,7 +9,10 @@ feature(stdarch_aarch64_feature_detection) )] #![cfg_attr( - all(target_arch = "powerpc", target_os = "linux"), + all( + any(target_arch = "powerpc", target_arch = "powerpc64"), + any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"), + ), feature(stdarch_powerpc_feature_detection) )] #![cfg_attr( @@ -111,7 +114,10 @@ fn aarch64_linux() { } #[test] -#[cfg(all(target_arch = "powerpc", target_os = "linux"))] +#[cfg(all( + any(target_arch = "powerpc", target_arch = "powerpc64"), + any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"), +))] fn powerpc_linux() { use std::arch::is_powerpc_feature_detected; // tidy-alphabetical-start @@ -121,17 +127,6 @@ fn powerpc_linux() { // tidy-alphabetical-end } -#[test] -#[cfg(all(target_arch = "powerpc64", target_os = "linux"))] -fn powerpc64_linux() { - use std::arch::is_powerpc64_feature_detected; - // tidy-alphabetical-start - println!("altivec: {}", is_powerpc64_feature_detected!("altivec")); - println!("power8: {}", is_powerpc64_feature_detected!("power8")); - println!("vsx: {}", is_powerpc64_feature_detected!("vsx")); - // tidy-alphabetical-end -} - #[test] #[cfg(all(target_arch = "s390x", target_os = "linux"))] fn s390x_linux() { diff --git a/library/std_detect/src/detect/arch/mod.rs b/library/std_detect/src/detect/arch/mod.rs index 2ca4cbd1e3fa0..d3ef2b65c87aa 100644 --- a/library/std_detect/src/detect/arch/mod.rs +++ b/library/std_detect/src/detect/arch/mod.rs @@ -12,8 +12,6 @@ mod riscv; #[macro_use] mod powerpc; #[macro_use] -mod powerpc64; -#[macro_use] mod mips; #[macro_use] mod mips64; @@ -39,14 +37,10 @@ cfg_select! { #[unstable(feature = "stdarch_riscv_feature_detection", issue = "111192")] pub use riscv::*; } - target_arch = "powerpc" => { + any(target_arch = "powerpc", target_arch = "powerpc64") => { #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] pub use powerpc::*; } - target_arch = "powerpc64" => { - #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] - pub use powerpc64::*; - } target_arch = "mips" => { #[unstable(feature = "stdarch_mips_feature_detection", issue = "111188")] pub use mips::*; diff --git a/library/std_detect/src/detect/arch/powerpc.rs b/library/std_detect/src/detect/arch/powerpc.rs index be2db0b81c2fc..02167e5935bdf 100644 --- a/library/std_detect/src/detect/arch/powerpc.rs +++ b/library/std_detect/src/detect/arch/powerpc.rs @@ -2,7 +2,7 @@ features! { @TARGET: powerpc; - @CFG: target_arch = "powerpc"; + @CFG: any(target_arch = "powerpc", target_arch = "powerpc64"); @MACRO_NAME: is_powerpc_feature_detected; @MACRO_ATTRS: /// Check for the presence of a CPU feature at runtime. diff --git a/library/std_detect/src/detect/arch/powerpc64.rs b/library/std_detect/src/detect/arch/powerpc64.rs deleted file mode 100644 index 98e8d5f32b75c..0000000000000 --- a/library/std_detect/src/detect/arch/powerpc64.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Run-time feature detection on PowerPC64. - -features! { - @TARGET: powerpc64; - @CFG: target_arch = "powerpc64"; - @MACRO_NAME: is_powerpc64_feature_detected; - @MACRO_ATTRS: - /// Check for the presence of a CPU feature at runtime. - /// - /// When the feature is known to be enabled at compile time (e.g. via `-Ctarget-feature`) - /// the macro expands to `true`. - #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] altivec: "altivec"; - /// Altivec - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] vsx: "vsx"; - /// VSX - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power8: "power8"; - without cfg check: true; - /// Power8 - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power8_altivec: "power8-altivec"; - /// Power8 altivec - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power8_vector: "power8-vector"; - /// Power8 vector - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power8_crypto: "power8-crypto"; - /// Power8 crypto - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power9: "power9"; - without cfg check: true; - /// Power9 - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power9_altivec: "power9-altivec"; - /// Power9 altivec - @FEATURE: #[unstable(feature = "stdarch_powerpc_feature_detection", issue = "111191")] power9_vector: "power9-vector"; - /// Power9 vector -} diff --git a/library/std_detect/src/lib.rs b/library/std_detect/src/lib.rs index f9d79df670a0f..d5805875ad9d6 100644 --- a/library/std_detect/src/lib.rs +++ b/library/std_detect/src/lib.rs @@ -10,7 +10,6 @@ //! * `mips`: [`is_mips_feature_detected`] //! * `mips64`: [`is_mips64_feature_detected`] //! * `powerpc`: [`is_powerpc_feature_detected`] -//! * `powerpc64`: [`is_powerpc64_feature_detected`] //! * `loongarch`: [`is_loongarch_feature_detected`] //! * `s390x`: [`is_s390x_feature_detected`] diff --git a/library/std_detect/tests/cpu-detection.rs b/library/std_detect/tests/cpu-detection.rs index a381d84cfbbc3..9e714679284b0 100644 --- a/library/std_detect/tests/cpu-detection.rs +++ b/library/std_detect/tests/cpu-detection.rs @@ -317,24 +317,16 @@ fn riscv_linux() { println!("p: {}", is_riscv_feature_detected!("p")); } -#[test] -#[cfg(all(target_arch = "powerpc", target_os = "linux"))] -fn powerpc_linux() { - println!("altivec: {}", is_powerpc_feature_detected!("altivec")); - println!("vsx: {}", is_powerpc_feature_detected!("vsx")); - println!("power8: {}", is_powerpc_feature_detected!("power8")); -} - #[test] #[cfg(all( - target_arch = "powerpc64", + any(target_arch = "powerpc", target_arch = "powerpc64"), any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"), ))] -fn powerpc64_linux_or_bsd() { - println!("altivec: {}", is_powerpc64_feature_detected!("altivec")); - println!("vsx: {}", is_powerpc64_feature_detected!("vsx")); - println!("power8: {}", is_powerpc64_feature_detected!("power8")); - println!("power9: {}", is_powerpc64_feature_detected!("power9")); +fn powerpc_linux_or_bsd() { + println!("altivec: {}", is_powerpc_feature_detected!("altivec")); + println!("vsx: {}", is_powerpc_feature_detected!("vsx")); + println!("power8: {}", is_powerpc_feature_detected!("power8")); + println!("power9: {}", is_powerpc_feature_detected!("power9")); } #[test] diff --git a/library/std_detect/tests/macro_trailing_commas.rs b/library/std_detect/tests/macro_trailing_commas.rs index a60b34acb872f..f1d2973b961e8 100644 --- a/library/std_detect/tests/macro_trailing_commas.rs +++ b/library/std_detect/tests/macro_trailing_commas.rs @@ -75,19 +75,12 @@ fn loongarch() { } #[test] -#[cfg(target_arch = "powerpc")] +#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] fn powerpc() { let _ = is_powerpc_feature_detected!("altivec"); let _ = is_powerpc_feature_detected!("altivec",); } -#[test] -#[cfg(target_arch = "powerpc64")] -fn powerpc64() { - let _ = is_powerpc64_feature_detected!("altivec"); - let _ = is_powerpc64_feature_detected!("altivec",); -} - #[test] #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] fn riscv() { diff --git a/library/stdarch/crates/simd-test-macro/src/lib.rs b/library/stdarch/crates/simd-test-macro/src/lib.rs index 9219540a1065f..51dd8aaf46fe7 100644 --- a/library/stdarch/crates/simd-test-macro/src/lib.rs +++ b/library/stdarch/crates/simd-test-macro/src/lib.rs @@ -67,8 +67,7 @@ pub fn simd_test( "arm" | "armv7" | "thumbv7neon" => "is_arm_feature_detected", "aarch64" | "arm64ec" | "aarch64_be" => "is_aarch64_feature_detected", maybe_riscv if maybe_riscv.starts_with("riscv") => "is_riscv_feature_detected", - "powerpc" | "powerpcle" => "is_powerpc_feature_detected", - "powerpc64" | "powerpc64le" => "is_powerpc64_feature_detected", + "powerpc" | "powerpcle" | "powerpc64" | "powerpc64le" => "is_powerpc_feature_detected", "loongarch32" | "loongarch64" => "is_loongarch_feature_detected", "s390x" => "is_s390x_feature_detected", "mips64" | "mips64el" => "is_mips64_feature_detected", From 85108f2c357abf45ed2121b0ae3299efc1cf67c7 Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Tue, 20 Oct 2026 11:28:53 +0000 Subject: [PATCH 37/45] Remap syntax contexts and expansion ids for det. metadata encoding --- compiler/rustc_hir_id/src/definitions.rs | 4 + compiler/rustc_metadata/src/rmeta/encoder.rs | 48 ++- .../rustc_middle/src/query/on_disk_cache.rs | 8 +- compiler/rustc_span/src/hygiene.rs | 282 +++++++++++++----- 4 files changed, 257 insertions(+), 85 deletions(-) diff --git a/compiler/rustc_hir_id/src/definitions.rs b/compiler/rustc_hir_id/src/definitions.rs index 557b87bedff85..52b64a2c7fcfc 100644 --- a/compiler/rustc_hir_id/src/definitions.rs +++ b/compiler/rustc_hir_id/src/definitions.rs @@ -18,6 +18,7 @@ pub use rustc_span::def_id::DefPathHash; use rustc_span::def_id::{ CRATE_DEF_INDEX, CrateNum, DefIndex, LOCAL_CRATE, LocalDefId, LocalDefIdMap, StableCrateId, }; +use rustc_span::hygiene::{HygieneRemapper, LocalExpnIdRemapper, SyntaxContextRemapper}; use rustc_span::{SpanEncoder, Symbol, kw, sym}; use tracing::{debug, instrument}; @@ -313,6 +314,9 @@ impl Definitions { let def_index = DefIndex::from_usize(self.def_id_to_key.len()); self.first_non_det_index.replace(def_index); + + SyntaxContextRemapper::with(|mut r| r.commit_end_of_determinism()); + LocalExpnIdRemapper::with(|mut r| r.commit_end_of_determinism()); } pub fn first_non_det_index(&self) -> DefIndex { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 674eff4a51f09..eaa60a9ec2842 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -36,10 +36,13 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; use rustc_session::config::mitigation_coverage::DeniedPartialMitigation; use rustc_session::config::{OptLevel, OutputType, TargetModifier}; use rustc_span::def_id::CRATE_MOD_ID; -use rustc_span::hygiene::HygieneEncodeContext; +use rustc_span::hygiene::{ + HygieneEncodeContext, HygieneRemapper, HygieneRemappingResults, LocalExpnIdRemapper, + SyntaxContextRemapper, +}; use rustc_span::{ - ByteSymbol, ExternalSource, FileName, SourceFile, SpanData, SpanEncoder, StableSourceFileId, - Symbol, SyntaxContext, bug, span_bug, sym, + ByteSymbol, ExternalSource, FileName, LocalExpnId, SourceFile, SpanData, SpanEncoder, + StableSourceFileId, Symbol, SyntaxContext, bug, span_bug, sym, }; use rustc_structures::CrateType; use tracing::{debug, instrument, trace}; @@ -80,6 +83,8 @@ pub(super) struct EncodeContext<'a, 'tcx> { // Used for both `Symbol`s and `ByteSymbol`s. symbol_index_table: FxHashMap, local_defs_remapping: LocalDefsRemapping, + syntax_context_remapping: HygieneRemappingResults, + local_expn_id_remapping: HygieneRemappingResults, } /// If the current crate is a proc-macro, returns early with `LazyArray::default()`. @@ -173,8 +178,8 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { } fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - let idx = self.hygiene_ctxt.borrow_mut().get_syntax_ctxt_encoding_index(syntax_context); - idx.encode(self); + self.hygiene_ctxt.borrow_mut().enqueue_syntax_context_for_encoding(syntax_context); + self.emit_u32(self.syntax_context_remapping.map_id(syntax_context).as_u32()); } fn encode_expn_id(&mut self, expn_id: ExpnId) { @@ -186,7 +191,13 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { self.hygiene_ctxt.borrow_mut().schedule_expn_data_for_encoding(expn_id); } expn_id.krate.encode(self); - expn_id.local_id.encode(self); + + let expn_index = match expn_id.as_local() { + Some(local_expn) => self.local_expn_id_remapping.map_id(local_expn).as_raw(), + None => expn_id.local_id, + }; + + expn_index.encode(self); } fn encode_span(&mut self, span: Span) { @@ -666,6 +677,14 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { // while `with_encode_metadata_header` is used in some other places too. self.local_defs_remapping = LocalDefsRemapping::new(tcx); + (self.syntax_context_remapping, self.local_expn_id_remapping) = tcx + .with_stable_hashing_context(|mut ctx| { + ( + SyntaxContextRemapper::with(|r| r.remapping_results(&mut ctx)), + LocalExpnIdRemapper::with(|r| r.remapping_results(&mut ctx)), + ) + }); + let mut stats: Vec<(&'static str, usize)> = Vec::with_capacity(32); macro_rules! stat { @@ -2061,15 +2080,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { HygieneEncodeContext::encode( &Rc::clone(&self.hygiene_ctxt), &mut (&mut *self, &mut syntax_contexts, &mut expn_data_table, &mut expn_hash_table), - |(this, syntax_contexts, _, _), index, ctxt_data| { - syntax_contexts.set_some(index, this.lazy(ctxt_data)); + |(this, syntax_contexts, _, _), ctxt, ctxt_data| { + let context = this.syntax_context_remapping.map_id(ctxt); + syntax_contexts.set_some(context.as_u32(), this.lazy(ctxt_data)); }, - |(this, _, expn_data_table, expn_hash_table), index, expn_data, hash| { - if let Some(index) = index.as_local() { - expn_data_table - .set_some(index.as_raw(), this.lazy(expn_data.expect("local expn"))); + |(this, _, expn_data_table, expn_hash_table), expn_id, expn_data, hash| { + if let Some(expn_id) = expn_id.as_local() { + let expn_index = this.local_expn_id_remapping.map_id(expn_id).as_raw(); - expn_hash_table.set_some(index.as_raw(), this.lazy(hash)); + expn_data_table.set_some(expn_index, this.lazy(expn_data.expect("local expn"))); + expn_hash_table.set_some(expn_index, this.lazy(hash)); } }, ); @@ -2756,6 +2776,8 @@ fn with_encode_metadata_header( hygiene_ctxt: Default::default(), symbol_index_table: Default::default(), local_defs_remapping: Default::default(), + syntax_context_remapping: Default::default(), + local_expn_id_remapping: Default::default(), }; // Encode the rustc version string in a predictable location. diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 7383ad23273be..7658112358d7c 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -283,10 +283,10 @@ impl OnDiskCache { HygieneEncodeContext::encode( &Rc::clone(&encoder.hygiene_context), &mut encoder, - |encoder, index, ctxt_data| { + |encoder, ctxt, ctxt_data| { let pos = AbsoluteBytePos::new(encoder.position()); encoder.encode_tagged(TAG_SYNTAX_CONTEXT, ctxt_data); - syntax_contexts.insert(index, pos); + syntax_contexts.insert(ctxt.as_u32(), pos); }, |encoder, expn_id, data, hash| { if expn_id.krate == LOCAL_CRATE { @@ -838,8 +838,8 @@ impl<'tcx> CacheEncoder<'tcx> { impl<'tcx> SpanEncoder for CacheEncoder<'tcx> { fn encode_syntax_context(&mut self, syntax_context: SyntaxContext) { - let idx = self.hygiene_context.borrow_mut().get_syntax_ctxt_encoding_index(syntax_context); - idx.encode(self); + self.hygiene_context.borrow_mut().enqueue_syntax_context_for_encoding(syntax_context); + self.emit_u32(syntax_context.as_u32()); } fn encode_expn_id(&mut self, expn_id: ExpnId) { diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 6489b3cc6d54b..ef259dc572693 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -25,7 +25,7 @@ // trigger runtime aborts. (Fortunately these are obvious and easy to fix.) use std::cell::RefCell; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::{fmt, iter, mem}; @@ -51,9 +51,23 @@ use crate::{DUMMY_SP, Span, SpanDecoder, SpanEncoder, with_session_globals}; /// A `SyntaxContext` represents a chain of pairs `(ExpnId, Transparency)` named "marks". /// /// See for more explanation. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)] pub struct SyntaxContext(u32); +impl From for SyntaxContext { + #[inline(always)] + fn from(value: u32) -> SyntaxContext { + SyntaxContext(value) + } +} + +impl Into for SyntaxContext { + #[inline(always)] + fn into(self) -> u32 { + self.0 + } +} + // To ensure correctness of incremental compilation, // `SyntaxContext` must not implement `Ord` or `PartialOrd`. // See https://github.com/rust-lang/rust/issues/90317. @@ -123,6 +137,12 @@ rustc_index::newtype_index! { pub struct LocalExpnId {} } +impl Default for LocalExpnId { + fn default() -> LocalExpnId { + LocalExpnId::ROOT + } +} + // To ensure correctness of incremental compilation, // `LocalExpnId` must not implement `Ord` or `PartialOrd`. // See https://github.com/rust-lang/rust/issues/90317. @@ -341,6 +361,141 @@ impl ExpnId { } } +#[derive(Default)] +pub struct HygieneRemappingResults { + pub remapping: FxHashMap, + pub first_non_det_index: T, +} + +impl + Copy> HygieneRemappingResults { + #[inline] + pub fn map_id(&self, idx: T) -> T { + if idx.into() < self.first_non_det_index.into() { + idx + } else { + self.remapping.get(&idx).copied().unwrap_or(idx) + } + } +} + +/// Includes common logic for remapping both expansion ids and syntax contexts. +/// FIXME: this solution does not take into account syntax contexts and expansion ids +/// that are allocated during metadata encoding, if we will have non-determinism there +/// this remapping will not help as it is constructed before metadata encoding. +pub trait HygieneRemapper { + fn stable_hash(&self, t: T, hcx: &mut impl StableHashCtxt, hasher: &mut StableHasher); + fn len(&self) -> usize; + fn first_non_det_index_mut(&mut self) -> &mut Option; + fn first_non_det_index(&self) -> u32; + + fn commit_end_of_determinism(&mut self) + where + T: From, + { + let len = self.len() as u32; + + let prev = self.first_non_det_index_mut().replace(T::from(len)); + assert!(prev.is_none(), "this function can be called only once") + } + + fn remapping_results(&self, hcx: &mut impl StableHashCtxt) -> HygieneRemappingResults + where + T: From + Hash + Eq + Copy, + { + let start = self.first_non_det_index(); + let range = start..self.len() as u32; + + let mut indices_to_remap = range.map(|raw_id| T::from(raw_id)).collect::>(); + indices_to_remap.sort_by_cached_key(|&key| { + let hasher = &mut StableHasher::new(); + self.stable_hash(key, hcx, hasher); + + hasher.finish() + }); + + let mut remapping = FxHashMap::::default(); + + for (idx, key) in indices_to_remap.into_iter().enumerate() { + let from = key; + let to = T::from(start + idx as u32); + + if from != to { + remapping.insert(from, to); + } + } + + HygieneRemappingResults { remapping, first_non_det_index: T::from(start) } + } +} + +pub struct SyntaxContextRemapper<'a>(&'a mut HygieneData); +impl SyntaxContextRemapper<'_> { + pub fn with(f: impl FnOnce(SyntaxContextRemapper<'_>) -> T) -> T { + HygieneData::with(|data| f(SyntaxContextRemapper(data))) + } +} + +pub struct LocalExpnIdRemapper<'a>(&'a mut HygieneData); +impl LocalExpnIdRemapper<'_> { + pub fn with(f: impl FnOnce(LocalExpnIdRemapper<'_>) -> T) -> T { + HygieneData::with(|data| f(LocalExpnIdRemapper(data))) + } +} + +impl HygieneRemapper for SyntaxContextRemapper<'_> { + #[inline] + fn len(&self) -> usize { + self.0.syntax_context_data.len() + } + + #[inline] + fn first_non_det_index_mut(&mut self) -> &mut Option { + &mut self.0.first_non_det_syntax_context + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_non_det_syntax_context.map(|s| s.as_u32()).expect("must be set at this point") + } + + #[inline] + fn stable_hash( + &self, + ctxt: SyntaxContext, + hcx: &mut impl StableHashCtxt, + hasher: &mut StableHasher, + ) { + ctxt.stable_hash_internal(Some(self.0), hcx, hasher); + } +} + +impl HygieneRemapper for LocalExpnIdRemapper<'_> { + #[inline] + fn len(&self) -> usize { + self.0.local_expn_data.len() + } + + #[inline] + fn first_non_det_index_mut(&mut self) -> &mut Option { + &mut self.0.first_non_det_local_expn_id + } + + #[inline] + fn first_non_det_index(&self) -> u32 { + self.0.first_non_det_local_expn_id.map(|l| l.as_u32()).expect("must be set at this point") + } + + #[inline] + fn stable_hash( + &self, + local_expn_id: LocalExpnId, + hcx: &mut impl StableHashCtxt, + hasher: &mut StableHasher, + ) { + local_expn_id.to_expn_id().stable_hash_internal(Some(self.0), hcx, hasher); + } +} + #[derive(Debug)] pub(crate) struct HygieneData { /// Each expansion should have an associated expansion data, but sometimes there's a delay @@ -354,6 +509,8 @@ pub(crate) struct HygieneData { foreign_expn_hashes: FxHashMap, expn_hash_to_expn_id: UnhashMap, syntax_context_data: Vec, + first_non_det_syntax_context: Option, + first_non_det_local_expn_id: Option, syntax_context_map: FxHashMap, /// Maps the `local_hash` of an `ExpnData` to the next disambiguator value. /// This is used by `update_disambiguator` to keep track of which `ExpnData`s @@ -386,6 +543,8 @@ impl HygieneData { syntax_context_data: vec![root_ctxt_data], syntax_context_map: iter::once((root_ctxt_data.key(), SyntaxContext(0))).collect(), expn_data_disambiguators: UnhashMap::default(), + first_non_det_syntax_context: None, + first_non_det_local_expn_id: None, } } @@ -708,7 +867,7 @@ impl SyntaxContext { } #[inline] - pub(crate) const fn as_u32(self) -> u32 { + pub const fn as_u32(self) -> u32 { self.0 } @@ -1284,43 +1443,10 @@ impl DesugaringKind { pub struct HygieneEncodeContext { /// All `SyntaxContexts` for which we have written `SyntaxContextData` into crate metadata. serialized_ctxts: FxHashSet, - /// The `SyntaxContexts` that we have serialized (e.g. as a result of encoding `Spans`) - /// in the most recent 'round' of serializing. Serializing `SyntaxContextData` - /// may cause us to serialize more `SyntaxContext`s, so serialize in a loop - /// until we reach a fixed point. - latest_ctxts: Vec<(u32 /* Encoding index */, SyntaxContext)>, + latest_ctxts: Vec, serialized_expns: FxHashSet, latest_expns: Vec, - - /// Maps every `SyntaxContext` into its encoding index. - /// Earlier the `ctxt.0` was used when writing metadata, however, - /// this results into non-deterministic metadata (see #129094). - /// The non-determinism is encountered when decoding syntax contexts - /// in `decode_syntax_context` function below. The syntax contexts from - /// other crate metadata can be decoded in different order, which results - /// into different ids assigned to decoded syntax contexts. - /// First invocation: - /// (ALLOC - syntax context id, ORIG - original id of decoded syntax context: - /// `raw_id` in `decode_syntax_context`) - /// ALLOC: #3, ORIG: 1 - /// ALLOC: #9, ORIG: 18769 - /// ALLOC: #10, ORIG: 25868 - /// ALLOC: #11, ORIG: 18822 - /// ALLOC: #12, ORIG: 23092 - /// - /// Second invocation: - /// ALLOC: #3, ORIG: 1 - /// ALLOC: #9, ORIG: 25868 - /// ALLOC: #10, ORIG: 18769 - /// ALLOC: #11, ORIG: 18822 - /// ALLOC: #12, ORIG: 23092 - /// - /// We see that `18769` and `25868` assigned different syntax context ids, - /// however, the order of encoding is deterministic, so we can remap allocated - /// syntax context ids into encoding indices and use them, thus outputting - /// same metadata. - encoding_indices: FxHashMap, } impl Default for HygieneEncodeContext { @@ -1330,20 +1456,11 @@ impl Default for HygieneEncodeContext { latest_ctxts: Default::default(), serialized_expns: Default::default(), latest_expns: Default::default(), - // Zero is taken by root syntax context. - encoding_indices: FxHashMap::from_iter(iter::once((SyntaxContext::root(), 0))), } } } impl HygieneEncodeContext { - #[inline] - fn get_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { - let map = &mut self.encoding_indices; - let len = map.len(); - *map.entry(ctxt).or_insert(len as u32) - } - /// Record the fact that we need to serialize the corresponding `ExpnData`. #[inline] pub fn schedule_expn_data_for_encoding(&mut self, expn: ExpnId) { @@ -1355,7 +1472,7 @@ impl HygieneEncodeContext { pub fn encode( h_ctxt: &RefCell, encoder: &mut T, - mut encode_ctxt: impl FnMut(&mut T, u32, &SyntaxContextKey), + mut encode_ctxt: impl FnMut(&mut T, SyntaxContext, &SyntaxContextKey), mut encode_expn: impl FnMut(&mut T, ExpnId, Option<&ExpnData>, ExpnHash), ) { // When we serialize a `SyntaxContextData`, we may end up serializing @@ -1376,9 +1493,9 @@ impl HygieneEncodeContext { #[allow(rustc::potential_query_instability)] let latest_contexts = { mem::take(&mut h_ctxt.borrow_mut().latest_ctxts) }.into_iter(); - for (idx, ctxt) in latest_contexts { + for ctxt in latest_contexts { let key = HygieneData::with(|data| data.syntax_context_data[ctxt.0 as usize].key()); - encode_ctxt(encoder, idx, &key); + encode_ctxt(encoder, ctxt, &key); } // Same as above, but for expansions instead of syntax contexts. @@ -1402,18 +1519,10 @@ impl HygieneEncodeContext { } #[inline] - pub fn get_syntax_ctxt_encoding_index(&mut self, ctxt: SyntaxContext) -> u32 { - let index = self.get_encoding_index(ctxt); + pub fn enqueue_syntax_context_for_encoding(&mut self, ctxt: SyntaxContext) { if self.serialized_ctxts.insert(ctxt) { - // If we created new encoding index then it is greater - // than any previous index, so this vector is in ascending order. - // We can't push existing, possibly out-of-order, index - // as we check if we already saw this syntax context above. - // This property is important for deterministic output (see #129094). - self.latest_ctxts.push((index, ctxt)); + self.latest_ctxts.push(ctxt); } - - index } } @@ -1579,8 +1688,13 @@ fn update_disambiguator(expn_data: &mut ExpnData, mut hcx: impl StableHashCtxt) ExpnHash::new(LOCAL_CRATE.as_def_id().to_stable_hash_key(&mut hcx).stable_crate_id(), expn_hash) } -impl StableHash for SyntaxContext { - fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { +impl SyntaxContext { + fn stable_hash_internal( + self, + data: Option<&HygieneData>, + hcx: &mut impl StableHashCtxt, + hasher: &mut StableHasher, + ) { const TAG_EXPANSION: u8 = 0; const TAG_NO_EXPANSION: u8 = 1; @@ -1591,32 +1705,64 @@ impl StableHash for SyntaxContext { // This duplicates `ExpnId::stable_hash`, but reads the outer mark and // expansion hash in a single `HygieneData::with` call to avoid extra locking. hcx.assert_default_stable_hash_controls("ExpnId"); - let (hash, transparency) = HygieneData::with(|data| { - let (expn_id, transparency) = data.outer_mark(*self); + + let get_hashing_data = |data: &HygieneData| { + let (expn_id, transparency) = data.outer_mark(self); (data.expn_hash(expn_id).0, transparency) - }); + }; + + let (hash, transparency) = match data { + Some(data) => get_hashing_data(data), + None => HygieneData::with(|data| get_hashing_data(data)), + }; + hash.stable_hash(hcx, hasher); transparency.stable_hash(hcx, hasher); } } } -impl StableHash for ExpnId { +impl StableHash for SyntaxContext { + #[inline] fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.stable_hash_internal(None, hcx, hasher); + } +} + +impl ExpnId { + fn stable_hash_internal( + self, + data: Option<&HygieneData>, + hcx: &mut impl StableHashCtxt, + hasher: &mut StableHasher, + ) { hcx.assert_default_stable_hash_controls("ExpnId"); - let hash = if *self == ExpnId::root() { + let hash = if self == ExpnId::root() { // Avoid fetching TLS storage for a trivial often-used value. Fingerprint::ZERO } else { - self.expn_hash().0 + let get_hashing_data = |data: &HygieneData| data.expn_hash(self).0; + + match data { + Some(data) => get_hashing_data(data), + None => HygieneData::with(|data| get_hashing_data(data)), + } }; hash.stable_hash(hcx, hasher); } } +impl StableHash for ExpnId { + #[inline] + fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { + self.stable_hash_internal(None, hcx, hasher) + } +} + impl StableHash for LocalExpnId { + #[inline] fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { - self.to_expn_id().stable_hash(hcx, hasher); + self.to_expn_id().stable_hash_internal(None, hcx, hasher); } } From 8ec19d9326727edeadbf871a4582c0a7bd3b3d1a Mon Sep 17 00:00:00 2001 From: Tess4552 Date: Wed, 30 Sep 2026 14:20:27 +0200 Subject: [PATCH 38/45] Attributes in function trait parameters --- compiler/rustc_ast_lowering/src/path.rs | 19 ++++-- .../rustc_ast_passes/src/ast_validation.rs | 8 +-- compiler/rustc_parse/src/diagnostics.rs | 8 --- compiler/rustc_parse/src/parser/path.rs | 8 ++- .../fn-trait-use-named-params-issue-140169.rs | 10 ++- ...trait-use-named-params-issue-140169.stderr | 48 ++++++++++++-- tests/ui/fn/fn-trait-use-named-params.rs | 23 +++++++ tests/ui/fn/fn-trait-use-named-params.stderr | 62 +++++++++++++++++++ 8 files changed, 160 insertions(+), 26 deletions(-) create mode 100644 tests/ui/fn/fn-trait-use-named-params.rs create mode 100644 tests/ui/fn/fn-trait-use-named-params.stderr diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index 14f10e2b1c416..f410a9d280347 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -1,10 +1,11 @@ use std::sync::Arc; use rustc_ast::{self as ast, *}; +use rustc_attr_ir::target::Target; use rustc_errors::StashKey; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, GenericArg}; +use rustc_hir::{self as hir, GenericArg, HirId}; use rustc_middle::middle::resolve::PartialRes; use rustc_middle::ty; use rustc_session::diagnostics::add_feature_diagnostics; @@ -263,6 +264,10 @@ impl<'hir> LoweringContext<'_, 'hir> { bound_modifier_allowed_features: Option>, ) -> hir::PathSegment<'hir> { debug!("path_span: {:?}, lower_path_segment(segment: {:?})", path_span, segment); + + let res = self.expect_full_res(segment.id); + let hir_id = self.lower_node_id(segment.id); + let (mut generic_args, infer_args) = if let Some(generic_args) = segment.args.as_deref() { match generic_args { GenericArgs::AngleBracketed(data) => { @@ -315,6 +320,7 @@ impl<'hir> LoweringContext<'_, 'hir> { data, itctx, bound_modifier_allowed_features, + hir_id, ), GenericArgsMode::Err => { // Suggest replacing parentheses with angle brackets `Trait(params...)` to `Trait` @@ -398,13 +404,10 @@ impl<'hir> LoweringContext<'_, 'hir> { ); } - let res = self.expect_full_res(segment.id); - let hir_id = self.lower_node_id(segment.id); debug!( "lower_path_segment: ident={:?} original-id={:?} new-id={:?}", segment.ident, segment.id, hir_id, ); - hir::PathSegment { ident: self.lower_ident(segment.ident), hir_id, @@ -508,6 +511,7 @@ impl<'hir> LoweringContext<'_, 'hir> { data: &ParenthesizedArgs, itctx: ImplTraitContext, bound_modifier_allowed_features: Option>, + segment_hir_id: HirId, ) -> (GenericArgsCtor<'hir>, bool) { // Switch to `PassThrough` mode for anonymous lifetimes; this // means that we permit things like `&Ref`, where `Ref` has @@ -515,6 +519,13 @@ impl<'hir> LoweringContext<'_, 'hir> { // compatibility, even in contexts like an impl header where // we generally don't permit such things (see #51008). let ParenthesizedArgs { span, inputs, inputs_span, output } = data; + + // FIXME uses the segments HIR id which is not correct, it should + // be the parameters HIR id instead. This is currently not lowered. + for input in inputs { + self.lower_attrs(segment_hir_id, &input.attrs, input.span, Target::Param); + } + let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| { self.lower_ty(¶m.ty, ImplTraitContext::Disallowed(ImplTraitPosition::FnTraitParam)) })); diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index d33c42132ecea..bd869a72f91dd 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -399,7 +399,7 @@ impl<'a> AstValidator<'a> { self.check_decl_num_args(fn_decl); let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); self.check_decl_splatting(fn_decl, c_variadic_span, splat_semantic); - self.check_decl_attrs(fn_decl); + self.check_decl_attrs(&fn_decl.inputs); self.check_decl_self_param(&fn_decl.inputs, self_semantic); self.check_decl_const_param(&fn_decl.inputs); } @@ -518,10 +518,9 @@ impl<'a> AstValidator<'a> { } } - fn check_decl_attrs(&self, fn_decl: &FnDecl) { + fn check_decl_attrs(&self, fn_inputs: &[Param]) { use SyntheticAttr::*; - fn_decl - .inputs + fn_inputs .iter() .flat_map(|i| i.attrs.as_ref()) .filter(|attr| match &attr.kind { @@ -2230,6 +2229,7 @@ impl Visitor<'_> for AstValidator<'_> { fn visit_path_segment(&mut self, seg: &PathSegment) -> Self::Result { if let Some(Parenthesized(args)) = &seg.args { self.check_decl_self_param(&args.inputs, SelfSemantic::No); + self.check_decl_attrs(&args.inputs); Self::check_decl_no_pat(&args.inputs, |span, _, _| { self.dcx().emit_err(diagnostics::PatternParenthesizedArgList { span }); }); diff --git a/compiler/rustc_parse/src/diagnostics.rs b/compiler/rustc_parse/src/diagnostics.rs index a942d6b060dab..6331eb6d499e2 100644 --- a/compiler/rustc_parse/src/diagnostics.rs +++ b/compiler/rustc_parse/src/diagnostics.rs @@ -2077,14 +2077,6 @@ pub(crate) struct PathFoundCVariadicParams { pub span: Span, } -#[derive(Diagnostic)] -#[diag("`Trait(...)` syntax does not support attributes in parameters")] -pub(crate) struct PathFoundAttributeInParams { - #[primary_span] - #[suggestion("remove the attributes", applicability = "machine-applicable", code = "")] - pub span: Span, -} - #[derive(Diagnostic)] #[diag("path separator must be a double colon")] pub(crate) struct PathSingleColon { diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs index 8dfe95a849449..738b207ad76ce 100644 --- a/compiler/rustc_parse/src/parser/path.rs +++ b/compiler/rustc_parse/src/parser/path.rs @@ -15,8 +15,8 @@ use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign}; use super::{Parser, Restrictions, TokenType}; use crate::ast::{PatKind, TyKind}; use crate::diagnostics::{ - self, ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, PathFoundAttributeInParams, - PathFoundCVariadicParams, PathSingleColon, PathTripleColon, + self, ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, PathFoundCVariadicParams, + PathSingleColon, PathTripleColon, }; use crate::exp; use crate::parser::{CommaRecoveryMode, Expr, FnContext, FnParseMode, RecoverColon, RecoverComma}; @@ -433,7 +433,9 @@ impl<'a> Parser<'a> { dcx.emit_err(PathFoundCVariadicParams { span: param.pat.span }); } if !param.attrs.is_empty() { - dcx.emit_err(PathFoundAttributeInParams { span: param.attrs[0].span }); + self.psess + .gated_spans + .gate(sym::named_fn_trait_parameters, param.attrs[0].span); } Ok(param) }); diff --git a/tests/ui/fn/fn-trait-use-named-params-issue-140169.rs b/tests/ui/fn/fn-trait-use-named-params-issue-140169.rs index 036627ae3d913..f832cdb2d9b12 100644 --- a/tests/ui/fn/fn-trait-use-named-params-issue-140169.rs +++ b/tests/ui/fn/fn-trait-use-named-params-issue-140169.rs @@ -6,7 +6,13 @@ fn f4(_: impl Fn(aaaa: u8, vvvv: u8)) {} //~| ERROR named parameters in parenthesized generic argument lists are experimental fn f5(_: impl Fn(u8, ...)) {} //~^ ERROR `Trait(...)` syntax does not support c_variadic parameters -fn f6(_: impl Fn(u8, #[allow(unused_attributes)] u8)) {} -//~^ ERROR `Trait(...)` syntax does not support attributes in parameters +fn f6(f: impl Fn(#[cfg(false)] u8)) { f() } //~ ERROR named parameters in parenthesized generic argument lists are experimental +fn f7(_: impl Fn(#[inline] u8)) {} +//~^ ERROR named parameters in parenthesized generic argument lists are experimental +//~| ERROR the `inline` attribute cannot be used on function params +//~| ERROR: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters +fn f8(_: impl Fn(u8, #[allow(unused_attributes)] u8)) {} +//~^ ERROR named parameters in parenthesized generic argument lists are experimental + fn main(){} diff --git a/tests/ui/fn/fn-trait-use-named-params-issue-140169.stderr b/tests/ui/fn/fn-trait-use-named-params-issue-140169.stderr index ac7758e9d5373..99e3b87e2e37c 100644 --- a/tests/ui/fn/fn-trait-use-named-params-issue-140169.stderr +++ b/tests/ui/fn/fn-trait-use-named-params-issue-140169.stderr @@ -4,11 +4,11 @@ error: `Trait(...)` syntax does not support c_variadic parameters LL | fn f5(_: impl Fn(u8, ...)) {} | ^^^ help: remove the `...` -error: `Trait(...)` syntax does not support attributes in parameters - --> $DIR/fn-trait-use-named-params-issue-140169.rs:9:22 +error: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters + --> $DIR/fn-trait-use-named-params-issue-140169.rs:10:18 | -LL | fn f6(_: impl Fn(u8, #[allow(unused_attributes)] u8)) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the attributes +LL | fn f7(_: impl Fn(#[inline] u8)) {} + | ^^^^^^^^^ error[E0658]: named parameters in parenthesized generic argument lists are experimental --> $DIR/fn-trait-use-named-params-issue-140169.rs:2:22 @@ -50,6 +50,44 @@ LL | fn f4(_: impl Fn(aaaa: u8, vvvv: u8)) {} = help: add `#![feature(named_fn_trait_parameters)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 6 previous errors +error[E0658]: named parameters in parenthesized generic argument lists are experimental + --> $DIR/fn-trait-use-named-params-issue-140169.rs:9:18 + | +LL | fn f6(f: impl Fn(#[cfg(false)] u8)) { f() } + | ^^^^^^^^^^^^^ + | + = note: see issue #158499 for more information + = help: add `#![feature(named_fn_trait_parameters)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: named parameters in parenthesized generic argument lists are experimental + --> $DIR/fn-trait-use-named-params-issue-140169.rs:10:18 + | +LL | fn f7(_: impl Fn(#[inline] u8)) {} + | ^^^^^^^^^ + | + = note: see issue #158499 for more information + = help: add `#![feature(named_fn_trait_parameters)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: named parameters in parenthesized generic argument lists are experimental + --> $DIR/fn-trait-use-named-params-issue-140169.rs:14:22 + | +LL | fn f8(_: impl Fn(u8, #[allow(unused_attributes)] u8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #158499 for more information + = help: add `#![feature(named_fn_trait_parameters)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: the `inline` attribute cannot be used on function params + --> $DIR/fn-trait-use-named-params-issue-140169.rs:10:20 + | +LL | fn f7(_: impl Fn(#[inline] u8)) {} + | ^^^^^^ + | + = help: the `inline` attribute can only be applied to functions + +error: aborting due to 10 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/fn/fn-trait-use-named-params.rs b/tests/ui/fn/fn-trait-use-named-params.rs new file mode 100644 index 0000000000000..0f1e1a73cb1d2 --- /dev/null +++ b/tests/ui/fn/fn-trait-use-named-params.rs @@ -0,0 +1,23 @@ +#![feature(named_fn_trait_parameters)] + +fn f1(f: impl Fn(#[cfg(false)] x: u8)) { f() } +fn f2(_: impl Fn(#[inline] x: u8)) {} +//~^ ERROR the `inline` attribute cannot be used on function params +//~| ERROR: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters +fn f3(_: impl Fn(#[cfg_attr(true, inline)] a1: usize)) {} +//~^ ERROR the `inline` attribute cannot be used on function params +//~| ERROR: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters +fn f4(_: impl Fn(/** */ x: u8)) {} +//~^ ERROR documentation comments cannot be applied to function parameters + +fn f5(_: impl Fn(#[forbid(non_snake_case)] NON_SNAKE: u8)) {} +fn f6(_: impl Fn(#[forbid(keyword_idents_2024)] gen: u8)) {} +//~^ ERROR `gen` is a keyword in the 2024 edition [keyword_idents_2024] +//~| WARN this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! +fn f7(_: impl Fn(#[allow(keyword_idents_2024)] gen: u8)) {} +fn f8(_: fn(#[rustc_splat] a: usize)) {} +//~^ ERROR the `rustc_splat` attribute is an experimental feature + + + +fn main(){} diff --git a/tests/ui/fn/fn-trait-use-named-params.stderr b/tests/ui/fn/fn-trait-use-named-params.stderr new file mode 100644 index 0000000000000..6b22fa58c2ffc --- /dev/null +++ b/tests/ui/fn/fn-trait-use-named-params.stderr @@ -0,0 +1,62 @@ +error: `gen` is a keyword in the 2024 edition + --> $DIR/fn-trait-use-named-params.rs:14:49 + | +LL | fn f6(_: impl Fn(#[forbid(keyword_idents_2024)] gen: u8)) {} + | ^^^ help: you can use a raw identifier to stay compatible: `r#gen` + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! + = note: for more information, see +note: the lint level is defined here + --> $DIR/fn-trait-use-named-params.rs:14:27 + | +LL | fn f6(_: impl Fn(#[forbid(keyword_idents_2024)] gen: u8)) {} + | ^^^^^^^^^^^^^^^^^^^ + +error: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters + --> $DIR/fn-trait-use-named-params.rs:4:18 + | +LL | fn f2(_: impl Fn(#[inline] x: u8)) {} + | ^^^^^^^^^ + +error: allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters + --> $DIR/fn-trait-use-named-params.rs:7:35 + | +LL | fn f3(_: impl Fn(#[cfg_attr(true, inline)] a1: usize)) {} + | ^^^^^^ + +error: documentation comments cannot be applied to function parameters + --> $DIR/fn-trait-use-named-params.rs:10:18 + | +LL | fn f4(_: impl Fn(/** */ x: u8)) {} + | ^^^^^^ doc comments are not allowed here + +error: the `inline` attribute cannot be used on function params + --> $DIR/fn-trait-use-named-params.rs:4:20 + | +LL | fn f2(_: impl Fn(#[inline] x: u8)) {} + | ^^^^^^ + | + = help: the `inline` attribute can only be applied to functions + +error: the `inline` attribute cannot be used on function params + --> $DIR/fn-trait-use-named-params.rs:7:35 + | +LL | fn f3(_: impl Fn(#[cfg_attr(true, inline)] a1: usize)) {} + | ^^^^^^ + | + = help: the `inline` attribute can only be applied to functions + +error[E0658]: the `rustc_splat` attribute is an experimental feature + --> $DIR/fn-trait-use-named-params.rs:18:15 + | +LL | fn f8(_: fn(#[rustc_splat] a: usize)) {} + | ^^^^^^^^^^^ + | + = note: see issue #153629 for more information + = help: add `#![feature(splat)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + = note: the `rustc_splat` attribute is experimental + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0658`. From 1d1969035cdc0c32f896c0e071a8b4aa7fc99a49 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Fri, 25 Sep 2026 12:06:29 +0000 Subject: [PATCH 39/45] Add test for incr comp fat LTO and fix assert_module_sources testing --- .../rustc_codegen_ssa/src/assert_module_sources.rs | 2 +- tests/incremental/lto.rs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 431783552bcd0..792ab9873378a 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -111,7 +111,7 @@ impl<'tcx> AssertModuleSource<'tcx> { if !self.check_config(cfg) { debug!("check_attr: config does not match, ignoring attr"); - return; + continue; } let user_path = module.as_str(); diff --git a/tests/incremental/lto.rs b/tests/incremental/lto.rs index ddeddf95f8aee..c82b040c238cb 100644 --- a/tests/incremental/lto.rs +++ b/tests/incremental/lto.rs @@ -1,11 +1,20 @@ //@ no-prefer-dynamic //@ revisions:rpass1 rpass2 -//@ compile-flags: -C lto +//@ compile-flags: -Z query-dep-graph -C lto=fat //@ ignore-backends: gcc +#![feature(rustc_attrs)] +#![rustc_partition_codegened(module = "lto", cfg = "rpass1")] +#![rustc_partition_codegened(module = "lto-x", cfg = "rpass1")] +#![rustc_partition_codegened(module = "lto-y", cfg = "rpass1")] +#![rustc_partition_codegened(module = "lto", cfg = "rpass2")] // FIXME should be reused +#![rustc_partition_codegened(module = "lto-x", cfg = "rpass2")] +#![rustc_partition_codegened(module = "lto-y", cfg = "rpass2")] // FIXME should be reused + mod x { pub struct X { - x: u32, y: u32, + x: u32, + y: u32, } #[cfg(rpass1)] From 51e5451fe8c5d44d1835bffb550808fec36b1829 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:17:05 +0000 Subject: [PATCH 40/45] Stop useless caching of post-LTO artifact caching for fat LTO These artifacts are never actually used. Only the pre-LTO artifacts would be useful, but caching that seems a bit more tricky and is not that important for now. Fat LTO itself is very slow anyway. --- compiler/rustc_codegen_ssa/src/back/write.rs | 43 ++++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 1b659ea9974d4..a42adae57f130 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -2135,15 +2135,25 @@ impl OngoingCodegen { let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); // Catch fatal errors to ensure shared_emitter_main.check() can emit the actual diagnostics - let compiled_modules = catch_fatal_errors(|| match maybe_lto_modules { + let compilation_output = catch_fatal_errors(|| match maybe_lto_modules { MaybeLtoModules::NoLto(compiled_modules) => { drop(shared_emitter); - compiled_modules + + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( + sess, + incr_comp_session, + &compiled_modules, + ); + + (compiled_modules, work_products) } MaybeLtoModules::FatLto { cgcx, needs_fat_lto } => { let tm_factory = self.backend.target_machine_factory(sess, cgcx.opt_level); - CompiledModules { + // FIXME cache local pre-LTO bitcode + let work_products = WorkProductMap::default(); + + let compiled_modules = CompiledModules { modules: vec![do_fat_lto( sess, &cgcx, @@ -2154,12 +2164,14 @@ impl OngoingCodegen { needs_fat_lto, )], allocator_module: None, - } + }; + + (compiled_modules, work_products) } MaybeLtoModules::ThinLto { cgcx, needs_thin_lto } => { let tm_factory = self.backend.target_machine_factory(sess, cgcx.opt_level); - CompiledModules { + let compiled_modules = CompiledModules { modules: do_thin_lto::( &cgcx, &sess.prof, @@ -2172,7 +2184,17 @@ impl OngoingCodegen { sess.opts.recommended_stack_size, ), allocator_module: None, - } + }; + + // FIXME include pre-LTO bitcode in workproduct tracking + // FIXME add separate incr comp session for post-LTO outputs to use during link step + let work_products = copy_all_cgu_workproducts_to_incr_comp_cache_dir( + sess, + incr_comp_session, + &compiled_modules, + ); + + (compiled_modules, work_products) } }); @@ -2180,19 +2202,14 @@ impl OngoingCodegen { sess.dcx().abort_if_errors(); - let mut compiled_modules = - compiled_modules.expect("fatal error emitted but not sent to SharedEmitter"); + let (mut compiled_modules, work_products) = + compilation_output.expect("fatal error emitted but not sent to SharedEmitter"); // Regardless of what order these modules completed in, report them to // the backend in the same order every time to ensure that we're handing // 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, - incr_comp_session, - &compiled_modules, - ); produce_final_output_artifacts(sess, &compiled_modules, &self.output_filenames); (compiled_modules, work_products) From c207b3a6fbe612564b727d30c16213f47613b028 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sun, 2 Aug 2026 23:11:42 +0200 Subject: [PATCH 41/45] unix impls of AsRef&Co. --- library/std/src/os/fd/owned.rs | 73 +++++++++++++++++++++++++ library/std/src/os/unix/net/datagram.rs | 9 ++- library/std/src/os/unix/net/listener.rs | 9 ++- library/std/src/os/unix/net/stream.rs | 9 ++- 4 files changed, 97 insertions(+), 3 deletions(-) diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs index 6c6f9b552c926..7b62116dff404 100644 --- a/library/std/src/os/fd/owned.rs +++ b/library/std/src/os/fd/owned.rs @@ -90,6 +90,27 @@ impl BorrowedFd<'_> { pub const unsafe fn borrow_raw(fd: RawFd) -> Self { Self { fd: ValidRawFd::new(fd).expect("fd != -1"), _phantom: PhantomData } } + + /// Converts a `BorrowedFd` into a reference to a file-like type. + /// + #[cfg_attr(unix, doc = "```")] + #[cfg_attr(not(unix), doc = "```ignore (needs unix)")] + /// # #![feature(fd_view)] + /// # use std::fs::File; + /// # use std::io; + /// # use std::os::fd::AsFd; + /// let meta = io::stdout().as_fd().to_view::().metadata().unwrap(); + /// if meta.is_file() { + /// println!("stdout is a regular file"); + /// } + /// ``` + #[unstable(feature = "fd_view", issue = "160402")] + pub fn to_view(&self) -> &T + where + Self: AsRef, + { + self.as_ref() + } } impl OwnedFd { @@ -435,6 +456,58 @@ impl From for crate::net::UdpSocket { } } +/// Implements `AsRef<$t>` conversions from `BorrowedFd` to the specified types. +/// +/// # Safety +/// +/// It must be sound to transmute `&BorrowedFd` to `&$t`. +/// +/// This typically implies that: +/// +/// - $t must be a wrapper with `BorrowedFd`, `OwnedFd` or `RawFd` being the single non-ZST-field. +/// Nominally it also requires repr(transparent), but std can cheat because we can stay in +/// sync with rustc's layout implementation details. +/// - `&$t` must not have any methods that would allow closing the file descriptor. +/// - `&$t` impls must tolerate instances being created from any kind of file-descriptor without +/// going through some dedicated constructor function, akin to `From` being a noop +/// conversion. +pub(crate) macro unsafe_impl_fd_asrefs { + ($($t:ty),*$(,)?) => {$( + #[unstable(feature = "fd_view", issue = "160402")] + #[unstable_feature_bound(fd_view)] + impl AsRef<$t> for BorrowedFd<'_> { + #[inline] + fn as_ref(&self) -> &$t { + use core::mem; + + // These are neither necessary nor sufficient, but generally + // a type that implements them is more likely to have the required behavior. + fn type_check + Into + AsFd>() {} + + // sanity check in case layouts change + const { + assert!(mem::size_of::() == mem::size_of::<$t>()); + assert!(mem::size_of::() == mem::size_of::>()); + } + type_check::<$t>(); + // SAFETY: See macro-level requirements. + unsafe { mem::transmute::<&BorrowedFd<'_>, &$t>(self) } + } + } + )*} +} + +#[cfg(not(target_os = "trusty"))] +unsafe_impl_fd_asrefs!( + crate::fs::File, + crate::net::TcpStream, + crate::net::TcpListener, + crate::net::UdpSocket, +); + +#[cfg(unix)] +unsafe_impl_fd_asrefs!(io::PipeWriter, io::PipeReader); + #[stable(feature = "asfd_ptrs", since = "1.64.0")] /// This impl allows implementing traits that require `AsFd` on Arc. /// ``` diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs index 548dca3592063..309f80f7923b2 100644 --- a/library/std/src/os/unix/net/datagram.rs +++ b/library/std/src/os/unix/net/datagram.rs @@ -20,7 +20,9 @@ use super::{SocketAncillary, recv_vectored_with_ancillary_from, send_vectored_wi #[cfg(any(doc, target_os = "android", target_os = "linux", target_os = "cygwin"))] use crate::io::{IoSlice, IoSliceMut}; use crate::net::Shutdown; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::os::unix::io::{ + AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, unsafe_impl_fd_asrefs, +}; use crate::path::Path; use crate::sys::net::Socket; use crate::sys::{AsInner, FromInner, IntoInner, cvt}; @@ -1015,6 +1017,11 @@ impl AsFd for UnixDatagram { } } +// SAFETY: It's a tower of 1-field wrappers around OwnedFd. +// No methods to close the socket, no methods with safety preconditions, +// other than the fd being valid. +unsafe_impl_fd_asrefs!(UnixDatagram); + #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { /// Takes ownership of a [`UnixDatagram`]'s socket file descriptor. diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs index 2e47a4b23a1cb..713ad2944a8cb 100644 --- a/library/std/src/os/unix/net/listener.rs +++ b/library/std/src/os/unix/net/listener.rs @@ -1,5 +1,7 @@ use super::{SocketAddr, UnixStream, sockaddr_un}; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::os::unix::io::{ + AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, unsafe_impl_fd_asrefs, +}; use crate::path::Path; use crate::sys::net::Socket; use crate::sys::{AsInner, FromInner, IntoInner, cvt}; @@ -355,6 +357,11 @@ impl From for UnixListener { } } +// SAFETY: It's a tower of 1-field wrappers around OwnedFd. +// No methods to close the socket, no methods with safety preconditions, +// other than the fd being valid. +unsafe_impl_fd_asrefs!(UnixListener); + #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { /// Takes ownership of a [`UnixListener`]'s socket file descriptor. diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs index dba49d0406bf1..00c3e75592ee2 100644 --- a/library/std/src/os/unix/net/stream.rs +++ b/library/std/src/os/unix/net/stream.rs @@ -40,7 +40,9 @@ use super::{UCred, peer_cred}; use crate::fmt; use crate::io::{self, IoSlice, IoSliceMut}; use crate::net::Shutdown; -use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}; +use crate::os::unix::io::{ + AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, unsafe_impl_fd_asrefs, +}; use crate::path::Path; use crate::sys::net::Socket; use crate::sys::{AsInner, FromInner, cvt}; @@ -745,6 +747,11 @@ impl AsFd for UnixStream { } } +// SAFETY: It's a tower of 1-field wrappers around OwnedFd. +// No methods to close the socket, no methods with safety preconditions, +// other than the fd being valid. +unsafe_impl_fd_asrefs!(UnixStream); + #[stable(feature = "io_safety", since = "1.63.0")] impl From for OwnedFd { /// Takes ownership of a [`UnixStream`]'s socket file descriptor. From 2de7d9c2747515f32d528f223a25bafdaf272738 Mon Sep 17 00:00:00 2001 From: The 8472 Date: Tue, 4 Aug 2026 00:07:17 +0200 Subject: [PATCH 42/45] windows impls of AsRef&Co. --- library/std/src/os/windows/io/handle.rs | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs index 01bd36155b110..ec20baf2bf2ce 100644 --- a/library/std/src/os/windows/io/handle.rs +++ b/library/std/src/os/windows/io/handle.rs @@ -151,6 +151,27 @@ impl BorrowedHandle<'_> { pub const unsafe fn borrow_raw(handle: RawHandle) -> Self { Self { handle, _phantom: PhantomData } } + + /// Converts a `BorrowedHandle` into a reference to a file-like type. + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// # #![feature(fd_view)] + /// # use std::fs::File; + /// # use std::io; + /// # use std::os::windows::io::AsHandle; + /// let meta = io::stdout().as_handle().to_view::().metadata().unwrap(); + /// if meta.is_file() { + /// println!("stdout is a regular file"); + /// } + /// ``` + #[unstable(feature = "fd_view", issue = "160402")] + pub fn to_view(&self) -> &T + where + Self: AsRef, + { + self.as_ref() + } } #[stable(feature = "io_safety", since = "1.63.0")] @@ -680,6 +701,49 @@ impl AsHandle for io::PipeWriter { } } +/// Implements `AsRef<$t>` conversions from `BorrowedHandle` to the specified types. +/// +/// # Safety +/// +/// It must be sound to transmute `&BorrowedHandle` to `&$t`. +/// +/// This typically implies that: +/// +/// - $t must be a wrapper with `BorrowedHandle`, `OwnedHandle` or `RawHandle` being the single non-ZST-field. +/// Nominally it also requires repr(transparent), but std can cheat because we can stay in +/// sync with rustc's layout implementation details. +/// - `&$t` must not have any methods that would allow closing the file descriptor. +/// - `&$t` impls must tolerate instances being created from any kind of file-descriptor without +/// going through some dedicated constructor function, akin to `From` being a noop +/// conversion. +pub(crate) macro unsafe_impl_handle_asrefs { + ($($t:ty),*$(,)?) => {$( + #[unstable(feature = "fd_view", issue = "160402")] + #[unstable_feature_bound(fd_view)] + impl AsRef<$t> for BorrowedHandle<'_> { + #[inline] + fn as_ref(&self) -> &$t { + use core::mem; + + // These are neither necessary nor sufficient, but generally + // a type that implements them is more likely to have the required behavior. + fn type_check + Into + AsHandle>() {} + + // sanity check in case layouts change + const { + assert!(mem::size_of::() == mem::size_of::<$t>()); + assert!(mem::size_of::() == mem::size_of::>()); + } + type_check::<$t>(); + // SAFETY: See macro-level requirements. + unsafe { mem::transmute::<&BorrowedHandle<'_>, &$t>(self) } + } + } + )*} +} + +unsafe_impl_handle_asrefs!(crate::fs::File, io::PipeWriter, io::PipeReader,); + #[stable(feature = "anonymous_pipe", since = "1.87.0")] impl From for OwnedHandle { fn from(pipe: io::PipeWriter) -> Self { From 7490ed168fa99ab0552f09068772b2d33f6887d2 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Tue, 20 Oct 2026 16:38:38 +0200 Subject: [PATCH 43/45] Stabilize `slice_split_once` --- library/core/src/slice/mod.rs | 6 ++---- library/coretests/tests/lib.rs | 1 - library/std/src/lib.rs | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 6deba9ce9752e..35af6488895a1 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -2523,7 +2523,6 @@ impl [T] { /// # Examples /// /// ``` - /// #![feature(slice_split_once)] /// let s = [1, 2, 3, 2, 4]; /// assert_eq!(s.split_once(|&x| x == 2), Some(( /// &[1][..], @@ -2531,7 +2530,7 @@ impl [T] { /// ))); /// assert_eq!(s.split_once(|&x| x == 0), None); /// ``` - #[unstable(feature = "slice_split_once", issue = "112811")] + #[stable(feature = "slice_split_once", since = "CURRENT_RUSTC_VERSION")] #[inline] pub fn split_once(&self, pred: F) -> Option<(&[T], &[T])> where @@ -2552,7 +2551,6 @@ impl [T] { /// # Examples /// /// ``` - /// #![feature(slice_split_once)] /// let s = [1, 2, 3, 2, 4]; /// assert_eq!(s.rsplit_once(|&x| x == 2), Some(( /// &[1, 2, 3][..], @@ -2560,7 +2558,7 @@ impl [T] { /// ))); /// assert_eq!(s.rsplit_once(|&x| x == 0), None); /// ``` - #[unstable(feature = "slice_split_once", issue = "112811")] + #[stable(feature = "slice_split_once", since = "CURRENT_RUSTC_VERSION")] #[inline] pub fn rsplit_once(&self, pred: F) -> Option<(&[T], &[T])> where diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 9054f9331f7bf..8f2fd10b9e475 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -107,7 +107,6 @@ #![feature(slice_internals)] #![feature(slice_partition_dedup)] #![feature(slice_shift)] -#![feature(slice_split_once)] #![feature(sliceindex_wrappers)] #![feature(splat)] #![feature(split_array)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 0d0d9e0759d82..e04f7b79751b8 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -381,7 +381,6 @@ #![feature(slice_internals)] #![feature(slice_ptr_get)] #![feature(slice_range)] -#![feature(slice_split_once)] #![feature(std_internals)] #![feature(str_internals)] #![feature(sync_unsafe_cell)] From baa3a660d9e625eb8b5dae13e4fbf525e98a2598 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Tue, 20 Oct 2026 14:45:07 +0000 Subject: [PATCH 44/45] Encode HashMap and HashSet in deterministic order when an encoder requires determinism Co-authored-by: Vadim Petrochenkov --- compiler/rustc_hir_id/src/lib.rs | 7 ++ compiler/rustc_metadata/src/rmeta/encoder.rs | 6 ++ .../rustc_middle/src/query/on_disk_cache.rs | 5 +- compiler/rustc_middle/src/ty/context.rs | 16 +++ .../src/offload/manifest.rs | 8 +- compiler/rustc_serialize/src/lib.rs | 2 +- compiler/rustc_serialize/src/serialize.rs | 102 +++++++++++++++--- compiler/rustc_span/src/def_id.rs | 23 +++- compiler/rustc_span/src/hygiene.rs | 8 +- compiler/rustc_span/src/lib.rs | 11 ++ compiler/rustc_span/src/symbol.rs | 7 ++ compiler/rustc_structures/src/crate_type.rs | 8 ++ .../generics-of-issue-163878.rs | 10 ++ .../parallel-reproducible-build/rmake.rs | 10 ++ 14 files changed, 206 insertions(+), 17 deletions(-) create mode 100644 tests/run-make/parallel-reproducible-build/generics-of-issue-163878.rs diff --git a/compiler/rustc_hir_id/src/lib.rs b/compiler/rustc_hir_id/src/lib.rs index 0e01600f9cb3d..7cb43e9a8ccf9 100644 --- a/compiler/rustc_hir_id/src/lib.rs +++ b/compiler/rustc_hir_id/src/lib.rs @@ -16,6 +16,7 @@ use rustc_data_structures::stable_hash::{ StableHash, StableHashCtxt, StableHasher, StableOrd, ToStableHashKey, }; use rustc_macros::{Decodable, Encodable, StableHash}; +use rustc_serialize::ContextOrd; use rustc_span::def_id::{CRATE_DEF_ID, DefId, DefIndex, DefPathHash, LocalDefId, LocalModId}; #[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] @@ -176,6 +177,12 @@ rustc_index::newtype_index! { pub struct ItemLocalId {} } +impl ContextOrd for ItemLocalId { + fn stable_cmp_with_context(_context: E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + ::cmp + } +} + impl ItemLocalId { /// Signal local id which should never be used. pub const INVALID: ItemLocalId = ItemLocalId::MAX; diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 2970bc0b7ed78..4b2fe1bc53bc7 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -102,6 +102,8 @@ macro_rules! encoder_methods { } impl<'a, 'tcx> Encoder for EncodeContext<'a, 'tcx> { + const DETERMINISTIC_HASH_MAP_ENCODE: bool = true; + encoder_methods! { emit_usize(usize); emit_u128(u128); @@ -150,6 +152,10 @@ impl<'a, 'tcx> Encodable> for ExpnIndex { } impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { + fn def_path_hasher(&self) -> impl Fn(DefId) -> DefPathHash { + self.tcx.prelocked_def_path_hasher() + } + fn encode_crate_num(&mut self, crate_num: CrateNum) { if crate_num != LOCAL_CRATE && self.is_proc_macro { panic!("Attempted to encode non-local CrateNum {crate_num:?} for proc-macro crate"); diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 7383ad23273be..d419c1df3b3d0 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -14,7 +14,7 @@ use rustc_hir::definitions::DefPathHash; use rustc_index::IndexVec; use rustc_macros::{Decodable, Encodable}; use rustc_serialize::opaque::{FileEncodeResult, FileEncoder, IntEncodedWithFixedSize, MemDecoder}; -use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +use rustc_serialize::{ContextOrd, Decodable, Decoder, Encodable, Encoder}; use rustc_session::Session; use rustc_span::hygiene::{ ExpnId, HygieneDecodeContext, HygieneEncodeContext, SyntaxContext, SyntaxContextKey, @@ -116,6 +116,9 @@ struct Footer { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Encodable, Decodable)] struct SourceFileIndex(u32); +// this is never used by the on disk cache, since it is not required to be deterministic +impl ContextOrd for SourceFileIndex {} + #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Encodable, Decodable)] struct AbsoluteBytePos(u64); diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 913f56eba36f5..20a2cc4dadaa7 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -1144,6 +1144,22 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Useful when one needs to access a lot of `DefPathHash`-es in quick succession. Stable + /// sorting a collection for example. + /// + /// Read locks the structures containing the `DefPathHash` mappings, allowing us to skip lots of + /// locking and releasing using `[Self::def_path_hash]` would do + pub fn prelocked_def_path_hasher( + self, + ) -> impl Fn(DefId) -> rustc_hir::definitions::DefPathHash { + let definitions = self.definitions_untracked(); + let cstore = self.cstore_untracked(); + move |def_id| match def_id.as_local() { + Some(def_id) => definitions.def_path_hash(def_id), + None => cstore.def_path_hash(def_id), + } + } + #[inline] pub fn crate_types(self) -> &'tcx [CrateType] { &self.crate_types diff --git a/compiler/rustc_monomorphize/src/offload/manifest.rs b/compiler/rustc_monomorphize/src/offload/manifest.rs index 8fdd8e10ab419..fdaa3f6b017fd 100644 --- a/compiler/rustc_monomorphize/src/offload/manifest.rs +++ b/compiler/rustc_monomorphize/src/offload/manifest.rs @@ -8,7 +8,7 @@ use std::fs; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lock; -use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE, StableCrateId}; +use rustc_hir::def_id::{DefId, DefIndex, DefPathHash, LOCAL_CRATE, StableCrateId}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mono::MonoItem; use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; @@ -44,6 +44,8 @@ impl<'a, 'tcx> OffloadManifestEncoder<'a, 'tcx> { } impl<'a, 'tcx> Encoder for OffloadManifestEncoder<'a, 'tcx> { + const DETERMINISTIC_HASH_MAP_ENCODE: bool = true; + fn emit_usize(&mut self, v: usize) { self.encoder.emit_usize(v); } @@ -86,6 +88,10 @@ impl<'a, 'tcx> Encoder for OffloadManifestEncoder<'a, 'tcx> { } impl<'a, 'tcx> SpanEncoder for OffloadManifestEncoder<'a, 'tcx> { + fn def_path_hasher(&self) -> impl Fn(DefId) -> DefPathHash { + self.tcx.prelocked_def_path_hasher() + } + fn encode_span(&mut self, _span: Span) { // Spans are not needed in the manifest, encode a dummy span. self.emit_usize(0); diff --git a/compiler/rustc_serialize/src/lib.rs b/compiler/rustc_serialize/src/lib.rs index 364ae2a10a1e7..6f1076619ca80 100644 --- a/compiler/rustc_serialize/src/lib.rs +++ b/compiler/rustc_serialize/src/lib.rs @@ -14,7 +14,7 @@ #[cfg(test)] extern crate self as rustc_serialize; -pub use self::serialize::{Decodable, Decoder, Encodable, Encoder}; +pub use self::serialize::{ContextOrd, Decodable, Decoder, Encodable, Encoder}; mod serialize; diff --git a/compiler/rustc_serialize/src/serialize.rs b/compiler/rustc_serialize/src/serialize.rs index 32a301a28ae8e..6426dc37e99a0 100644 --- a/compiler/rustc_serialize/src/serialize.rs +++ b/compiler/rustc_serialize/src/serialize.rs @@ -85,6 +85,67 @@ pub trait Encoder { } fn emit_raw_bytes(&mut self, s: &[u8]); + + /// Controls whether `HashMap`-s are stable sorted by their key before encoding. Some encoders + /// don't need determinism, improving performance. + const DETERMINISTIC_HASH_MAP_ENCODE: bool = false; + + fn encode_hash_map(&mut self, map: &HashMap) + where + Self: Sized, + K: Encodable + for<'a> ContextOrd<&'a Self>, + V: Encodable, + S: BuildHasher, + { + self.emit_usize(map.len()); + if Self::DETERMINISTIC_HASH_MAP_ENCODE { + // we are sorting right after + #[expect(rustc::potential_query_instability)] + let mut values: Vec<_> = map.iter().collect(); + let mut cmp = >::stable_cmp_with_context(self); + values.sort_by(|(k1, _), (k2, _)| cmp(*k1, *k2)); + drop(cmp); + + for (key, val) in values { + key.encode(self); + val.encode(self); + } + } else { + // this is explicitly allowed when Self::DETERMINISTIC_HASH_MAP_ENCODE is false + #[expect(rustc::potential_query_instability)] + for (key, val) in map.iter() { + key.encode(self); + val.encode(self); + } + } + } + + fn encode_hash_set(&mut self, map: &HashSet) + where + Self: Sized, + K: Encodable + for<'a> ContextOrd<&'a Self>, + S: BuildHasher, + { + self.emit_usize(map.len()); + if Self::DETERMINISTIC_HASH_MAP_ENCODE { + // we are sorting right after + #[expect(rustc::potential_query_instability)] + let mut values: Vec<_> = map.iter().collect(); + let mut cmp = >::stable_cmp_with_context(self); + values.sort_by(|k1, k2| cmp(*k1, *k2)); + drop(cmp); + + for key in values { + key.encode(self); + } + } else { + // this is explicitly allowed when Self::DETERMINISTIC_HASH_MAP_ENCODE is false + #[expect(rustc::potential_query_instability)] + for key in map.iter() { + key.encode(self); + } + } + } } // Note: all the methods in this trait are infallible, which may be surprising. @@ -608,16 +669,13 @@ where impl Encodable for HashMap where - K: Encodable + Eq, + E: Sized, + K: Encodable + Eq + for<'a> ContextOrd<&'a E>, V: Encodable, S: BuildHasher, { fn encode(&self, e: &mut E) { - e.emit_usize(self.len()); - for (key, val) in self { - key.encode(e); - val.encode(e); - } + e.encode_hash_map(self) } } @@ -635,14 +693,11 @@ where impl Encodable for HashSet where - T: Encodable + Eq, + T: Encodable + for<'a> ContextOrd<&'a E>, S: BuildHasher, { - fn encode(&self, s: &mut E) { - s.emit_usize(self.len()); - for e in self { - e.encode(s); - } + fn encode(&self, e: &mut E) { + e.encode_hash_set(self) } } @@ -763,3 +818,26 @@ impl Decodable for Hash128 { Self::new(u128::from_le_bytes(d.read_raw_bytes(16).try_into().unwrap())) } } + +pub trait ContextOrd { + /// The returned comparator must be stable. + /// E. g. compares `DefPathHash` instead of `DefIndex` + fn stable_cmp_with_context(_context: Ctx) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + unimplemented!(); + #[allow(unused)] + |_i, _i2| unreachable!() + } +} + +impl ContextOrd for String { + fn stable_cmp_with_context(_context: Ctx) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + ::cmp + } +} + +// FIXME remove this. This is only here because CacheEncoder, which does not need to be +// deterministic, encodes a HashMap. If a real implementation is ever needed, that use should +// be switched to a new type instead of u32 +impl ContextOrd for u32 {} + +impl ContextOrd for i64 {} diff --git a/compiler/rustc_span/src/def_id.rs b/compiler/rustc_span/src/def_id.rs index e0143923fb5d5..7a51bc4af828a 100644 --- a/compiler/rustc_span/src/def_id.rs +++ b/compiler/rustc_span/src/def_id.rs @@ -10,7 +10,7 @@ use rustc_data_structures::unhash::Unhasher; use rustc_hashes::Hash64; use rustc_index::Idx; use rustc_macros::{BlobDecodable, Decodable, Encodable, StableHash}; -use rustc_serialize::{Decodable, Encodable}; +use rustc_serialize::{ContextOrd, Decodable, Encodable}; use crate::{BlobDecoder, SpanEncoder, Symbol}; @@ -23,6 +23,13 @@ rustc_index::newtype_index! { pub struct CrateNum {} } +impl ContextOrd<&E> for CrateNum { + fn stable_cmp_with_context(context: &E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + let hasher = context.def_path_hasher(); + move |id1, id2| hasher(id1.as_def_id()).cmp(&hasher(id2.as_def_id())) + } +} + /// Item definitions in the currently-compiled crate would have the `CrateNum` /// `LOCAL_CRATE` in their `DefId`. pub const LOCAL_CRATE: CrateNum = CrateNum::ZERO; @@ -410,6 +417,13 @@ impl Decodable for LocalDefId { } } +impl ContextOrd<&E> for LocalDefId { + fn stable_cmp_with_context(context: &E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + let hasher = context.def_path_hasher(); + move |id1, id2| hasher(id1.to_def_id()).cmp(&hasher(id2.to_def_id())) + } +} + rustc_data_structures::define_id_collections!( LocalDefIdMap, LocalDefIdSet, @@ -417,6 +431,13 @@ rustc_data_structures::define_id_collections!( LocalDefId ); +impl ContextOrd<&E> for DefId { + fn stable_cmp_with_context(context: &E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + let hasher = context.def_path_hasher(); + move |id1, id2| hasher(*id1).cmp(&hasher(*id2)) + } +} + impl StableHash for DefId { #[inline] fn stable_hash(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 6489b3cc6d54b..8015bf6f8e50f 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -39,7 +39,7 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_hashes::Hash64; use rustc_index::IndexVec; use rustc_macros::{Decodable, Encodable, StableHash}; -use rustc_serialize::{Decodable, Decoder, Encodable}; +use rustc_serialize::{ContextOrd, Decodable, Decoder, Encodable}; use tracing::{debug, trace}; use crate::def_id::{CRATE_DEF_ID, CrateNum, DefId, LOCAL_CRATE, ModId, StableCrateId}; @@ -133,6 +133,12 @@ impl !PartialOrd for LocalExpnId {} #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encodable, Decodable, StableHash)] pub struct ExpnHash(Fingerprint); +impl ContextOrd for ExpnHash { + fn stable_cmp_with_context(_context: E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + |h1, h2| h1.0.cmp(&h2.0) + } +} + impl ExpnHash { /// Returns the [StableCrateId] identifying the crate this [ExpnHash] /// originates from. diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index f69efebf5c950..a0806126177cd 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -46,6 +46,7 @@ pub mod source_map; use source_map::{SourceMap, SourceMapInputs}; pub use self::caching_source_map_view::CachingSourceMapView; +use crate::def_id::DefPathHash; use crate::fatal_error::FatalError; pub mod edition; @@ -1344,6 +1345,16 @@ rustc_index::newtype_index! { /// This trait is used to allow encoder specific encodings of certain types. /// It is similar to rustc_type_ir's TyEncoder. pub trait SpanEncoder: Encoder { + /// Only needed if the encoder wants deterministic encoding for `HashMap` and `HashSet` with + /// `CrateNum`, `DefId` or `LocalDefId` keys. Can be unimplemented otherwise + fn def_path_hasher(&self) -> impl Fn(DefId) -> DefPathHash { + panic!( + "Deterministic encoding of HashMap requires overriding `SpanEncoder::def_path_hasher`" + ); + #[allow(unreachable_code)] + |_def_id| unimplemented!() + } + fn encode_span(&mut self, span: Span); fn encode_symbol(&mut self, sym: Symbol); fn encode_byte_symbol(&mut self, byte_sym: ByteSymbol); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 4ab0a6d988b84..65bea89e1c713 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -11,6 +11,7 @@ use rustc_data_structures::hash_table::{Entry, HashTable}; use rustc_data_structures::stable_hash::{StableCompare, StableHash, StableHashCtxt, StableHasher}; use rustc_data_structures::sync::Lock; use rustc_macros::{Decodable, Encodable, StableHash, symbols}; +use rustc_serialize::ContextOrd; use crate::edit_distance::find_best_match_for_name; use crate::{DUMMY_SP, Edition, Span, with_session_globals}; @@ -2735,6 +2736,12 @@ impl fmt::Display for MacroRulesNormalizedIdent { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Symbol(SymbolIndex); +impl ContextOrd for Symbol { + fn stable_cmp_with_context(_context: E) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + StableCompare::stable_cmp + } +} + // Used within both `Symbol` and `ByteSymbol`. rustc_index::newtype_index! { #[orderable] diff --git a/compiler/rustc_structures/src/crate_type.rs b/compiler/rustc_structures/src/crate_type.rs index 505370a524281..58f192bc1d952 100644 --- a/compiler/rustc_structures/src/crate_type.rs +++ b/compiler/rustc_structures/src/crate_type.rs @@ -1,3 +1,4 @@ +use rustc_serialize::ContextOrd; #[cfg(feature = "nightly")] use { rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash}, @@ -25,6 +26,13 @@ pub enum CrateType { // Unstable; feature(export_stable) Sdylib, } + +impl ContextOrd for CrateType { + fn stable_cmp_with_context(_context: Ctx) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + ::cmp + } +} + #[cfg(feature = "nightly")] impl CrateType { /// Pairs of each `#[crate_type] = "..."` value and the crate type it resolves to diff --git a/tests/run-make/parallel-reproducible-build/generics-of-issue-163878.rs b/tests/run-make/parallel-reproducible-build/generics-of-issue-163878.rs new file mode 100644 index 0000000000000..815218b58b20b --- /dev/null +++ b/tests/run-make/parallel-reproducible-build/generics-of-issue-163878.rs @@ -0,0 +1,10 @@ +struct A {} + +impl A { + fn f1(&self, b1: &()) -> impl Iterator + '_ { + std::iter::empty() + } + fn f2(&self, b2: &()) -> impl Iterator + '_ { + std::iter::empty() + } +} diff --git a/tests/run-make/parallel-reproducible-build/rmake.rs b/tests/run-make/parallel-reproducible-build/rmake.rs index 01de08e0551f3..24c441aa9bd32 100644 --- a/tests/run-make/parallel-reproducible-build/rmake.rs +++ b/tests/run-make/parallel-reproducible-build/rmake.rs @@ -47,6 +47,16 @@ fn main() { "-Clink-dead-code=true", ], ), + ( + "generics-of-issue-163878", + &[ + "-Zthreads=2", + "--edition=2024", + "-Zremap-cwd-prefix=reproducible_dir", + "-Ccodegen-units=1", + "--crate-type=lib", + ], + ), ]; for (file, args) in TESTS { From d3a392da7bcbd5dcd73244f94634b820dad6eb50 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 20 Oct 2026 23:33:00 +0800 Subject: [PATCH 45/45] docs: suggest to not use `--allow-noop` for now --- src/tools/rustfmt/Subtree sync procedure.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/Subtree sync procedure.md b/src/tools/rustfmt/Subtree sync procedure.md index cedca7b40f5da..01f581a6fb570 100644 --- a/src/tools/rustfmt/Subtree sync procedure.md +++ b/src/tools/rustfmt/Subtree sync procedure.md @@ -19,6 +19,11 @@ sync process (both pulls and pushes). You can install it with `cargo install --g 4. Run `rustc-josh-sync pull` 5. Confirm the creation of a PR directly by `rustc-josh-sync`, or create a PR to `rust-lang/rustfmt` repository +> [!CAUTION] +> +> For now, do not use the `--allow-noop` option of `rustc-josh-sync`. See +> . + The `rustfmt` maintainers will run Diff Check against the PR to catch any unexpected formatting changes. - Maintainers should trigger Diff-Check for the combinations of Edition {2021, 2024} x Style @@ -32,7 +37,7 @@ Where applicable, we may need to update the CHANGELOG entries with merged PRs (b repository and also in the `rust-lang/rust` `rustfmt` subtree that was included in the subtree-push merge), and then bump rustfmt version number. -## Subtree push direction: syncing changes from `rustfmt` to rust-lang/rust` +## Subtree push direction: syncing changes from `rustfmt` to `rust-lang/rust` 1. Be in a `rustfmt` checkout 2. Checkout the latest `main` branch from upstream