From 013d2d212351e0553c0b1b129ad811b6c8bdfddc Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 16:17:02 +0100 Subject: [PATCH 01/58] std: unix process_unsupported: Provide a wait status emulation Fixes #114593 Needs FCP due to behavioural changes. --- .../std/src/sys/unix/process/process_unix.rs | 5 + .../sys/unix/process/process_unsupported.rs | 52 +------- .../process_unsupported/wait_status.rs | 113 ++++++++++++++++++ 3 files changed, 120 insertions(+), 50 deletions(-) create mode 100644 library/std/src/sys/unix/process/process_unsupported/wait_status.rs diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 3963e7f52d5..7d10a16cfd1 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -1061,3 +1061,8 @@ fn take_pidfd(&mut self) -> io::Result { #[cfg(test)] #[path = "process_unix/tests.rs"] mod tests; + +// See [`process_unsupported_wait_status::compare_with_linux`]; +#[cfg(all(test, target_os = "linux"))] +#[path = "process_unsupported/wait_status.rs"] +mod process_unsupported_wait_status; diff --git a/library/std/src/sys/unix/process/process_unsupported.rs b/library/std/src/sys/unix/process/process_unsupported.rs index 8e0b971af73..325d3f23be7 100644 --- a/library/std/src/sys/unix/process/process_unsupported.rs +++ b/library/std/src/sys/unix/process/process_unsupported.rs @@ -55,56 +55,8 @@ pub fn try_wait(&mut self) -> io::Result> { } } -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus(c_int); - -impl ExitStatus { - #[cfg_attr(target_os = "horizon", allow(unused))] - pub fn success(&self) -> bool { - self.code() == Some(0) - } - - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - Err(ExitStatusError(1.try_into().unwrap())) - } - - pub fn code(&self) -> Option { - None - } - - pub fn signal(&self) -> Option { - None - } - - pub fn core_dumped(&self) -> bool { - false - } - - pub fn stopped_signal(&self) -> Option { - None - } - - pub fn continued(&self) -> bool { - false - } - - pub fn into_raw(&self) -> c_int { - 0 - } -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. -impl From for ExitStatus { - fn from(a: c_int) -> ExitStatus { - ExitStatus(a as i32) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "exit code: {}", self.0) - } -} +mod wait_status; +pub use wait_status::ExitStatus; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct ExitStatusError(NonZero_c_int); diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs new file mode 100644 index 00000000000..30fc78db9a1 --- /dev/null +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs @@ -0,0 +1,113 @@ +//! Emulated wait status for non-Unix #[cfg(unix) platforms +//! +//! Separate module to facilitate testing against a real Unix implementation. + +use crate::ffi::c_int; +use crate::fmt; + +/// Emulated wait status for use by `process_unsupported.rs` +/// +/// Uses the "traditional unix" encoding. For use on platfors which are `#[cfg(unix)]` +/// but do not actually support subprocesses at all. +/// +/// These platforms aren't Unix, but are simply pretending to be for porting convenience. +/// So, we provide a faithful pretence here. +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +pub struct ExitStatus { + wait_status: c_int, +} + +/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it +impl From for ExitStatus { + fn from(wait_status: c_int) -> ExitStatus { + ExitStatus { wait_status } + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "emulated wait status: {}", self.wait_status) + } +} + +impl ExitStatus { + pub fn code(&self) -> Option { + // Linux and FreeBSD both agree that values linux 0x80 + // count as "WIFEXITED" even though this is quite mad. + // Likewise the macros disregard all the high bits, so are happy to declare + // out-of-range values to be WIFEXITED, WIFSTOPPED, etc. + let w = self.wait_status; + if (w & 0x7f) == 0 { + Some((w & 0xff00) >> 8) + } else { + None + } + } + + pub fn signal(&self) -> Option { + let signal = self.wait_status & 0x007f; + if signal > 0 && signal < 0x7f { + Some(signal) + } else { + None + } + } + + pub fn core_dumped(&self) -> bool { + self.signal().is_some() && (self.wait_status & 0x80) != 0 + } + + pub fn stopped_signal(&self) -> Option { + let w = self.wait_status; + if (w & 0xff) == 0x7f { + Some((w & 0xff00) >> 8) + } else { + None + } + } + + pub fn continued(&self) -> bool { + self.wait_status == 0xffff + } + + pub fn into_raw(&self) -> c_int { + self.wait_status + } +} + +// Test that our emulation exactly matches Linux +// +// This test runs *on Linux* but it tests +// the implementation used on non-Unix `#[cfg(unix)]` platforms. +// +// I.e. we're using Linux as a proxy for "trad unix". +#[cfg(all(test, target_os = "linux"))] +mod compare_with_linux { + use super::ExitStatus as Emulated; + use crate::process::ExitStatus as Real; + use crate::os::unix::process::ExitStatusExt as _; + + #[test] + fn compare() { + // Check that we handle out-of-range values similarly, too. + for wstatus in -0xf_ffff..0xf_ffff { + let emulated = Emulated::from(wstatus); + let real = Real::from_raw(wstatus); + + macro_rules! compare { { $method:ident } => { + assert_eq!( + emulated.$method(), + real.$method(), + "{wstatus:#x}.{}()", + stringify!($method), + ); + } } + compare!(code); + compare!(signal); + compare!(core_dumped); + compare!(stopped_signal); + compare!(continued); + compare!(into_raw); + } + } +} From 06567ad7fafcf0a2b380a8908d004a3ca8c88333 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 17:29:17 +0100 Subject: [PATCH 02/58] std: unix process: Test exit statuses / wait statuses This is a pretty basic test but should spot any other platforms which are `#[cfg(unix)]` but not Unix and where the wait status representation is wrong. (And any actual Unix platforms where it's not as expected, but I don't think they exist.) --- .../sys/unix/process/process_common/tests.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/library/std/src/sys/unix/process/process_common/tests.rs b/library/std/src/sys/unix/process/process_common/tests.rs index 03631e4e33b..082c564700f 100644 --- a/library/std/src/sys/unix/process/process_common/tests.rs +++ b/library/std/src/sys/unix/process/process_common/tests.rs @@ -159,3 +159,36 @@ fn test_program_kind() { ); } } + +// Test that Rust std handles wait status values (`ExitStatus`) the way that Unix does, +// at least for the values which represent a Unix exit status (`ExitCode`). +// Should work on every #[cfg(unix)] platform. However: +#[cfg(not(any( + // Fuchsia is not Unix and has totally broken std::os::unix. + // https://github.com/rust-lang/rust/issues/58590#issuecomment-836535609 + target_os = "fuchsia", +)))] +#[test] +fn unix_exit_statuses() { + use crate::num::NonZeroI32; + use crate::os::unix::process::ExitStatusExt; + use crate::process::*; + + for exit_code in 0..=0xff { + // TODO impl From for ExitStatus and then test that here too; + // the two ExitStatus values should be the same + let raw_wait_status = exit_code << 8; + let exit_status = ExitStatus::from_raw(raw_wait_status); + + assert_eq!(exit_status.code(), Some(exit_code)); + + if let Ok(nz) = NonZeroI32::try_from(exit_code) { + assert!(!exit_status.success()); + let es_error = exit_status.exit_ok().unwrap_err(); + assert_eq!(es_error.code().unwrap(), i32::from(nz)); + } else { + assert!(exit_status.success()); + assert_eq!(exit_status.exit_ok(), Ok(())); + } + } +} From 9738a07f986eb6c59ad309e66fa5f94a95d91309 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 19:01:46 +0100 Subject: [PATCH 03/58] fixup! std: unix process_unsupported: Provide a wait status emulation --- .../std/src/sys/unix/process/process_unsupported/wait_status.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs index 30fc78db9a1..17004779a62 100644 --- a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs @@ -84,8 +84,8 @@ pub fn into_raw(&self) -> c_int { #[cfg(all(test, target_os = "linux"))] mod compare_with_linux { use super::ExitStatus as Emulated; - use crate::process::ExitStatus as Real; use crate::os::unix::process::ExitStatusExt as _; + use crate::process::ExitStatus as Real; #[test] fn compare() { From 2727f82526ba1191ed6faaa5ab118833aa2fffdf Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 19:02:06 +0100 Subject: [PATCH 04/58] std: unix process_unsupported: Provide a wait status emulation (fmt) Worsify formatting as required by rustfmt. --- .../process/process_unsupported/wait_status.rs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs index 17004779a62..f465946a528 100644 --- a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs @@ -37,20 +37,12 @@ pub fn code(&self) -> Option { // Likewise the macros disregard all the high bits, so are happy to declare // out-of-range values to be WIFEXITED, WIFSTOPPED, etc. let w = self.wait_status; - if (w & 0x7f) == 0 { - Some((w & 0xff00) >> 8) - } else { - None - } + if (w & 0x7f) == 0 { Some((w & 0xff00) >> 8) } else { None } } pub fn signal(&self) -> Option { let signal = self.wait_status & 0x007f; - if signal > 0 && signal < 0x7f { - Some(signal) - } else { - None - } + if signal > 0 && signal < 0x7f { Some(signal) } else { None } } pub fn core_dumped(&self) -> bool { @@ -59,11 +51,7 @@ pub fn core_dumped(&self) -> bool { pub fn stopped_signal(&self) -> Option { let w = self.wait_status; - if (w & 0xff) == 0x7f { - Some((w & 0xff00) >> 8) - } else { - None - } + if (w & 0xff) == 0x7f { Some((w & 0xff00) >> 8) } else { None } } pub fn continued(&self) -> bool { From 281b0501f622601251972a012c9405aa80726471 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 19:43:00 +0100 Subject: [PATCH 05/58] fixup! std: unix process: Test exit statuses / wait statuses --- library/std/src/sys/unix/process/process_common/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/sys/unix/process/process_common/tests.rs b/library/std/src/sys/unix/process/process_common/tests.rs index 082c564700f..4e41efc9096 100644 --- a/library/std/src/sys/unix/process/process_common/tests.rs +++ b/library/std/src/sys/unix/process/process_common/tests.rs @@ -175,7 +175,7 @@ fn unix_exit_statuses() { use crate::process::*; for exit_code in 0..=0xff { - // TODO impl From for ExitStatus and then test that here too; + // FIXME impl From for ExitStatus and then test that here too; // the two ExitStatus values should be the same let raw_wait_status = exit_code << 8; let exit_status = ExitStatus::from_raw(raw_wait_status); From f6255287f7fccbf6062afe180fd54d5b11b62795 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Tue, 22 Aug 2023 20:19:58 +0100 Subject: [PATCH 06/58] std: unix process_unsupported: Provide a wait status emulation (tidy) Move tests into a module as demanded by tidy. --- .../process_unsupported/wait_status.rs | 39 ++----------------- .../process_unsupported/wait_status/tests.rs | 36 +++++++++++++++++ 2 files changed, 39 insertions(+), 36 deletions(-) create mode 100644 library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs index f465946a528..3c649db1a35 100644 --- a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs @@ -63,39 +63,6 @@ pub fn into_raw(&self) -> c_int { } } -// Test that our emulation exactly matches Linux -// -// This test runs *on Linux* but it tests -// the implementation used on non-Unix `#[cfg(unix)]` platforms. -// -// I.e. we're using Linux as a proxy for "trad unix". -#[cfg(all(test, target_os = "linux"))] -mod compare_with_linux { - use super::ExitStatus as Emulated; - use crate::os::unix::process::ExitStatusExt as _; - use crate::process::ExitStatus as Real; - - #[test] - fn compare() { - // Check that we handle out-of-range values similarly, too. - for wstatus in -0xf_ffff..0xf_ffff { - let emulated = Emulated::from(wstatus); - let real = Real::from_raw(wstatus); - - macro_rules! compare { { $method:ident } => { - assert_eq!( - emulated.$method(), - real.$method(), - "{wstatus:#x}.{}()", - stringify!($method), - ); - } } - compare!(code); - compare!(signal); - compare!(core_dumped); - compare!(stopped_signal); - compare!(continued); - compare!(into_raw); - } - } -} +#[cfg(test)] +#[path = "wait_status/tests.rs"] // needed because of strange layout of process_unsupported +mod tests; diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs new file mode 100644 index 00000000000..5132eab10a1 --- /dev/null +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status/tests.rs @@ -0,0 +1,36 @@ +// Note that tests in this file are run on Linux as well as on platforms using process_unsupported + +// Test that our emulation exactly matches Linux +// +// This test runs *on Linux* but it tests +// the implementation used on non-Unix `#[cfg(unix)]` platforms. +// +// I.e. we're using Linux as a proxy for "trad unix". +#[cfg(target_os = "linux")] +#[test] +fn compare_with_linux() { + use super::ExitStatus as Emulated; + use crate::os::unix::process::ExitStatusExt as _; + use crate::process::ExitStatus as Real; + + // Check that we handle out-of-range values similarly, too. + for wstatus in -0xf_ffff..0xf_ffff { + let emulated = Emulated::from(wstatus); + let real = Real::from_raw(wstatus); + + macro_rules! compare { { $method:ident } => { + assert_eq!( + emulated.$method(), + real.$method(), + "{wstatus:#x}.{}()", + stringify!($method), + ); + } } + compare!(code); + compare!(signal); + compare!(core_dumped); + compare!(stopped_signal); + compare!(continued); + compare!(into_raw); + } +} From a993a8bf3f9221c84120a6f111fd1b64a5f19ea4 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 4 Sep 2023 09:25:39 +0200 Subject: [PATCH 07/58] const-eval: make misalignment a hard error --- .../src/const_eval/eval_queries.rs | 14 +---- .../src/const_eval/machine.rs | 55 ++----------------- .../rustc_const_eval/src/interpret/machine.rs | 13 +---- .../rustc_const_eval/src/interpret/memory.rs | 49 ++++++----------- .../rustc_const_eval/src/interpret/place.rs | 3 +- compiler/rustc_lint/src/lib.rs | 5 ++ compiler/rustc_lint_defs/src/builtin.rs | 40 -------------- .../rustc_mir_transform/src/const_prop.rs | 21 +------ .../src/dataflow_const_prop.rs | 17 +----- src/tools/miri/src/machine.rs | 18 +----- 10 files changed, 42 insertions(+), 193 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 3d758cd01d3..4adfbe336af 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -1,6 +1,3 @@ -use crate::const_eval::CheckAlignment; -use crate::errors::ConstEvalError; - use either::{Left, Right}; use rustc_hir::def::DefKind; @@ -15,7 +12,9 @@ use rustc_target::abi::{self, Abi}; use super::{CanAccessStatics, CompileTimeEvalContext, CompileTimeInterpreter}; +use crate::const_eval::CheckAlignment; use crate::errors; +use crate::errors::ConstEvalError; use crate::interpret::eval_nullary_intrinsic; use crate::interpret::{ intern_const_alloc_recursive, CtfeValidationMode, GlobalId, Immediate, InternKind, InterpCx, @@ -290,14 +289,7 @@ pub fn eval_to_allocation_raw_provider<'tcx>( key.param_env, // Statics (and promoteds inside statics) may access other statics, because unlike consts // they do not have to behave "as if" they were evaluated at runtime. - CompileTimeInterpreter::new( - CanAccessStatics::from(is_static), - if tcx.sess.opts.unstable_opts.extra_const_ub_checks { - CheckAlignment::Error - } else { - CheckAlignment::FutureIncompat - }, - ), + CompileTimeInterpreter::new(CanAccessStatics::from(is_static), CheckAlignment::Error), ); let res = ecx.load_mir(cid.instance.def, cid.promoted); diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 14b9894aad5..623b682535e 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -1,10 +1,9 @@ use rustc_hir::def::DefKind; -use rustc_hir::{LangItem, CRATE_HIR_ID}; +use rustc_hir::LangItem; use rustc_middle::mir; use rustc_middle::mir::interpret::PointerArithmetic; use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout}; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::lint::builtin::INVALID_ALIGNMENT; use std::borrow::Borrow; use std::hash::Hash; use std::ops::ControlFlow; @@ -21,11 +20,11 @@ use rustc_target::spec::abi::Abi as CallAbi; use crate::errors::{LongRunning, LongRunningWarn}; +use crate::fluent_generated as fluent; use crate::interpret::{ self, compile_time_machine, AllocId, ConstAllocation, FnArg, FnVal, Frame, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, Scalar, }; -use crate::{errors, fluent_generated as fluent}; use super::error::*; @@ -65,22 +64,11 @@ pub struct CompileTimeInterpreter<'mir, 'tcx> { #[derive(Copy, Clone)] pub enum CheckAlignment { - /// Ignore alignment when following relocations. + /// Ignore all alignment requirements. /// This is mainly used in interning. No, /// Hard error when dereferencing a misaligned pointer. Error, - /// Emit a future incompat lint when dereferencing a misaligned pointer. - FutureIncompat, -} - -impl CheckAlignment { - pub fn should_check(&self) -> bool { - match self { - CheckAlignment::No => false, - CheckAlignment::Error | CheckAlignment::FutureIncompat => true, - } - } } #[derive(Copy, Clone, PartialEq)] @@ -358,8 +346,8 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir, const PANIC_ON_ALLOC_FAIL: bool = false; // will be raised as a proper error #[inline(always)] - fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment { - ecx.machine.check_alignment + fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + matches!(ecx.machine.check_alignment, CheckAlignment::Error) } #[inline(always)] @@ -367,39 +355,6 @@ fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>, layout: TyAndLayout<'tcx>) ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks || layout.abi.is_uninhabited() } - fn alignment_check_failed( - ecx: &InterpCx<'mir, 'tcx, Self>, - has: Align, - required: Align, - check: CheckAlignment, - ) -> InterpResult<'tcx, ()> { - let err = err_ub!(AlignmentCheckFailed { has, required }).into(); - match check { - CheckAlignment::Error => Err(err), - CheckAlignment::No => span_bug!( - ecx.cur_span(), - "`alignment_check_failed` called when no alignment check requested" - ), - CheckAlignment::FutureIncompat => { - let (_, backtrace) = err.into_parts(); - backtrace.print_backtrace(); - let (span, frames) = super::get_span_and_frames(&ecx); - - ecx.tcx.emit_spanned_lint( - INVALID_ALIGNMENT, - ecx.stack().iter().find_map(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID), - span, - errors::AlignmentCheckFailed { - has: has.bytes(), - required: required.bytes(), - frames, - }, - ); - Ok(()) - } - } - } - fn load_mir( ecx: &InterpCx<'mir, 'tcx, Self>, instance: ty::InstanceDef<'tcx>, diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index aaa674a598f..73b2251c41e 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -11,11 +11,9 @@ use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::def_id::DefId; -use rustc_target::abi::{Align, Size}; +use rustc_target::abi::Size; use rustc_target::spec::abi::Abi as CallAbi; -use crate::const_eval::CheckAlignment; - use super::{ AllocBytes, AllocId, AllocRange, Allocation, ConstAllocation, FnArg, Frame, ImmTy, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, PlaceTy, Pointer, Provenance, @@ -134,7 +132,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized { const POST_MONO_CHECKS: bool = true; /// Whether memory accesses should be alignment-checked. - fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment; + fn enforce_alignment(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool; /// Whether, when checking alignment, we should look at the actual address and thus support /// custom alignment logic based on whatever the integer address happens to be. @@ -142,13 +140,6 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized { /// If this returns true, Provenance::OFFSET_IS_ADDR must be true. fn use_addr_for_alignment_check(ecx: &InterpCx<'mir, 'tcx, Self>) -> bool; - fn alignment_check_failed( - ecx: &InterpCx<'mir, 'tcx, Self>, - has: Align, - required: Align, - check: CheckAlignment, - ) -> InterpResult<'tcx, ()>; - /// Whether to enforce the validity invariant for a specific layout. fn enforce_validity(ecx: &InterpCx<'mir, 'tcx, Self>, layout: TyAndLayout<'tcx>) -> bool; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 436c4d521a5..13b042cb86a 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -18,7 +18,6 @@ use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt}; use rustc_target::abi::{Align, HasDataLayout, Size}; -use crate::const_eval::CheckAlignment; use crate::fluent_generated as fluent; use super::{ @@ -373,8 +372,7 @@ fn get_ptr_access( self.check_and_deref_ptr( ptr, size, - align, - M::enforce_alignment(self), + M::enforce_alignment(self).then_some(align), CheckInAllocMsg::MemoryAccessTest, |alloc_id, offset, prov| { let (size, align) = self @@ -395,17 +393,10 @@ pub fn check_ptr_access_align( align: Align, msg: CheckInAllocMsg, ) -> InterpResult<'tcx> { - self.check_and_deref_ptr( - ptr, - size, - align, - CheckAlignment::Error, - msg, - |alloc_id, _, _| { - let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?; - Ok((size, align, ())) - }, - )?; + self.check_and_deref_ptr(ptr, size, Some(align), msg, |alloc_id, _, _| { + let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?; + Ok((size, align, ())) + })?; Ok(()) } @@ -419,8 +410,7 @@ fn check_and_deref_ptr( &self, ptr: Pointer>, size: Size, - align: Align, - check: CheckAlignment, + align: Option, msg: CheckInAllocMsg, alloc_size: impl FnOnce( AllocId, @@ -436,8 +426,8 @@ fn check_and_deref_ptr( throw_ub!(DanglingIntPointer(addr, msg)); } // Must be aligned. - if check.should_check() { - self.check_offset_align(addr, align, check)?; + if let Some(align) = align { + self.check_offset_align(addr, align)?; } None } @@ -460,16 +450,16 @@ fn check_and_deref_ptr( } // Test align. Check this last; if both bounds and alignment are violated // we want the error to be about the bounds. - if check.should_check() { + if let Some(align) = align { if M::use_addr_for_alignment_check(self) { // `use_addr_for_alignment_check` can only be true if `OFFSET_IS_ADDR` is true. - self.check_offset_align(ptr.addr().bytes(), align, check)?; + self.check_offset_align(ptr.addr().bytes(), align)?; } else { // Check allocation alignment and offset alignment. if alloc_align.bytes() < align.bytes() { - M::alignment_check_failed(self, alloc_align, align, check)?; + throw_ub!(AlignmentCheckFailed { has: alloc_align, required: align }); } - self.check_offset_align(offset.bytes(), align, check)?; + self.check_offset_align(offset.bytes(), align)?; } } @@ -480,18 +470,16 @@ fn check_and_deref_ptr( }) } - fn check_offset_align( - &self, - offset: u64, - align: Align, - check: CheckAlignment, - ) -> InterpResult<'tcx> { + fn check_offset_align(&self, offset: u64, align: Align) -> InterpResult<'tcx> { if offset % align.bytes() == 0 { Ok(()) } else { // The biggest power of two through which `offset` is divisible. let offset_pow2 = 1 << offset.trailing_zeros(); - M::alignment_check_failed(self, Align::from_bytes(offset_pow2).unwrap(), align, check) + throw_ub!(AlignmentCheckFailed { + has: Align::from_bytes(offset_pow2).unwrap(), + required: align + }); } } } @@ -609,8 +597,7 @@ pub fn get_ptr_alloc<'a>( let ptr_and_alloc = self.check_and_deref_ptr( ptr, size, - align, - M::enforce_alignment(self), + M::enforce_alignment(self).then_some(align), CheckInAllocMsg::MemoryAccessTest, |alloc_id, offset, prov| { let alloc = self.get_alloc_raw(alloc_id)?; diff --git a/compiler/rustc_const_eval/src/interpret/place.rs b/compiler/rustc_const_eval/src/interpret/place.rs index 503004cbbe1..4617e1f0143 100644 --- a/compiler/rustc_const_eval/src/interpret/place.rs +++ b/compiler/rustc_const_eval/src/interpret/place.rs @@ -500,8 +500,7 @@ pub fn check_mplace(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResu .size_and_align_of_mplace(&mplace)? .unwrap_or((mplace.layout.size, mplace.layout.align.abi)); // Due to packed places, only `mplace.align` matters. - let align = - if M::enforce_alignment(self).should_check() { mplace.align } else { Align::ONE }; + let align = if M::enforce_alignment(self) { mplace.align } else { Align::ONE }; self.check_ptr_access_align(mplace.ptr(), size, align, CheckInAllocMsg::DerefTest)?; Ok(()) } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 84434d585d3..6a4b16f50e3 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -505,6 +505,11 @@ macro_rules! add_lint_group { "replaced with another group of lints, see RFC \ for more information", ); + store.register_removed( + "invalid_alignment", + "converted into hard error, see PR #104616 \ + for more information", + ); } fn register_internals(store: &mut LintStore) { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 21575bf6b04..f492d84012b 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -986,45 +986,6 @@ "detects trivial casts of numeric types which could be removed" } -declare_lint! { - /// The `invalid_alignment` lint detects dereferences of misaligned pointers during - /// constant evaluation. - /// - /// ### Example - /// - /// ```rust,compile_fail - /// #![feature(const_mut_refs)] - /// const FOO: () = unsafe { - /// let x = &[0_u8; 4]; - /// let y = x.as_ptr().cast::(); - /// let mut z = 123; - /// y.copy_to_nonoverlapping(&mut z, 1); // the address of a `u8` array is unknown - /// // and thus we don't know if it is aligned enough for copying a `u32`. - /// }; - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// The compiler allowed dereferencing raw pointers irrespective of alignment - /// during const eval due to the const evaluator at the time not making it easy - /// or cheap to check. Now that it is both, this is not accepted anymore. - /// - /// Since it was undefined behaviour to begin with, this breakage does not violate - /// Rust's stability guarantees. Using undefined behaviour can cause arbitrary - /// behaviour, including failure to build. - /// - /// [future-incompatible]: ../index.md#future-incompatible-lints - pub INVALID_ALIGNMENT, - Deny, - "raw pointers must be aligned before dereferencing", - @future_incompatible = FutureIncompatibleInfo { - reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps, - reference: "issue #68585 ", - }; -} - declare_lint! { /// The `exported_private_dependencies` lint detects private dependencies /// that are exposed in a public interface. @@ -3378,7 +3339,6 @@ INDIRECT_STRUCTURAL_MATCH, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, INLINE_NO_SANITIZE, - INVALID_ALIGNMENT, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS, INVALID_TYPE_PARAM_DEFAULT, diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs index 4fc78b28580..d379db5e07d 100644 --- a/compiler/rustc_mir_transform/src/const_prop.rs +++ b/compiler/rustc_mir_transform/src/const_prop.rs @@ -2,8 +2,6 @@ //! assertion failures use either::Right; - -use rustc_const_eval::const_eval::CheckAlignment; use rustc_const_eval::ReportErrorExt; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def::DefKind; @@ -16,7 +14,7 @@ use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::{self, GenericArgs, Instance, ParamEnv, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::{def_id::DefId, Span}; -use rustc_target::abi::{self, Align, HasDataLayout, Size, TargetDataLayout}; +use rustc_target::abi::{self, HasDataLayout, Size, TargetDataLayout}; use rustc_target::spec::abi::Abi as CallAbi; use crate::dataflow_const_prop::Patch; @@ -141,27 +139,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> type MemoryKind = !; #[inline(always)] - fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment { - // We do not check for alignment to avoid having to carry an `Align` - // in `ConstValue::Indirect`. - CheckAlignment::No + fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + false // no reason to enforce alignment } #[inline(always)] fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool { false // for now, we don't enforce validity } - fn alignment_check_failed( - ecx: &InterpCx<'mir, 'tcx, Self>, - _has: Align, - _required: Align, - _check: CheckAlignment, - ) -> InterpResult<'tcx, ()> { - span_bug!( - ecx.cur_span(), - "`alignment_check_failed` called when no alignment check requested" - ) - } fn load_mir( _ecx: &InterpCx<'mir, 'tcx, Self>, diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs index 7b14fef6153..85a0be8a44c 100644 --- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs +++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs @@ -2,7 +2,6 @@ //! //! Currently, this pass only propagates scalar values. -use rustc_const_eval::const_eval::CheckAlignment; use rustc_const_eval::interpret::{ImmTy, Immediate, InterpCx, OpTy, Projectable}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::DefKind; @@ -17,7 +16,7 @@ use rustc_mir_dataflow::{lattice::FlatSet, Analysis, Results, ResultsVisitor}; use rustc_span::def_id::DefId; use rustc_span::DUMMY_SP; -use rustc_target::abi::{Align, FieldIdx, VariantIdx}; +use rustc_target::abi::{FieldIdx, VariantIdx}; use crate::MirPass; @@ -709,23 +708,13 @@ impl<'mir, 'tcx: 'mir> rustc_const_eval::interpret::Machine<'mir, 'tcx> for Dumm const PANIC_ON_ALLOC_FAIL: bool = true; #[inline(always)] - fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> CheckAlignment { - // We do not check for alignment to avoid having to carry an `Align` - // in `ConstValue::ByRef`. - CheckAlignment::No + fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool { + false // no reason to enforce alignment } fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool { unimplemented!() } - fn alignment_check_failed( - _ecx: &InterpCx<'mir, 'tcx, Self>, - _has: Align, - _required: Align, - _check: CheckAlignment, - ) -> interpret::InterpResult<'tcx, ()> { - unimplemented!() - } fn before_access_global( _tcx: TyCtxt<'tcx>, diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index f1c50794ca8..457cfc25357 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -12,7 +12,6 @@ use rand::SeedableRng; use rustc_ast::ast::Mutability; -use rustc_const_eval::const_eval::CheckAlignment; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; #[allow(unused)] use rustc_data_structures::static_assert_size; @@ -886,12 +885,8 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> { const PANIC_ON_ALLOC_FAIL: bool = false; #[inline(always)] - fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> CheckAlignment { - if ecx.machine.check_alignment == AlignmentCheck::None { - CheckAlignment::No - } else { - CheckAlignment::Error - } + fn enforce_alignment(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { + ecx.machine.check_alignment != AlignmentCheck::None } #[inline(always)] @@ -899,15 +894,6 @@ fn use_addr_for_alignment_check(ecx: &MiriInterpCx<'mir, 'tcx>) -> bool { ecx.machine.check_alignment == AlignmentCheck::Int } - fn alignment_check_failed( - _ecx: &InterpCx<'mir, 'tcx, Self>, - has: Align, - required: Align, - _check: CheckAlignment, - ) -> InterpResult<'tcx, ()> { - throw_ub!(AlignmentCheckFailed { has, required }) - } - #[inline(always)] fn enforce_validity(ecx: &MiriInterpCx<'mir, 'tcx>, _layout: TyAndLayout<'tcx>) -> bool { ecx.machine.validate From bd33846253b77ec876e130d8e010c221c80182e0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 4 Sep 2023 09:32:23 +0200 Subject: [PATCH 08/58] add misalignment const-eval test and some other raw pointer shenanigans while we are at it --- tests/ui/consts/const-eval/raw-pointer-ub.rs | 36 +++++++++++++++++++ .../consts/const-eval/raw-pointer-ub.stderr | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tests/ui/consts/const-eval/raw-pointer-ub.rs create mode 100644 tests/ui/consts/const-eval/raw-pointer-ub.stderr diff --git a/tests/ui/consts/const-eval/raw-pointer-ub.rs b/tests/ui/consts/const-eval/raw-pointer-ub.rs new file mode 100644 index 00000000000..e53865309eb --- /dev/null +++ b/tests/ui/consts/const-eval/raw-pointer-ub.rs @@ -0,0 +1,36 @@ +// normalize-stderr-test "alloc\d+" -> "allocN" +#![feature(const_pointer_byte_offsets)] +#![feature(pointer_byte_offsets)] +#![feature(const_mut_refs)] + +const MISALIGNED_LOAD: () = unsafe { + let mem = [0u32; 8]; + let ptr = mem.as_ptr().byte_add(1); + let _val = *ptr; //~ERROR: evaluation of constant value failed + //~^NOTE: accessing memory with alignment 1, but alignment 4 is required +}; + +const MISALIGNED_STORE: () = unsafe { + let mut mem = [0u32; 8]; + let ptr = mem.as_mut_ptr().byte_add(1); + *ptr = 0; //~ERROR: evaluation of constant value failed + //~^NOTE: accessing memory with alignment 1, but alignment 4 is required +}; + +const MISALIGNED_COPY: () = unsafe { + let x = &[0_u8; 4]; + let y = x.as_ptr().cast::(); + let mut z = 123; + y.copy_to_nonoverlapping(&mut z, 1); + //~^NOTE + // The actual error points into the implementation of `copy_to_nonoverlapping`. +}; + +const OOB: () = unsafe { + let mem = [0u32; 1]; + let ptr = mem.as_ptr().cast::(); + let _val = *ptr; //~ERROR: evaluation of constant value failed + //~^NOTE: size 4, so pointer to 8 bytes starting at offset 0 is out-of-bounds +}; + +fn main() {} diff --git a/tests/ui/consts/const-eval/raw-pointer-ub.stderr b/tests/ui/consts/const-eval/raw-pointer-ub.stderr new file mode 100644 index 00000000000..96b7f4f58f9 --- /dev/null +++ b/tests/ui/consts/const-eval/raw-pointer-ub.stderr @@ -0,0 +1,36 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/raw-pointer-ub.rs:9:16 + | +LL | let _val = *ptr; + | ^^^^ accessing memory with alignment 1, but alignment 4 is required + +error[E0080]: evaluation of constant value failed + --> $DIR/raw-pointer-ub.rs:16:5 + | +LL | *ptr = 0; + | ^^^^^^^^ accessing memory with alignment 1, but alignment 4 is required + +error[E0080]: evaluation of constant value failed + --> $SRC_DIR/core/src/intrinsics.rs:LL:COL + | + = note: accessing memory with alignment 1, but alignment 4 is required + | +note: inside `copy_nonoverlapping::` + --> $SRC_DIR/core/src/intrinsics.rs:LL:COL +note: inside `ptr::const_ptr::::copy_to_nonoverlapping` + --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL +note: inside `MISALIGNED_COPY` + --> $DIR/raw-pointer-ub.rs:24:5 + | +LL | y.copy_to_nonoverlapping(&mut z, 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0080]: evaluation of constant value failed + --> $DIR/raw-pointer-ub.rs:32:16 + | +LL | let _val = *ptr; + | ^^^^ dereferencing pointer failed: allocN has size 4, so pointer to 8 bytes starting at offset 0 is out-of-bounds + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0080`. From 58ed8ad0dfd224fbdd77ba38f4f9f71d9261674b Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:09:32 +0200 Subject: [PATCH 09/58] Stabilize `const_maybe_uninit_assume_init_read` --- library/core/src/mem/maybe_uninit.rs | 5 ++++- library/core/tests/lib.rs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs index d09a24b4b1d..6274a916f3e 100644 --- a/library/core/src/mem/maybe_uninit.rs +++ b/library/core/src/mem/maybe_uninit.rs @@ -686,7 +686,10 @@ pub const fn as_mut_ptr(&mut self) -> *mut T { /// // they both get dropped! /// ``` #[stable(feature = "maybe_uninit_extra", since = "1.60.0")] - #[rustc_const_unstable(feature = "const_maybe_uninit_assume_init_read", issue = "63567")] + #[rustc_const_stable( + feature = "const_maybe_uninit_assume_init_read", + since = "CURRENT_RUSTC_VERSION" + )] #[inline(always)] #[track_caller] pub const unsafe fn assume_init_read(&self) -> T { diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 773f2b955d8..98f141e44d2 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -15,7 +15,6 @@ #![feature(const_hash)] #![feature(const_heap)] #![feature(const_maybe_uninit_as_mut_ptr)] -#![feature(const_maybe_uninit_assume_init_read)] #![feature(const_nonnull_new)] #![feature(const_pointer_byte_offsets)] #![feature(const_pointer_is_aligned)] From 8f9cd3d1e820eb114c32627cc34f3897cda7903e Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 15:17:52 +0200 Subject: [PATCH 10/58] Rework range splitting api --- .../src/thir/pattern/deconstruct_pat.rs | 202 ++++++++---------- 1 file changed, 90 insertions(+), 112 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 3ee4befa121..d63190985a3 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -39,7 +39,7 @@ //! //! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting -//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`SplitIntRange`]; for slices, see +//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`IntRange::split`]; for slices, see //! [`SplitVarLenSlice`]. use std::cell::Cell; @@ -186,6 +186,93 @@ fn suspicious_intersection(&self, other: &Self) -> bool { (lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton() } + /// See `Constructor::is_covered_by` + fn is_covered_by(&self, other: &Self) -> bool { + if self.intersection(other).is_some() { + // Constructor splitting should ensure that all intersections we encounter are actually + // inclusions. + assert!(self.is_subrange(other)); + true + } else { + false + } + } + + /// Partition a range of integers into disjoint subranges. This does constructor splitting for + /// integer ranges as explained at the top of the file. + /// + /// This returns an output that covers `self`. The output is split so that the only + /// intersections between an output range and a column range are inclusions. No output range + /// straddles the boundary of one of the inputs. + /// + /// The following input: + /// ```text + /// |-------------------------| // `self` + /// |------| |----------| |----| + /// |-------| |-------| + /// ``` + /// would be iterated over as follows: + /// ```text + /// ||---|--||-|---|---|---|--| + /// ``` + fn split( + &self, + column_ranges: impl Iterator, + ) -> impl Iterator { + /// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be + /// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + enum IntBoundary { + JustBefore(u128), + AfterMax, + } + + fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] { + use IntBoundary::*; + let (lo, hi) = range.boundaries(); + let lo = JustBefore(lo); + let hi = match hi.checked_add(1) { + Some(m) => JustBefore(m), + None => AfterMax, + }; + [lo, hi] + } + + // The boundaries of ranges in `column_ranges` intersected with `self`. + let mut boundaries: Vec = column_ranges + .filter_map(|r| self.intersection(&r)) + .map(unpack_intrange) + .flat_map(|[lo, hi]| [lo, hi]) + .collect(); + boundaries.sort_unstable(); + + let [self_start, self_end] = unpack_intrange(self.clone()); + // Gather pairs of adjacent boundaries. + let mut prev_bdy = self_start; + boundaries + .into_iter() + // End with the end of the range. + .chain(once(self_end)) + // List pairs of adjacent boundaries. + .map(move |bdy| { + let ret = (prev_bdy, bdy); + prev_bdy = bdy; + ret + }) + // Skip duplicates. + .filter(|&(prev_bdy, bdy)| prev_bdy != bdy) + // Convert back to ranges. + .map(move |(prev_bdy, bdy)| { + use IntBoundary::*; + let range = match (prev_bdy, bdy) { + (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), + (JustBefore(n), AfterMax) => n..=u128::MAX, + _ => unreachable!(), // Ruled out by the sorting and filtering we did + }; + IntRange { range } + }) + } + /// Only used for displaying the range properly. fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); @@ -254,18 +341,6 @@ pub(super) fn lint_overlapping_range_endpoints<'a, 'p: 'a, 'tcx: 'a>( ); } } - - /// See `Constructor::is_covered_by` - fn is_covered_by(&self, other: &Self) -> bool { - if self.intersection(other).is_some() { - // Constructor splitting should ensure that all intersections we encounter are actually - // inclusions. - assert!(self.is_subrange(other)); - true - } else { - false - } - } } /// Note: this is often not what we want: e.g. `false` is converted into the range `0..=0` and @@ -279,101 +354,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } } -/// Represents a border between 2 integers. Because the intervals spanning borders must be able to -/// cover every integer, we need to be able to represent 2^128 + 1 such borders. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -enum IntBorder { - JustBefore(u128), - AfterMax, -} - -/// A range of integers that is partitioned into disjoint subranges. This does constructor -/// splitting for integer ranges as explained at the top of the file. -/// -/// This is fed multiple ranges, and returns an output that covers the input, but is split so that -/// the only intersections between an output range and a seen range are inclusions. No output range -/// straddles the boundary of one of the inputs. -/// -/// The following input: -/// ```text -/// |-------------------------| // `self` -/// |------| |----------| |----| -/// |-------| |-------| -/// ``` -/// would be iterated over as follows: -/// ```text -/// ||---|--||-|---|---|---|--| -/// ``` -#[derive(Debug, Clone)] -struct SplitIntRange { - /// The range we are splitting - range: IntRange, - /// The borders of ranges we have seen. They are all contained within `range`. This is kept - /// sorted. - borders: Vec, -} - -impl SplitIntRange { - fn new(range: IntRange) -> Self { - SplitIntRange { range, borders: Vec::new() } - } - - /// Internal use - fn to_borders(r: IntRange) -> [IntBorder; 2] { - use IntBorder::*; - let (lo, hi) = r.boundaries(); - let lo = JustBefore(lo); - let hi = match hi.checked_add(1) { - Some(m) => JustBefore(m), - None => AfterMax, - }; - [lo, hi] - } - - /// Add ranges relative to which we split. - fn split(&mut self, ranges: impl Iterator) { - let this_range = &self.range; - let included_ranges = ranges.filter_map(|r| this_range.intersection(&r)); - let included_borders = included_ranges.flat_map(|r| { - let borders = Self::to_borders(r); - once(borders[0]).chain(once(borders[1])) - }); - self.borders.extend(included_borders); - self.borders.sort_unstable(); - } - - /// Iterate over the contained ranges. - fn iter(&self) -> impl Iterator + Captures<'_> { - use IntBorder::*; - - let self_range = Self::to_borders(self.range.clone()); - // Start with the start of the range. - let mut prev_border = self_range[0]; - self.borders - .iter() - .copied() - // End with the end of the range. - .chain(once(self_range[1])) - // List pairs of adjacent borders. - .map(move |border| { - let ret = (prev_border, border); - prev_border = border; - ret - }) - // Skip duplicates. - .filter(|(prev_border, border)| prev_border != border) - // Finally, convert to ranges. - .map(move |(prev_border, border)| { - let range = match (prev_border, border) { - (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), - (JustBefore(n), AfterMax) => n..=u128::MAX, - _ => unreachable!(), // Ruled out by the sorting and filtering we did - }; - IntRange { range } - }) - } -} - #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum SliceKind { /// Patterns of length `n` (`[x, y]`). @@ -733,10 +713,8 @@ pub(super) fn split<'a>( // Fast-track if the range is trivial. In particular, we don't do the overlapping // ranges check. IntRange(ctor_range) if !ctor_range.is_singleton() => { - let mut split_range = SplitIntRange::new(ctor_range.clone()); - let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()); - split_range.split(int_ranges.cloned()); - split_range.iter().map(IntRange).collect() + let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()).cloned(); + ctor_range.split(int_ranges).map(IntRange).collect() } &Slice(Slice { kind: VarLen(self_prefix, self_suffix), array_len }) => { let mut split_self = SplitVarLenSlice::new(self_prefix, self_suffix, array_len); From 590edee3209906b3906f09bad029586b0a15e29f Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 14:38:40 +0200 Subject: [PATCH 11/58] Rework slice splitting api --- .../src/thir/pattern/deconstruct_pat.rs | 255 +++++++++--------- 1 file changed, 121 insertions(+), 134 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index d63190985a3..6755e665bba 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -40,7 +40,7 @@ //! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting //! wildcards, see [`SplitWildcard`]; for integer ranges, see [`IntRange::split`]; for slices, see -//! [`SplitVarLenSlice`]. +//! [`Slice::split`]. use std::cell::Cell; use std::cmp::{self, max, min, Ordering}; @@ -410,141 +410,130 @@ fn arity(self) -> usize { fn is_covered_by(self, other: Self) -> bool { other.kind.covers_length(self.arity()) } -} -/// This computes constructor splitting for variable-length slices, as explained at the top of the -/// file. -/// -/// A slice pattern `[x, .., y]` behaves like the infinite or-pattern `[x, y] | [x, _, y] | [x, _, -/// _, y] | ...`. The corresponding value constructors are fixed-length array constructors above a -/// given minimum length. We obviously can't list this infinitude of constructors. Thankfully, -/// it turns out that for each finite set of slice patterns, all sufficiently large array lengths -/// are equivalent. -/// -/// Let's look at an example, where we are trying to split the last pattern: -/// ``` -/// # fn foo(x: &[bool]) { -/// match x { -/// [true, true, ..] => {} -/// [.., false, false] => {} -/// [..] => {} -/// } -/// # } -/// ``` -/// Here are the results of specialization for the first few lengths: -/// ``` -/// # fn foo(x: &[bool]) { match x { -/// // length 0 -/// [] => {} -/// // length 1 -/// [_] => {} -/// // length 2 -/// [true, true] => {} -/// [false, false] => {} -/// [_, _] => {} -/// // length 3 -/// [true, true, _ ] => {} -/// [_, false, false] => {} -/// [_, _, _ ] => {} -/// // length 4 -/// [true, true, _, _ ] => {} -/// [_, _, false, false] => {} -/// [_, _, _, _ ] => {} -/// // length 5 -/// [true, true, _, _, _ ] => {} -/// [_, _, _, false, false] => {} -/// [_, _, _, _, _ ] => {} -/// # _ => {} -/// # }} -/// ``` -/// -/// If we went above length 5, we would simply be inserting more columns full of wildcards in the -/// middle. This means that the set of witnesses for length `l >= 5` if equivalent to the set for -/// any other `l' >= 5`: simply add or remove wildcards in the middle to convert between them. -/// -/// This applies to any set of slice patterns: there will be a length `L` above which all lengths -/// behave the same. This is exactly what we need for constructor splitting. Therefore a -/// variable-length slice can be split into a variable-length slice of minimal length `L`, and many -/// fixed-length slices of lengths `< L`. -/// -/// For each variable-length pattern `p` with a prefix of length `plₚ` and suffix of length `slₚ`, -/// only the first `plₚ` and the last `slₚ` elements are examined. Therefore, as long as `L` is -/// positive (to avoid concerns about empty types), all elements after the maximum prefix length -/// and before the maximum suffix length are not examined by any variable-length pattern, and -/// therefore can be added/removed without affecting them - creating equivalent patterns from any -/// sufficiently-large length. -/// -/// Of course, if fixed-length patterns exist, we must be sure that our length is large enough to -/// miss them all, so we can pick `L = max(max(FIXED_LEN)+1, max(PREFIX_LEN) + max(SUFFIX_LEN))` -/// -/// `max_slice` below will be made to have arity `L`. -#[derive(Debug)] -struct SplitVarLenSlice { - /// If the type is an array, this is its size. - array_len: Option, - /// The arity of the input slice. - arity: usize, - /// The smallest slice bigger than any slice seen. `max_slice.arity()` is the length `L` - /// described above. - max_slice: SliceKind, -} - -impl SplitVarLenSlice { - fn new(prefix: usize, suffix: usize, array_len: Option) -> Self { - SplitVarLenSlice { array_len, arity: prefix + suffix, max_slice: VarLen(prefix, suffix) } - } - - /// Pass a set of slices relative to which to split this one. - fn split(&mut self, slices: impl Iterator) { - let VarLen(max_prefix_len, max_suffix_len) = &mut self.max_slice else { - // No need to split - return; - }; - // We grow `self.max_slice` to be larger than all slices encountered, as described above. - // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that - // `L = max_prefix_len + max_suffix_len`. - let mut max_fixed_len = 0; - for slice in slices { - match slice { - FixedLen(len) => { - max_fixed_len = cmp::max(max_fixed_len, len); + /// This computes constructor splitting for variable-length slices, as explained at the top of + /// the file. + /// + /// A slice pattern `[x, .., y]` behaves like the infinite or-pattern `[x, y] | [x, _, y] | [x, + /// _, _, y] | etc`. The corresponding value constructors are fixed-length array constructors of + /// corresponding lengths. We obviously can't list this infinitude of constructors. + /// Thankfully, it turns out that for each finite set of slice patterns, all sufficiently large + /// array lengths are equivalent. + /// + /// Let's look at an example, where we are trying to split the last pattern: + /// ``` + /// # fn foo(x: &[bool]) { + /// match x { + /// [true, true, ..] => {} + /// [.., false, false] => {} + /// [..] => {} // `self` + /// } + /// # } + /// ``` + /// Here are the results of specialization for the first few lengths: + /// ``` + /// # fn foo(x: &[bool]) { match x { + /// // length 0 + /// [] => {} + /// // length 1 + /// [_] => {} + /// // length 2 + /// [true, true] => {} + /// [false, false] => {} + /// [_, _] => {} + /// // length 3 + /// [true, true, _ ] => {} + /// [_, false, false] => {} + /// [_, _, _ ] => {} + /// // length 4 + /// [true, true, _, _ ] => {} + /// [_, _, false, false] => {} + /// [_, _, _, _ ] => {} + /// // length 5 + /// [true, true, _, _, _ ] => {} + /// [_, _, _, false, false] => {} + /// [_, _, _, _, _ ] => {} + /// # _ => {} + /// # }} + /// ``` + /// + /// We see that above length 4, we are simply inserting columns full of wildcards in the middle. + /// This means that specialization and witness computation with slices of length `l >= 4` will + /// give equivalent results independently of `l`. This applies to any set of slice patterns: + /// there will be a length `L` above which all lengths behave the same. This is exactly what we + /// need for constructor splitting. + /// + /// A variable-length slice pattern covers all lengths from its arity up to infinity. As we just + /// saw, we can split this in two: lengths below `L` are treated individually with a + /// fixed-length slice each; lengths above `L` are grouped into a single variable-length slice + /// constructor. + /// + /// For each variable-length slice pattern `p` with a prefix of length `plₚ` and suffix of + /// length `slₚ`, only the first `plₚ` and the last `slₚ` elements are examined. Therefore, as + /// long as `L` is positive (to avoid concerns about empty types), all elements after the + /// maximum prefix length and before the maximum suffix length are not examined by any + /// variable-length pattern, and therefore can be ignored. This gives us a way to compute `L`. + /// + /// Additionally, if fixed-length patterns exist, we must pick an `L` large enough to miss them, + /// so we can pick `L = max(max(FIXED_LEN)+1, max(PREFIX_LEN) + max(SUFFIX_LEN))`. + /// `max_slice` below will be made to have this arity `L`. + /// + /// If `self` is fixed-length, it is returned as-is. + fn split(self, column_slices: impl Iterator) -> impl Iterator { + // Range of lengths below `L`. + let smaller_lengths; + let mut max_slice = self.kind; + match &mut max_slice { + VarLen(max_prefix_len, max_suffix_len) => { + // We grow `max_slice` to be larger than all slices encountered, as described above. + // For diagnostics, we keep the prefix and suffix lengths separate, but grow them so that + // `L = max_prefix_len + max_suffix_len`. + let mut max_fixed_len = 0; + for slice in column_slices { + match slice.kind { + FixedLen(len) => { + max_fixed_len = cmp::max(max_fixed_len, len); + } + VarLen(prefix, suffix) => { + *max_prefix_len = cmp::max(*max_prefix_len, prefix); + *max_suffix_len = cmp::max(*max_suffix_len, suffix); + } + } } - VarLen(prefix, suffix) => { - *max_prefix_len = cmp::max(*max_prefix_len, prefix); - *max_suffix_len = cmp::max(*max_suffix_len, suffix); + // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and + // suffix separate. + if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len { + // The subtraction can't overflow thanks to the above check. + // The new `max_prefix_len` is larger than its previous value. + *max_prefix_len = max_fixed_len + 1 - *max_suffix_len; } + + // We cap the arity of `max_slice` at the array size. + match self.array_len { + Some(len) if max_slice.arity() >= len => max_slice = FixedLen(len), + _ => {} + } + + smaller_lengths = match self.array_len { + // The only admissible fixed-length slice is one of the array size. Whether `max_slice` + // is fixed-length or variable-length, it will be the only relevant slice to output + // here. + Some(_) => 0..0, // empty range + // We need to cover all arities in the range `(arity..infinity)`. We split that + // range into two: lengths smaller than `max_slice.arity()` are treated + // independently as fixed-lengths slices, and lengths above are captured by + // `max_slice`. + None => self.arity()..max_slice.arity(), + }; + } + FixedLen(_) => { + // No need to split. + smaller_lengths = 0..0; } - } - // We want `L = max(L, max_fixed_len + 1)`, modulo the fact that we keep prefix and - // suffix separate. - if max_fixed_len + 1 >= *max_prefix_len + *max_suffix_len { - // The subtraction can't overflow thanks to the above check. - // The new `max_prefix_len` is larger than its previous value. - *max_prefix_len = max_fixed_len + 1 - *max_suffix_len; - } - - // We cap the arity of `max_slice` at the array size. - match self.array_len { - Some(len) if self.max_slice.arity() >= len => self.max_slice = FixedLen(len), - _ => {} - } - } - - /// Iterate over the partition of this slice. - fn iter(&self) -> impl Iterator + Captures<'_> { - let smaller_lengths = match self.array_len { - // The only admissible fixed-length slice is one of the array size. Whether `max_slice` - // is fixed-length or variable-length, it will be the only relevant slice to output - // here. - Some(_) => 0..0, // empty range - // We cover all arities in the range `(self.arity..infinity)`. We split that range into - // two: lengths smaller than `max_slice.arity()` are treated independently as - // fixed-lengths slices, and lengths above are captured by `max_slice`. - None => self.arity..self.max_slice.arity(), }; smaller_lengths .map(FixedLen) - .chain(once(self.max_slice)) + .chain(once(max_slice)) .map(move |kind| Slice::new(self.array_len, kind)) } } @@ -716,11 +705,9 @@ pub(super) fn split<'a>( let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()).cloned(); ctor_range.split(int_ranges).map(IntRange).collect() } - &Slice(Slice { kind: VarLen(self_prefix, self_suffix), array_len }) => { - let mut split_self = SplitVarLenSlice::new(self_prefix, self_suffix, array_len); - let slices = ctors.filter_map(|c| c.as_slice()).map(|s| s.kind); - split_self.split(slices); - split_self.iter().map(Slice).collect() + &Slice(slice @ Slice { kind: VarLen(..), .. }) => { + let slices = ctors.filter_map(|c| c.as_slice()); + slice.split(slices).map(Slice).collect() } // Any other constructor can be used unchanged. _ => smallvec![self.clone()], From 429770a48eb7dcb11831d4670a25a01c698f704c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 16:21:40 +0200 Subject: [PATCH 12/58] Splitting ensures subrange comparison is all we need --- .../src/thir/pattern/deconstruct_pat.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6755e665bba..b4bc4569878 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -186,18 +186,6 @@ fn suspicious_intersection(&self, other: &Self) -> bool { (lo == other_hi || hi == other_lo) && !self.is_singleton() && !other.is_singleton() } - /// See `Constructor::is_covered_by` - fn is_covered_by(&self, other: &Self) -> bool { - if self.intersection(other).is_some() { - // Constructor splitting should ensure that all intersections we encounter are actually - // inclusions. - assert!(self.is_subrange(other)); - true - } else { - false - } - } - /// Partition a range of integers into disjoint subranges. This does constructor splitting for /// integer ranges as explained at the top of the file. /// @@ -730,7 +718,7 @@ pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, - (IntRange(self_range), IntRange(other_range)) => self_range.is_covered_by(other_range), + (IntRange(self_range), IntRange(other_range)) => self_range.is_subrange(other_range), (F32Range(self_from, self_to, self_end), F32Range(other_from, other_to, other_end)) => { self_from.ge(other_from) && match self_to.partial_cmp(other_to) { From c1800ef93fa44ac583c213632a315956b5b2f31b Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 15:30:05 +0200 Subject: [PATCH 13/58] Replace SplitWildcard with a cleaner ConstructorSet abstraction --- .../src/thir/pattern/deconstruct_pat.rs | 636 +++++++++++------- .../src/thir/pattern/usefulness.rs | 132 ++-- 2 files changed, 442 insertions(+), 326 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index b4bc4569878..ab30f5109c7 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -39,8 +39,8 @@ //! //! Splitting is implemented in the [`Constructor::split`] function. We don't do splitting for //! or-patterns; instead we just try the alternatives one-by-one. For details on splitting -//! wildcards, see [`SplitWildcard`]; for integer ranges, see [`IntRange::split`]; for slices, see -//! [`Slice::split`]. +//! wildcards, see [`Constructor::split`]; for integer ranges, see +//! [`IntRange::split`]; for slices, see [`Slice::split`]. use std::cell::Cell; use std::cmp::{self, max, min, Ordering}; @@ -52,6 +52,7 @@ use rustc_apfloat::ieee::{DoubleS, IeeeFloat, SingleS}; use rustc_data_structures::captures::Captures; +use rustc_data_structures::fx::FxHashSet; use rustc_hir::{HirId, RangeEnd}; use rustc_index::Idx; use rustc_middle::middle::stability::EvalResult; @@ -86,6 +87,13 @@ fn expand<'p, 'tcx>(pat: &'p Pat<'tcx>, vec: &mut Vec<&'p Pat<'tcx>>) { pats } +/// Whether we have seen a constructor in the column or not. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum Presence { + Unseen, + Seen, +} + /// An inclusive interval, used for precise integer exhaustiveness checking. /// `IntRange`s always store a contiguous range. This means that values are /// encoded such that `0` encodes the minimum value for the integer, @@ -203,10 +211,12 @@ fn suspicious_intersection(&self, other: &Self) -> bool { /// ```text /// ||---|--||-|---|---|---|--| /// ``` + /// + /// Additionally, we track for each output range whether it is covered by one of the column ranges or not. fn split( &self, column_ranges: impl Iterator, - ) -> impl Iterator { + ) -> impl Iterator { /// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be /// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -227,41 +237,57 @@ enum IntBoundary { } // The boundaries of ranges in `column_ranges` intersected with `self`. - let mut boundaries: Vec = column_ranges + // We do parenthesis matching for input ranges. A boundary counts as +1 if it starts + // a range and -1 if it ends it. When the count is > 0 between two boundaries, we + // are within an input range. + let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges .filter_map(|r| self.intersection(&r)) .map(unpack_intrange) - .flat_map(|[lo, hi]| [lo, hi]) + .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) .collect(); boundaries.sort_unstable(); + // Counter for parenthesis matching. + let mut paren_counter = 0isize; + let boundaries_with_paren_counts = boundaries + .into_iter() + // Accumulate parenthesis counts. + .map(move |(bdy, delta)| { + paren_counter += delta; + (bdy, paren_counter) + }); + let [self_start, self_end] = unpack_intrange(self.clone()); // Gather pairs of adjacent boundaries. let mut prev_bdy = self_start; - boundaries - .into_iter() - // End with the end of the range. - .chain(once(self_end)) + let mut prev_paren_count = 0; + boundaries_with_paren_counts + // End with the end of the range. The count is irrelevant. + .chain(once((self_end, 0))) // List pairs of adjacent boundaries. - .map(move |bdy| { - let ret = (prev_bdy, bdy); + .map(move |(bdy, paren_count)| { + let ret = (prev_bdy, prev_paren_count, bdy); prev_bdy = bdy; + prev_paren_count = paren_count; ret }) // Skip duplicates. - .filter(|&(prev_bdy, bdy)| prev_bdy != bdy) + .filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy) // Convert back to ranges. - .map(move |(prev_bdy, bdy)| { + .map(move |(prev_bdy, paren_count, bdy)| { use IntBoundary::*; + use Presence::*; + let presence = if paren_count > 0 { Seen } else { Unseen }; let range = match (prev_bdy, bdy) { (JustBefore(n), JustBefore(m)) if n < m => n..=(m - 1), (JustBefore(n), AfterMax) => n..=u128::MAX, _ => unreachable!(), // Ruled out by the sorting and filtering we did }; - IntRange { range } + (presence, IntRange { range }) }) } - /// Only used for displaying the range properly. + /// Only used for displaying the range. fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> { let (lo, hi) = self.boundaries(); @@ -414,7 +440,7 @@ fn is_covered_by(self, other: Self) -> bool { /// match x { /// [true, true, ..] => {} /// [.., false, false] => {} - /// [..] => {} // `self` + /// [..] => {} /// } /// # } /// ``` @@ -447,9 +473,9 @@ fn is_covered_by(self, other: Self) -> bool { /// /// We see that above length 4, we are simply inserting columns full of wildcards in the middle. /// This means that specialization and witness computation with slices of length `l >= 4` will - /// give equivalent results independently of `l`. This applies to any set of slice patterns: - /// there will be a length `L` above which all lengths behave the same. This is exactly what we - /// need for constructor splitting. + /// give equivalent results regardless of `l`. This applies to any set of slice patterns: there + /// will be a length `L` above which all lengths behave the same. This is exactly what we need + /// for constructor splitting. /// /// A variable-length slice pattern covers all lengths from its arity up to infinity. As we just /// saw, we can split this in two: lengths below `L` are treated individually with a @@ -467,10 +493,18 @@ fn is_covered_by(self, other: Self) -> bool { /// `max_slice` below will be made to have this arity `L`. /// /// If `self` is fixed-length, it is returned as-is. - fn split(self, column_slices: impl Iterator) -> impl Iterator { + /// + /// Additionally, we track for each output slice whether it is covered by one of the column slices or not. + fn split( + self, + column_slices: impl Iterator, + ) -> impl Iterator { // Range of lengths below `L`. let smaller_lengths; + let arity = self.arity(); let mut max_slice = self.kind; + let mut min_var_len = usize::MAX; + let mut seen_fixed_lens = FxHashSet::default(); match &mut max_slice { VarLen(max_prefix_len, max_suffix_len) => { // We grow `max_slice` to be larger than all slices encountered, as described above. @@ -481,10 +515,14 @@ fn split(self, column_slices: impl Iterator) -> impl Iterator { max_fixed_len = cmp::max(max_fixed_len, len); + if arity <= len { + seen_fixed_lens.insert(len); + } } VarLen(prefix, suffix) => { *max_prefix_len = cmp::max(*max_prefix_len, prefix); *max_suffix_len = cmp::max(*max_suffix_len, suffix); + min_var_len = cmp::min(min_var_len, prefix + suffix); } } } @@ -515,14 +553,32 @@ fn split(self, column_slices: impl Iterator) -> impl Iterator { - // No need to split. + // No need to split here. We only track presence. + for slice in column_slices { + match slice.kind { + FixedLen(len) => { + if len == arity { + seen_fixed_lens.insert(len); + } + } + VarLen(prefix, suffix) => { + min_var_len = cmp::min(min_var_len, prefix + suffix); + } + } + } smaller_lengths = 0..0; } }; - smaller_lengths - .map(FixedLen) - .chain(once(max_slice)) - .map(move |kind| Slice::new(self.array_len, kind)) + + smaller_lengths.map(FixedLen).chain(once(max_slice)).map(move |kind| { + let arity = kind.arity(); + let seen = if min_var_len <= arity || seen_fixed_lens.contains(&arity) { + Presence::Seen + } else { + Presence::Unseen + }; + (seen, Slice::new(self.array_len, kind)) + }) } } @@ -556,8 +612,8 @@ pub(super) enum Constructor<'tcx> { /// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, - /// Stands for constructors that are not seen in the matrix, as explained in the documentation - /// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` + /// Stands for constructors that are not seen in the matrix, as explained in the code for + /// [`Constructor::split`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` /// lint. Missing { nonexhaustive_enum_missing_real_variants: bool, @@ -577,13 +633,18 @@ pub(super) fn is_non_exhaustive(&self) -> bool { matches!(self, NonExhaustive) } + pub(super) fn as_variant(&self) -> Option { + match self { + Variant(i) => Some(*i), + _ => None, + } + } fn as_int_range(&self) -> Option<&IntRange> { match self { IntRange(range) => Some(range), _ => None, } } - fn as_slice(&self) -> Option { match self { Slice(slice) => Some(*slice), @@ -660,19 +721,19 @@ pub(super) fn arity(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> usize { } } - /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of actual - /// constructors (like variants, integers or fixed-sized slices). When specializing for these - /// constructors, we want to be specialising for the actual underlying constructors. + /// Some constructors (namely `Wildcard`, `IntRange` and `Slice`) actually stand for a set of + /// actual constructors (like variants, integers or fixed-sized slices). When specializing for + /// these constructors, we want to be specialising for the actual underlying constructors. /// Naively, we would simply return the list of constructors they correspond to. We instead are - /// more clever: if there are constructors that we know will behave the same wrt the current - /// matrix, we keep them grouped. For example, all slices of a sufficiently large length - /// will either be all useful or all non-useful with a given matrix. + /// more clever: if there are constructors that we know will behave the same w.r.t. the current + /// matrix, we keep them grouped. For example, all slices of a sufficiently large length will + /// either be all useful or all non-useful with a given matrix. /// /// See the branches for details on how the splitting is done. /// - /// This function may discard some irrelevant constructors if this preserves behavior and - /// diagnostics. Eg. for the `_` case, we ignore the constructors already present in the - /// matrix, unless all of them are. + /// This function may discard some irrelevant constructors if this preserves behavior. Eg. for + /// the `_` case, we ignore the constructors already present in the column, unless all of them + /// are. pub(super) fn split<'a>( &self, pcx: &PatCtxt<'_, '_, 'tcx>, @@ -683,19 +744,74 @@ pub(super) fn split<'a>( { match self { Wildcard => { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, ctors); - split_wildcard.into_ctors(pcx) + let split_set = ConstructorSet::for_ty(pcx.cx, pcx.ty).split(pcx, ctors); + if !split_set.missing.is_empty() { + // We are splitting a wildcard in order to compute its usefulness. Some constructors are + // not present in the column. The first thing we note is that specializing with any of + // the missing constructors would select exactly the rows with wildcards. Moreover, they + // would all return equivalent results. We can therefore group them all into a + // fictitious `Missing` constructor. + // + // As an important optimization, this function will skip all the present constructors. + // This is correct because specializing with any of the present constructors would + // select a strict superset of the wildcard rows, and thus would only find witnesses + // already found with the `Missing` constructor. + // This does mean that diagnostics are incomplete: in + // ``` + // match x { + // Some(true) => {} + // } + // ``` + // we report `None` as missing but not `Some(false)`. + // + // When all the constructors are missing we can equivalently return the `Wildcard` + // constructor on its own. The difference between `Wildcard` and `Missing` will then + // only be in diagnostics. + + // If some constructors are missing, we typically want to report those constructors, + // e.g.: + // ``` + // enum Direction { N, S, E, W } + // let Direction::N = ...; + // ``` + // we can report 3 witnesses: `S`, `E`, and `W`. + // + // However, if the user didn't actually specify a constructor + // in this arm, e.g., in + // ``` + // let x: (Direction, Direction, bool) = ...; + // let (_, _, false) = x; + // ``` + // we don't want to show all 16 possible witnesses `(, , + // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we + // prefer to report just a wildcard `_`. + // + // The exception is: if we are at the top-level, for example in an empty match, we + // usually prefer to report the full list of constructors. + let all_missing = split_set.present.is_empty(); + let report_when_all_missing = + pcx.is_top_level && !IntRange::is_integral(pcx.ty); + let ctor = if all_missing && !report_when_all_missing { + Wildcard + } else { + Missing { + nonexhaustive_enum_missing_real_variants: split_set + .nonexhaustive_enum_missing_real_variants, + } + }; + smallvec![ctor] + } else { + split_set.present + } } - // Fast-track if the range is trivial. In particular, we don't do the overlapping - // ranges check. - IntRange(ctor_range) if !ctor_range.is_singleton() => { - let int_ranges = ctors.filter_map(|ctor| ctor.as_int_range()).cloned(); - ctor_range.split(int_ranges).map(IntRange).collect() + // Fast-track if the range is trivial. + IntRange(this_range) if !this_range.is_singleton() => { + let column_ranges = ctors.filter_map(|ctor| ctor.as_int_range()).cloned(); + this_range.split(column_ranges).map(|(_, range)| IntRange(range)).collect() } - &Slice(slice @ Slice { kind: VarLen(..), .. }) => { - let slices = ctors.filter_map(|c| c.as_slice()); - slice.split(slices).map(Slice).collect() + Slice(this_slice @ Slice { kind: VarLen(..), .. }) => { + let column_slices = ctors.filter_map(|c| c.as_slice()); + this_slice.split(column_slices).map(|(_, slice)| Slice(slice)).collect() } // Any other constructor can be used unchanged. _ => smallvec![self.clone()], @@ -755,96 +871,112 @@ pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self ), } } - - /// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is - /// assumed to be built from `matrix.head_ctors()` with wildcards and opaques filtered out, - /// and `self` is assumed to have been split from a wildcard. - fn is_covered_by_any<'p>( - &self, - pcx: &PatCtxt<'_, 'p, 'tcx>, - used_ctors: &[Constructor<'tcx>], - ) -> bool { - if used_ctors.is_empty() { - return false; - } - - // This must be kept in sync with `is_covered_by`. - match self { - // If `self` is `Single`, `used_ctors` cannot contain anything else than `Single`s. - Single => !used_ctors.is_empty(), - Variant(vid) => used_ctors.iter().any(|c| matches!(c, Variant(i) if i == vid)), - IntRange(range) => used_ctors - .iter() - .filter_map(|c| c.as_int_range()) - .any(|other| range.is_covered_by(other)), - Slice(slice) => used_ctors - .iter() - .filter_map(|c| c.as_slice()) - .any(|other| slice.is_covered_by(other)), - // This constructor is never covered by anything else - NonExhaustive => false, - Str(..) | F32Range(..) | F64Range(..) | Opaque | Missing { .. } | Wildcard | Or => { - span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self) - } - } - } } -/// A wildcard constructor that we split relative to the constructors in the matrix, as explained -/// at the top of the file. -/// -/// A constructor that is not present in the matrix rows will only be covered by the rows that have -/// wildcards. Thus we can group all of those constructors together; we call them "missing -/// constructors". Splitting a wildcard would therefore list all present constructors individually -/// (or grouped if they are integers or slices), and then all missing constructors together as a -/// group. -/// -/// However we can go further: since any constructor will match the wildcard rows, and having more -/// rows can only reduce the amount of usefulness witnesses, we can skip the present constructors -/// and only try the missing ones. -/// This will not preserve the whole list of witnesses, but will preserve whether the list is empty -/// or not. In fact this is quite natural from the point of view of diagnostics too. This is done -/// in `to_ctors`: in some cases we only return `Missing`. -#[derive(Debug)] -pub(super) struct SplitWildcard<'tcx> { - /// Constructors (other than wildcards and opaques) seen in the matrix. - matrix_ctors: Vec>, - /// All the constructors for this type - all_ctors: SmallVec<[Constructor<'tcx>; 1]>, +/// Describes the set of all constructors for a type. +pub(super) enum ConstructorSet { + /// The type has a single constructor, e.g. `&T` or a struct. + Single, + /// This type has the following list of constructors. + Variants { variants: Vec, non_exhaustive: bool }, + /// The type is spanned by integer values. The range or ranges give the set of allowed values. + /// The second range is only useful for `char`. + /// This is reused for bool. FIXME: don't. + /// `non_exhaustive` is used when the range is not allowed to be matched exhaustively (that's + /// for usize/isize). + Integers { range_1: IntRange, range_2: Option, non_exhaustive: bool }, + /// The type is matched by slices. The usize is the compile-time length of the array, if known. + Slice(Option), + /// The type is matched by slices whose elements are uninhabited. + SliceOfEmpty, + /// The constructors cannot be listed, and the type cannot be matched exhaustively. E.g. `str`, + /// floats. + Unlistable, + /// The type has no inhabitants. + Uninhabited, } -impl<'tcx> SplitWildcard<'tcx> { - pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { - debug!("SplitWildcard::new({:?})", pcx.ty); - let cx = pcx.cx; - let make_range = |start, end| { - IntRange( - // `unwrap()` is ok because we know the type is an integer. - IntRange::from_range(cx.tcx, start, end, pcx.ty, RangeEnd::Included), - ) - }; - // This determines the set of all possible constructors for the type `pcx.ty`. For numbers, +/// Describes the result of analyzing the constructors in a column of a match. +/// +/// `present` is morally the set of constructors present in the column, and `missing` is the set of +/// constructors that exist in the type but are not present in the column. +/// +/// More formally, they respect the following constraints: +/// - the union of `present` and `missing` covers the whole type +/// - `present` and `missing` are disjoint +/// - neither contains wildcards +/// - each constructor in `present` is covered by some non-wildcard constructor in the column +/// - together, the constructors in `present` cover all the non-wildcard constructor in the column +/// - non-wildcards in the column do no cover anything in `missing` +/// - constructors in `present` and `missing` are split for the column; in other words, they are +/// either fully included in or disjoint from each constructor in the column. This avoids +/// non-trivial intersections like between `0..10` and `5..15`. +struct SplitConstructorSet<'tcx> { + present: SmallVec<[Constructor<'tcx>; 1]>, + missing: Vec>, + /// For the `non_exhaustive_omitted_patterns` lint. + nonexhaustive_enum_missing_real_variants: bool, +} + +impl ConstructorSet { + pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self { + debug!("ConstructorSet::for_ty({:?})", ty); + let make_range = + |start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included); + // This determines the set of all possible constructors for the type `ty`. For numbers, // arrays and slices we use ranges and variable-length slices when appropriate. // // If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that // are statically impossible. E.g., for `Option`, we do not include `Some(_)` in the // returned list of constructors. - // Invariant: this is empty if and only if the type is uninhabited (as determined by + // Invariant: this is `Uninhabited` if and only if the type is uninhabited (as determined by // `cx.is_uninhabited()`). - let all_ctors = match pcx.ty.kind() { - ty::Bool => smallvec![make_range(0, 1)], + match ty.kind() { + ty::Bool => { + Self::Integers { range_1: make_range(0, 1), range_2: None, non_exhaustive: false } + } + ty::Char => { + // The valid Unicode Scalar Value ranges. + Self::Integers { + range_1: make_range('\u{0000}' as u128, '\u{D7FF}' as u128), + range_2: Some(make_range('\u{E000}' as u128, '\u{10FFFF}' as u128)), + non_exhaustive: false, + } + } + &ty::Int(ity) => { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; + let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; + let min = 1u128 << (bits - 1); + let max = min - 1; + Self::Integers { range_1: make_range(min, max), non_exhaustive, range_2: None } + } + &ty::Uint(uty) => { + // `usize`/`isize` are not allowed to be matched exhaustively unless the + // `precise_pointer_size_matching` feature is enabled. + let non_exhaustive = + ty.is_ptr_sized_integral() && !cx.tcx.features().precise_pointer_size_matching; + let size = Integer::from_uint_ty(&cx.tcx, uty).size(); + let max = size.truncate(u128::MAX); + Self::Integers { range_1: make_range(0, max), non_exhaustive, range_2: None } + } ty::Array(sub_ty, len) if len.try_eval_target_usize(cx.tcx, cx.param_env).is_some() => { let len = len.eval_target_usize(cx.tcx, cx.param_env) as usize; if len != 0 && cx.is_uninhabited(*sub_ty) { - smallvec![] + Self::Uninhabited } else { - smallvec![Slice(Slice::new(Some(len), VarLen(0, 0)))] + Self::Slice(Some(len)) } } // Treat arrays of a constant but unknown length like slices. ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { - let kind = if cx.is_uninhabited(*sub_ty) { FixedLen(0) } else { VarLen(0, 0) }; - smallvec![Slice(Slice::new(None, kind))] + if cx.is_uninhabited(*sub_ty) { + Self::SliceOfEmpty + } else { + Self::Slice(None) + } } ty::Adt(def, args) if def.is_enum() => { // If the enum is declared as `#[non_exhaustive]`, we treat it as if it had an @@ -863,19 +995,14 @@ pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { // // we don't want to show every possible IO error, but instead have only `_` as the // witness. - let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty); + let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(ty); - let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; - - // If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it - // as though it had an "unknown" constructor to avoid exposing its emptiness. The - // exception is if the pattern is at the top level, because we want empty matches to be - // considered exhaustive. - let is_secretly_empty = - def.variants().is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level; - - let mut ctors: SmallVec<[_; 1]> = - def.variants() + if def.variants().is_empty() && !is_declared_nonexhaustive { + Self::Uninhabited + } else { + let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; + let variants: Vec<_> = def + .variants() .iter_enumerated() .filter(|(_, v)| { // If `exhaustive_patterns` is enabled, we exclude variants known to be @@ -885,135 +1012,150 @@ pub(super) fn new<'p>(pcx: &PatCtxt<'_, 'p, 'tcx>) -> Self { .instantiate(cx.tcx, args) .apply(cx.tcx, cx.param_env, cx.module) }) - .map(|(idx, _)| Variant(idx)) + .map(|(idx, _)| idx) .collect(); - if is_secretly_empty || is_declared_nonexhaustive { - ctors.push(NonExhaustive); + Self::Variants { variants, non_exhaustive: is_declared_nonexhaustive } } - ctors } - ty::Char => { - smallvec![ - // The valid Unicode Scalar Value ranges. - make_range('\u{0000}' as u128, '\u{D7FF}' as u128), - make_range('\u{E000}' as u128, '\u{10FFFF}' as u128), - ] - } - ty::Int(_) | ty::Uint(_) - if pcx.ty.is_ptr_sized_integral() - && !cx.tcx.features().precise_pointer_size_matching => - { - // `usize`/`isize` are not allowed to be matched exhaustively unless the - // `precise_pointer_size_matching` feature is enabled. So we treat those types like - // `#[non_exhaustive]` enums by returning a special unmatchable constructor. - smallvec![NonExhaustive] - } - &ty::Int(ity) => { - let bits = Integer::from_int_ty(&cx.tcx, ity).size().bits() as u128; - let min = 1u128 << (bits - 1); - let max = min - 1; - smallvec![make_range(min, max)] - } - &ty::Uint(uty) => { - let size = Integer::from_uint_ty(&cx.tcx, uty).size(); - let max = size.truncate(u128::MAX); - smallvec![make_range(0, max)] - } - // If `exhaustive_patterns` is disabled and our scrutinee is the never type, we cannot - // expose its emptiness. The exception is if the pattern is at the top level, because we - // want empty matches to be considered exhaustive. - ty::Never if !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => { - smallvec![NonExhaustive] - } - ty::Never => smallvec![], - _ if cx.is_uninhabited(pcx.ty) => smallvec![], - ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => smallvec![Single], + ty::Never => Self::Uninhabited, + _ if cx.is_uninhabited(ty) => Self::Uninhabited, + ty::Adt(..) | ty::Tuple(..) | ty::Ref(..) => Self::Single, // This type is one for which we cannot list constructors, like `str` or `f64`. - _ => smallvec![NonExhaustive], - }; - - SplitWildcard { matrix_ctors: Vec::new(), all_ctors } + _ => Self::Unlistable, + } } - /// Pass a set of constructors relative to which to split this one. Don't call twice, it won't - /// do what you want. - pub(super) fn split<'a>( - &mut self, + /// This is the core logical operation of exhaustiveness checking. This analyzes a column a + /// constructors to 1/ determine which constructors of the type (if any) are missing; 2/ split + /// constructors to handle non-trivial intersections e.g. on ranges or slices. + fn split<'a, 'tcx>( + &self, pcx: &PatCtxt<'_, '_, 'tcx>, ctors: impl Iterator> + Clone, - ) where + ) -> SplitConstructorSet<'tcx> + where 'tcx: 'a, { - // Since `all_ctors` never contains wildcards, this won't recurse further. - self.all_ctors = - self.all_ctors.iter().flat_map(|ctor| ctor.split(pcx, ctors.clone())).collect(); - self.matrix_ctors = ctors.filter(|c| !matches!(c, Wildcard | Opaque)).cloned().collect(); - } - - /// Whether there are any value constructors for this type that are not present in the matrix. - fn any_missing(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool { - self.iter_missing(pcx).next().is_some() - } - - /// Iterate over the constructors for this type that are not present in the matrix. - pub(super) fn iter_missing<'a, 'p>( - &'a self, - pcx: &'a PatCtxt<'a, 'p, 'tcx>, - ) -> impl Iterator> + Captures<'p> { - self.all_ctors.iter().filter(move |ctor| !ctor.is_covered_by_any(pcx, &self.matrix_ctors)) - } - - /// Return the set of constructors resulting from splitting the wildcard. As explained at the - /// top of the file, if any constructors are missing we can ignore the present ones. - fn into_ctors(self, pcx: &PatCtxt<'_, '_, 'tcx>) -> SmallVec<[Constructor<'tcx>; 1]> { - if self.any_missing(pcx) { - // Some constructors are missing, thus we can specialize with the special `Missing` - // constructor, which stands for those constructors that are not seen in the matrix, - // and matches the same rows as any of them (namely the wildcard rows). See the top of - // the file for details. - // However, when all constructors are missing we can also specialize with the full - // `Wildcard` constructor. The difference will depend on what we want in diagnostics. - - // If some constructors are missing, we typically want to report those constructors, - // e.g.: - // ``` - // enum Direction { N, S, E, W } - // let Direction::N = ...; - // ``` - // we can report 3 witnesses: `S`, `E`, and `W`. - // - // However, if the user didn't actually specify a constructor - // in this arm, e.g., in - // ``` - // let x: (Direction, Direction, bool) = ...; - // let (_, _, false) = x; - // ``` - // we don't want to show all 16 possible witnesses `(, , - // true)` - we are satisfied with `(_, _, true)`. So if all constructors are missing we - // prefer to report just a wildcard `_`. - // - // The exception is: if we are at the top-level, for example in an empty match, we - // sometimes prefer reporting the list of constructors instead of just `_`. - let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); - let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing { - if pcx.is_non_exhaustive { - Missing { - nonexhaustive_enum_missing_real_variants: self - .iter_missing(pcx) - .any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))), - } - } else { - Missing { nonexhaustive_enum_missing_real_variants: false } + let mut missing = Vec::new(); + let mut present: SmallVec<[_; 1]> = SmallVec::new(); + // Constructors in `ctors`, except wildcards. + let mut seen = Vec::new(); + for ctor in ctors.cloned() { + match ctor { + // Wildcards in `ctors` are irrelevant to splitting + Opaque | Wildcard => {} + _ => { + seen.push(ctor); } - } else { - Wildcard - }; - return smallvec![ctor]; + } + } + let mut nonexhaustive_enum_missing_real_variants = false; + match self { + ConstructorSet::Single => { + if seen.is_empty() { + missing.push(Single); + } else { + present.push(Single); + } + } + ConstructorSet::Variants { variants, non_exhaustive } => { + let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); + let mut skipped_a_hidden_variant = false; + for variant in variants { + let ctor = Variant(*variant); + if seen_set.contains(&variant) { + present.push(ctor); + } else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) { + // We don't want to mention any variants that are `doc(hidden)` or behind an + // unstable feature gate if they aren't present in the match. + skipped_a_hidden_variant = true; + } else { + missing.push(ctor); + } + } + + if *non_exhaustive { + nonexhaustive_enum_missing_real_variants = !missing.is_empty(); + missing.push(NonExhaustive); + } else if skipped_a_hidden_variant { + // FIXME(Nadrieril): This represents the skipped variants, but isn't super + // clean. Using `NonExhaustive` breaks things elsewhere. + missing.push(Wildcard); + } + } + ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { + let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()).cloned(); + for (seen, splitted_range) in range_1.split(seen_ranges.clone()) { + match seen { + Presence::Unseen => missing.push(IntRange(splitted_range)), + Presence::Seen => present.push(IntRange(splitted_range)), + } + } + if let Some(range_2) = range_2 { + for (seen, splitted_range) in range_2.split(seen_ranges) { + match seen { + Presence::Unseen => missing.push(IntRange(splitted_range)), + Presence::Seen => present.push(IntRange(splitted_range)), + } + } + } + + if *non_exhaustive { + missing.push(NonExhaustive); + } + } + &ConstructorSet::Slice(array_len) => { + let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()); + let base_slice = Slice { kind: VarLen(0, 0), array_len }; + for (seen, splitted_slice) in base_slice.split(seen_slices) { + let ctor = Slice(splitted_slice); + match seen { + Presence::Unseen => missing.push(ctor), + Presence::Seen => present.push(ctor), + } + } + } + ConstructorSet::SliceOfEmpty => { + // Behaves essentially like `Single`. + let slice = Slice(Slice::new(None, FixedLen(0))); + if seen.is_empty() { + missing.push(slice); + } else { + present.push(slice); + } + } + ConstructorSet::Unlistable => { + // Since we can't list constructors, we take the ones in the column. This might list + // some constructors several times but there's not much we can do. + present.extend(seen.iter().cloned()); + missing.push(NonExhaustive); + } + // If `exhaustive_patterns` is disabled and our scrutinee is an empty type, we cannot + // expose its emptiness. The exception is if the pattern is at the top level, because we + // want empty matches to be considered exhaustive. + ConstructorSet::Uninhabited + if !pcx.cx.tcx.features().exhaustive_patterns && !pcx.is_top_level => + { + missing.push(NonExhaustive); + } + ConstructorSet::Uninhabited => {} } - // All the constructors are present in the matrix, so we just go through them all. - self.all_ctors + SplitConstructorSet { present, missing, nonexhaustive_enum_missing_real_variants } + } + + /// Compute the set of constructors missing from this column. + /// This is only used for reporting to the user. + pub(super) fn compute_missing<'a, 'tcx>( + &self, + pcx: &PatCtxt<'_, '_, 'tcx>, + ctors: impl Iterator> + Clone, + ) -> Vec> + where + 'tcx: 'a, + { + self.split(pcx, ctors).missing } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 21031e8ba9d..68ca0e2ac04 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -307,7 +307,7 @@ use self::ArmType::*; use self::Usefulness::*; -use super::deconstruct_pat::{Constructor, DeconstructedPat, Fields, SplitWildcard}; +use super::deconstruct_pat::{Constructor, ConstructorSet, DeconstructedPat, Fields}; use crate::errors::{NonExhaustiveOmittedPattern, Uncovered}; use rustc_data_structures::captures::Captures; @@ -368,8 +368,6 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a /// subpattern. pub(super) is_top_level: bool, - /// Whether the current pattern is from a `non_exhaustive` enum. - pub(super) is_non_exhaustive: bool, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -616,62 +614,41 @@ fn apply_constructor( WithWitnesses(ref witnesses) if witnesses.is_empty() => self, WithWitnesses(witnesses) => { let new_witnesses = if let Constructor::Missing { .. } = ctor { - // We got the special `Missing` constructor, so each of the missing constructors - // gives a new pattern that is not caught by the match. We list those patterns. - if pcx.is_non_exhaustive { - witnesses - .into_iter() - // Here we don't want the user to try to list all variants, we want them to add - // a wildcard, so we only suggest that. - .map(|witness| { - witness.apply_constructor(pcx, &Constructor::NonExhaustive) - }) - .collect() - } else { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - - // This lets us know if we skipped any variants because they are marked - // `doc(hidden)` or they are unstable feature gate (only stdlib types). - let mut hide_variant_show_wild = false; - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - let mut new_patterns: Vec> = split_wildcard - .iter_missing(pcx) - .filter_map(|missing_ctor| { - // Check if this variant is marked `doc(hidden)` - if missing_ctor.is_doc_hidden_variant(pcx) - || missing_ctor.is_unstable_variant(pcx) - { - hide_variant_show_wild = true; - return None; - } - Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone())) - }) - .collect(); - - if hide_variant_show_wild { - new_patterns.push(DeconstructedPat::wildcard(pcx.ty, pcx.span)); - } - - witnesses - .into_iter() - .flat_map(|witness| { - new_patterns.iter().map(move |pat| { - Witness( - witness - .0 - .iter() - .chain(once(pat)) - .map(DeconstructedPat::clone_and_forget_reachability) - .collect(), - ) - }) - }) - .collect() + let mut missing = ConstructorSet::for_ty(pcx.cx, pcx.ty) + .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor)); + if missing.iter().any(|c| c.is_non_exhaustive()) { + // We only report `_` here; listing other constructors would be redundant. + missing = vec![Constructor::NonExhaustive]; } + + // We got the special `Missing` constructor, so each of the missing constructors + // gives a new pattern that is not caught by the match. + // We construct for each missing constructor a version of this constructor with + // wildcards for fields, i.e. that matches everything that can be built with it. + // For example, if `ctor` is a `Constructor::Variant` for `Option::Some`, we get + // the pattern `Some(_)`. + let new_patterns: Vec> = missing + .into_iter() + .map(|missing_ctor| { + DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone()) + }) + .collect(); + + witnesses + .into_iter() + .flat_map(|witness| { + new_patterns.iter().map(move |pat| { + Witness( + witness + .0 + .iter() + .chain(once(pat)) + .map(DeconstructedPat::clone_and_forget_reachability) + .collect(), + ) + }) + }) + .collect() } else { witnesses .into_iter() @@ -844,9 +821,8 @@ fn is_useful<'p, 'tcx>( ty = row.head().ty(); } } - let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); debug!("v.head: {:?}, v.span: {:?}", v.head(), v.head().span()); - let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level, is_non_exhaustive }; + let pcx = &PatCtxt { cx, ty, span: v.head().span(), is_top_level }; let v_ctor = v.head().ctor(); debug!(?v_ctor); @@ -861,7 +837,8 @@ fn is_useful<'p, 'tcx>( } // We split the head constructor of `v`. let split_ctors = v_ctor.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard(); + let is_non_exhaustive_and_wild = + cx.is_foreign_non_exhaustive_enum(ty) && v_ctor.is_wildcard(); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let start_matrix = &matrix; @@ -898,24 +875,21 @@ fn is_useful<'p, 'tcx>( Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } ) { - let patterns = { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - split_wildcard - .iter_missing(pcx) - // Filter out the `NonExhaustive` because we want to list only real - // variants. Also remove any unstable feature gated variants. - // Because of how we computed `nonexhaustive_enum_missing_real_variants`, - // this will not return an empty `Vec`. - .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) - .cloned() - .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) - .collect::>() - }; + let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty) + .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor)); + // Construct for each missing constructor a "wild" version of this + // constructor, that matches everything that can be built with + // it. For example, if `ctor` is a `Constructor::Variant` for + // `Option::Some`, we get the pattern `Some(_)`. + let patterns = missing + .into_iter() + // Filter out the `NonExhaustive` because we want to list only real + // variants. Also remove any unstable feature gated variants. + // Because of how we computed `nonexhaustive_enum_missing_real_variants`, + // this will not return an empty `Vec`. + .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) + .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) + .collect::>(); // Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` // is not exhaustive enough. From 2f4cab4d214dd8a5e0b3e90d6e517aa97bd94fd9 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 17:09:20 +0200 Subject: [PATCH 14/58] Clarify handling of hidden variants --- .../src/thir/pattern/deconstruct_pat.rs | 133 +++++++++--------- .../src/thir/pattern/usefulness.rs | 15 +- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index ab30f5109c7..2a3fd416a52 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -609,19 +609,23 @@ pub(super) enum Constructor<'tcx> { /// boxes for the purposes of exhaustiveness: we must not inspect them, and they /// don't count towards making a match exhaustive. Opaque, + /// Or-pattern. + Or, + /// Wildcard pattern. + Wildcard, /// Fake extra constructor for enums that aren't allowed to be matched exhaustively. Also used /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, - /// Stands for constructors that are not seen in the matrix, as explained in the code for - /// [`Constructor::split`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` - /// lint. + /// Fake extra constructor for variants that should not be mentioned in diagnostics. + /// We use this for variants behind an unstable gate as well as + /// `#[doc(hidden)]` ones. + Hidden, + /// Fake extra constructor for constructors that are not seen in the matrix, as explained in the + /// code for [`Constructor::split`]. The carried `bool` is used for the + /// `non_exhaustive_omitted_patterns` lint. Missing { - nonexhaustive_enum_missing_real_variants: bool, + nonexhaustive_enum_missing_visible_variants: bool, }, - /// Wildcard pattern. - Wildcard, - /// Or-pattern. - Or, } impl<'tcx> Constructor<'tcx> { @@ -652,32 +656,6 @@ fn as_slice(&self) -> Option { } } - /// Checks if the `Constructor` is a variant and `TyCtxt::eval_stability` returns - /// `EvalResult::Deny { .. }`. - /// - /// This means that the variant has a stdlib unstable feature marking it. - pub(super) fn is_unstable_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool { - if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() { - let variant_def_id = adt.variant(*idx).def_id; - // Filter variants that depend on a disabled unstable feature. - return matches!( - pcx.cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None), - EvalResult::Deny { .. } - ); - } - false - } - - /// Checks if the `Constructor` is a `Constructor::Variant` with a `#[doc(hidden)]` - /// attribute from a type not local to the current crate. - pub(super) fn is_doc_hidden_variant(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> bool { - if let Constructor::Variant(idx) = self && let ty::Adt(adt, _) = pcx.ty.kind() { - let variant_def_id = adt.variants()[*idx].def_id; - return pcx.cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local(); - } - false - } - fn variant_index_for_adt(&self, adt: ty::AdtDef<'tcx>) -> VariantIdx { match *self { Variant(idx) => idx, @@ -713,8 +691,9 @@ pub(super) fn arity(&self, pcx: &PatCtxt<'_, '_, 'tcx>) -> usize { | F32Range(..) | F64Range(..) | IntRange(..) - | NonExhaustive | Opaque + | NonExhaustive + | Hidden | Missing { .. } | Wildcard => 0, Or => bug!("The `Or` constructor doesn't have a fixed arity"), @@ -795,8 +774,8 @@ pub(super) fn split<'a>( Wildcard } else { Missing { - nonexhaustive_enum_missing_real_variants: split_set - .nonexhaustive_enum_missing_real_variants, + nonexhaustive_enum_missing_visible_variants: split_set + .nonexhaustive_enum_missing_visible_variants, } }; smallvec![ctor] @@ -828,8 +807,8 @@ pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self match (self, other) { // Wildcards cover anything (_, Wildcard) => true, - // The missing ctors are not covered by anything in the matrix except wildcards. - (Missing { .. } | Wildcard, _) => false, + // Only a wildcard pattern can match these special constructors. + (Wildcard | Missing { .. } | NonExhaustive | Hidden, _) => false, (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, @@ -860,8 +839,6 @@ pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self // We are trying to inspect an opaque constant. Thus we skip the row. (Opaque, _) | (_, Opaque) => false, - // Only a wildcard pattern can match the special extra constructor. - (NonExhaustive, _) => false, _ => span_bug!( pcx.span, @@ -878,7 +855,14 @@ pub(super) enum ConstructorSet { /// The type has a single constructor, e.g. `&T` or a struct. Single, /// This type has the following list of constructors. - Variants { variants: Vec, non_exhaustive: bool }, + /// Some variants are hidden, which means they won't be mentioned in diagnostics unless the user + /// mentioned them first. We use this for variants behind an unstable gate as well as + /// `#[doc(hidden)]` ones. + Variants { + visible_variants: Vec, + hidden_variants: Vec, + non_exhaustive: bool, + }, /// The type is spanned by integer values. The range or ranges give the set of allowed values. /// The second range is only useful for `char`. /// This is reused for bool. FIXME: don't. @@ -915,7 +899,7 @@ struct SplitConstructorSet<'tcx> { present: SmallVec<[Constructor<'tcx>; 1]>, missing: Vec>, /// For the `non_exhaustive_omitted_patterns` lint. - nonexhaustive_enum_missing_real_variants: bool, + nonexhaustive_enum_missing_visible_variants: bool, } impl ConstructorSet { @@ -1001,7 +985,7 @@ pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> S Self::Uninhabited } else { let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns; - let variants: Vec<_> = def + let (hidden_variants, visible_variants) = def .variants() .iter_enumerated() .filter(|(_, v)| { @@ -1013,9 +997,24 @@ pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> S .apply(cx.tcx, cx.param_env, cx.module) }) .map(|(idx, _)| idx) - .collect(); + .partition(|idx| { + let variant_def_id = def.variant(*idx).def_id; + // Filter variants that depend on a disabled unstable feature. + let is_unstable = matches!( + cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None), + EvalResult::Deny { .. } + ); + // Filter foreign `#[doc(hidden)]` variants. + let is_doc_hidden = + cx.tcx.is_doc_hidden(variant_def_id) && !variant_def_id.is_local(); + is_unstable || is_doc_hidden + }); - Self::Variants { variants, non_exhaustive: is_declared_nonexhaustive } + Self::Variants { + visible_variants, + hidden_variants, + non_exhaustive: is_declared_nonexhaustive, + } } } ty::Never => Self::Uninhabited, @@ -1050,7 +1049,7 @@ fn split<'a, 'tcx>( } } } - let mut nonexhaustive_enum_missing_real_variants = false; + let mut nonexhaustive_enum_missing_visible_variants = false; match self { ConstructorSet::Single => { if seen.is_empty() { @@ -1059,29 +1058,34 @@ fn split<'a, 'tcx>( present.push(Single); } } - ConstructorSet::Variants { variants, non_exhaustive } => { + ConstructorSet::Variants { visible_variants, hidden_variants, non_exhaustive } => { let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); let mut skipped_a_hidden_variant = false; - for variant in variants { + for variant in visible_variants { let ctor = Variant(*variant); if seen_set.contains(&variant) { present.push(ctor); - } else if ctor.is_doc_hidden_variant(pcx) || ctor.is_unstable_variant(pcx) { - // We don't want to mention any variants that are `doc(hidden)` or behind an - // unstable feature gate if they aren't present in the match. - skipped_a_hidden_variant = true; } else { missing.push(ctor); } } + nonexhaustive_enum_missing_visible_variants = + *non_exhaustive && !missing.is_empty(); + + for variant in hidden_variants { + let ctor = Variant(*variant); + if seen_set.contains(&variant) { + present.push(ctor); + } else { + skipped_a_hidden_variant = true; + } + } + if skipped_a_hidden_variant { + missing.push(Hidden); + } if *non_exhaustive { - nonexhaustive_enum_missing_real_variants = !missing.is_empty(); missing.push(NonExhaustive); - } else if skipped_a_hidden_variant { - // FIXME(Nadrieril): This represents the skipped variants, but isn't super - // clean. Using `NonExhaustive` breaks things elsewhere. - missing.push(Wildcard); } } ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { @@ -1142,7 +1146,7 @@ fn split<'a, 'tcx>( ConstructorSet::Uninhabited => {} } - SplitConstructorSet { present, missing, nonexhaustive_enum_missing_real_variants } + SplitConstructorSet { present, missing, nonexhaustive_enum_missing_visible_variants } } /// Compute the set of constructors missing from this column. @@ -1272,8 +1276,9 @@ pub(super) fn wildcards(pcx: &PatCtxt<'_, 'p, 'tcx>, constructor: &Constructor<' | F32Range(..) | F64Range(..) | IntRange(..) - | NonExhaustive | Opaque + | NonExhaustive + | Hidden | Missing { .. } | Wildcard => Fields::empty(), Or => { @@ -1587,7 +1592,7 @@ pub(crate) fn to_pat(&self, cx: &MatchCheckCtxt<'p, 'tcx>) -> Pat<'tcx> { } &Str(value) => PatKind::Constant { value }, IntRange(range) => return range.to_pat(cx.tcx, self.ty), - Wildcard | NonExhaustive => PatKind::Wild, + Wildcard | NonExhaustive | Hidden => PatKind::Wild, Missing { .. } => bug!( "trying to convert a `Missing` constructor into a `Pat`; this is probably a bug, `Missing` should have been processed in `apply_constructors`" @@ -1770,15 +1775,15 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { F32Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), F64Range(lo, hi, end) => write!(f, "{lo}{end}{hi}"), IntRange(range) => write!(f, "{range:?}"), // Best-effort, will render e.g. `false` as `0..=0` - Wildcard | Missing { .. } | NonExhaustive => write!(f, "_ : {:?}", self.ty), + Str(value) => write!(f, "{value}"), + Opaque => write!(f, ""), Or => { for pat in self.iter_fields() { write!(f, "{}{:?}", start_or_continue(" | "), pat)?; } Ok(()) } - Str(value) => write!(f, "{value}"), - Opaque => write!(f, ""), + Wildcard | Missing { .. } | NonExhaustive | Hidden => write!(f, "_ : {:?}", self.ty), } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 68ca0e2ac04..6cd73c7eaa9 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -872,22 +872,19 @@ fn is_useful<'p, 'tcx>( && usefulness.is_useful() && matches!(witness_preference, RealArm) && matches!( &ctor, - Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } + Constructor::Missing { nonexhaustive_enum_missing_visible_variants: true } ) { let missing = ConstructorSet::for_ty(pcx.cx, pcx.ty) .compute_missing(pcx, matrix.heads().map(DeconstructedPat::ctor)); - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. + // Construct for each missing constructor a "wild" version of this constructor, that + // matches everything that can be built with it. For example, if `ctor` is a + // `Constructor::Variant` for `Option::Some`, we get the pattern `Some(_)`. let patterns = missing .into_iter() - // Filter out the `NonExhaustive` because we want to list only real - // variants. Also remove any unstable feature gated variants. - // Because of how we computed `nonexhaustive_enum_missing_real_variants`, + // Because of how we computed `nonexhaustive_enum_missing_visible_variants`, // this will not return an empty `Vec`. - .filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))) + .filter(|c| !(matches!(c, Constructor::NonExhaustive | Constructor::Hidden))) .map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor)) .collect::>(); From fda0301b3395ebc6d33d9da566027010b37e1adc Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Tue, 3 Oct 2023 19:51:18 +0200 Subject: [PATCH 15/58] Don't collect `seen` if not needed --- .../src/thir/pattern/deconstruct_pat.rs | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 2a3fd416a52..a2bbde250c3 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -1036,30 +1036,21 @@ fn split<'a, 'tcx>( where 'tcx: 'a, { - let mut missing = Vec::new(); let mut present: SmallVec<[_; 1]> = SmallVec::new(); + let mut missing = Vec::new(); // Constructors in `ctors`, except wildcards. - let mut seen = Vec::new(); - for ctor in ctors.cloned() { - match ctor { - // Wildcards in `ctors` are irrelevant to splitting - Opaque | Wildcard => {} - _ => { - seen.push(ctor); - } - } - } + let mut seen = ctors.filter(|c| !(matches!(c, Opaque | Wildcard))); let mut nonexhaustive_enum_missing_visible_variants = false; match self { ConstructorSet::Single => { - if seen.is_empty() { + if seen.next().is_none() { missing.push(Single); } else { present.push(Single); } } ConstructorSet::Variants { visible_variants, hidden_variants, non_exhaustive } => { - let seen_set: FxHashSet<_> = seen.iter().map(|c| c.as_variant().unwrap()).collect(); + let seen_set: FxHashSet<_> = seen.map(|c| c.as_variant().unwrap()).collect(); let mut skipped_a_hidden_variant = false; for variant in visible_variants { let ctor = Variant(*variant); @@ -1089,15 +1080,16 @@ fn split<'a, 'tcx>( } } ConstructorSet::Integers { range_1, range_2, non_exhaustive } => { - let seen_ranges = seen.iter().map(|ctor| ctor.as_int_range().unwrap()).cloned(); - for (seen, splitted_range) in range_1.split(seen_ranges.clone()) { + let seen_ranges: Vec<_> = + seen.map(|ctor| ctor.as_int_range().unwrap().clone()).collect(); + for (seen, splitted_range) in range_1.split(seen_ranges.iter().cloned()) { match seen { Presence::Unseen => missing.push(IntRange(splitted_range)), Presence::Seen => present.push(IntRange(splitted_range)), } } if let Some(range_2) = range_2 { - for (seen, splitted_range) in range_2.split(seen_ranges) { + for (seen, splitted_range) in range_2.split(seen_ranges.into_iter()) { match seen { Presence::Unseen => missing.push(IntRange(splitted_range)), Presence::Seen => present.push(IntRange(splitted_range)), @@ -1110,7 +1102,7 @@ fn split<'a, 'tcx>( } } &ConstructorSet::Slice(array_len) => { - let seen_slices = seen.iter().map(|c| c.as_slice().unwrap()); + let seen_slices = seen.map(|c| c.as_slice().unwrap()); let base_slice = Slice { kind: VarLen(0, 0), array_len }; for (seen, splitted_slice) in base_slice.split(seen_slices) { let ctor = Slice(splitted_slice); @@ -1123,7 +1115,7 @@ fn split<'a, 'tcx>( ConstructorSet::SliceOfEmpty => { // Behaves essentially like `Single`. let slice = Slice(Slice::new(None, FixedLen(0))); - if seen.is_empty() { + if seen.next().is_none() { missing.push(slice); } else { present.push(slice); @@ -1132,7 +1124,7 @@ fn split<'a, 'tcx>( ConstructorSet::Unlistable => { // Since we can't list constructors, we take the ones in the column. This might list // some constructors several times but there's not much we can do. - present.extend(seen.iter().cloned()); + present.extend(seen.cloned()); missing.push(NonExhaustive); } // If `exhaustive_patterns` is disabled and our scrutinee is an empty type, we cannot From edf6a2d3371218043e4858b81fe48c99535cabe7 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 4 Oct 2023 15:59:16 +0200 Subject: [PATCH 16/58] Clarify for review --- .../src/thir/pattern/deconstruct_pat.rs | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index a2bbde250c3..dd00982f7f6 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -201,18 +201,26 @@ fn suspicious_intersection(&self, other: &Self) -> bool { /// intersections between an output range and a column range are inclusions. No output range /// straddles the boundary of one of the inputs. /// + /// Additionally, we track for each output range whether it is covered by one of the column ranges or not. + /// /// The following input: /// ```text - /// |-------------------------| // `self` - /// |------| |----------| |----| - /// |-------| |-------| + /// (--------------------------) // `self` + /// (------) (----------) (-) + /// (------) (--------) /// ``` - /// would be iterated over as follows: + /// is first intersected with `self`: /// ```text - /// ||---|--||-|---|---|---|--| + /// (--------------------------) // `self` + /// (----) (----------) (-) + /// (------) (--------) /// ``` - /// - /// Additionally, we track for each output range whether it is covered by one of the column ranges or not. + /// and then iterated over as follows: + /// ```text + /// (-(--)-(-)-(------)-)--(-)- + /// ``` + /// where each sequence of dashes is an output range, and dashes outside parentheses are marked + /// as `Presence::Missing`. fn split( &self, column_ranges: impl Iterator, @@ -245,33 +253,30 @@ enum IntBoundary { .map(unpack_intrange) .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)]) .collect(); + // We sort by boundary, and for each boundary we sort the "closing parentheses" first. The + // order of +1/-1 for a same boundary value is actually irrelevant, because we only look at + // the accumulated count between distinct boundary values. boundaries.sort_unstable(); - // Counter for parenthesis matching. - let mut paren_counter = 0isize; - let boundaries_with_paren_counts = boundaries - .into_iter() - // Accumulate parenthesis counts. - .map(move |(bdy, delta)| { - paren_counter += delta; - (bdy, paren_counter) - }); - let [self_start, self_end] = unpack_intrange(self.clone()); + // Accumulate parenthesis counts. + let mut paren_counter = 0isize; // Gather pairs of adjacent boundaries. let mut prev_bdy = self_start; - let mut prev_paren_count = 0; - boundaries_with_paren_counts - // End with the end of the range. The count is irrelevant. + boundaries + .into_iter() + // End with the end of the range. The count is ignored. .chain(once((self_end, 0))) - // List pairs of adjacent boundaries. - .map(move |(bdy, paren_count)| { - let ret = (prev_bdy, prev_paren_count, bdy); + // List pairs of adjacent boundaries and the count between them. + .map(move |(bdy, delta)| { + // `delta` affects the count as we cross `bdy`, so the relevant count between + // `prev_bdy` and `bdy` is untouched by `delta`. + let ret = (prev_bdy, paren_counter, bdy); prev_bdy = bdy; - prev_paren_count = paren_count; + paren_counter += delta; ret }) - // Skip duplicates. + // Skip empty ranges. .filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy) // Convert back to ranges. .map(move |(prev_bdy, paren_count, bdy)| { @@ -503,7 +508,10 @@ fn split( let smaller_lengths; let arity = self.arity(); let mut max_slice = self.kind; + // Tracks the smallest variable-length slice we've seen. Any slice arity above it is + // therefore `Presence::Seen` in the column. let mut min_var_len = usize::MAX; + // Tracks the fixed-length slices we've seen, to mark them as `Presence::Seen`. let mut seen_fixed_lens = FxHashSet::default(); match &mut max_slice { VarLen(max_prefix_len, max_suffix_len) => { From c1b29b338dc37f96f0695e393743ce35508d3704 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Thu, 5 Oct 2023 00:58:14 +0200 Subject: [PATCH 17/58] Fix handling slices of empty types --- .../src/thir/pattern/deconstruct_pat.rs | 29 ++++++++++---- tests/ui/pattern/usefulness/slice_of_empty.rs | 22 +++++++++++ .../pattern/usefulness/slice_of_empty.stderr | 39 +++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 tests/ui/pattern/usefulness/slice_of_empty.rs create mode 100644 tests/ui/pattern/usefulness/slice_of_empty.stderr diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index dd00982f7f6..a7a000ba31c 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -859,6 +859,7 @@ pub(super) fn is_covered_by<'p>(&self, pcx: &PatCtxt<'_, 'p, 'tcx>, other: &Self } /// Describes the set of all constructors for a type. +#[derive(Debug)] pub(super) enum ConstructorSet { /// The type has a single constructor, e.g. `&T` or a struct. Single, @@ -903,6 +904,7 @@ pub(super) enum ConstructorSet { /// - constructors in `present` and `missing` are split for the column; in other words, they are /// either fully included in or disjoint from each constructor in the column. This avoids /// non-trivial intersections like between `0..10` and `5..15`. +#[derive(Debug)] struct SplitConstructorSet<'tcx> { present: SmallVec<[Constructor<'tcx>; 1]>, missing: Vec>, @@ -911,8 +913,8 @@ struct SplitConstructorSet<'tcx> { } impl ConstructorSet { + #[instrument(level = "debug", skip(cx), ret)] pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self { - debug!("ConstructorSet::for_ty({:?})", ty); let make_range = |start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included); // This determines the set of all possible constructors for the type `ty`. For numbers, @@ -1036,6 +1038,7 @@ pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> S /// This is the core logical operation of exhaustiveness checking. This analyzes a column a /// constructors to 1/ determine which constructors of the type (if any) are missing; 2/ split /// constructors to handle non-trivial intersections e.g. on ranges or slices. + #[instrument(level = "debug", skip(self, pcx, ctors), ret)] fn split<'a, 'tcx>( &self, pcx: &PatCtxt<'_, '_, 'tcx>, @@ -1111,7 +1114,7 @@ fn split<'a, 'tcx>( } &ConstructorSet::Slice(array_len) => { let seen_slices = seen.map(|c| c.as_slice().unwrap()); - let base_slice = Slice { kind: VarLen(0, 0), array_len }; + let base_slice = Slice::new(array_len, VarLen(0, 0)); for (seen, splitted_slice) in base_slice.split(seen_slices) { let ctor = Slice(splitted_slice); match seen { @@ -1121,12 +1124,22 @@ fn split<'a, 'tcx>( } } ConstructorSet::SliceOfEmpty => { - // Behaves essentially like `Single`. - let slice = Slice(Slice::new(None, FixedLen(0))); - if seen.next().is_none() { - missing.push(slice); - } else { - present.push(slice); + // This one is tricky because even though there's only one possible value of this + // type (namely `[]`), slice patterns of all lengths are allowed, they're just + // unreachable if length != 0. + // We still gather the seen constructors in `present`, but the only slice that can + // go in `missing` is `[]`. + let seen_slices = seen.map(|c| c.as_slice().unwrap()); + let base_slice = Slice::new(None, VarLen(0, 0)); + for (seen, splitted_slice) in base_slice.split(seen_slices) { + let ctor = Slice(splitted_slice); + match seen { + Presence::Seen => present.push(ctor), + Presence::Unseen if splitted_slice.arity() == 0 => { + missing.push(Slice(Slice::new(None, FixedLen(0)))) + } + Presence::Unseen => {} + } } } ConstructorSet::Unlistable => { diff --git a/tests/ui/pattern/usefulness/slice_of_empty.rs b/tests/ui/pattern/usefulness/slice_of_empty.rs new file mode 100644 index 00000000000..fe068871195 --- /dev/null +++ b/tests/ui/pattern/usefulness/slice_of_empty.rs @@ -0,0 +1,22 @@ +#![feature(never_type)] +#![feature(exhaustive_patterns)] +#![deny(unreachable_patterns)] + +fn main() {} + +fn foo(nevers: &[!]) { + match nevers { + &[] => (), + }; + + match nevers { + &[] => (), + &[_] => (), //~ ERROR unreachable pattern + &[_, _, ..] => (), //~ ERROR unreachable pattern + }; + + match nevers { + //~^ ERROR non-exhaustive patterns: `&[]` not covered + &[_] => (), //~ ERROR unreachable pattern + }; +} diff --git a/tests/ui/pattern/usefulness/slice_of_empty.stderr b/tests/ui/pattern/usefulness/slice_of_empty.stderr new file mode 100644 index 00000000000..07bb6b3a67d --- /dev/null +++ b/tests/ui/pattern/usefulness/slice_of_empty.stderr @@ -0,0 +1,39 @@ +error: unreachable pattern + --> $DIR/slice_of_empty.rs:14:9 + | +LL | &[_] => (), + | ^^^^ + | +note: the lint level is defined here + --> $DIR/slice_of_empty.rs:3:9 + | +LL | #![deny(unreachable_patterns)] + | ^^^^^^^^^^^^^^^^^^^^ + +error: unreachable pattern + --> $DIR/slice_of_empty.rs:15:9 + | +LL | &[_, _, ..] => (), + | ^^^^^^^^^^^ + +error: unreachable pattern + --> $DIR/slice_of_empty.rs:20:9 + | +LL | &[_] => (), + | ^^^^ + +error[E0004]: non-exhaustive patterns: `&[]` not covered + --> $DIR/slice_of_empty.rs:18:11 + | +LL | match nevers { + | ^^^^^^ pattern `&[]` not covered + | + = note: the matched value is of type `&[!]` +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown + | +LL | &[_] => (), &[] => todo!(), + | ++++++++++++++++ + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0004`. From ae9cec58394d7a38aac17e2873d213d5fcd85f7a Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Fri, 6 Oct 2023 01:17:09 -0700 Subject: [PATCH 18/58] Copy 1-element arrays as scalars, not vectors For `[T; 1]` it's silly to copy as `<1 x T>` when we can just copy as `T`. --- compiler/rustc_codegen_llvm/src/type_of.rs | 7 +++- .../x86_64-array-pair-load-store-merge.rs | 19 +++++++++++ tests/codegen/array-codegen.rs | 22 +++++++++++++ tests/codegen/array-optimized.rs | 33 +++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/assembly/x86_64-array-pair-load-store-merge.rs create mode 100644 tests/codegen/array-optimized.rs diff --git a/compiler/rustc_codegen_llvm/src/type_of.rs b/compiler/rustc_codegen_llvm/src/type_of.rs index dcc62d314ff..fd4c9572af2 100644 --- a/compiler/rustc_codegen_llvm/src/type_of.rs +++ b/compiler/rustc_codegen_llvm/src/type_of.rs @@ -397,7 +397,12 @@ fn scalar_copy_llvm_type<'a>(&self, cx: &CodegenCx<'a, 'tcx>) -> Option<&'a Type // extracts all the individual values. let ety = element.llvm_type(cx); - return Some(cx.type_vector(ety, *count)); + if *count == 1 { + // Emitting `<1 x T>` would be silly; just use the scalar. + return Some(ety); + } else { + return Some(cx.type_vector(ety, *count)); + } } // FIXME: The above only handled integer arrays; surely more things diff --git a/tests/assembly/x86_64-array-pair-load-store-merge.rs b/tests/assembly/x86_64-array-pair-load-store-merge.rs new file mode 100644 index 00000000000..4a8e40f8530 --- /dev/null +++ b/tests/assembly/x86_64-array-pair-load-store-merge.rs @@ -0,0 +1,19 @@ +// assembly-output: emit-asm +// compile-flags: --crate-type=lib -O -C llvm-args=-x86-asm-syntax=intel +// only-x86_64 +// ignore-sgx + +// Depending on various codegen choices, this might end up copying +// a `<2 x i8>`, an `i16`, or two `i8`s. +// Regardless of those choices, make sure the instructions use (2-byte) words. + +// CHECK-LABEL: array_copy_2_elements: +#[no_mangle] +pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) { + // CHECK-NOT: byte + // CHECK-NOT: mov + // CHECK: mov{{.+}}, word ptr + // CHECK-NEXT: mov word ptr + // CHECK-NEXT: ret + *p = *a; +} diff --git a/tests/codegen/array-codegen.rs b/tests/codegen/array-codegen.rs index ba0d444f97e..bf5ae74679b 100644 --- a/tests/codegen/array-codegen.rs +++ b/tests/codegen/array-codegen.rs @@ -32,3 +32,25 @@ // CHECK: store <4 x i8> %[[TEMP2]], ptr %p, align 1 *p = *a; } + +// CHECK-LABEL: @array_copy_1_element +#[no_mangle] +pub fn array_copy_1_element(a: &[u8; 1], p: &mut [u8; 1]) { + // CHECK: %[[LOCAL:.+]] = alloca [1 x i8], align 1 + // CHECK: %[[TEMP1:.+]] = load i8, ptr %a, align 1 + // CHECK: store i8 %[[TEMP1]], ptr %[[LOCAL]], align 1 + // CHECK: %[[TEMP2:.+]] = load i8, ptr %[[LOCAL]], align 1 + // CHECK: store i8 %[[TEMP2]], ptr %p, align 1 + *p = *a; +} + +// CHECK-LABEL: @array_copy_2_elements +#[no_mangle] +pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) { + // CHECK: %[[LOCAL:.+]] = alloca [2 x i8], align 1 + // CHECK: %[[TEMP1:.+]] = load <2 x i8>, ptr %a, align 1 + // CHECK: store <2 x i8> %[[TEMP1]], ptr %[[LOCAL]], align 1 + // CHECK: %[[TEMP2:.+]] = load <2 x i8>, ptr %[[LOCAL]], align 1 + // CHECK: store <2 x i8> %[[TEMP2]], ptr %p, align 1 + *p = *a; +} diff --git a/tests/codegen/array-optimized.rs b/tests/codegen/array-optimized.rs new file mode 100644 index 00000000000..27448fdcfad --- /dev/null +++ b/tests/codegen/array-optimized.rs @@ -0,0 +1,33 @@ +// compile-flags: -O + +#![crate_type = "lib"] + +// CHECK-LABEL: @array_copy_1_element +#[no_mangle] +pub fn array_copy_1_element(a: &[u8; 1], p: &mut [u8; 1]) { + // CHECK-NOT: alloca + // CHECK: %[[TEMP:.+]] = load i8, ptr %a, align 1 + // CHECK: store i8 %[[TEMP]], ptr %p, align 1 + // CHECK: ret + *p = *a; +} + +// CHECK-LABEL: @array_copy_2_elements +#[no_mangle] +pub fn array_copy_2_elements(a: &[u8; 2], p: &mut [u8; 2]) { + // CHECK-NOT: alloca + // CHECK: %[[TEMP:.+]] = load <2 x i8>, ptr %a, align 1 + // CHECK: store <2 x i8> %[[TEMP]], ptr %p, align 1 + // CHECK: ret + *p = *a; +} + +// CHECK-LABEL: @array_copy_4_elements +#[no_mangle] +pub fn array_copy_4_elements(a: &[u8; 4], p: &mut [u8; 4]) { + // CHECK-NOT: alloca + // CHECK: %[[TEMP:.+]] = load <4 x i8>, ptr %a, align 1 + // CHECK: store <4 x i8> %[[TEMP]], ptr %p, align 1 + // CHECK: ret + *p = *a; +} From 6b9ee90c2c49e1abf89a294ee2942efbc80aae7b Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 16 Sep 2023 14:36:23 +0000 Subject: [PATCH 19/58] Reuse determine_cgu_reuse from cg_ssa in cg_clif --- .../rustc_codegen_cranelift/src/driver/aot.rs | 30 +------------------ compiler/rustc_codegen_ssa/src/base.rs | 2 +- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index cc2f5d72714..7a94c3aaef6 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -8,6 +8,7 @@ use cranelift_object::{ObjectBuilder, ObjectModule}; use rustc_codegen_ssa::back::metadata::create_compressed_metadata_file; +use rustc_codegen_ssa::base::determine_cgu_reuse; use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind}; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; @@ -489,32 +490,3 @@ pub(crate) fn run_aot( concurrency_limiter, }) } - -// Adapted from https://github.com/rust-lang/rust/blob/303d8aff6092709edd4dbd35b1c88e9aa40bf6d8/src/librustc_codegen_ssa/base.rs#L922-L953 -fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse { - if !tcx.dep_graph.is_fully_enabled() { - return CguReuse::No; - } - - let work_product_id = &cgu.work_product_id(); - if tcx.dep_graph.previous_work_product(work_product_id).is_none() { - // We don't have anything cached for this CGU. This can happen - // if the CGU did not exist in the previous session. - return CguReuse::No; - } - - // Try to mark the CGU as green. If it we can do so, it means that nothing - // affecting the LLVM module has changed and we can re-use a cached version. - // If we compile with any kind of LTO, this means we can re-use the bitcode - // of the Pre-LTO stage (possibly also the Post-LTO version but we'll only - // know that later). If we are not doing LTO, there is only one optimized - // version of each module, so we re-use that. - let dep_node = cgu.codegen_dep_node(tcx); - assert!( - !tcx.dep_graph.dep_node_exists(&dep_node), - "CompileCodegenUnit dep-node for CGU `{}` already exists before marking.", - cgu.name() - ); - - if tcx.try_mark_green(&dep_node) { CguReuse::PostLto } else { CguReuse::No } -} diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 1e4ea73a172..ede1dc3dfcd 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -994,7 +994,7 @@ pub fn provide(providers: &mut Providers) { }; } -fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse { +pub fn determine_cgu_reuse<'tcx>(tcx: TyCtxt<'tcx>, cgu: &CodegenUnit<'tcx>) -> CguReuse { if !tcx.dep_graph.is_fully_enabled() { return CguReuse::No; } From 3dfd9dfe7ce9bf4bcbb4d1d880cd2c75a85b310c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 16 Sep 2023 14:36:49 +0000 Subject: [PATCH 20/58] Remove an LTO dependent cgu_reuse_tracker.set_actual_reuse call --- compiler/rustc_codegen_llvm/src/back/lto.rs | 2 -- compiler/rustc_codegen_ssa/src/back/write.rs | 4 ---- compiler/rustc_session/src/cgu_reuse_tracker.rs | 8 +------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/back/lto.rs b/compiler/rustc_codegen_llvm/src/back/lto.rs index cb5acf79135..8655aeec13d 100644 --- a/compiler/rustc_codegen_llvm/src/back/lto.rs +++ b/compiler/rustc_codegen_llvm/src/back/lto.rs @@ -19,7 +19,6 @@ use rustc_middle::bug; use rustc_middle::dep_graph::WorkProduct; use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel}; -use rustc_session::cgu_reuse_tracker::CguReuse; use rustc_session::config::{self, CrateType, Lto}; use std::ffi::{CStr, CString}; @@ -584,7 +583,6 @@ fn thin_lto( copy_jobs.push(work_product); info!(" - {}: re-used", module_name); assert!(cgcx.incr_comp_session_dir.is_some()); - cgcx.cgu_reuse_tracker.set_actual_reuse(module_name, CguReuse::PostLto); continue; } } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index f192747c8ab..bc4578719e5 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -26,7 +26,6 @@ use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::middle::exported_symbols::SymbolExportInfo; use rustc_middle::ty::TyCtxt; -use rustc_session::cgu_reuse_tracker::CguReuseTracker; use rustc_session::config::{self, CrateType, Lto, OutFileName, OutputFilenames, OutputType}; use rustc_session::config::{Passes, SwitchWithOptPath}; use rustc_session::Session; @@ -366,8 +365,6 @@ pub struct CodegenContext { /// The incremental compilation session directory, or None if we are not /// compiling incrementally pub incr_comp_session_dir: Option, - /// Used to update CGU re-use information during the thinlto phase. - pub cgu_reuse_tracker: CguReuseTracker, /// Channel back to the main control thread to send messages to pub coordinator_send: Sender>, } @@ -1119,7 +1116,6 @@ fn start_executing_work( remark: sess.opts.cg.remark.clone(), remark_dir, incr_comp_session_dir: sess.incr_comp_session_dir_opt().map(|r| r.clone()), - cgu_reuse_tracker: sess.cgu_reuse_tracker.clone(), coordinator_send, expanded_args: tcx.sess.expanded_args.clone(), diag_emitter: shared_emitter.clone(), diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs index 8703e575465..74b68fa7d35 100644 --- a/compiler/rustc_session/src/cgu_reuse_tracker.rs +++ b/compiler/rustc_session/src/cgu_reuse_tracker.rs @@ -75,13 +75,7 @@ pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); - - if let Some(prev_reuse) = prev_reuse { - // The only time it is legal to overwrite reuse state is when - // we discover during ThinLTO that we can actually reuse the - // post-LTO version of a CGU. - assert_eq!(prev_reuse, CguReuse::PreLto); - } + assert!(prev_reuse.is_none()); } } From e9fa2ca6add1db7f77c90bdc06210be694ed2571 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:23:35 +0000 Subject: [PATCH 21/58] Remove cgu_reuse_tracker from Session This removes a bit of global mutable state --- Cargo.lock | 1 + .../rustc_codegen_cranelift/src/driver/aot.rs | 68 ++++---- compiler/rustc_codegen_ssa/Cargo.toml | 1 + compiler/rustc_codegen_ssa/messages.ftl | 24 +++ .../src/assert_module_sources.rs | 149 +++++++++++++++++- compiler/rustc_codegen_ssa/src/back/write.rs | 2 - compiler/rustc_codegen_ssa/src/base.rs | 10 +- compiler/rustc_codegen_ssa/src/errors.rs | 69 ++++++++ compiler/rustc_codegen_ssa/src/lib.rs | 1 + compiler/rustc_incremental/messages.ftl | 14 -- compiler/rustc_incremental/src/errors.rs | 50 ------ compiler/rustc_incremental/src/lib.rs | 1 - compiler/rustc_interface/src/passes.rs | 3 +- compiler/rustc_session/messages.ftl | 9 -- .../rustc_session/src/cgu_reuse_tracker.rs | 130 --------------- compiler/rustc_session/src/errors.rs | 19 --- compiler/rustc_session/src/lib.rs | 1 - compiler/rustc_session/src/session.rs | 11 -- 18 files changed, 284 insertions(+), 279 deletions(-) rename compiler/{rustc_incremental => rustc_codegen_ssa}/src/assert_module_sources.rs (55%) delete mode 100644 compiler/rustc_session/src/cgu_reuse_tracker.rs diff --git a/Cargo.lock b/Cargo.lock index 082bb4be93c..1861f986d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3650,6 +3650,7 @@ dependencies = [ "serde_json", "smallvec", "tempfile", + "thin-vec", "thorin-dwp", "tracing", "windows", diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index 7a94c3aaef6..aaead1ffc5f 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -7,6 +7,7 @@ use std::thread::JoinHandle; use cranelift_object::{ObjectBuilder, ObjectModule}; +use rustc_codegen_ssa::assert_module_sources::CguReuse; use rustc_codegen_ssa::back::metadata::create_compressed_metadata_file; use rustc_codegen_ssa::base::determine_cgu_reuse; use rustc_codegen_ssa::{CodegenResults, CompiledModule, CrateInfo, ModuleKind}; @@ -15,7 +16,6 @@ use rustc_metadata::EncodedMetadata; use rustc_middle::dep_graph::{WorkProduct, WorkProductId}; use rustc_middle::mir::mono::{CodegenUnit, MonoItem}; -use rustc_session::cgu_reuse_tracker::CguReuse; use rustc_session::config::{DebugInfo, OutputFilenames, OutputType}; use rustc_session::Session; @@ -375,43 +375,47 @@ pub(crate) fn run_aot( } } + // Calculate the CGU reuse + let cgu_reuse = tcx.sess.time("find_cgu_reuse", || { + cgus.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::>() + }); + + rustc_codegen_ssa::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| { + for (i, cgu) in cgus.iter().enumerate() { + let cgu_reuse = cgu_reuse[i]; + cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); + } + }); + let global_asm_config = Arc::new(crate::global_asm::GlobalAsmConfig::new(tcx)); let mut concurrency_limiter = ConcurrencyLimiter::new(tcx.sess, cgus.len()); let modules = tcx.sess.time("codegen mono items", || { cgus.iter() - .map(|cgu| { - let cgu_reuse = if backend_config.disable_incr_cache { - CguReuse::No - } else { - determine_cgu_reuse(tcx, cgu) - }; - tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); - - match cgu_reuse { - CguReuse::No => { - let dep_node = cgu.codegen_dep_node(tcx); - tcx.dep_graph - .with_task( - dep_node, - tcx, - ( - backend_config.clone(), - global_asm_config.clone(), - cgu.name(), - concurrency_limiter.acquire(tcx.sess.diagnostic()), - ), - module_codegen, - Some(rustc_middle::dep_graph::hash_result), - ) - .0 - } - CguReuse::PreLto => unreachable!(), - CguReuse::PostLto => { - concurrency_limiter.job_already_done(); - OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu)) - } + .enumerate() + .map(|(i, cgu)| match cgu_reuse[i] { + CguReuse::No => { + let dep_node = cgu.codegen_dep_node(tcx); + tcx.dep_graph + .with_task( + dep_node, + tcx, + ( + backend_config.clone(), + global_asm_config.clone(), + cgu.name(), + concurrency_limiter.acquire(tcx.sess.diagnostic()), + ), + module_codegen, + Some(rustc_middle::dep_graph::hash_result), + ) + .0 + } + CguReuse::PreLto => unreachable!(), + CguReuse::PostLto => { + concurrency_limiter.job_already_done(); + OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu)) } }) .collect::>() diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 34d0e2d1df6..454e2f80676 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -16,6 +16,7 @@ pathdiff = "0.2.0" serde_json = "1.0.59" smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } regex = "1.4" +thin-vec = "0.2.12" rustc_serialize = { path = "../rustc_serialize" } rustc_arena = { path = "../rustc_arena" } diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 66482667336..5881c6236ec 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -11,6 +11,9 @@ codegen_ssa_atomic_compare_exchange = Atomic compare-exchange intrinsic missing codegen_ssa_binary_output_to_tty = option `-o` or `--emit` is used to write binary output type `{$shorthand}` to stdout, but stdout is a tty +codegen_ssa_cgu_not_recorded = + CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded + codegen_ssa_check_installed_visual_studio = please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. codegen_ssa_copy_path = could not copy {$from} to {$to}: {$error} @@ -39,6 +42,8 @@ codegen_ssa_failed_to_get_layout = failed to get layout for {$ty}: {$err} codegen_ssa_failed_to_write = failed to write {$path}: {$error} +codegen_ssa_field_associated_value_expected = associated value expected for `{$name}` + codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced @@ -46,6 +51,12 @@ codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files w codegen_ssa_illegal_link_ordinal_format = illegal ordinal format in `link_ordinal` .note = an unsuffixed integer value, e.g., `1`, is expected +codegen_ssa_incorrect_cgu_reuse_type = + CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> + [one] {"at least "} + *[other] {""} + }`{$expected_reuse}` + codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient. codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]` @@ -153,12 +164,18 @@ codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status} +codegen_ssa_malformed_cgu_name = + found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). + codegen_ssa_metadata_object_file_write = error writing metadata object file: {$error} codegen_ssa_missing_cpp_build_tool_component = or a necessary component may be missing from the "C++ build tools" workload codegen_ssa_missing_memory_ordering = Atomic intrinsic missing memory ordering +codegen_ssa_missing_query_depgraph = + found CGU-reuse attribute but `-Zquery-dep-graph` was not specified + codegen_ssa_msvc_missing_linker = the msvc targets depend on the msvc linker but `link.exe` was not found codegen_ssa_multiple_external_func_decl = multiple declarations of external function `{$function}` from library `{$library_name}` have different calling conventions @@ -166,6 +183,11 @@ codegen_ssa_multiple_external_func_decl = multiple declarations of external func codegen_ssa_multiple_main_functions = entry symbol `main` declared multiple times .help = did you use `#[no_mangle]` on `fn main`? Use `#[start]` instead +codegen_ssa_no_field = no field `{$name}` + +codegen_ssa_no_module_named = + no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} + codegen_ssa_no_natvis_directory = error enumerating natvis directory: {$error} codegen_ssa_processing_dymutil_failed = processing debug info with `dsymutil` failed: {$status} @@ -297,6 +319,8 @@ codegen_ssa_unknown_atomic_operation = unknown atomic operation codegen_ssa_unknown_atomic_ordering = unknown ordering in atomic intrinsic +codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified + codegen_ssa_unsupported_arch = unsupported arch `{$arch}` for os `{$os}` codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target diff --git a/compiler/rustc_incremental/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs similarity index 55% rename from compiler/rustc_incremental/src/assert_module_sources.rs rename to compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 8e22ab4083e..a5a16a929ae 100644 --- a/compiler/rustc_incremental/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -25,16 +25,22 @@ use crate::errors; use rustc_ast as ast; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unord::UnordSet; +use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; use rustc_hir::def_id::LOCAL_CRATE; use rustc_middle::mir::mono::CodegenUnitNameBuilder; use rustc_middle::ty::TyCtxt; -use rustc_session::cgu_reuse_tracker::*; -use rustc_span::symbol::{sym, Symbol}; +use rustc_session::Session; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol}; +use std::borrow::Cow; +use std::fmt::{self}; +use std::sync::{Arc, Mutex}; use thin_vec::ThinVec; #[allow(missing_docs)] -pub fn assert_module_sources(tcx: TyCtxt<'_>) { +pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&CguReuseTracker)) { tcx.dep_graph.with_ignore(|| { if tcx.sess.opts.incremental.is_none() { return; @@ -43,17 +49,30 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>) { let available_cgus = tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect(); - let ams = AssertModuleSource { tcx, available_cgus }; + let ams = AssertModuleSource { + tcx, + available_cgus, + cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph { + CguReuseTracker::new() + } else { + CguReuseTracker::new_disabled() + }, + }; for attr in tcx.hir().attrs(rustc_hir::CRATE_HIR_ID) { ams.check_attr(attr); } - }) + + set_reuse(&ams.cgu_reuse_tracker); + + ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess); + }); } struct AssertModuleSource<'tcx> { tcx: TyCtxt<'tcx>, available_cgus: UnordSet, + cgu_reuse_tracker: CguReuseTracker, } impl<'tcx> AssertModuleSource<'tcx> { @@ -129,7 +148,7 @@ fn check_attr(&self, attr: &ast::Attribute) { }); } - self.tcx.sess.cgu_reuse_tracker.set_expectation( + self.cgu_reuse_tracker.set_expectation( cgu_name, &user_path, attr.span, @@ -169,3 +188,121 @@ fn check_config(&self, attr: &ast::Attribute) -> bool { false } } + +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum CguReuse { + No, + PreLto, + PostLto, +} + +impl fmt::Display for CguReuse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CguReuse::No => write!(f, "No"), + CguReuse::PreLto => write!(f, "PreLto "), + CguReuse::PostLto => write!(f, "PostLto "), + } + } +} + +impl IntoDiagnosticArg for CguReuse { + fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { + DiagnosticArgValue::Str(Cow::Owned(self.to_string())) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ComparisonKind { + Exact, + AtLeast, +} + +struct TrackerData { + actual_reuse: FxHashMap, + expected_reuse: FxHashMap, +} + +// Span does not implement `Send`, so we can't just store it in the shared +// `TrackerData` object. Instead of splitting up `TrackerData` into shared and +// non-shared parts (which would be complicated), we just mark the `Span` here +// explicitly as `Send`. That's safe because the span data here is only ever +// accessed from the main thread. +struct SendSpan(Span); +unsafe impl Send for SendSpan {} + +#[derive(Clone)] +pub struct CguReuseTracker { + data: Option>>, +} + +impl CguReuseTracker { + pub fn new() -> CguReuseTracker { + let data = + TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; + + CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } + } + + pub fn new_disabled() -> CguReuseTracker { + CguReuseTracker { data: None } + } + + pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { + if let Some(ref data) = self.data { + debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); + + let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); + assert!(prev_reuse.is_none()); + } + } + + pub fn set_expectation( + &self, + cgu_name: Symbol, + cgu_user_name: &str, + error_span: Span, + expected_reuse: CguReuse, + comparison_kind: ComparisonKind, + ) { + if let Some(ref data) = self.data { + debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); + let mut data = data.lock().unwrap(); + + data.expected_reuse.insert( + cgu_name.to_string(), + (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), + ); + } + } + + pub fn check_expected_reuse(&self, sess: &Session) { + if let Some(ref data) = self.data { + let data = data.lock().unwrap(); + + for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in + &data.expected_reuse + { + if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { + let (error, at_least) = match comparison_kind { + ComparisonKind::Exact => (expected_reuse != actual_reuse, false), + ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), + }; + + if error { + let at_least = if at_least { 1 } else { 0 }; + errors::IncorrectCguReuseType { + span: error_span.0, + cgu_user_name, + actual_reuse, + expected_reuse, + at_least, + }; + } + } else { + sess.emit_fatal(errors::CguNotRecorded { cgu_user_name, cgu_name }); + } + } + } + } +} diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index bc4578719e5..3d6a2124334 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -1965,8 +1965,6 @@ pub fn join(self, sess: &Session) -> (CodegenResults, FxIndexMap( codegen_units.iter().map(|cgu| determine_cgu_reuse(tcx, &cgu)).collect::>() }); + crate::assert_module_sources::assert_module_sources(tcx, &|cgu_reuse_tracker| { + for (i, cgu) in codegen_units.iter().enumerate() { + let cgu_reuse = cgu_reuse[i]; + cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); + } + }); + let mut total_codegen_time = Duration::new(0, 0); let start_rss = tcx.sess.opts.unstable_opts.time_passes.then(|| get_resident_set_size()); @@ -727,7 +734,6 @@ pub fn codegen_crate( ongoing_codegen.check_for_errors(tcx.sess); let cgu_reuse = cgu_reuse[i]; - tcx.sess.cgu_reuse_tracker.set_actual_reuse(cgu.name().as_str(), cgu_reuse); match cgu_reuse { CguReuse::No => { diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 39b2fa37fbf..ed6ac9f9c5d 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1,5 +1,6 @@ //! Errors emitted by codegen_ssa +use crate::assert_module_sources::CguReuse; use crate::back::command::Command; use crate::fluent_generated as fluent; use rustc_errors::{ @@ -16,6 +17,74 @@ use std::path::{Path, PathBuf}; use std::process::ExitStatus; +#[derive(Diagnostic)] +#[diag(codegen_ssa_incorrect_cgu_reuse_type)] +pub struct IncorrectCguReuseType<'a> { + #[primary_span] + pub span: Span, + pub cgu_user_name: &'a str, + pub actual_reuse: CguReuse, + pub expected_reuse: CguReuse, + pub at_least: u8, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_cgu_not_recorded)] +pub struct CguNotRecorded<'a> { + pub cgu_user_name: &'a str, + pub cgu_name: &'a str, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_unknown_reuse_kind)] +pub struct UnknownReuseKind { + #[primary_span] + pub span: Span, + pub kind: Symbol, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_missing_query_depgraph)] +pub struct MissingQueryDepGraph { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_malformed_cgu_name)] +pub struct MalformedCguName { + #[primary_span] + pub span: Span, + pub user_path: String, + pub crate_name: String, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_no_module_named)] +pub struct NoModuleNamed<'a> { + #[primary_span] + pub span: Span, + pub user_path: &'a str, + pub cgu_name: Symbol, + pub cgu_names: String, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_field_associated_value_expected)] +pub struct FieldAssociatedValueExpected { + #[primary_span] + pub span: Span, + pub name: Symbol, +} + +#[derive(Diagnostic)] +#[diag(codegen_ssa_no_field)] +pub struct NoField { + #[primary_span] + pub span: Span, + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag(codegen_ssa_lib_def_write_failure)] pub struct LibDefWriteFailure { diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index f6186a290f8..008da2a7ce0 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -43,6 +43,7 @@ use std::io; use std::path::{Path, PathBuf}; +pub mod assert_module_sources; pub mod back; pub mod base; pub mod codegen_attrs; diff --git a/compiler/rustc_incremental/messages.ftl b/compiler/rustc_incremental/messages.ftl index 9fa4e0fb27c..5d885e07192 100644 --- a/compiler/rustc_incremental/messages.ftl +++ b/compiler/rustc_incremental/messages.ftl @@ -46,8 +46,6 @@ incremental_delete_partial = failed to delete partly initialized session dir `{$ incremental_delete_workproduct = file-system error deleting outdated file `{$path}`: {$err} -incremental_field_associated_value_expected = associated value expected for `{$name}` - incremental_finalize = error finalizing incremental compilation session directory `{$path}`: {$err} incremental_finalized_gc_failed = @@ -63,25 +61,15 @@ incremental_load_dep_graph = could not load dep-graph from `{$path}`: {$err} incremental_lock_unsupported = the filesystem for the incremental path at {$session_dir} does not appear to support locking, consider changing the incremental path to a filesystem that supports locking or disable incremental compilation -incremental_malformed_cgu_name = - found malformed codegen unit name `{$user_path}`. codegen units names must always start with the name of the crate (`{$crate_name}` in this case). incremental_missing_depnode = missing `DepNode` variant incremental_missing_if_this_changed = no `#[rustc_if_this_changed]` annotation detected -incremental_missing_query_depgraph = - found CGU-reuse attribute but `-Zquery-dep-graph` was not specified - incremental_move_dep_graph = failed to move dependency graph from `{$from}` to `{$to}`: {$err} incremental_no_cfg = no cfg attribute -incremental_no_field = no field `{$name}` - -incremental_no_module_named = - no module named `{$user_path}` (mangled: {$cgu_name}). available modules: {$cgu_names} - incremental_no_path = no path from `{$source}` to `{$target}` incremental_not_clean = `{$dep_node_str}` should be clean but is not @@ -107,8 +95,6 @@ incremental_undefined_clean_dirty_assertions_item = incremental_unknown_item = unknown item `{$name}` -incremental_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified - incremental_unrecognized_depnode = unrecognized `DepNode` variant: {$name} incremental_unrecognized_depnode_label = dep-node label `{$label}` not recognized diff --git a/compiler/rustc_incremental/src/errors.rs b/compiler/rustc_incremental/src/errors.rs index deb87678365..05ed4f7598d 100644 --- a/compiler/rustc_incremental/src/errors.rs +++ b/compiler/rustc_incremental/src/errors.rs @@ -40,56 +40,6 @@ pub struct NoPath { pub source: String, } -#[derive(Diagnostic)] -#[diag(incremental_unknown_reuse_kind)] -pub struct UnknownReuseKind { - #[primary_span] - pub span: Span, - pub kind: Symbol, -} - -#[derive(Diagnostic)] -#[diag(incremental_missing_query_depgraph)] -pub struct MissingQueryDepGraph { - #[primary_span] - pub span: Span, -} - -#[derive(Diagnostic)] -#[diag(incremental_malformed_cgu_name)] -pub struct MalformedCguName { - #[primary_span] - pub span: Span, - pub user_path: String, - pub crate_name: String, -} - -#[derive(Diagnostic)] -#[diag(incremental_no_module_named)] -pub struct NoModuleNamed<'a> { - #[primary_span] - pub span: Span, - pub user_path: &'a str, - pub cgu_name: Symbol, - pub cgu_names: String, -} - -#[derive(Diagnostic)] -#[diag(incremental_field_associated_value_expected)] -pub struct FieldAssociatedValueExpected { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - -#[derive(Diagnostic)] -#[diag(incremental_no_field)] -pub struct NoField { - #[primary_span] - pub span: Span, - pub name: Symbol, -} - #[derive(Diagnostic)] #[diag(incremental_assertion_auto)] pub struct AssertionAuto<'a> { diff --git a/compiler/rustc_incremental/src/lib.rs b/compiler/rustc_incremental/src/lib.rs index 220ea194a6d..e4050099fc1 100644 --- a/compiler/rustc_incremental/src/lib.rs +++ b/compiler/rustc_incremental/src/lib.rs @@ -13,7 +13,6 @@ extern crate tracing; mod assert_dep_graph; -pub mod assert_module_sources; mod errors; mod persist; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 0e8f93cef17..718dbaaafcc 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -957,10 +957,9 @@ pub fn start_codegen<'tcx>( codegen_backend.codegen_crate(tcx, metadata, need_metadata_module) }); - // Don't run these test assertions when not doing codegen. Compiletest tries to build + // Don't run this test assertions when not doing codegen. Compiletest tries to build // build-fail tests in check mode first and expects it to not give an error in that case. if tcx.sess.opts.output_types.should_codegen() { - rustc_incremental::assert_module_sources::assert_module_sources(tcx); rustc_symbol_mangling::test::report_symbol_names(tcx); } diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index b356b503aa5..fa1b6f9f13d 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -5,9 +5,6 @@ session_cannot_enable_crt_static_linux = sanitizer is incompatible with statical session_cannot_mix_and_match_sanitizers = `-Zsanitizer={$first}` is incompatible with `-Zsanitizer={$second}` -session_cgu_not_recorded = - CGU-reuse for `{$cgu_user_name}` is (mangled: `{$cgu_name}`) was not recorded - session_cli_feature_diagnostic_help = add `-Zcrate-attr="feature({$feature})"` to the command-line options to enable @@ -34,12 +31,6 @@ session_hexadecimal_float_literal_not_supported = hexadecimal float literal is n session_incompatible_linker_flavor = linker flavor `{$flavor}` is incompatible with the current target .note = compatible flavors are: {$compatible_list} -session_incorrect_cgu_reuse_type = - CGU-reuse for `{$cgu_user_name}` is `{$actual_reuse}` but should be {$at_least -> - [one] {"at least "} - *[other] {""} - }`{$expected_reuse}` - session_instrumentation_not_supported = {$us} instrumentation is not supported for this target session_int_literal_too_large = integer literal is too large diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs deleted file mode 100644 index 74b68fa7d35..00000000000 --- a/compiler/rustc_session/src/cgu_reuse_tracker.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Some facilities for tracking how codegen-units are reused during incremental -//! compilation. This is used for incremental compilation tests and debug -//! output. - -use crate::errors::{CguNotRecorded, IncorrectCguReuseType}; -use crate::Session; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagnosticArgValue, IntoDiagnosticArg}; -use rustc_span::{Span, Symbol}; -use std::borrow::Cow; -use std::fmt::{self}; -use std::sync::{Arc, Mutex}; - -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] -pub enum CguReuse { - No, - PreLto, - PostLto, -} - -impl fmt::Display for CguReuse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - CguReuse::No => write!(f, "No"), - CguReuse::PreLto => write!(f, "PreLto "), - CguReuse::PostLto => write!(f, "PostLto "), - } - } -} - -impl IntoDiagnosticArg for CguReuse { - fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> { - DiagnosticArgValue::Str(Cow::Owned(self.to_string())) - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum ComparisonKind { - Exact, - AtLeast, -} - -struct TrackerData { - actual_reuse: FxHashMap, - expected_reuse: FxHashMap, -} - -// Span does not implement `Send`, so we can't just store it in the shared -// `TrackerData` object. Instead of splitting up `TrackerData` into shared and -// non-shared parts (which would be complicated), we just mark the `Span` here -// explicitly as `Send`. That's safe because the span data here is only ever -// accessed from the main thread. -struct SendSpan(Span); -unsafe impl Send for SendSpan {} - -#[derive(Clone)] -pub struct CguReuseTracker { - data: Option>>, -} - -impl CguReuseTracker { - pub fn new() -> CguReuseTracker { - let data = - TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; - - CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } - } - - pub fn new_disabled() -> CguReuseTracker { - CguReuseTracker { data: None } - } - - pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { - if let Some(ref data) = self.data { - debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); - - let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); - assert!(prev_reuse.is_none()); - } - } - - pub fn set_expectation( - &self, - cgu_name: Symbol, - cgu_user_name: &str, - error_span: Span, - expected_reuse: CguReuse, - comparison_kind: ComparisonKind, - ) { - if let Some(ref data) = self.data { - debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); - let mut data = data.lock().unwrap(); - - data.expected_reuse.insert( - cgu_name.to_string(), - (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), - ); - } - } - - pub fn check_expected_reuse(&self, sess: &Session) { - if let Some(ref data) = self.data { - let data = data.lock().unwrap(); - - for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in - &data.expected_reuse - { - if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { - let (error, at_least) = match comparison_kind { - ComparisonKind::Exact => (expected_reuse != actual_reuse, false), - ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), - }; - - if error { - let at_least = if at_least { 1 } else { 0 }; - IncorrectCguReuseType { - span: error_span.0, - cgu_user_name, - actual_reuse, - expected_reuse, - at_least, - }; - } - } else { - sess.emit_fatal(CguNotRecorded { cgu_user_name, cgu_name }); - } - } - } - } -} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 5f8bbfca890..31094e0d266 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -1,6 +1,5 @@ use std::num::NonZeroU32; -use crate::cgu_reuse_tracker::CguReuse; use crate::parse::ParseSess; use rustc_ast::token; use rustc_ast::util::literal::LitError; @@ -9,24 +8,6 @@ use rustc_span::{BytePos, Span, Symbol}; use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTriple}; -#[derive(Diagnostic)] -#[diag(session_incorrect_cgu_reuse_type)] -pub struct IncorrectCguReuseType<'a> { - #[primary_span] - pub span: Span, - pub cgu_user_name: &'a str, - pub actual_reuse: CguReuse, - pub expected_reuse: CguReuse, - pub at_least: u8, -} - -#[derive(Diagnostic)] -#[diag(session_cgu_not_recorded)] -pub struct CguNotRecorded<'a> { - pub cgu_user_name: &'a str, - pub cgu_name: &'a str, -} - pub struct FeatureGateError { pub span: MultiSpan, pub explain: DiagnosticMessage, diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index d6c746a7bd8..66a5092ccd4 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -22,7 +22,6 @@ use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; use rustc_fluent_macro::fluent_messages; -pub mod cgu_reuse_tracker; pub mod utils; pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pass}; pub use rustc_lint_defs as lint; diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b484978eed2..5cac11cc8f7 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,4 +1,3 @@ -use crate::cgu_reuse_tracker::CguReuseTracker; use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::{ @@ -153,9 +152,6 @@ pub struct Session { pub io: CompilerIO, incr_comp_session: OneThread>, - /// Used for incremental compilation tests. Will only be populated if - /// `-Zquery-dep-graph` is specified. - pub cgu_reuse_tracker: CguReuseTracker, /// Used by `-Z self-profile`. pub prof: SelfProfilerRef, @@ -1431,12 +1427,6 @@ pub fn build_session( }); let print_fuel = AtomicU64::new(0); - let cgu_reuse_tracker = if sopts.unstable_opts.query_dep_graph { - CguReuseTracker::new() - } else { - CguReuseTracker::new_disabled() - }; - let prof = SelfProfilerRef::new( self_profiler, sopts.unstable_opts.time_passes.then(|| sopts.unstable_opts.time_passes_format), @@ -1461,7 +1451,6 @@ pub fn build_session( sysroot, io, incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)), - cgu_reuse_tracker, prof, perf_stats: PerfStats { symbol_hash_time: Lock::new(Duration::from_secs(0)), From 7f5af72e64ec08addd1398c7b809f149f5ff57c5 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:26:46 +0000 Subject: [PATCH 22/58] Remove interior mutability from CguReuseTracker --- .../src/assert_module_sources.rs | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index a5a16a929ae..27873dd2e1a 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -35,12 +35,11 @@ use rustc_span::symbol::sym; use rustc_span::{Span, Symbol}; use std::borrow::Cow; -use std::fmt::{self}; -use std::sync::{Arc, Mutex}; +use std::fmt; use thin_vec::ThinVec; #[allow(missing_docs)] -pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&CguReuseTracker)) { +pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&mut CguReuseTracker)) { tcx.dep_graph.with_ignore(|| { if tcx.sess.opts.incremental.is_none() { return; @@ -49,7 +48,7 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&CguReuseTracke let available_cgus = tcx.collect_and_partition_mono_items(()).1.iter().map(|cgu| cgu.name()).collect(); - let ams = AssertModuleSource { + let mut ams = AssertModuleSource { tcx, available_cgus, cgu_reuse_tracker: if tcx.sess.opts.unstable_opts.query_dep_graph { @@ -63,7 +62,7 @@ pub fn assert_module_sources(tcx: TyCtxt<'_>, set_reuse: &dyn Fn(&CguReuseTracke ams.check_attr(attr); } - set_reuse(&ams.cgu_reuse_tracker); + set_reuse(&mut ams.cgu_reuse_tracker); ams.cgu_reuse_tracker.check_expected_reuse(tcx.sess); }); @@ -76,7 +75,7 @@ struct AssertModuleSource<'tcx> { } impl<'tcx> AssertModuleSource<'tcx> { - fn check_attr(&self, attr: &ast::Attribute) { + fn check_attr(&mut self, attr: &ast::Attribute) { let (expected_reuse, comp_kind) = if attr.has_name(sym::rustc_partition_reused) { (CguReuse::PreLto, ComparisonKind::AtLeast) } else if attr.has_name(sym::rustc_partition_codegened) { @@ -220,20 +219,11 @@ pub enum ComparisonKind { struct TrackerData { actual_reuse: FxHashMap, - expected_reuse: FxHashMap, + expected_reuse: FxHashMap, } -// Span does not implement `Send`, so we can't just store it in the shared -// `TrackerData` object. Instead of splitting up `TrackerData` into shared and -// non-shared parts (which would be complicated), we just mark the `Span` here -// explicitly as `Send`. That's safe because the span data here is only ever -// accessed from the main thread. -struct SendSpan(Span); -unsafe impl Send for SendSpan {} - -#[derive(Clone)] pub struct CguReuseTracker { - data: Option>>, + data: Option, } impl CguReuseTracker { @@ -241,45 +231,42 @@ pub fn new() -> CguReuseTracker { let data = TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; - CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } + CguReuseTracker { data: Some(data) } } pub fn new_disabled() -> CguReuseTracker { CguReuseTracker { data: None } } - pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { - if let Some(ref data) = self.data { + pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) { + if let Some(data) = &mut self.data { debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); - let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); + let prev_reuse = data.actual_reuse.insert(cgu_name.to_string(), kind); assert!(prev_reuse.is_none()); } } pub fn set_expectation( - &self, + &mut self, cgu_name: Symbol, cgu_user_name: &str, error_span: Span, expected_reuse: CguReuse, comparison_kind: ComparisonKind, ) { - if let Some(ref data) = self.data { + if let Some(data) = &mut self.data { debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); - let mut data = data.lock().unwrap(); data.expected_reuse.insert( cgu_name.to_string(), - (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), + (cgu_user_name.to_string(), error_span, expected_reuse, comparison_kind), ); } } pub fn check_expected_reuse(&self, sess: &Session) { if let Some(ref data) = self.data { - let data = data.lock().unwrap(); - for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in &data.expected_reuse { @@ -292,7 +279,7 @@ pub fn check_expected_reuse(&self, sess: &Session) { if error { let at_least = if at_least { 1 } else { 0 }; errors::IncorrectCguReuseType { - span: error_span.0, + span: *error_span, cgu_user_name, actual_reuse, expected_reuse, From f0b5820fa5b29d840e700ee5d760b55d291311c3 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:49:10 +0000 Subject: [PATCH 23/58] Fix review comments --- compiler/rustc_codegen_cranelift/src/driver/aot.rs | 2 +- compiler/rustc_codegen_ssa/src/assert_module_sources.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index aaead1ffc5f..49f51f9f956 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -412,7 +412,7 @@ pub(crate) fn run_aot( ) .0 } - CguReuse::PreLto => unreachable!(), + CguReuse::PreLto => unreachable!("LTO not yet supported"), CguReuse::PostLto => { concurrency_limiter.job_already_done(); OngoingModuleCodegen::Sync(reuse_workproduct_for_cgu(tcx, cgu)) diff --git a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs index 27873dd2e1a..16bb7b12bd3 100644 --- a/compiler/rustc_codegen_ssa/src/assert_module_sources.rs +++ b/compiler/rustc_codegen_ssa/src/assert_module_sources.rs @@ -227,14 +227,14 @@ pub struct CguReuseTracker { } impl CguReuseTracker { - pub fn new() -> CguReuseTracker { + fn new() -> CguReuseTracker { let data = TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; CguReuseTracker { data: Some(data) } } - pub fn new_disabled() -> CguReuseTracker { + fn new_disabled() -> CguReuseTracker { CguReuseTracker { data: None } } @@ -247,7 +247,7 @@ pub fn set_actual_reuse(&mut self, cgu_name: &str, kind: CguReuse) { } } - pub fn set_expectation( + fn set_expectation( &mut self, cgu_name: Symbol, cgu_user_name: &str, @@ -265,7 +265,7 @@ pub fn set_expectation( } } - pub fn check_expected_reuse(&self, sess: &Session) { + fn check_expected_reuse(&self, sess: &Session) { if let Some(ref data) = self.data { for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in &data.expected_reuse From b6b11c72c96d69bc622c7a82484c29c3c3365ce7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 14:02:42 +1100 Subject: [PATCH 24/58] Rejig some top-level `rustc_hir_pretty` functions. There are several that are unused and can be removed. And there are some calls to `to_string`, which can be expressed more nicely as a `foo_to_string` call, and then `to_string` need not be `pub`. (This requires adding `pat_to_string`). --- compiler/rustc_hir_pretty/src/lib.rs | 38 ++----------------- compiler/rustc_hir_typeck/src/pat.rs | 12 ++---- .../src/matches/match_wild_err_arm.rs | 2 +- .../clippy/clippy_lints/src/mut_reference.rs | 2 +- 4 files changed, 8 insertions(+), 46 deletions(-) diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 8587b009f25..bc5b2a10d9f 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -187,7 +187,7 @@ fn attrs(&self, id: hir::HirId) -> &'a [ast::Attribute] { } } -pub fn to_string(ann: &dyn PpAnn, f: F) -> String +fn to_string(ann: &dyn PpAnn, f: F) -> String where F: FnOnce(&mut State<'_>), { @@ -196,48 +196,16 @@ pub fn to_string(ann: &dyn PpAnn, f: F) -> String printer.s.eof() } -pub fn generic_params_to_string(generic_params: &[GenericParam<'_>]) -> String { - to_string(NO_ANN, |s| s.print_generic_params(generic_params)) -} - -pub fn bounds_to_string<'b>(bounds: impl IntoIterator>) -> String { - to_string(NO_ANN, |s| s.print_bounds("", bounds)) -} - pub fn ty_to_string(ty: &hir::Ty<'_>) -> String { to_string(NO_ANN, |s| s.print_type(ty)) } -pub fn path_segment_to_string(segment: &hir::PathSegment<'_>) -> String { - to_string(NO_ANN, |s| s.print_path_segment(segment)) -} - -pub fn path_to_string(segment: &hir::Path<'_>) -> String { - to_string(NO_ANN, |s| s.print_path(segment, false)) -} - pub fn qpath_to_string(segment: &hir::QPath<'_>) -> String { to_string(NO_ANN, |s| s.print_qpath(segment, false)) } -pub fn fn_to_string( - decl: &hir::FnDecl<'_>, - header: hir::FnHeader, - name: Option, - generics: &hir::Generics<'_>, - arg_names: &[Ident], - body_id: Option, -) -> String { - to_string(NO_ANN, |s| s.print_fn(decl, header, name, generics, arg_names, body_id)) -} - -pub fn enum_def_to_string( - enum_definition: &hir::EnumDef<'_>, - generics: &hir::Generics<'_>, - name: Symbol, - span: rustc_span::Span, -) -> String { - to_string(NO_ANN, |s| s.print_enum_def(enum_definition, generics, name, span)) +pub fn pat_to_string(pat: &hir::Pat<'_>) -> String { + to_string(NO_ANN, |s| s.print_pat(pat)) } impl<'a> State<'a> { diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 3f9c9b3381b..072c7a9f36a 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -1504,9 +1504,7 @@ fn error_tuple_variant_index_shorthand( { let has_shorthand_field_name = field_patterns.iter().any(|field| field.is_shorthand); if has_shorthand_field_name { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(qpath, false) - }); + let path = rustc_hir_pretty::qpath_to_string(qpath); let mut err = struct_span_err!( self.tcx.sess, pat.span, @@ -1688,9 +1686,7 @@ fn error_tuple_variant_as_struct_pat( return None; } - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(qpath, false) - }); + let path = rustc_hir_pretty::qpath_to_string(qpath); let mut err = struct_span_err!( self.tcx.sess, pat.span, @@ -1740,9 +1736,7 @@ fn get_suggested_tuple_struct_pattern( f } } - Err(_) => rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_pat(field.pat) - }), + Err(_) => rustc_hir_pretty::pat_to_string(field.pat), } }) .collect::>() diff --git a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs index de911f7a028..a2903e52ae0 100644 --- a/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs +++ b/src/tools/clippy/clippy_lints/src/matches/match_wild_err_arm.rs @@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<' if is_type_diagnostic_item(cx, ex_ty, sym::Result) { for arm in arms { if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { - let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); + let path_str = rustc_hir_pretty::qpath_to_string(path); if path_str == "Err" { let mut matching_wild = inner.iter().any(is_wild); let mut ident_bind_name = kw::Underscore; diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs index e53e146ec5d..01b850cdb11 100644 --- a/src/tools/clippy/clippy_lints/src/mut_reference.rs +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -49,7 +49,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { cx, arguments.iter().collect(), cx.typeck_results().expr_ty(fn_expr), - &rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)), + &rustc_hir_pretty::qpath_to_string(path), "function", ); } From 51e8c807277580f3575df4bbd01ebf2bfde941ff Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Tue, 10 Oct 2023 02:40:26 -0400 Subject: [PATCH 25/58] Add unstable book page for the no-jump-tables codegen option --- .../src/compiler-flags/no-jump-tables.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/doc/unstable-book/src/compiler-flags/no-jump-tables.md diff --git a/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md b/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md new file mode 100644 index 00000000000..f096c20f4bd --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/no-jump-tables.md @@ -0,0 +1,19 @@ +# `no-jump-tables` + +The tracking issue for this feature is [#116592](https://github.com/rust-lang/rust/issues/116592) + +--- + +This option enables the `-fno-jump-tables` flag for LLVM, which makes the +codegen backend avoid generating jump tables when lowering switches. + +This option adds the LLVM `no-jump-tables=true` attribute to every function. + +The option can be used to help provide protection against +jump-oriented-programming (JOP) attacks, such as with the linux kernel's [IBT]. + +```sh +RUSTFLAGS="-Zno-jump-tables" cargo +nightly build -Z build-std +``` + +[IBT]: https://www.phoronix.com/news/Linux-IBT-By-Default-Tip From 664b1858aac90a75e6bbaca9f3c0726877a04ab3 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 14:01:36 +1100 Subject: [PATCH 26/58] Remove many unneeded `pub`s. --- compiler/rustc_hir_pretty/src/lib.rs | 117 ++++++++++++++------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index bc5b2a10d9f..e6388f34eea 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -75,7 +75,7 @@ pub struct State<'a> { } impl<'a> State<'a> { - pub fn print_node(&mut self, node: Node<'_>) { + fn print_node(&mut self, node: Node<'_>) { match node { Node::Param(a) => self.print_param(a), Node::Item(a) => self.print_item(a), @@ -144,7 +144,7 @@ fn print_generic_args(&mut self, _: &ast::GenericArgs, _colons_before_params: bo } } -pub const INDENT_UNIT: isize = 4; +const INDENT_UNIT: isize = 4; /// Requires you to pass an input filename and reader so that /// it can scan the input text for comments to copy forward. @@ -167,7 +167,7 @@ pub fn print_crate<'a>( } impl<'a> State<'a> { - pub fn new_from_input( + fn new_from_input( sm: &'a SourceMap, filename: FileName, input: String, @@ -209,7 +209,7 @@ pub fn pat_to_string(pat: &hir::Pat<'_>) -> String { } impl<'a> State<'a> { - pub fn bclose_maybe_open(&mut self, span: rustc_span::Span, close_box: bool) { + fn bclose_maybe_open(&mut self, span: rustc_span::Span, close_box: bool) { self.maybe_print_comment(span.hi()); self.break_offset_if_not_bol(1, -INDENT_UNIT); self.word("}"); @@ -218,11 +218,11 @@ pub fn bclose_maybe_open(&mut self, span: rustc_span::Span, close_box: bool) { } } - pub fn bclose(&mut self, span: rustc_span::Span) { + fn bclose(&mut self, span: rustc_span::Span) { self.bclose_maybe_open(span, true) } - pub fn commasep_cmnt(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G) + fn commasep_cmnt(&mut self, b: Breaks, elts: &[T], mut op: F, mut get_span: G) where F: FnMut(&mut State<'_>, &T), G: FnMut(&T) -> rustc_span::Span, @@ -243,25 +243,25 @@ pub fn commasep_cmnt(&mut self, b: Breaks, elts: &[T], mut op: F, mut g self.end(); } - pub fn commasep_exprs(&mut self, b: Breaks, exprs: &[hir::Expr<'_>]) { + fn commasep_exprs(&mut self, b: Breaks, exprs: &[hir::Expr<'_>]) { self.commasep_cmnt(b, exprs, |s, e| s.print_expr(e), |e| e.span); } - pub fn print_mod(&mut self, _mod: &hir::Mod<'_>, attrs: &[ast::Attribute]) { + fn print_mod(&mut self, _mod: &hir::Mod<'_>, attrs: &[ast::Attribute]) { self.print_inner_attributes(attrs); for &item_id in _mod.item_ids { self.ann.nested(self, Nested::Item(item_id)); } } - pub fn print_opt_lifetime(&mut self, lifetime: &hir::Lifetime) { + fn print_opt_lifetime(&mut self, lifetime: &hir::Lifetime) { if !lifetime.is_elided() { self.print_lifetime(lifetime); self.nbsp(); } } - pub fn print_type(&mut self, ty: &hir::Ty<'_>) { + fn print_type(&mut self, ty: &hir::Ty<'_>) { self.maybe_print_comment(ty.span.lo()); self.ibox(0); match ty.kind { @@ -339,7 +339,7 @@ pub fn print_type(&mut self, ty: &hir::Ty<'_>) { self.end() } - pub fn print_foreign_item(&mut self, item: &hir::ForeignItem<'_>) { + fn print_foreign_item(&mut self, item: &hir::ForeignItem<'_>) { self.hardbreak_if_not_bol(); self.maybe_print_comment(item.span.lo()); self.print_outer_attributes(self.attrs(item.hir_id())); @@ -447,7 +447,7 @@ fn print_item_type( } /// Pretty-print an item - pub fn print_item(&mut self, item: &hir::Item<'_>) { + fn print_item(&mut self, item: &hir::Item<'_>) { self.hardbreak_if_not_bol(); self.maybe_print_comment(item.span.lo()); let attrs = self.attrs(item.hir_id()); @@ -672,7 +672,7 @@ pub fn print_item(&mut self, item: &hir::Item<'_>) { self.ann.post(self, AnnNode::Item(item)) } - pub fn print_trait_ref(&mut self, t: &hir::TraitRef<'_>) { + fn print_trait_ref(&mut self, t: &hir::TraitRef<'_>) { self.print_path(t.path, false); } @@ -689,7 +689,7 @@ fn print_poly_trait_ref(&mut self, t: &hir::PolyTraitRef<'_>) { self.print_trait_ref(&t.trait_ref); } - pub fn print_enum_def( + fn print_enum_def( &mut self, enum_definition: &hir::EnumDef<'_>, generics: &hir::Generics<'_>, @@ -704,7 +704,7 @@ pub fn print_enum_def( self.print_variants(enum_definition.variants, span); } - pub fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span::Span) { + fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span::Span) { self.bopen(); for v in variants { self.space_if_not_bol(); @@ -719,14 +719,14 @@ pub fn print_variants(&mut self, variants: &[hir::Variant<'_>], span: rustc_span self.bclose(span) } - pub fn print_defaultness(&mut self, defaultness: hir::Defaultness) { + fn print_defaultness(&mut self, defaultness: hir::Defaultness) { match defaultness { hir::Defaultness::Default { .. } => self.word_nbsp("default"), hir::Defaultness::Final => (), } } - pub fn print_struct( + fn print_struct( &mut self, struct_def: &hir::VariantData<'_>, generics: &hir::Generics<'_>, @@ -775,7 +775,7 @@ pub fn print_struct( } } - pub fn print_variant(&mut self, v: &hir::Variant<'_>) { + fn print_variant(&mut self, v: &hir::Variant<'_>) { self.head(""); let generics = hir::Generics::empty(); self.print_struct(&v.data, generics, v.ident.name, v.span, false); @@ -785,7 +785,8 @@ pub fn print_variant(&mut self, v: &hir::Variant<'_>) { self.print_anon_const(d); } } - pub fn print_method_sig( + + fn print_method_sig( &mut self, ident: Ident, m: &hir::FnSig<'_>, @@ -796,7 +797,7 @@ pub fn print_method_sig( self.print_fn(m.decl, m.header, Some(ident.name), generics, arg_names, body_id); } - pub fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) { + fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) { self.ann.pre(self, AnnNode::SubItem(ti.hir_id())); self.hardbreak_if_not_bol(); self.maybe_print_comment(ti.span.lo()); @@ -824,7 +825,7 @@ pub fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) { self.ann.post(self, AnnNode::SubItem(ti.hir_id())) } - pub fn print_impl_item(&mut self, ii: &hir::ImplItem<'_>) { + fn print_impl_item(&mut self, ii: &hir::ImplItem<'_>) { self.ann.pre(self, AnnNode::SubItem(ii.hir_id())); self.hardbreak_if_not_bol(); self.maybe_print_comment(ii.span.lo()); @@ -849,7 +850,7 @@ pub fn print_impl_item(&mut self, ii: &hir::ImplItem<'_>) { self.ann.post(self, AnnNode::SubItem(ii.hir_id())) } - pub fn print_local( + fn print_local( &mut self, init: Option<&hir::Expr<'_>>, els: Option<&hir::Block<'_>>, @@ -882,7 +883,7 @@ pub fn print_local( self.end() } - pub fn print_stmt(&mut self, st: &hir::Stmt<'_>) { + fn print_stmt(&mut self, st: &hir::Stmt<'_>) { self.maybe_print_comment(st.span.lo()); match st.kind { hir::StmtKind::Local(loc) => { @@ -905,19 +906,19 @@ pub fn print_stmt(&mut self, st: &hir::Stmt<'_>) { self.maybe_print_trailing_comment(st.span, None) } - pub fn print_block(&mut self, blk: &hir::Block<'_>) { + fn print_block(&mut self, blk: &hir::Block<'_>) { self.print_block_with_attrs(blk, &[]) } - pub fn print_block_unclosed(&mut self, blk: &hir::Block<'_>) { + fn print_block_unclosed(&mut self, blk: &hir::Block<'_>) { self.print_block_maybe_unclosed(blk, &[], false) } - pub fn print_block_with_attrs(&mut self, blk: &hir::Block<'_>, attrs: &[ast::Attribute]) { + fn print_block_with_attrs(&mut self, blk: &hir::Block<'_>, attrs: &[ast::Attribute]) { self.print_block_maybe_unclosed(blk, attrs, true) } - pub fn print_block_maybe_unclosed( + fn print_block_maybe_unclosed( &mut self, blk: &hir::Block<'_>, attrs: &[ast::Attribute], @@ -973,7 +974,7 @@ fn print_else(&mut self, els: Option<&hir::Expr<'_>>) { } } - pub fn print_if( + fn print_if( &mut self, test: &hir::Expr<'_>, blk: &hir::Expr<'_>, @@ -986,14 +987,14 @@ pub fn print_if( self.print_else(elseopt) } - pub fn print_array_length(&mut self, len: &hir::ArrayLen) { + fn print_array_length(&mut self, len: &hir::ArrayLen) { match len { hir::ArrayLen::Infer(_, _) => self.word("_"), hir::ArrayLen::Body(ct) => self.print_anon_const(ct), } } - pub fn print_anon_const(&mut self, constant: &hir::AnonConst) { + fn print_anon_const(&mut self, constant: &hir::AnonConst) { self.ann.nested(self, Nested::Body(constant.body)) } @@ -1009,7 +1010,7 @@ fn print_expr_maybe_paren(&mut self, expr: &hir::Expr<'_>, prec: i8) { /// Prints an expr using syntax that's acceptable in a condition position, such as the `cond` in /// `if cond { ... }`. - pub fn print_expr_as_cond(&mut self, expr: &hir::Expr<'_>) { + fn print_expr_as_cond(&mut self, expr: &hir::Expr<'_>) { self.print_expr_cond_paren(expr, Self::cond_needs_par(expr)) } @@ -1328,7 +1329,7 @@ enum AsmArg<'a> { self.pclose(); } - pub fn print_expr(&mut self, expr: &hir::Expr<'_>) { + fn print_expr(&mut self, expr: &hir::Expr<'_>) { self.maybe_print_comment(expr.span.lo()); self.print_outer_attributes(self.attrs(expr.hir_id)); self.ibox(INDENT_UNIT); @@ -1561,7 +1562,7 @@ pub fn print_expr(&mut self, expr: &hir::Expr<'_>) { self.end() } - pub fn print_local_decl(&mut self, loc: &hir::Local<'_>) { + fn print_local_decl(&mut self, loc: &hir::Local<'_>) { self.print_pat(loc.pat); if let Some(ty) = loc.ty { self.word_space(":"); @@ -1569,11 +1570,11 @@ pub fn print_local_decl(&mut self, loc: &hir::Local<'_>) { } } - pub fn print_name(&mut self, name: Symbol) { + fn print_name(&mut self, name: Symbol) { self.print_ident(Ident::with_dummy_span(name)) } - pub fn print_path(&mut self, path: &hir::Path<'_, R>, colons_before_params: bool) { + fn print_path(&mut self, path: &hir::Path<'_, R>, colons_before_params: bool) { self.maybe_print_comment(path.span.lo()); for (i, segment) in path.segments.iter().enumerate() { @@ -1587,14 +1588,14 @@ pub fn print_path(&mut self, path: &hir::Path<'_, R>, colons_before_params: b } } - pub fn print_path_segment(&mut self, segment: &hir::PathSegment<'_>) { + fn print_path_segment(&mut self, segment: &hir::PathSegment<'_>) { if segment.ident.name != kw::PathRoot { self.print_ident(segment.ident); self.print_generic_args(segment.args(), false); } } - pub fn print_qpath(&mut self, qpath: &hir::QPath<'_>, colons_before_params: bool) { + fn print_qpath(&mut self, qpath: &hir::QPath<'_>, colons_before_params: bool) { match *qpath { hir::QPath::Resolved(None, path) => self.print_path(path, colons_before_params), hir::QPath::Resolved(Some(qself), path) => { @@ -1711,7 +1712,7 @@ fn print_generic_args( } } - pub fn print_type_binding(&mut self, binding: &hir::TypeBinding<'_>) { + fn print_type_binding(&mut self, binding: &hir::TypeBinding<'_>) { self.print_ident(binding.ident); self.print_generic_args(binding.gen_args, false); self.space(); @@ -1729,7 +1730,7 @@ pub fn print_type_binding(&mut self, binding: &hir::TypeBinding<'_>) { } } - pub fn print_pat(&mut self, pat: &hir::Pat<'_>) { + fn print_pat(&mut self, pat: &hir::Pat<'_>) { self.maybe_print_comment(pat.span.lo()); self.ann.pre(self, AnnNode::Pat(pat)); // Pat isn't normalized, but the beauty of it @@ -1873,7 +1874,7 @@ pub fn print_pat(&mut self, pat: &hir::Pat<'_>) { self.ann.post(self, AnnNode::Pat(pat)) } - pub fn print_patfield(&mut self, field: &hir::PatField<'_>) { + fn print_patfield(&mut self, field: &hir::PatField<'_>) { if self.attrs(field.hir_id).is_empty() { self.space(); } @@ -1887,12 +1888,12 @@ pub fn print_patfield(&mut self, field: &hir::PatField<'_>) { self.end(); } - pub fn print_param(&mut self, arg: &hir::Param<'_>) { + fn print_param(&mut self, arg: &hir::Param<'_>) { self.print_outer_attributes(self.attrs(arg.hir_id)); self.print_pat(arg.pat); } - pub fn print_arm(&mut self, arm: &hir::Arm<'_>) { + fn print_arm(&mut self, arm: &hir::Arm<'_>) { // I have no idea why this check is necessary, but here it // is :( if self.attrs(arm.hir_id).is_empty() { @@ -1944,7 +1945,7 @@ pub fn print_arm(&mut self, arm: &hir::Arm<'_>) { self.end() // close enclosing cbox } - pub fn print_fn( + fn print_fn( &mut self, decl: &hir::FnDecl<'_>, header: hir::FnHeader, @@ -2024,14 +2025,14 @@ fn print_closure_params(&mut self, decl: &hir::FnDecl<'_>, body_id: hir::BodyId) } } - pub fn print_capture_clause(&mut self, capture_clause: hir::CaptureBy) { + fn print_capture_clause(&mut self, capture_clause: hir::CaptureBy) { match capture_clause { hir::CaptureBy::Value => self.word_space("move"), hir::CaptureBy::Ref => {} } } - pub fn print_closure_binder( + fn print_closure_binder( &mut self, binder: hir::ClosureBinder, generic_params: &[GenericParam<'_>], @@ -2067,7 +2068,7 @@ pub fn print_closure_binder( } } - pub fn print_bounds<'b>( + fn print_bounds<'b>( &mut self, prefix: &'static str, bounds: impl IntoIterator>, @@ -2105,7 +2106,7 @@ pub fn print_bounds<'b>( } } - pub fn print_generic_params(&mut self, generic_params: &[GenericParam<'_>]) { + fn print_generic_params(&mut self, generic_params: &[GenericParam<'_>]) { if !generic_params.is_empty() { self.word("<"); @@ -2115,7 +2116,7 @@ pub fn print_generic_params(&mut self, generic_params: &[GenericParam<'_>]) { } } - pub fn print_generic_param(&mut self, param: &GenericParam<'_>) { + fn print_generic_param(&mut self, param: &GenericParam<'_>) { if let GenericParamKind::Const { .. } = param.kind { self.word_space("const"); } @@ -2143,11 +2144,11 @@ pub fn print_generic_param(&mut self, param: &GenericParam<'_>) { } } - pub fn print_lifetime(&mut self, lifetime: &hir::Lifetime) { + fn print_lifetime(&mut self, lifetime: &hir::Lifetime) { self.print_ident(lifetime.ident) } - pub fn print_where_clause(&mut self, generics: &hir::Generics<'_>) { + fn print_where_clause(&mut self, generics: &hir::Generics<'_>) { if generics.predicates.is_empty() { return; } @@ -2204,7 +2205,7 @@ pub fn print_where_clause(&mut self, generics: &hir::Generics<'_>) { } } - pub fn print_mutability(&mut self, mutbl: hir::Mutability, print_const: bool) { + fn print_mutability(&mut self, mutbl: hir::Mutability, print_const: bool) { match mutbl { hir::Mutability::Mut => self.word_nbsp("mut"), hir::Mutability::Not => { @@ -2215,12 +2216,12 @@ pub fn print_mutability(&mut self, mutbl: hir::Mutability, print_const: bool) { } } - pub fn print_mt(&mut self, mt: &hir::MutTy<'_>, print_const: bool) { + fn print_mt(&mut self, mt: &hir::MutTy<'_>, print_const: bool) { self.print_mutability(mt.mutbl, print_const); self.print_type(mt.ty); } - pub fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) { + fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) { if let hir::FnRetTy::DefaultReturn(..) = decl.output { return; } @@ -2239,7 +2240,7 @@ pub fn print_fn_output(&mut self, decl: &hir::FnDecl<'_>) { } } - pub fn print_ty_fn( + fn print_ty_fn( &mut self, abi: Abi, unsafety: hir::Unsafety, @@ -2267,7 +2268,7 @@ pub fn print_ty_fn( self.end(); } - pub fn print_fn_header_info(&mut self, header: hir::FnHeader) { + fn print_fn_header_info(&mut self, header: hir::FnHeader) { self.print_constness(header.constness); match header.asyncness { @@ -2285,21 +2286,21 @@ pub fn print_fn_header_info(&mut self, header: hir::FnHeader) { self.word("fn") } - pub fn print_constness(&mut self, s: hir::Constness) { + fn print_constness(&mut self, s: hir::Constness) { match s { hir::Constness::NotConst => {} hir::Constness::Const => self.word_nbsp("const"), } } - pub fn print_unsafety(&mut self, s: hir::Unsafety) { + fn print_unsafety(&mut self, s: hir::Unsafety) { match s { hir::Unsafety::Normal => {} hir::Unsafety::Unsafe => self.word_nbsp("unsafe"), } } - pub fn print_is_auto(&mut self, s: hir::IsAuto) { + fn print_is_auto(&mut self, s: hir::IsAuto) { match s { hir::IsAuto::Yes => self.word_nbsp("auto"), hir::IsAuto::No => {} From 494bc8514aa98b7f4786b66a7774004902ef4aa2 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 14:14:13 +1100 Subject: [PATCH 27/58] Tweak comments. - Remove an out-of-date comment. (There is no `PpAnn` implementation for `hir::Crate`.) - Remove a low-value comment. - And break a very long comment. --- compiler/rustc_hir_pretty/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index e6388f34eea..077f9e947e6 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -52,8 +52,6 @@ fn post(&self, _state: &mut State<'_>, _node: AnnNode<'_>) {} impl PpAnn for NoAnn {} pub const NO_ANN: &dyn PpAnn = &NoAnn; -/// Identical to the `PpAnn` implementation for `hir::Crate`, -/// except it avoids creating a dependency on the whole crate. impl PpAnn for &dyn rustc_hir::intravisit::Map<'_> { fn nested(&self, state: &mut State<'_>, nested: Nested) { match nested { @@ -446,7 +444,6 @@ fn print_item_type( self.end(); // end the outer ibox } - /// Pretty-print an item fn print_item(&mut self, item: &hir::Item<'_>) { self.hardbreak_if_not_bol(); self.maybe_print_comment(item.span.lo()); @@ -2052,7 +2049,8 @@ fn print_closure_binder( match binder { hir::ClosureBinder::Default => {} - // we need to distinguish `|...| {}` from `for<> |...| {}` as `for<>` adds additional restrictions + // We need to distinguish `|...| {}` from `for<> |...| {}` as `for<>` adds additional + // restrictions. hir::ClosureBinder::For { .. } if generic_params.is_empty() => self.word("for<>"), hir::ClosureBinder::For { .. } => { self.word("for"); From 7d35902c6275f5236f77427c2cca8d58f8f8a579 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 14:23:08 +1100 Subject: [PATCH 28/58] Fiddle with `State` functions. Remove and inline `new_from_input`, because it has a single call site. And move `attrs` into the earlier `impl` block. --- compiler/rustc_hir_pretty/src/lib.rs | 32 +++++++++------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 077f9e947e6..ab6ab391484 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -73,6 +73,10 @@ pub struct State<'a> { } impl<'a> State<'a> { + fn attrs(&self, id: hir::HirId) -> &'a [ast::Attribute] { + (self.attrs)(id) + } + fn print_node(&mut self, node: Node<'_>) { match node { Node::Param(a) => self.print_param(a), @@ -154,7 +158,12 @@ pub fn print_crate<'a>( attrs: &'a dyn Fn(hir::HirId) -> &'a [ast::Attribute], ann: &'a dyn PpAnn, ) -> String { - let mut s = State::new_from_input(sm, filename, input, attrs, ann); + let mut s = State { + s: pp::Printer::new(), + comments: Some(Comments::new(sm, filename, input)), + attrs, + ann, + }; // When printing the AST, we sometimes need to inject `#[no_std]` here. // Since you can't compile the HIR, it's not necessary. @@ -164,27 +173,6 @@ pub fn print_crate<'a>( s.s.eof() } -impl<'a> State<'a> { - fn new_from_input( - sm: &'a SourceMap, - filename: FileName, - input: String, - attrs: &'a dyn Fn(hir::HirId) -> &'a [ast::Attribute], - ann: &'a dyn PpAnn, - ) -> State<'a> { - State { - s: pp::Printer::new(), - comments: Some(Comments::new(sm, filename, input)), - attrs, - ann, - } - } - - fn attrs(&self, id: hir::HirId) -> &'a [ast::Attribute] { - (self.attrs)(id) - } -} - fn to_string(ann: &dyn PpAnn, f: F) -> String where F: FnOnce(&mut State<'_>), From 232aaeba7c6779233659b0a57ed75a5d7a48cfb0 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Wed, 11 Oct 2023 21:57:53 +0200 Subject: [PATCH 29/58] Handle several `#[diagnostic::on_unimplemented]` attributes correctly This PR fixes an issues where rustc would ignore subsequent `#[diagnostic::on_unimplemented]` attributes. The [corresponding RFC](https://rust-lang.github.io/rfcs/3368-diagnostic-attribute-namespace.html) specifies that the first matching instance of each option is used. Invalid attributes are linted and otherwise ignored. --- .../error_reporting/on_unimplemented.rs | 46 +++++++++++----- ...ed_options_and_continue_to_use_fallback.rs | 22 ++++++++ ...ptions_and_continue_to_use_fallback.stderr | 52 +++++++++++++++++++ 3 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs create mode 100644 tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs index d9059e46a8c..cc1f13a2f0f 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs @@ -1,6 +1,6 @@ use super::{ObligationCauseCode, PredicateObligation}; use crate::infer::error_reporting::TypeErrCtxt; -use rustc_ast::{MetaItem, NestedMetaItem}; +use rustc_ast::{Attribute, MetaItem, NestedMetaItem}; use rustc_attr as attr; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{struct_span_err, ErrorGuaranteed}; @@ -474,18 +474,40 @@ fn parse( } pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result, ErrorGuaranteed> { - let mut is_diagnostic_namespace_variant = false; - let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented).or_else(|| { - if tcx.features().diagnostic_namespace { - is_diagnostic_namespace_variant = true; - tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]).next() - } else { - None - } - }) else { - return Ok(None); - }; + if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) { + return Self::parse_attribute(attr, false, tcx, item_def_id); + } else if tcx.features().diagnostic_namespace { + tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]) + .filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose()) + .try_fold(None, |aggr: Option, directive| { + let directive = directive?; + if let Some(aggr) = aggr { + let mut subcommands = aggr.subcommands; + subcommands.extend(directive.subcommands); + Ok(Some(Self { + condition: aggr.condition.or(directive.condition), + subcommands, + message: aggr.message.or(directive.message), + label: aggr.label.or(directive.label), + note: aggr.note.or(directive.note), + parent_label: aggr.parent_label.or(directive.parent_label), + append_const_msg: aggr.append_const_msg.or(directive.append_const_msg), + })) + } else { + Ok(Some(directive)) + } + }) + } else { + Ok(None) + } + } + fn parse_attribute( + attr: &Attribute, + is_diagnostic_namespace_variant: bool, + tcx: TyCtxt<'tcx>, + item_def_id: DefId, + ) -> Result, ErrorGuaranteed> { let result = if let Some(items) = attr.meta_item_list() { Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant) } else if let Some(value) = attr.value_str() { diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs new file mode 100644 index 00000000000..35307586391 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs @@ -0,0 +1,22 @@ +#![feature(diagnostic_namespace)] + +#[diagnostic::on_unimplemented( + //~^WARN malformed `on_unimplemented` attribute + //~|WARN malformed `on_unimplemented` attribute + if(Self = ()), + message = "not used yet", + label = "not used yet", + note = "not used yet" +)] +#[diagnostic::on_unimplemented(message = "fallback!!")] +#[diagnostic::on_unimplemented(label = "fallback label")] +#[diagnostic::on_unimplemented(note = "fallback note")] +#[diagnostic::on_unimplemented(message = "fallback2!!")] +trait Foo {} + +fn takes_foo(_: impl Foo) {} + +fn main() { + takes_foo(()); + //~^ERROR fallback!! +} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr new file mode 100644 index 00000000000..6a83d8e39c6 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr @@ -0,0 +1,52 @@ +warning: malformed `on_unimplemented` attribute + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1 + | +LL | / #[diagnostic::on_unimplemented( +LL | | +LL | | +LL | | if(Self = ()), +... | +LL | | note = "not used yet" +LL | | )] + | |__^ + | + = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default + +warning: malformed `on_unimplemented` attribute + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1 + | +LL | / #[diagnostic::on_unimplemented( +LL | | +LL | | +LL | | if(Self = ()), +... | +LL | | note = "not used yet" +LL | | )] + | |__^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0277]: fallback!! + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:20:15 + | +LL | takes_foo(()); + | --------- ^^ fallback label + | | + | required by a bound introduced by this call + | + = help: the trait `Foo` is not implemented for `()` + = note: fallback note +help: this trait has no implementations, consider adding one + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:1 + | +LL | trait Foo {} + | ^^^^^^^^^ +note: required by a bound in `takes_foo` + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:17:22 + | +LL | fn takes_foo(_: impl Foo) {} + | ^^^ required by this bound in `takes_foo` + +error: aborting due to previous error; 2 warnings emitted + +For more information about this error, try `rustc --explain E0277`. From a7ae2a6e6c68f0af5c3afcac015eea2bd18f6e27 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Oct 2023 17:41:11 +1100 Subject: [PATCH 30/58] coverage: Simplify the detection of reloop edges to be given expressions --- .../src/coverage/counters.rs | 119 +++++++----------- .../rustc_mir_transform/src/coverage/graph.rs | 18 ++- 2 files changed, 56 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 78845af0162..06770dafbaa 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -510,18 +510,11 @@ fn choose_preferred_expression_branch( traversal: &TraverseCoverageGraphWithLoops, branches: &[BcbBranch], ) -> BcbBranch { - let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch); - - let some_reloop_branch = self.find_some_reloop_branch(traversal, &branches); - if let Some(reloop_branch_without_counter) = - some_reloop_branch.filter(branch_needs_a_counter) - { - debug!( - "Selecting reloop_branch={:?} that still needs a counter, to get the \ - `Expression`", - reloop_branch_without_counter - ); - reloop_branch_without_counter + let good_reloop_branch = self.find_good_reloop_branch(traversal, &branches); + if let Some(reloop_branch) = good_reloop_branch { + assert!(self.branch_has_no_counter(&reloop_branch)); + debug!("Selecting reloop branch {reloop_branch:?} to get an expression"); + reloop_branch } else { let &branch_without_counter = branches.iter().find(|&branch| self.branch_has_no_counter(branch)).expect( @@ -538,75 +531,52 @@ fn choose_preferred_expression_branch( } } - /// At most, one of the branches (or its edge, from the branching_bcb, if the branch has - /// multiple incoming edges) can have a counter computed by expression. - /// - /// If at least one of the branches leads outside of a loop (`found_loop_exit` is - /// true), and at least one other branch does not exit the loop (the first of which - /// is captured in `some_reloop_branch`), it's likely any reloop branch will be - /// executed far more often than loop exit branch, making the reloop branch a better - /// candidate for an expression. - fn find_some_reloop_branch( + /// Tries to find a branch that leads back to the top of a loop, and that + /// doesn't already have a counter. Such branches are good candidates to + /// be given an expression (instead of a physical counter), because they + /// will tend to be executed more times than a loop-exit branch. + fn find_good_reloop_branch( &self, traversal: &TraverseCoverageGraphWithLoops, branches: &[BcbBranch], ) -> Option { - let branch_needs_a_counter = |branch: &BcbBranch| self.branch_has_no_counter(branch); + // Consider each loop on the current traversal context stack, top-down. + for reloop_bcbs in traversal.reloop_bcbs_per_loop() { + let mut all_branches_exit_this_loop = true; - let mut some_reloop_branch: Option = None; - for context in traversal.context_stack.iter().rev() { - if let Some((backedge_from_bcbs, _)) = &context.loop_backedges { - let mut found_loop_exit = false; - for &branch in branches.iter() { - if backedge_from_bcbs.iter().any(|&backedge_from_bcb| { - self.bcb_dominates(branch.target_bcb, backedge_from_bcb) - }) { - if let Some(reloop_branch) = some_reloop_branch { - if self.branch_has_no_counter(&reloop_branch) { - // we already found a candidate reloop_branch that still - // needs a counter - continue; - } - } - // The path from branch leads back to the top of the loop. Set this - // branch as the `reloop_branch`. If this branch already has a - // counter, and we find another reloop branch that doesn't have a - // counter yet, that branch will be selected as the `reloop_branch` - // instead. - some_reloop_branch = Some(branch); - } else { - // The path from branch leads outside this loop - found_loop_exit = true; - } - if found_loop_exit - && some_reloop_branch.filter(branch_needs_a_counter).is_some() - { - // Found both a branch that exits the loop and a branch that returns - // to the top of the loop (`reloop_branch`), and the `reloop_branch` - // doesn't already have a counter. - break; + // Try to find a branch that doesn't exit this loop and doesn't + // already have a counter. + for &branch in branches { + // A branch is a reloop branch if it dominates any BCB that has + // an edge back to the loop header. (Other branches are exits.) + let is_reloop_branch = reloop_bcbs.iter().any(|&reloop_bcb| { + self.basic_coverage_blocks.dominates(branch.target_bcb, reloop_bcb) + }); + + if is_reloop_branch { + all_branches_exit_this_loop = false; + if self.branch_has_no_counter(&branch) { + // We found a good branch to be given an expression. + return Some(branch); } + // Keep looking for another reloop branch without a counter. + } else { + // This branch exits the loop. } - if !found_loop_exit { - debug!( - "No branches exit the loop, so any branch without an existing \ - counter can have the `Expression`." - ); - break; - } - if some_reloop_branch.is_some() { - debug!( - "Found a branch that exits the loop and a branch the loops back to \ - the top of the loop (`reloop_branch`). The `reloop_branch` will \ - get the `Expression`, as long as it still needs a counter." - ); - break; - } - // else all branches exited this loop context, so run the same checks with - // the outer loop(s) } + + if !all_branches_exit_this_loop { + // We found one or more reloop branches, but all of them already + // have counters. Let the caller choose one of the exit branches. + debug!("All reloop branches had counters; skip checking the other loops"); + return None; + } + + // All of the branches exit this loop, so keep looking for a good + // reloop branch for one of the outer loops. } - some_reloop_branch + + None } #[inline] @@ -652,9 +622,4 @@ fn branch_counter(&self, branch: &BcbBranch) -> Option<&BcbCounter> { fn bcb_has_one_path_to_target(&self, bcb: BasicCoverageBlock) -> bool { self.bcb_predecessors(bcb).len() <= 1 } - - #[inline] - fn bcb_dominates(&self, dom: BasicCoverageBlock, node: BasicCoverageBlock) -> bool { - self.basic_coverage_blocks.dominates(dom, node) - } } diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 39027d21f06..cf917ecee86 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -386,17 +386,17 @@ fn bcb_filtered_successors<'a, 'tcx>( #[derive(Debug)] pub(super) struct TraversalContext { /// From one or more backedges returning to a loop header. - pub loop_backedges: Option<(Vec, BasicCoverageBlock)>, + loop_backedges: Option<(Vec, BasicCoverageBlock)>, /// worklist, to be traversed, of CoverageGraph in the loop with the given loop /// backedges, such that the loop is the inner inner-most loop containing these /// CoverageGraph - pub worklist: Vec, + worklist: Vec, } pub(super) struct TraverseCoverageGraphWithLoops { - pub backedges: IndexVec>, - pub context_stack: Vec, + backedges: IndexVec>, + context_stack: Vec, visited: BitSet, } @@ -414,6 +414,16 @@ pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self { Self { backedges, context_stack, visited } } + /// For each loop on the loop context stack (top-down), yields a list of BCBs + /// within that loop that have an outgoing edge back to the loop header. + pub(super) fn reloop_bcbs_per_loop(&self) -> impl Iterator { + self.context_stack + .iter() + .rev() + .filter_map(|context| context.loop_backedges.as_ref()) + .map(|(from_bcbs, _to_bcb)| from_bcbs.as_slice()) + } + pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option { debug!( "TraverseCoverageGraphWithLoops::next - context_stack: {:?}", From d1920c518151b29e3326cdacc757a219817f3c73 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Oct 2023 12:59:11 +1100 Subject: [PATCH 31/58] coverage: Rename `next_bcb` to just `bcb` This is the only BCB that `TraverseCoverageGraphWithLoops::next` works with, so calling it `next_bcb` just makes the code less clear. --- .../rustc_mir_transform/src/coverage/graph.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index cf917ecee86..12fb897bd79 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -431,21 +431,22 @@ pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option 0 { - debug!("{:?} is a loop header! Start a new TraversalContext...", next_bcb); + debug!("Visiting {bcb:?}"); + + if self.backedges[bcb].len() > 0 { + debug!("{bcb:?} is a loop header! Start a new TraversalContext..."); self.context_stack.push(TraversalContext { - loop_backedges: Some((self.backedges[next_bcb].clone(), next_bcb)), + loop_backedges: Some((self.backedges[bcb].clone(), bcb)), worklist: Vec::new(), }); } - self.extend_worklist(basic_coverage_blocks, next_bcb); - return Some(next_bcb); + self.extend_worklist(basic_coverage_blocks, bcb); + return Some(bcb); } else { // Strip contexts with empty worklists from the top of the stack self.context_stack.pop(); From ea3fb7bc2cd64fc124209e6f4ffcda8d053fd7ae Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Oct 2023 12:32:21 +1100 Subject: [PATCH 32/58] coverage: Use a `VecDeque` for loop traversal worklists The previous code was storing the worklist in a vector, and then selectively adding items to the start or end of the vector. That's a perfect use-case for a double-ended queue. This change also reveals that the existing code was a bit confused about which end of the worklist is the front or back. For now, items are always removed from the front of the queue (instead of the back), and code that adds items to the queue has been flipped, to preserve the existing behaviour. --- .../rustc_mir_transform/src/coverage/graph.rs | 49 +++++++------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 12fb897bd79..8dc13117b9f 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -6,6 +6,7 @@ use rustc_middle::mir::{self, BasicBlock, TerminatorKind}; use std::cmp::Ordering; +use std::collections::VecDeque; use std::ops::{Index, IndexMut}; /// A coverage-specific simplification of the MIR control flow graph (CFG). The `CoverageGraph`s @@ -388,10 +389,8 @@ pub(super) struct TraversalContext { /// From one or more backedges returning to a loop header. loop_backedges: Option<(Vec, BasicCoverageBlock)>, - /// worklist, to be traversed, of CoverageGraph in the loop with the given loop - /// backedges, such that the loop is the inner inner-most loop containing these - /// CoverageGraph - worklist: Vec, + /// Worklist of BCBs to be processed in this context. + worklist: VecDeque, } pub(super) struct TraverseCoverageGraphWithLoops { @@ -402,10 +401,11 @@ pub(super) struct TraverseCoverageGraphWithLoops { impl TraverseCoverageGraphWithLoops { pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self { - let start_bcb = basic_coverage_blocks.start_node(); let backedges = find_loop_backedges(basic_coverage_blocks); - let context_stack = - vec![TraversalContext { loop_backedges: None, worklist: vec![start_bcb] }]; + + let worklist = VecDeque::from([basic_coverage_blocks.start_node()]); + let context_stack = vec![TraversalContext { loop_backedges: None, worklist }]; + // `context_stack` starts with a `TraversalContext` for the main function context (beginning // with the `start` BasicCoverageBlock of the function). New worklists are pushed to the top // of the stack as loops are entered, and popped off of the stack when a loop's worklist is @@ -431,7 +431,7 @@ pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option Option 1 { - debug!( - "{:?} successor is branching. Prioritize it at the beginning of \ - the {}", - successor_to_add, - if let Some(loop_header) = some_loop_header { - format!("worklist for the loop headed by {loop_header:?}") - } else { - String::from("non-loop worklist") - }, - ); - context.worklist.insert(0, successor_to_add); + context.worklist.push_back(successor_to_add); } else { - debug!( - "{:?} successor is non-branching. Defer it to the end of the {}", - successor_to_add, - if let Some(loop_header) = some_loop_header { - format!("worklist for the loop headed by {loop_header:?}") - } else { - String::from("non-loop worklist") - }, - ); - context.worklist.push(successor_to_add); + context.worklist.push_front(successor_to_add); } break; } From 15360b3bc8f75c33af8a41da5dc9a1cab19691ba Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 11 Oct 2023 17:40:37 +1100 Subject: [PATCH 33/58] coverage: Store a graph reference in the graph traversal struct Having to keep passing in a graph reference was a holdover from when the graph was partly mutated during traversal. As of #114354 that is no longer necessary, so we can simplify the traversal code by storing a graph reference as a field in `TraverseCoverageGraphWithLoops`. --- .../src/coverage/counters.rs | 10 ++++----- .../rustc_mir_transform/src/coverage/graph.rs | 21 +++++++++---------- .../rustc_mir_transform/src/coverage/tests.rs | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index 06770dafbaa..77e3dee1fef 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -245,13 +245,13 @@ fn make_bcb_counters( // the loop. The `traversal` state includes a `context_stack`, providing a way to know if // the current BCB is in one or more nested loops or not. let mut traversal = TraverseCoverageGraphWithLoops::new(&self.basic_coverage_blocks); - while let Some(bcb) = traversal.next(self.basic_coverage_blocks) { + while let Some(bcb) = traversal.next() { if bcb_has_coverage_spans(bcb) { debug!("{:?} has at least one coverage span. Get or make its counter", bcb); let branching_counter_operand = self.get_or_make_counter_operand(bcb)?; if self.bcb_needs_branch_counters(bcb) { - self.make_branch_counters(&mut traversal, bcb, branching_counter_operand)?; + self.make_branch_counters(&traversal, bcb, branching_counter_operand)?; } } else { debug!( @@ -274,7 +274,7 @@ fn make_bcb_counters( fn make_branch_counters( &mut self, - traversal: &mut TraverseCoverageGraphWithLoops, + traversal: &TraverseCoverageGraphWithLoops<'_>, branching_bcb: BasicCoverageBlock, branching_counter_operand: Operand, ) -> Result<(), Error> { @@ -507,7 +507,7 @@ fn recursive_get_or_make_edge_counter_operand( /// found, select any branch. fn choose_preferred_expression_branch( &self, - traversal: &TraverseCoverageGraphWithLoops, + traversal: &TraverseCoverageGraphWithLoops<'_>, branches: &[BcbBranch], ) -> BcbBranch { let good_reloop_branch = self.find_good_reloop_branch(traversal, &branches); @@ -537,7 +537,7 @@ fn choose_preferred_expression_branch( /// will tend to be executed more times than a loop-exit branch. fn find_good_reloop_branch( &self, - traversal: &TraverseCoverageGraphWithLoops, + traversal: &TraverseCoverageGraphWithLoops<'_>, branches: &[BcbBranch], ) -> Option { // Consider each loop on the current traversal context stack, top-down. diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 8dc13117b9f..02bc62a7d26 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -393,14 +393,16 @@ pub(super) struct TraversalContext { worklist: VecDeque, } -pub(super) struct TraverseCoverageGraphWithLoops { +pub(super) struct TraverseCoverageGraphWithLoops<'a> { + basic_coverage_blocks: &'a CoverageGraph, + backedges: IndexVec>, context_stack: Vec, visited: BitSet, } -impl TraverseCoverageGraphWithLoops { - pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self { +impl<'a> TraverseCoverageGraphWithLoops<'a> { + pub(super) fn new(basic_coverage_blocks: &'a CoverageGraph) -> Self { let backedges = find_loop_backedges(basic_coverage_blocks); let worklist = VecDeque::from([basic_coverage_blocks.start_node()]); @@ -411,7 +413,7 @@ pub fn new(basic_coverage_blocks: &CoverageGraph) -> Self { // of the stack as loops are entered, and popped off of the stack when a loop's worklist is // exhausted. let visited = BitSet::new_empty(basic_coverage_blocks.num_nodes()); - Self { backedges, context_stack, visited } + Self { basic_coverage_blocks, backedges, context_stack, visited } } /// For each loop on the loop context stack (top-down), yields a list of BCBs @@ -424,7 +426,7 @@ pub(super) fn reloop_bcbs_per_loop(&self) -> impl Iterator Option { + pub(super) fn next(&mut self) -> Option { debug!( "TraverseCoverageGraphWithLoops::next - context_stack: {:?}", self.context_stack.iter().rev().collect::>() @@ -445,7 +447,7 @@ pub fn next(&mut self, basic_coverage_blocks: &CoverageGraph) -> Option Option Date: Wed, 11 Oct 2023 20:33:26 +1100 Subject: [PATCH 34/58] coverage: Don't store loop backedges in the traversal context As long as we store the loop header BCB, we can look up its incoming loop backedges as needed. --- .../rustc_mir_transform/src/coverage/graph.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 02bc62a7d26..2a88a5f7f44 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -386,8 +386,11 @@ fn bcb_filtered_successors<'a, 'tcx>( /// ensures a loop is completely traversed before processing Blocks after the end of the loop. #[derive(Debug)] pub(super) struct TraversalContext { - /// From one or more backedges returning to a loop header. - loop_backedges: Option<(Vec, BasicCoverageBlock)>, + /// BCB with one or more incoming loop backedges, indicating which loop + /// this context is for. + /// + /// If `None`, this is the non-loop context for the function as a whole. + loop_header: Option, /// Worklist of BCBs to be processed in this context. worklist: VecDeque, @@ -406,7 +409,7 @@ pub(super) fn new(basic_coverage_blocks: &'a CoverageGraph) -> Self { let backedges = find_loop_backedges(basic_coverage_blocks); let worklist = VecDeque::from([basic_coverage_blocks.start_node()]); - let context_stack = vec![TraversalContext { loop_backedges: None, worklist }]; + let context_stack = vec![TraversalContext { loop_header: None, worklist }]; // `context_stack` starts with a `TraversalContext` for the main function context (beginning // with the `start` BasicCoverageBlock of the function). New worklists are pushed to the top @@ -422,8 +425,8 @@ pub(super) fn reloop_bcbs_per_loop(&self) -> impl Iterator Option { @@ -443,7 +446,7 @@ pub(super) fn next(&mut self) -> Option { if self.backedges[bcb].len() > 0 { debug!("{bcb:?} is a loop header! Start a new TraversalContext..."); self.context_stack.push(TraversalContext { - loop_backedges: Some((self.backedges[bcb].clone(), bcb)), + loop_header: Some(bcb), worklist: VecDeque::new(), }); } @@ -484,7 +487,7 @@ pub fn extend_worklist(&mut self, bcb: BasicCoverageBlock) { // `Expression`s by creating a Counter in a `BasicCoverageBlock` that the // branching block would have given an `Expression` (or vice versa). let (some_successor_to_add, _) = - if let Some((_, loop_header)) = context.loop_backedges { + if let Some(loop_header) = context.loop_header { if basic_coverage_blocks.dominates(loop_header, successor) { (Some(successor), Some(loop_header)) } else { From d99ab97b027917b434790b76492386310b47978d Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 12 Oct 2023 13:31:35 +1100 Subject: [PATCH 35/58] coverage: Simplify adding BCB successors to the traversal worklists --- .../rustc_mir_transform/src/coverage/graph.rs | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 2a88a5f7f44..9a7adaada09 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -450,7 +450,7 @@ pub(super) fn next(&mut self) -> Option { worklist: VecDeque::new(), }); } - self.extend_worklist(bcb); + self.add_successors_to_worklists(bcb); return Some(bcb); } else { // Strip contexts with empty worklists from the top of the stack @@ -461,10 +461,10 @@ pub(super) fn next(&mut self) -> Option { None } - pub fn extend_worklist(&mut self, bcb: BasicCoverageBlock) { - let Self { basic_coverage_blocks, .. } = *self; - let successors = &basic_coverage_blocks.successors[bcb]; + pub fn add_successors_to_worklists(&mut self, bcb: BasicCoverageBlock) { + let successors = &self.basic_coverage_blocks.successors[bcb]; debug!("{:?} has {} successors:", bcb, successors.len()); + for &successor in successors { if successor == bcb { debug!( @@ -473,43 +473,44 @@ pub fn extend_worklist(&mut self, bcb: BasicCoverageBlock) { bcb ); // Don't re-add this successor to the worklist. We are already processing it. + // FIXME: This claims to skip just the self-successor, but it actually skips + // all other successors as well. Does that matter? break; } - for context in self.context_stack.iter_mut().rev() { - // Add successors of the current BCB to the appropriate context. Successors that - // stay within a loop are added to the BCBs context worklist. Successors that - // exit the loop (they are not dominated by the loop header) must be reachable - // from other BCBs outside the loop, and they will be added to a different - // worklist. - // - // Branching blocks (with more than one successor) must be processed before - // blocks with only one successor, to prevent unnecessarily complicating - // `Expression`s by creating a Counter in a `BasicCoverageBlock` that the - // branching block would have given an `Expression` (or vice versa). - let (some_successor_to_add, _) = - if let Some(loop_header) = context.loop_header { - if basic_coverage_blocks.dominates(loop_header, successor) { - (Some(successor), Some(loop_header)) - } else { - (None, None) - } - } else { - (Some(successor), None) - }; - // FIXME: The code below had debug messages claiming to add items to a - // particular end of the worklist, but was confused about which end was - // which. The existing behaviour has been preserved for now, but it's - // unclear what the intended behaviour was. + // Add successors of the current BCB to the appropriate context. Successors that + // stay within a loop are added to the BCBs context worklist. Successors that + // exit the loop (they are not dominated by the loop header) must be reachable + // from other BCBs outside the loop, and they will be added to a different + // worklist. + // + // Branching blocks (with more than one successor) must be processed before + // blocks with only one successor, to prevent unnecessarily complicating + // `Expression`s by creating a Counter in a `BasicCoverageBlock` that the + // branching block would have given an `Expression` (or vice versa). - if let Some(successor_to_add) = some_successor_to_add { - if basic_coverage_blocks.successors[successor_to_add].len() > 1 { - context.worklist.push_back(successor_to_add); - } else { - context.worklist.push_front(successor_to_add); + let context = self + .context_stack + .iter_mut() + .rev() + .find(|context| match context.loop_header { + Some(loop_header) => { + self.basic_coverage_blocks.dominates(loop_header, successor) } - break; - } + None => true, + }) + .unwrap_or_else(|| bug!("should always fall back to the root non-loop context")); + debug!("adding to worklist for {:?}", context.loop_header); + + // FIXME: The code below had debug messages claiming to add items to a + // particular end of the worklist, but was confused about which end was + // which. The existing behaviour has been preserved for now, but it's + // unclear what the intended behaviour was. + + if self.basic_coverage_blocks.successors[successor].len() > 1 { + context.worklist.push_back(successor); + } else { + context.worklist.push_front(successor); } } } From 8309097163103910a08c0ed79def88ae178699ac Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 12 Oct 2023 08:45:02 -0700 Subject: [PATCH 36/58] Fix mips platform support entries. --- src/doc/rustc/src/platform-support.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index d25fa661c3f..1fb5e56db5d 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -286,10 +286,6 @@ target | std | host | notes `mipsel-unknown-linux-musl` | ✓ | | MIPS (little endian) Linux with musl libc `mipsel-sony-psp` | * | | MIPS (LE) Sony PlayStation Portable (PSP) [`mipsel-sony-psx`](platform-support/mipsel-sony-psx.md) | * | | MIPS (LE) Sony PlayStation 1 (PSX) -`mips-unknown-linux-gnu` | MIPS Linux (kernel 4.4, glibc 2.23) -`mips64-unknown-linux-gnuabi64` | MIPS64 Linux, n64 ABI (kernel 4.4, glibc 2.23) -`mips64el-unknown-linux-gnuabi64` | MIPS64 (LE) Linux, n64 ABI (kernel 4.4, glibc 2.23) -`mipsel-unknown-linux-gnu` | MIPS (LE) Linux (kernel 4.4, glibc 2.23) `mipsel-unknown-linux-uclibc` | ✓ | | MIPS (LE) Linux with uClibc `mipsel-unknown-none` | * | | Bare MIPS (LE) softfloat [`mipsisa32r6-unknown-linux-gnu`](platform-support/mips-release-6.md) | ? | | 32-bit MIPS Release 6 Big Endian From 72815dc08fd676ec22463055d71855523cf67e44 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 12 Oct 2023 16:46:27 +0200 Subject: [PATCH 37/58] check-cfg: adjust expected names and values when useful --- compiler/rustc_lint/src/context.rs | 13 ++++++++++- tests/ui/check-cfg/allow-same-level.stderr | 1 + tests/ui/check-cfg/compact-names.stderr | 1 + tests/ui/check-cfg/diagnotics.stderr | 1 - tests/ui/check-cfg/empty-names.stderr | 1 + tests/ui/check-cfg/mix.stderr | 26 ++++++++++++++++++++++ tests/ui/check-cfg/stmt-no-ice.stderr | 1 + 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 3c5cde4309b..93d155505c4 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -727,11 +727,14 @@ fn lookup_with_diagnostics( .collect::>(); possibilities.sort(); + let mut should_print_possibilities = true; if let Some((value, value_span)) = value { if best_match_values.contains(&Some(value)) { db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect); + should_print_possibilities = false; } else if best_match_values.contains(&None) { db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect); + should_print_possibilities = false; } else if let Some(first_value) = possibilities.first() { db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect); } else { @@ -741,13 +744,21 @@ fn lookup_with_diagnostics( db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect); } - if !possibilities.is_empty() { + if !possibilities.is_empty() && should_print_possibilities { let possibilities = possibilities.join("`, `"); db.help(format!("expected values for `{best_match}` are: `{possibilities}`")); } } else { db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect); } + } else if !possibilities.is_empty() { + let mut possibilities = possibilities.iter() + .map(Symbol::as_str) + .collect::>(); + possibilities.sort(); + let possibilities = possibilities.join("`, `"); + + db.help(format!("expected names are: `{possibilities}`")); } }, BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => { diff --git a/tests/ui/check-cfg/allow-same-level.stderr b/tests/ui/check-cfg/allow-same-level.stderr index 7797de584b9..b46720f62c4 100644 --- a/tests/ui/check-cfg/allow-same-level.stderr +++ b/tests/ui/check-cfg/allow-same-level.stderr @@ -4,6 +4,7 @@ warning: unexpected `cfg` condition name LL | #[cfg(FALSE)] | ^^^^^ | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` = note: `#[warn(unexpected_cfgs)]` on by default warning: 1 warning emitted diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr index f1fc4285a71..4a05c783bff 100644 --- a/tests/ui/check-cfg/compact-names.stderr +++ b/tests/ui/check-cfg/compact-names.stderr @@ -4,6 +4,7 @@ warning: unexpected `cfg` condition name LL | #[cfg(target(os = "linux", architecture = "arm"))] | ^^^^^^^^^^^^^^^^^^^^ | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` = note: `#[warn(unexpected_cfgs)]` on by default warning: 1 warning emitted diff --git a/tests/ui/check-cfg/diagnotics.stderr b/tests/ui/check-cfg/diagnotics.stderr index 8b9fef09d09..39ceb24ee65 100644 --- a/tests/ui/check-cfg/diagnotics.stderr +++ b/tests/ui/check-cfg/diagnotics.stderr @@ -13,7 +13,6 @@ warning: unexpected `cfg` condition name LL | #[cfg(featur = "foo")] | ^^^^^^^^^^^^^^ | - = help: expected values for `feature` are: `foo` help: there is a config with a similar name and value | LL | #[cfg(feature = "foo")] diff --git a/tests/ui/check-cfg/empty-names.stderr b/tests/ui/check-cfg/empty-names.stderr index f926d1133cc..4443f5dd55b 100644 --- a/tests/ui/check-cfg/empty-names.stderr +++ b/tests/ui/check-cfg/empty-names.stderr @@ -4,6 +4,7 @@ warning: unexpected `cfg` condition name LL | #[cfg(unknown_key = "value")] | ^^^^^^^^^^^^^^^^^^^^^ | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` = note: `#[warn(unexpected_cfgs)]` on by default warning: 1 warning emitted diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr index 07c514aed52..d2dedece20e 100644 --- a/tests/ui/check-cfg/mix.stderr +++ b/tests/ui/check-cfg/mix.stderr @@ -35,6 +35,8 @@ warning: unexpected `cfg` condition name | LL | #[cfg_attr(uu, test)] | ^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected condition value `bar` for condition name `feature` | @@ -71,18 +73,24 @@ warning: unexpected `cfg` condition name | LL | cfg!(xxx = "foo"); | ^^^^^^^^^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:48:10 | LL | cfg!(xxx); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:50:14 | LL | cfg!(any(xxx, windows)); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value --> $DIR/mix.rs:52:14 @@ -97,36 +105,48 @@ warning: unexpected `cfg` condition name | LL | cfg!(any(windows, xxx)); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:56:20 | LL | cfg!(all(unix, xxx)); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:58:14 | LL | cfg!(all(aa, bb)); | ^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:58:18 | LL | cfg!(all(aa, bb)); | ^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:61:14 | LL | cfg!(any(aa, bb)); | ^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:61:18 | LL | cfg!(any(aa, bb)); | ^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value --> $DIR/mix.rs:64:20 @@ -141,6 +161,8 @@ warning: unexpected `cfg` condition name | LL | cfg!(any(xxx, feature = "zebra")); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value --> $DIR/mix.rs:66:19 @@ -155,12 +177,16 @@ warning: unexpected `cfg` condition name | LL | cfg!(any(xxx, unix, xxx)); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name --> $DIR/mix.rs:69:25 | LL | cfg!(any(xxx, unix, xxx)); | ^^^ + | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value --> $DIR/mix.rs:72:14 diff --git a/tests/ui/check-cfg/stmt-no-ice.stderr b/tests/ui/check-cfg/stmt-no-ice.stderr index da65b596911..cf8ff580292 100644 --- a/tests/ui/check-cfg/stmt-no-ice.stderr +++ b/tests/ui/check-cfg/stmt-no-ice.stderr @@ -4,6 +4,7 @@ warning: unexpected `cfg` condition name LL | #[cfg(crossbeam_loom)] | ^^^^^^^^^^^^^^ | + = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` = note: `#[warn(unexpected_cfgs)]` on by default warning: 1 warning emitted From dcfc484b094f28ed6579ad10dbdc2d18ddf59e4f Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 12 Oct 2023 16:49:56 +0200 Subject: [PATCH 38/58] check-cfg: mention the unexpected name and value in the primary message --- compiler/rustc_attr/src/builtin.rs | 8 ++- tests/ui/check-cfg/allow-same-level.stderr | 2 +- tests/ui/check-cfg/compact-names.stderr | 2 +- tests/ui/check-cfg/compact-values.stderr | 2 +- tests/ui/check-cfg/diagnotics.stderr | 12 ++--- tests/ui/check-cfg/empty-names.stderr | 2 +- tests/ui/check-cfg/empty-values.stderr | 2 +- tests/ui/check-cfg/invalid-cfg-name.stderr | 2 +- tests/ui/check-cfg/invalid-cfg-value.stderr | 4 +- tests/ui/check-cfg/mix.stderr | 52 +++++++++---------- tests/ui/check-cfg/no-values.stderr | 4 +- .../order-independant.names_after.stderr | 4 +- .../order-independant.names_before.stderr | 4 +- tests/ui/check-cfg/stmt-no-ice.stderr | 2 +- tests/ui/check-cfg/values-target-json.stderr | 2 +- tests/ui/check-cfg/well-known-names.stderr | 6 +-- tests/ui/check-cfg/well-known-values.stderr | 10 ++-- 17 files changed, 62 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index f013ff45a4f..76659458dad 100644 --- a/compiler/rustc_attr/src/builtin.rs +++ b/compiler/rustc_attr/src/builtin.rs @@ -548,7 +548,11 @@ pub fn cfg_matches( UNEXPECTED_CFGS, cfg.span, lint_node_id, - "unexpected `cfg` condition value", + if let Some(value) = cfg.value { + format!("unexpected `cfg` condition value: `{value}`") + } else { + format!("unexpected `cfg` condition value: (none)") + }, BuiltinLintDiagnostics::UnexpectedCfgValue( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), @@ -560,7 +564,7 @@ pub fn cfg_matches( UNEXPECTED_CFGS, cfg.span, lint_node_id, - "unexpected `cfg` condition name", + format!("unexpected `cfg` condition name: `{}`", cfg.name), BuiltinLintDiagnostics::UnexpectedCfgName( (cfg.name, cfg.name_span), cfg.value.map(|v| (v, cfg.value_span.unwrap())), diff --git a/tests/ui/check-cfg/allow-same-level.stderr b/tests/ui/check-cfg/allow-same-level.stderr index b46720f62c4..b0c459fabf8 100644 --- a/tests/ui/check-cfg/allow-same-level.stderr +++ b/tests/ui/check-cfg/allow-same-level.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `FALSE` --> $DIR/allow-same-level.rs:7:7 | LL | #[cfg(FALSE)] diff --git a/tests/ui/check-cfg/compact-names.stderr b/tests/ui/check-cfg/compact-names.stderr index 4a05c783bff..b0228774b75 100644 --- a/tests/ui/check-cfg/compact-names.stderr +++ b/tests/ui/check-cfg/compact-names.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `target_architecture` --> $DIR/compact-names.rs:11:28 | LL | #[cfg(target(os = "linux", architecture = "arm"))] diff --git a/tests/ui/check-cfg/compact-values.stderr b/tests/ui/check-cfg/compact-values.stderr index b7269a652ea..bb2f4915b5e 100644 --- a/tests/ui/check-cfg/compact-values.stderr +++ b/tests/ui/check-cfg/compact-values.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `X` --> $DIR/compact-values.rs:11:28 | LL | #[cfg(target(os = "linux", arch = "X"))] diff --git a/tests/ui/check-cfg/diagnotics.stderr b/tests/ui/check-cfg/diagnotics.stderr index 39ceb24ee65..31c0db03a7e 100644 --- a/tests/ui/check-cfg/diagnotics.stderr +++ b/tests/ui/check-cfg/diagnotics.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `featur` --> $DIR/diagnotics.rs:4:7 | LL | #[cfg(featur)] @@ -7,7 +7,7 @@ LL | #[cfg(featur)] = help: expected values for `feature` are: `foo` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `featur` --> $DIR/diagnotics.rs:8:7 | LL | #[cfg(featur = "foo")] @@ -18,7 +18,7 @@ help: there is a config with a similar name and value LL | #[cfg(feature = "foo")] | ~~~~~~~ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `featur` --> $DIR/diagnotics.rs:12:7 | LL | #[cfg(featur = "fo")] @@ -30,13 +30,13 @@ help: there is a config with a similar name and different values LL | #[cfg(feature = "foo")] | ~~~~~~~~~~~~~~~ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `no_value` --> $DIR/diagnotics.rs:19:7 | LL | #[cfg(no_value)] | ^^^^^^^^ help: there is a config with a similar name: `no_values` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `no_value` --> $DIR/diagnotics.rs:23:7 | LL | #[cfg(no_value = "foo")] @@ -47,7 +47,7 @@ help: there is a config with a similar name and no value LL | #[cfg(no_values)] | ~~~~~~~~~ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `bar` --> $DIR/diagnotics.rs:27:7 | LL | #[cfg(no_values = "bar")] diff --git a/tests/ui/check-cfg/empty-names.stderr b/tests/ui/check-cfg/empty-names.stderr index 4443f5dd55b..9bba852c6de 100644 --- a/tests/ui/check-cfg/empty-names.stderr +++ b/tests/ui/check-cfg/empty-names.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `unknown_key` --> $DIR/empty-names.rs:6:7 | LL | #[cfg(unknown_key = "value")] diff --git a/tests/ui/check-cfg/empty-values.stderr b/tests/ui/check-cfg/empty-values.stderr index a0168b2caa8..932651c5bfe 100644 --- a/tests/ui/check-cfg/empty-values.stderr +++ b/tests/ui/check-cfg/empty-values.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `value` --> $DIR/empty-values.rs:6:7 | LL | #[cfg(test = "value")] diff --git a/tests/ui/check-cfg/invalid-cfg-name.stderr b/tests/ui/check-cfg/invalid-cfg-name.stderr index ed09f8cb66d..8c3c72c9667 100644 --- a/tests/ui/check-cfg/invalid-cfg-name.stderr +++ b/tests/ui/check-cfg/invalid-cfg-name.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `widnows` --> $DIR/invalid-cfg-name.rs:7:7 | LL | #[cfg(widnows)] diff --git a/tests/ui/check-cfg/invalid-cfg-value.stderr b/tests/ui/check-cfg/invalid-cfg-value.stderr index 776d264a7ad..947beac6ffd 100644 --- a/tests/ui/check-cfg/invalid-cfg-value.stderr +++ b/tests/ui/check-cfg/invalid-cfg-value.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `sedre` --> $DIR/invalid-cfg-value.rs:7:7 | LL | #[cfg(feature = "sedre")] @@ -9,7 +9,7 @@ LL | #[cfg(feature = "sedre")] = note: expected values for `feature` are: `full`, `serde` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `rand` --> $DIR/invalid-cfg-value.rs:14:7 | LL | #[cfg(feature = "rand")] diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr index d2dedece20e..b862239bfdc 100644 --- a/tests/ui/check-cfg/mix.stderr +++ b/tests/ui/check-cfg/mix.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `widnows` --> $DIR/mix.rs:11:7 | LL | #[cfg(widnows)] @@ -6,7 +6,7 @@ LL | #[cfg(widnows)] | = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: (none) --> $DIR/mix.rs:15:7 | LL | #[cfg(feature)] @@ -14,7 +14,7 @@ LL | #[cfg(feature)] | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `bar` --> $DIR/mix.rs:22:7 | LL | #[cfg(feature = "bar")] @@ -22,7 +22,7 @@ LL | #[cfg(feature = "bar")] | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:26:7 | LL | #[cfg(feature = "zebra")] @@ -30,7 +30,7 @@ LL | #[cfg(feature = "zebra")] | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `uu` --> $DIR/mix.rs:30:12 | LL | #[cfg_attr(uu, test)] @@ -46,13 +46,13 @@ warning: unexpected `unknown_name` as condition name | = help: was set with `--cfg` but isn't in the `--check-cfg` expected names -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `widnows` --> $DIR/mix.rs:39:10 | LL | cfg!(widnows); | ^^^^^^^ help: there is a config with a similar name: `windows` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `bar` --> $DIR/mix.rs:42:10 | LL | cfg!(feature = "bar"); @@ -60,7 +60,7 @@ LL | cfg!(feature = "bar"); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:44:10 | LL | cfg!(feature = "zebra"); @@ -68,7 +68,7 @@ LL | cfg!(feature = "zebra"); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:46:10 | LL | cfg!(xxx = "foo"); @@ -76,7 +76,7 @@ LL | cfg!(xxx = "foo"); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:48:10 | LL | cfg!(xxx); @@ -84,7 +84,7 @@ LL | cfg!(xxx); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:50:14 | LL | cfg!(any(xxx, windows)); @@ -92,7 +92,7 @@ LL | cfg!(any(xxx, windows)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `bad` --> $DIR/mix.rs:52:14 | LL | cfg!(any(feature = "bad", windows)); @@ -100,7 +100,7 @@ LL | cfg!(any(feature = "bad", windows)); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:54:23 | LL | cfg!(any(windows, xxx)); @@ -108,7 +108,7 @@ LL | cfg!(any(windows, xxx)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:56:20 | LL | cfg!(all(unix, xxx)); @@ -116,7 +116,7 @@ LL | cfg!(all(unix, xxx)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `aa` --> $DIR/mix.rs:58:14 | LL | cfg!(all(aa, bb)); @@ -124,7 +124,7 @@ LL | cfg!(all(aa, bb)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `bb` --> $DIR/mix.rs:58:18 | LL | cfg!(all(aa, bb)); @@ -132,7 +132,7 @@ LL | cfg!(all(aa, bb)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `aa` --> $DIR/mix.rs:61:14 | LL | cfg!(any(aa, bb)); @@ -140,7 +140,7 @@ LL | cfg!(any(aa, bb)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `bb` --> $DIR/mix.rs:61:18 | LL | cfg!(any(aa, bb)); @@ -148,7 +148,7 @@ LL | cfg!(any(aa, bb)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:64:20 | LL | cfg!(any(unix, feature = "zebra")); @@ -156,7 +156,7 @@ LL | cfg!(any(unix, feature = "zebra")); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:66:14 | LL | cfg!(any(xxx, feature = "zebra")); @@ -164,7 +164,7 @@ LL | cfg!(any(xxx, feature = "zebra")); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:66:19 | LL | cfg!(any(xxx, feature = "zebra")); @@ -172,7 +172,7 @@ LL | cfg!(any(xxx, feature = "zebra")); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:69:14 | LL | cfg!(any(xxx, unix, xxx)); @@ -180,7 +180,7 @@ LL | cfg!(any(xxx, unix, xxx)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:69:25 | LL | cfg!(any(xxx, unix, xxx)); @@ -188,7 +188,7 @@ LL | cfg!(any(xxx, unix, xxx)); | = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:72:14 | LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra")); @@ -196,7 +196,7 @@ LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra")); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:72:33 | LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra")); @@ -204,7 +204,7 @@ LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra")); | = note: expected values for `feature` are: `foo` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:72:52 | LL | cfg!(all(feature = "zebra", feature = "zebra", feature = "zebra")); diff --git a/tests/ui/check-cfg/no-values.stderr b/tests/ui/check-cfg/no-values.stderr index ffa87dc58f2..b05a569dd01 100644 --- a/tests/ui/check-cfg/no-values.stderr +++ b/tests/ui/check-cfg/no-values.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `foo` --> $DIR/no-values.rs:6:7 | LL | #[cfg(feature = "foo")] @@ -9,7 +9,7 @@ LL | #[cfg(feature = "foo")] = note: no expected value for `feature` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `foo` --> $DIR/no-values.rs:10:7 | LL | #[cfg(test = "foo")] diff --git a/tests/ui/check-cfg/order-independant.names_after.stderr b/tests/ui/check-cfg/order-independant.names_after.stderr index 91b81428b38..a308358e485 100644 --- a/tests/ui/check-cfg/order-independant.names_after.stderr +++ b/tests/ui/check-cfg/order-independant.names_after.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: (none) --> $DIR/order-independant.rs:8:7 | LL | #[cfg(a)] @@ -7,7 +7,7 @@ LL | #[cfg(a)] = note: expected values for `a` are: `b` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `unk` --> $DIR/order-independant.rs:12:7 | LL | #[cfg(a = "unk")] diff --git a/tests/ui/check-cfg/order-independant.names_before.stderr b/tests/ui/check-cfg/order-independant.names_before.stderr index 91b81428b38..a308358e485 100644 --- a/tests/ui/check-cfg/order-independant.names_before.stderr +++ b/tests/ui/check-cfg/order-independant.names_before.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: (none) --> $DIR/order-independant.rs:8:7 | LL | #[cfg(a)] @@ -7,7 +7,7 @@ LL | #[cfg(a)] = note: expected values for `a` are: `b` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `unk` --> $DIR/order-independant.rs:12:7 | LL | #[cfg(a = "unk")] diff --git a/tests/ui/check-cfg/stmt-no-ice.stderr b/tests/ui/check-cfg/stmt-no-ice.stderr index cf8ff580292..900ea4e4da0 100644 --- a/tests/ui/check-cfg/stmt-no-ice.stderr +++ b/tests/ui/check-cfg/stmt-no-ice.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `crossbeam_loom` --> $DIR/stmt-no-ice.rs:7:11 | LL | #[cfg(crossbeam_loom)] diff --git a/tests/ui/check-cfg/values-target-json.stderr b/tests/ui/check-cfg/values-target-json.stderr index c705152d9fc..e71149f337f 100644 --- a/tests/ui/check-cfg/values-target-json.stderr +++ b/tests/ui/check-cfg/values-target-json.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `linuz` --> $DIR/values-target-json.rs:13:7 | LL | #[cfg(target_os = "linuz")] diff --git a/tests/ui/check-cfg/well-known-names.stderr b/tests/ui/check-cfg/well-known-names.stderr index 34c5d6172d9..a5d38a99eee 100644 --- a/tests/ui/check-cfg/well-known-names.stderr +++ b/tests/ui/check-cfg/well-known-names.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `target_oz` --> $DIR/well-known-names.rs:6:7 | LL | #[cfg(target_oz = "linux")] @@ -8,7 +8,7 @@ LL | #[cfg(target_oz = "linux")] | = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `features` --> $DIR/well-known-names.rs:13:7 | LL | #[cfg(features = "foo")] @@ -16,7 +16,7 @@ LL | #[cfg(features = "foo")] | | | help: there is a config with a similar name: `feature` -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `uniw` --> $DIR/well-known-names.rs:20:7 | LL | #[cfg(uniw)] diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr index b381f5a4a0a..6877d8f5bb7 100644 --- a/tests/ui/check-cfg/well-known-values.stderr +++ b/tests/ui/check-cfg/well-known-values.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `linuz` --> $DIR/well-known-values.rs:7:7 | LL | #[cfg(target_os = "linuz")] @@ -9,7 +9,7 @@ LL | #[cfg(target_os = "linuz")] = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous` = note: `#[warn(unexpected_cfgs)]` on by default -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `0` --> $DIR/well-known-values.rs:14:7 | LL | #[cfg(target_has_atomic = "0")] @@ -19,7 +19,7 @@ LL | #[cfg(target_has_atomic = "0")] | = note: expected values for `target_has_atomic` are: (none), `128`, `16`, `32`, `64`, `8`, `ptr` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `aa` --> $DIR/well-known-values.rs:21:7 | LL | #[cfg(unix = "aa")] @@ -29,7 +29,7 @@ LL | #[cfg(unix = "aa")] | = note: no expected value for `unix` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `miri` --> $DIR/well-known-values.rs:28:7 | LL | #[cfg(miri = "miri")] @@ -39,7 +39,7 @@ LL | #[cfg(miri = "miri")] | = note: no expected value for `miri` -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `linux` --> $DIR/well-known-values.rs:35:7 | LL | #[cfg(doc = "linux")] From 001a65c4b0d82e3cc578f7172accb7bd6673f7e0 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 12 Oct 2023 17:01:55 +0200 Subject: [PATCH 39/58] check-cfg: only print the list of expected names once --- .../rustc_errors/src/diagnostic_builder.rs | 1 + compiler/rustc_lint/src/context.rs | 6 ++++- tests/ui/check-cfg/mix.stderr | 24 ------------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs index 5e23ae655fe..85acf8ab5aa 100644 --- a/compiler/rustc_errors/src/diagnostic_builder.rs +++ b/compiler/rustc_errors/src/diagnostic_builder.rs @@ -659,6 +659,7 @@ pub fn span_labels( msg: impl Into, ) -> &mut Self); forward!(pub fn help(&mut self, msg: impl Into) -> &mut Self); + forward!(pub fn help_once(&mut self, msg: impl Into) -> &mut Self); forward!(pub fn span_help( &mut self, sp: impl Into, diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 93d155505c4..1ed9b86d66c 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -758,7 +758,11 @@ fn lookup_with_diagnostics( possibilities.sort(); let possibilities = possibilities.join("`, `"); - db.help(format!("expected names are: `{possibilities}`")); + // The list of expected names can be long (even by default) and + // so the diagnostic produced can take a lot of space. To avoid + // cloging the user output we only want to print that diagnostic + // once. + db.help_once(format!("expected names are: `{possibilities}`")); } }, BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => { diff --git a/tests/ui/check-cfg/mix.stderr b/tests/ui/check-cfg/mix.stderr index b862239bfdc..23da9f22a72 100644 --- a/tests/ui/check-cfg/mix.stderr +++ b/tests/ui/check-cfg/mix.stderr @@ -73,24 +73,18 @@ warning: unexpected `cfg` condition name: `xxx` | LL | cfg!(xxx = "foo"); | ^^^^^^^^^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:48:10 | LL | cfg!(xxx); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:50:14 | LL | cfg!(any(xxx, windows)); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value: `bad` --> $DIR/mix.rs:52:14 @@ -105,48 +99,36 @@ warning: unexpected `cfg` condition name: `xxx` | LL | cfg!(any(windows, xxx)); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:56:20 | LL | cfg!(all(unix, xxx)); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `aa` --> $DIR/mix.rs:58:14 | LL | cfg!(all(aa, bb)); | ^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `bb` --> $DIR/mix.rs:58:18 | LL | cfg!(all(aa, bb)); | ^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `aa` --> $DIR/mix.rs:61:14 | LL | cfg!(any(aa, bb)); | ^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `bb` --> $DIR/mix.rs:61:18 | LL | cfg!(any(aa, bb)); | ^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:64:20 @@ -161,8 +143,6 @@ warning: unexpected `cfg` condition name: `xxx` | LL | cfg!(any(xxx, feature = "zebra")); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:66:19 @@ -177,16 +157,12 @@ warning: unexpected `cfg` condition name: `xxx` | LL | cfg!(any(xxx, unix, xxx)); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition name: `xxx` --> $DIR/mix.rs:69:25 | LL | cfg!(any(xxx, unix, xxx)); | ^^^ - | - = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows` warning: unexpected `cfg` condition value: `zebra` --> $DIR/mix.rs:72:14 From ed922d8c727979d8fe8b03061e83a0841a0e959b Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 12 Oct 2023 18:04:05 +0200 Subject: [PATCH 40/58] check-cfg: update rustdoc ui check-cfg tests --- tests/rustdoc-ui/check-cfg/check-cfg.stderr | 2 +- tests/rustdoc-ui/doctest/check-cfg-test.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rustdoc-ui/check-cfg/check-cfg.stderr b/tests/rustdoc-ui/check-cfg/check-cfg.stderr index 03fb6f96fb5..d010c1f7ec6 100644 --- a/tests/rustdoc-ui/check-cfg/check-cfg.stderr +++ b/tests/rustdoc-ui/check-cfg/check-cfg.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition name +warning: unexpected `cfg` condition name: `uniz` --> $DIR/check-cfg.rs:5:7 | LL | #[cfg(uniz)] diff --git a/tests/rustdoc-ui/doctest/check-cfg-test.stderr b/tests/rustdoc-ui/doctest/check-cfg-test.stderr index f84543c2072..0bfd569e381 100644 --- a/tests/rustdoc-ui/doctest/check-cfg-test.stderr +++ b/tests/rustdoc-ui/doctest/check-cfg-test.stderr @@ -1,4 +1,4 @@ -warning: unexpected `cfg` condition value +warning: unexpected `cfg` condition value: `invalid` --> $DIR/check-cfg-test.rs:9:7 | LL | #[cfg(feature = "invalid")] From f5cdd3e130dce081a72735eeeb0eca283912f48b Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Thu, 12 Oct 2023 11:03:02 -0700 Subject: [PATCH 41/58] Exclude apple from assembly test --- tests/assembly/x86_64-array-pair-load-store-merge.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/assembly/x86_64-array-pair-load-store-merge.rs b/tests/assembly/x86_64-array-pair-load-store-merge.rs index 4a8e40f8530..55e317e91bf 100644 --- a/tests/assembly/x86_64-array-pair-load-store-merge.rs +++ b/tests/assembly/x86_64-array-pair-load-store-merge.rs @@ -2,6 +2,7 @@ // compile-flags: --crate-type=lib -O -C llvm-args=-x86-asm-syntax=intel // only-x86_64 // ignore-sgx +// ignore-macos (manipulates rsp too) // Depending on various codegen choices, this might end up copying // a `<2 x i8>`, an `i16`, or two `i8`s. From ef8701a4a02e21230401ace80a19169f9f9a6ff8 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 10:01:52 +1100 Subject: [PATCH 42/58] Rename some things. - Rename `pprust` as `pprust_ast`, to align with `pprust_hir`. - Rename `PrinterSupport` as `AstPrinterSupport`, to align with `HirPrinterSupport`. --- compiler/rustc_driver_impl/src/pretty.rs | 70 ++++++++++++------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 222c7b5d6a7..5d49c78dea4 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -1,7 +1,7 @@ //! The various pretty-printing routines. use rustc_ast as ast; -use rustc_ast_pretty::pprust; +use rustc_ast_pretty::pprust as pprust_ast; use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir_pretty as pprust_hir; @@ -26,26 +26,25 @@ // analysis results) on to the chosen pretty-printer, along with the // `&PpAnn` object. // -// Note that since the `&PrinterSupport` is freshly constructed on each +// Note that since the `&AstPrinterSupport` is freshly constructed on each // call, it would not make sense to try to attach the lifetime of `self` // to the lifetime of the `&PrinterObject`. -/// Constructs a `PrinterSupport` object and passes it to `f`. -fn call_with_pp_support<'tcx, A, F>( +/// Constructs an `AstPrinterSupport` object and passes it to `f`. +fn call_with_pp_support_ast<'tcx, A, F>( ppmode: &PpSourceMode, sess: &'tcx Session, tcx: Option>, f: F, ) -> A where - F: FnOnce(&dyn PrinterSupport) -> A, + F: FnOnce(&dyn AstPrinterSupport) -> A, { match *ppmode { Normal | Expanded => { let annotation = NoAnn { sess, tcx }; f(&annotation) } - Identified | ExpandedIdentified => { let annotation = IdentifiedAnnotation { sess, tcx }; f(&annotation) @@ -65,7 +64,6 @@ fn call_with_pp_support_hir(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> let annotation = NoAnn { sess: tcx.sess, tcx: Some(tcx) }; f(&annotation, tcx.hir()) } - PpHirMode::Identified => { let annotation = IdentifiedAnnotation { sess: tcx.sess, tcx: Some(tcx) }; f(&annotation, tcx.hir()) @@ -79,7 +77,7 @@ fn call_with_pp_support_hir(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> } } -trait PrinterSupport: pprust::PpAnn { +trait AstPrinterSupport: pprust_ast::PpAnn { /// Provides a uniform interface for re-extracting a reference to a /// `Session` from a value that now owns it. fn sess(&self) -> &Session; @@ -88,7 +86,7 @@ trait PrinterSupport: pprust::PpAnn { /// /// (Rust does not yet support upcasting from a trait object to /// an object for one of its supertraits.) - fn pp_ann(&self) -> &dyn pprust::PpAnn; + fn pp_ann(&self) -> &dyn pprust_ast::PpAnn; } trait HirPrinterSupport<'hir>: pprust_hir::PpAnn { @@ -112,12 +110,12 @@ struct NoAnn<'hir> { tcx: Option>, } -impl<'hir> PrinterSupport for NoAnn<'hir> { +impl<'hir> AstPrinterSupport for NoAnn<'hir> { fn sess(&self) -> &Session { self.sess } - fn pp_ann(&self) -> &dyn pprust::PpAnn { + fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { self } } @@ -136,7 +134,7 @@ fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { } } -impl<'hir> pprust::PpAnn for NoAnn<'hir> {} +impl<'hir> pprust_ast::PpAnn for NoAnn<'hir> {} impl<'hir> pprust_hir::PpAnn for NoAnn<'hir> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { if let Some(tcx) = self.tcx { @@ -150,44 +148,46 @@ struct IdentifiedAnnotation<'hir> { tcx: Option>, } -impl<'hir> PrinterSupport for IdentifiedAnnotation<'hir> { +impl<'hir> AstPrinterSupport for IdentifiedAnnotation<'hir> { fn sess(&self) -> &Session { self.sess } - fn pp_ann(&self) -> &dyn pprust::PpAnn { + fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { self } } -impl<'hir> pprust::PpAnn for IdentifiedAnnotation<'hir> { - fn pre(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) { - if let pprust::AnnNode::Expr(_) = node { +impl<'hir> pprust_ast::PpAnn for IdentifiedAnnotation<'hir> { + fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { + if let pprust_ast::AnnNode::Expr(_) = node { s.popen(); } } - fn post(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) { + fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { - pprust::AnnNode::Crate(_) | pprust::AnnNode::Ident(_) | pprust::AnnNode::Name(_) => {} + pprust_ast::AnnNode::Crate(_) + | pprust_ast::AnnNode::Ident(_) + | pprust_ast::AnnNode::Name(_) => {} - pprust::AnnNode::Item(item) => { + pprust_ast::AnnNode::Item(item) => { s.s.space(); s.synth_comment(item.id.to_string()) } - pprust::AnnNode::SubItem(id) => { + pprust_ast::AnnNode::SubItem(id) => { s.s.space(); s.synth_comment(id.to_string()) } - pprust::AnnNode::Block(blk) => { + pprust_ast::AnnNode::Block(blk) => { s.s.space(); s.synth_comment(format!("block {}", blk.id)) } - pprust::AnnNode::Expr(expr) => { + pprust_ast::AnnNode::Expr(expr) => { s.s.space(); s.synth_comment(expr.id.to_string()); s.pclose() } - pprust::AnnNode::Pat(pat) => { + pprust_ast::AnnNode::Pat(pat) => { s.s.space(); s.synth_comment(format!("pat {}", pat.id)); } @@ -256,28 +256,28 @@ struct HygieneAnnotation<'a> { sess: &'a Session, } -impl<'a> PrinterSupport for HygieneAnnotation<'a> { +impl<'a> AstPrinterSupport for HygieneAnnotation<'a> { fn sess(&self) -> &Session { self.sess } - fn pp_ann(&self) -> &dyn pprust::PpAnn { + fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { self } } -impl<'a> pprust::PpAnn for HygieneAnnotation<'a> { - fn post(&self, s: &mut pprust::State<'_>, node: pprust::AnnNode<'_>) { +impl<'a> pprust_ast::PpAnn for HygieneAnnotation<'a> { + fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { - pprust::AnnNode::Ident(&Ident { name, span }) => { + pprust_ast::AnnNode::Ident(&Ident { name, span }) => { s.s.space(); s.synth_comment(format!("{}{:?}", name.as_u32(), span.ctxt())) } - pprust::AnnNode::Name(&name) => { + pprust_ast::AnnNode::Name(&name) => { s.s.space(); s.synth_comment(name.as_u32().to_string()) } - pprust::AnnNode::Crate(_) => { + pprust_ast::AnnNode::Crate(_) => { s.s.hardbreak(); let verbose = self.sess.verbose(); s.synth_comment(rustc_span::hygiene::debug_hygiene_data(verbose)); @@ -366,11 +366,11 @@ pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { let out = match ppm { Source(s) => { // Silently ignores an identified node. - call_with_pp_support(&s, sess, None, move |annotation| { + call_with_pp_support_ast(&s, sess, None, move |annotation| { debug!("pretty printing source code {:?}", s); let sess = annotation.sess(); let parse = &sess.parse_sess; - pprust::print_crate( + pprust_ast::print_crate( sess.source_map(), krate, src_name, @@ -403,11 +403,11 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { let out = match ppm { Source(s) => { // Silently ignores an identified node. - call_with_pp_support(&s, tcx.sess, Some(tcx), move |annotation| { + call_with_pp_support_ast(&s, tcx.sess, Some(tcx), move |annotation| { debug!("pretty printing source code {:?}", s); let sess = annotation.sess(); let parse = &sess.parse_sess; - pprust::print_crate( + pprust_ast::print_crate( sess.source_map(), &tcx.resolver_for_lowering(()).borrow().1, src_name, From c5cfcdc4ac0880b171dc0004c660e3a6fd315ee0 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 10:53:12 +1100 Subject: [PATCH 43/58] Remove unnecessary call to `call_with_pp_support_hir`. The callback is trivial and no pp support is actually needed. This makes the `HirTree` case more like the `AstTree` case above. --- compiler/rustc_driver_impl/src/pretty.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 5d49c78dea4..bae0d3dd6a6 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -441,10 +441,8 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { }), HirTree => { - call_with_pp_support_hir(&PpHirMode::Normal, tcx, move |_annotation, hir_map| { - debug!("pretty printing HIR tree"); - format!("{:#?}", hir_map.krate()) - }) + debug!("pretty printing HIR tree"); + format!("{:#?}", tcx.hir().krate()) } _ => unreachable!(), From 87090a97e3de431bf15832d9389bf07a969e2791 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 10:54:36 +1100 Subject: [PATCH 44/58] Remove an outdated comment. `phase_3_run_analysis_passes` no longer exists, and AFAICT this code has been refactored so much since this comment was written that it no longer has any useful meaning. --- compiler/rustc_driver_impl/src/pretty.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index bae0d3dd6a6..4a5198080a2 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -451,10 +451,6 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { write_or_print(&out, tcx.sess); } -// In an ideal world, this would be a public function called by the driver after -// analysis is performed. However, we want to call `phase_3_run_analysis_passes` -// with a different callback than the standard driver, so that isn't easy. -// Instead, we call that function ourselves. fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuaranteed> { tcx.analysis(())?; let out = match ppm { From 1467ba06b6b50633541dca9ddebcdacd16a499b1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 12:17:06 +1100 Subject: [PATCH 45/58] Remove PpAstTreeMode. It's simpler to distinguish the two AST modes directly in `PpMode`. --- compiler/rustc_driver_impl/src/pretty.rs | 6 +++--- compiler/rustc_session/src/config.rs | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 4a5198080a2..b258643e97c 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -8,7 +8,7 @@ use rustc_middle::hir::map as hir_map; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::config::{OutFileName, PpAstTreeMode, PpHirMode, PpMode, PpSourceMode}; +use rustc_session::config::{OutFileName, PpHirMode, PpMode, PpSourceMode}; use rustc_session::Session; use rustc_span::symbol::Ident; use rustc_span::FileName; @@ -382,7 +382,7 @@ pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { ) }) } - AstTree(PpAstTreeMode::Normal) => { + AstTree => { debug!("pretty printing AST tree"); format!("{krate:#?}") } @@ -420,7 +420,7 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { }) } - AstTree(PpAstTreeMode::Expanded) => { + AstTreeExpanded => { debug!("pretty-printing expanded AST"); format!("{:#?}", tcx.resolver_for_lowering(()).borrow().1) } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 58461856eb1..056986a8144 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2925,8 +2925,8 @@ fn parse_pretty(handler: &EarlyErrorHandler, unstable_opts: &UnstableOptions) -> "expanded" => Source(PpSourceMode::Expanded), "expanded,identified" => Source(PpSourceMode::ExpandedIdentified), "expanded,hygiene" => Source(PpSourceMode::ExpandedHygiene), - "ast-tree" => AstTree(PpAstTreeMode::Normal), - "ast-tree,expanded" => AstTree(PpAstTreeMode::Expanded), + "ast-tree" => AstTree, + "ast-tree,expanded" => AstTreeExpanded, "hir" => Hir(PpHirMode::Normal), "hir,identified" => Hir(PpHirMode::Identified), "hir,typed" => Hir(PpHirMode::Typed), @@ -3083,14 +3083,6 @@ pub enum PpSourceMode { ExpandedHygiene, } -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum PpAstTreeMode { - /// `-Zunpretty=ast` - Normal, - /// `-Zunpretty=ast,expanded` - Expanded, -} - #[derive(Copy, Clone, PartialEq, Debug)] pub enum PpHirMode { /// `-Zunpretty=hir` @@ -3106,7 +3098,10 @@ pub enum PpMode { /// Options that print the source code, i.e. /// `-Zunpretty=normal` and `-Zunpretty=expanded` Source(PpSourceMode), - AstTree(PpAstTreeMode), + /// `-Zunpretty=ast-tree` + AstTree, + /// `-Zunpretty=ast-tree,expanded` + AstTreeExpanded, /// Options that print the HIR, i.e. `-Zunpretty=hir` Hir(PpHirMode), /// `-Zunpretty=hir-tree` @@ -3126,10 +3121,10 @@ pub fn needs_ast_map(&self) -> bool { use PpMode::*; use PpSourceMode::*; match *self { - Source(Normal | Identified) | AstTree(PpAstTreeMode::Normal) => false, + Source(Normal | Identified) | AstTree => false, Source(Expanded | ExpandedIdentified | ExpandedHygiene) - | AstTree(PpAstTreeMode::Expanded) + | AstTreeExpanded | Hir(_) | HirTree | ThirTree @@ -3141,7 +3136,7 @@ pub fn needs_ast_map(&self) -> bool { pub fn needs_hir(&self) -> bool { use PpMode::*; match *self { - Source(_) | AstTree(_) => false, + Source(_) | AstTree | AstTreeExpanded => false, Hir(_) | HirTree | ThirTree | ThirFlat | Mir | MirCFG => true, } From d5e7c5f3cc9919bf1ad0b61568d74c65639d4e4e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 12:22:22 +1100 Subject: [PATCH 46/58] Remove unused `PrinterSupport::hir_map` method. --- compiler/rustc_driver_impl/src/pretty.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index b258643e97c..b647bf1c313 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -94,10 +94,6 @@ trait HirPrinterSupport<'hir>: pprust_hir::PpAnn { /// `Session` from a value that now owns it. fn sess(&self) -> &Session; - /// Provides a uniform interface for re-extracting a reference to an - /// `hir_map::Map` from a value that now owns it. - fn hir_map(&self) -> Option>; - /// Produces the pretty-print annotation object. /// /// (Rust does not yet support upcasting from a trait object to @@ -125,10 +121,6 @@ fn sess(&self) -> &Session { self.sess } - fn hir_map(&self) -> Option> { - self.tcx.map(|tcx| tcx.hir()) - } - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { self } @@ -200,10 +192,6 @@ fn sess(&self) -> &Session { self.sess } - fn hir_map(&self) -> Option> { - self.tcx.map(|tcx| tcx.hir()) - } - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { self } @@ -298,10 +286,6 @@ fn sess(&self) -> &Session { self.tcx.sess } - fn hir_map(&self) -> Option> { - Some(self.tcx.hir()) - } - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { self } From e3d8bbbfe29ab73afe1d258d6f69c2d6840a30ff Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 10 Oct 2023 12:58:12 +1100 Subject: [PATCH 47/58] Simplify support traits. First, both `AstPrinterSupport` and `HirPrinterSupport` have a `sess` method. This commit introduces a `Sess` trait and makes the support traits be subtraits of `Sess`, to avoid some duplication. Second, both support traits have a `pp_ann` method that isn't needed if we enable `trait_upcasting`. This commit removes those methods. (Both of these traits will be removed in a subsequent commit, as will the `trait_upcasting` use.) --- compiler/rustc_driver_impl/src/lib.rs | 5 +- compiler/rustc_driver_impl/src/pretty.rs | 95 +++++++----------------- 2 files changed, 30 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 17556c45296..bbecf1a1093 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -8,10 +8,11 @@ #![cfg_attr(not(bootstrap), doc(rust_logo))] #![cfg_attr(not(bootstrap), feature(rustdoc_internals))] #![cfg_attr(not(bootstrap), allow(internal_features))] -#![feature(lazy_cell)] #![feature(decl_macro)] -#![feature(panic_update_hook)] +#![feature(lazy_cell)] #![feature(let_chains)] +#![feature(panic_update_hook)] +#![feature(trait_upcasting)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index b647bf1c313..ec118bd33eb 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -55,9 +55,10 @@ fn call_with_pp_support_ast<'tcx, A, F>( } } } + fn call_with_pp_support_hir(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> A where - F: FnOnce(&dyn HirPrinterSupport<'_>, hir_map::Map<'_>) -> A, + F: FnOnce(&dyn HirPrinterSupport, hir_map::Map<'_>) -> A, { match *ppmode { PpHirMode::Normal => { @@ -77,54 +78,28 @@ fn call_with_pp_support_hir(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> } } -trait AstPrinterSupport: pprust_ast::PpAnn { +trait Sess { /// Provides a uniform interface for re-extracting a reference to a - /// `Session` from a value that now owns it. + /// `Session`. fn sess(&self) -> &Session; - - /// Produces the pretty-print annotation object. - /// - /// (Rust does not yet support upcasting from a trait object to - /// an object for one of its supertraits.) - fn pp_ann(&self) -> &dyn pprust_ast::PpAnn; } -trait HirPrinterSupport<'hir>: pprust_hir::PpAnn { - /// Provides a uniform interface for re-extracting a reference to a - /// `Session` from a value that now owns it. - fn sess(&self) -> &Session; - - /// Produces the pretty-print annotation object. - /// - /// (Rust does not yet support upcasting from a trait object to - /// an object for one of its supertraits.) - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn; -} +trait AstPrinterSupport: pprust_ast::PpAnn + Sess {} +trait HirPrinterSupport: pprust_hir::PpAnn + Sess {} struct NoAnn<'hir> { sess: &'hir Session, tcx: Option>, } -impl<'hir> AstPrinterSupport for NoAnn<'hir> { +impl<'hir> Sess for NoAnn<'hir> { fn sess(&self) -> &Session { self.sess } - - fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { - self - } } -impl<'hir> HirPrinterSupport<'hir> for NoAnn<'hir> { - fn sess(&self) -> &Session { - self.sess - } - - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { - self - } -} +impl<'tcx> AstPrinterSupport for NoAnn<'tcx> {} +impl<'hir> HirPrinterSupport for NoAnn<'hir> {} impl<'hir> pprust_ast::PpAnn for NoAnn<'hir> {} impl<'hir> pprust_hir::PpAnn for NoAnn<'hir> { @@ -140,22 +115,21 @@ struct IdentifiedAnnotation<'hir> { tcx: Option>, } -impl<'hir> AstPrinterSupport for IdentifiedAnnotation<'hir> { +impl<'hir> Sess for IdentifiedAnnotation<'hir> { fn sess(&self) -> &Session { self.sess } - - fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { - self - } } +impl<'hir> AstPrinterSupport for IdentifiedAnnotation<'hir> {} + impl<'hir> pprust_ast::PpAnn for IdentifiedAnnotation<'hir> { fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { if let pprust_ast::AnnNode::Expr(_) = node { s.popen(); } } + fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { pprust_ast::AnnNode::Crate(_) @@ -187,15 +161,7 @@ fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { } } -impl<'hir> HirPrinterSupport<'hir> for IdentifiedAnnotation<'hir> { - fn sess(&self) -> &Session { - self.sess - } - - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { - self - } -} +impl<'hir> HirPrinterSupport for IdentifiedAnnotation<'hir> {} impl<'hir> pprust_hir::PpAnn for IdentifiedAnnotation<'hir> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { @@ -203,11 +169,13 @@ fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested) } } + fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { if let pprust_hir::AnnNode::Expr(_) = node { s.popen(); } } + fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { match node { pprust_hir::AnnNode::Name(_) => {} @@ -244,16 +212,14 @@ struct HygieneAnnotation<'a> { sess: &'a Session, } -impl<'a> AstPrinterSupport for HygieneAnnotation<'a> { +impl<'a> Sess for HygieneAnnotation<'a> { fn sess(&self) -> &Session { self.sess } - - fn pp_ann(&self) -> &dyn pprust_ast::PpAnn { - self - } } +impl<'a> AstPrinterSupport for HygieneAnnotation<'a> {} + impl<'a> pprust_ast::PpAnn for HygieneAnnotation<'a> { fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { @@ -281,16 +247,14 @@ struct TypedAnnotation<'tcx> { maybe_typeck_results: Cell>>, } -impl<'tcx> HirPrinterSupport<'tcx> for TypedAnnotation<'tcx> { +impl<'tcx> Sess for TypedAnnotation<'tcx> { fn sess(&self) -> &Session { self.tcx.sess } - - fn pp_ann(&self) -> &dyn pprust_hir::PpAnn { - self - } } +impl<'tcx> HirPrinterSupport for TypedAnnotation<'tcx> {} + impl<'tcx> pprust_hir::PpAnn for TypedAnnotation<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { let old_maybe_typeck_results = self.maybe_typeck_results.get(); @@ -301,11 +265,13 @@ fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) pprust_hir::PpAnn::nested(pp_ann, state, nested); self.maybe_typeck_results.set(old_maybe_typeck_results); } + fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { if let pprust_hir::AnnNode::Expr(_) = node { s.popen(); } } + fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { if let pprust_hir::AnnNode::Expr(expr) = node { let typeck_results = self.maybe_typeck_results.get().or_else(|| { @@ -359,7 +325,7 @@ pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { krate, src_name, src, - annotation.pp_ann(), + annotation, false, parse.edition, &sess.parse_sess.attr_id_generator, @@ -396,7 +362,7 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { &tcx.resolver_for_lowering(()).borrow().1, src_name, src, - annotation.pp_ann(), + annotation, true, parse.edition, &sess.parse_sess.attr_id_generator, @@ -414,14 +380,7 @@ pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { let sess = annotation.sess(); let sm = sess.source_map(); let attrs = |id| hir_map.attrs(id); - pprust_hir::print_crate( - sm, - hir_map.root_module(), - src_name, - src, - &attrs, - annotation.pp_ann(), - ) + pprust_hir::print_crate(sm, hir_map.root_module(), src_name, src, &attrs, annotation) }), HirTree => { From 7d145a0fde3771662648df2de63e8f64033bf598 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Oct 2023 08:49:24 +1100 Subject: [PATCH 48/58] Merge `print_*` functions. The handling of the `PpMode` variants is currently spread across three functions: `print_after_parsing`, `print_after_hir_lowering`, and `print_with_analysis`. Each one handles some of the variants. This split is primarily because `print_after_parsing` has slightly different arguments to the other two. This commit changes the structure. It merges the three functions into a single `print` function, and encapsulates the different arguments in a new enum `PrintExtra`. Benefits: - The code is a little shorter. - All the `PpMode` variants are handled in a single `match`, with no need for `unreachable!` arms. - It enables the trait removal in the subsequent commit by reducing the number of `call_with_pp_support_ast` call sites from two to one. --- compiler/rustc_driver_impl/src/lib.rs | 4 +- compiler/rustc_driver_impl/src/pretty.rs | 124 ++++++++++------------- 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index bbecf1a1093..1cddd561ded 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -396,7 +396,7 @@ fn run_compiler( if ppm.needs_ast_map() { queries.global_ctxt()?.enter(|tcx| { tcx.ensure().early_lint_checks(()); - pretty::print_after_hir_lowering(tcx, *ppm); + pretty::print(sess, *ppm, pretty::PrintExtra::NeedsAstMap { tcx }); Ok(()) })?; @@ -405,7 +405,7 @@ fn run_compiler( queries.global_ctxt()?.enter(|tcx| tcx.output_filenames(())); } else { let krate = queries.parse()?.steal(); - pretty::print_after_parsing(sess, &krate, *ppm); + pretty::print(sess, *ppm, pretty::PrintExtra::AfterParsing { krate }); } trace!("finished pretty-printing"); return early_exit(); diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index ec118bd33eb..837f725abe9 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -2,9 +2,9 @@ use rustc_ast as ast; use rustc_ast_pretty::pprust as pprust_ast; -use rustc_errors::ErrorGuaranteed; use rustc_hir as hir; use rustc_hir_pretty as pprust_hir; +use rustc_middle::bug; use rustc_middle::hir::map as hir_map; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; @@ -310,7 +310,37 @@ fn write_or_print(out: &str, sess: &Session) { sess.io.output_file.as_ref().unwrap_or(&OutFileName::Stdout).overwrite(out, sess); } -pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { +// Extra data for pretty-printing, the form of which depends on what kind of +// pretty-printing we are doing. +pub enum PrintExtra<'tcx> { + AfterParsing { krate: ast::Crate }, + NeedsAstMap { tcx: TyCtxt<'tcx> }, +} + +impl<'tcx> PrintExtra<'tcx> { + fn with_krate(&self, f: F) -> R + where + F: FnOnce(&ast::Crate) -> R + { + match self { + PrintExtra::AfterParsing { krate, .. } => f(krate), + PrintExtra::NeedsAstMap { tcx } => f(&tcx.resolver_for_lowering(()).borrow().1), + } + } + + fn tcx(&self) -> TyCtxt<'tcx> { + match self { + PrintExtra::AfterParsing { .. } => bug!("PrintExtra::tcx"), + PrintExtra::NeedsAstMap { tcx } => *tcx, + } + } +} + +pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { + if ppm.needs_analysis() { + abort_on_err(ex.tcx().analysis(()), sess); + } + let (src, src_name) = get_source(sess); let out = match ppm { @@ -320,96 +350,52 @@ pub fn print_after_parsing(sess: &Session, krate: &ast::Crate, ppm: PpMode) { debug!("pretty printing source code {:?}", s); let sess = annotation.sess(); let parse = &sess.parse_sess; - pprust_ast::print_crate( - sess.source_map(), - krate, - src_name, - src, - annotation, - false, - parse.edition, - &sess.parse_sess.attr_id_generator, + let is_expanded = ppm.needs_ast_map(); + ex.with_krate(|krate| + pprust_ast::print_crate( + sess.source_map(), + krate, + src_name, + src, + annotation, + is_expanded, + parse.edition, + &sess.parse_sess.attr_id_generator, + ) ) }) } AstTree => { debug!("pretty printing AST tree"); - format!("{krate:#?}") + ex.with_krate(|krate| format!("{krate:#?}")) } - _ => unreachable!(), - }; - - write_or_print(&out, sess); -} - -pub fn print_after_hir_lowering<'tcx>(tcx: TyCtxt<'tcx>, ppm: PpMode) { - if ppm.needs_analysis() { - abort_on_err(print_with_analysis(tcx, ppm), tcx.sess); - return; - } - - let (src, src_name) = get_source(tcx.sess); - - let out = match ppm { - Source(s) => { - // Silently ignores an identified node. - call_with_pp_support_ast(&s, tcx.sess, Some(tcx), move |annotation| { - debug!("pretty printing source code {:?}", s); - let sess = annotation.sess(); - let parse = &sess.parse_sess; - pprust_ast::print_crate( - sess.source_map(), - &tcx.resolver_for_lowering(()).borrow().1, - src_name, - src, - annotation, - true, - parse.edition, - &sess.parse_sess.attr_id_generator, - ) - }) - } - AstTreeExpanded => { debug!("pretty-printing expanded AST"); - format!("{:#?}", tcx.resolver_for_lowering(()).borrow().1) + format!("{:#?}", ex.tcx().resolver_for_lowering(()).borrow().1) } - - Hir(s) => call_with_pp_support_hir(&s, tcx, move |annotation, hir_map| { + Hir(s) => call_with_pp_support_hir(&s, ex.tcx(), move |annotation, hir_map| { debug!("pretty printing HIR {:?}", s); let sess = annotation.sess(); let sm = sess.source_map(); let attrs = |id| hir_map.attrs(id); pprust_hir::print_crate(sm, hir_map.root_module(), src_name, src, &attrs, annotation) }), - HirTree => { debug!("pretty printing HIR tree"); - format!("{:#?}", tcx.hir().krate()) + format!("{:#?}", ex.tcx().hir().krate()) } - - _ => unreachable!(), - }; - - write_or_print(&out, tcx.sess); -} - -fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuaranteed> { - tcx.analysis(())?; - let out = match ppm { Mir => { let mut out = Vec::new(); - write_mir_pretty(tcx, None, &mut out).unwrap(); + write_mir_pretty(ex.tcx(), None, &mut out).unwrap(); String::from_utf8(out).unwrap() } - MirCFG => { let mut out = Vec::new(); - write_mir_graphviz(tcx, None, &mut out).unwrap(); + write_mir_graphviz(ex.tcx(), None, &mut out).unwrap(); String::from_utf8(out).unwrap() } - ThirTree => { + let tcx = ex.tcx(); let mut out = String::new(); abort_on_err(rustc_hir_analysis::check_crate(tcx), tcx.sess); debug!("pretty printing THIR tree"); @@ -418,8 +404,8 @@ fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuarante } out } - ThirFlat => { + let tcx = ex.tcx(); let mut out = String::new(); abort_on_err(rustc_hir_analysis::check_crate(tcx), tcx.sess); debug!("pretty printing THIR flat"); @@ -428,11 +414,7 @@ fn print_with_analysis(tcx: TyCtxt<'_>, ppm: PpMode) -> Result<(), ErrorGuarante } out } - - _ => unreachable!(), }; - write_or_print(&out, tcx.sess); - - Ok(()) + write_or_print(&out, sess); } From 060851b7646dff523c25958930b2a7cbe6ce91a1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Oct 2023 08:55:41 +1100 Subject: [PATCH 49/58] Remove pretty-printing traits. `call_with_pp_support_ast` and `call_with_pp_support_hir` how each have a single call site. This commit inlines and removes them, which also removes the need for all the supporting traits: `Sess`, `AstPrinterSupport`, and `HirPrinterSupport`. The `sess` member is also removed from several structs. --- compiler/rustc_driver_impl/src/lib.rs | 1 - compiler/rustc_driver_impl/src/pretty.rs | 181 +++++++---------------- 2 files changed, 52 insertions(+), 130 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 1cddd561ded..4074d57433e 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -12,7 +12,6 @@ #![feature(lazy_cell)] #![feature(let_chains)] #![feature(panic_update_hook)] -#![feature(trait_upcasting)] #![recursion_limit = "256"] #![allow(rustc::potential_query_instability)] #![deny(rustc::untranslatable_diagnostic)] diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 837f725abe9..4145b5baeb0 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -5,7 +5,6 @@ use rustc_hir as hir; use rustc_hir_pretty as pprust_hir; use rustc_middle::bug; -use rustc_middle::hir::map as hir_map; use rustc_middle::mir::{write_mir_graphviz, write_mir_pretty}; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::config::{OutFileName, PpHirMode, PpMode, PpSourceMode}; @@ -20,87 +19,10 @@ pub use self::PpSourceMode::*; use crate::abort_on_err; -// This slightly awkward construction is to allow for each PpMode to -// choose whether it needs to do analyses (which can consume the -// Session) and then pass through the session (now attached to the -// analysis results) on to the chosen pretty-printer, along with the -// `&PpAnn` object. -// -// Note that since the `&AstPrinterSupport` is freshly constructed on each -// call, it would not make sense to try to attach the lifetime of `self` -// to the lifetime of the `&PrinterObject`. - -/// Constructs an `AstPrinterSupport` object and passes it to `f`. -fn call_with_pp_support_ast<'tcx, A, F>( - ppmode: &PpSourceMode, - sess: &'tcx Session, - tcx: Option>, - f: F, -) -> A -where - F: FnOnce(&dyn AstPrinterSupport) -> A, -{ - match *ppmode { - Normal | Expanded => { - let annotation = NoAnn { sess, tcx }; - f(&annotation) - } - Identified | ExpandedIdentified => { - let annotation = IdentifiedAnnotation { sess, tcx }; - f(&annotation) - } - ExpandedHygiene => { - let annotation = HygieneAnnotation { sess }; - f(&annotation) - } - } -} - -fn call_with_pp_support_hir(ppmode: &PpHirMode, tcx: TyCtxt<'_>, f: F) -> A -where - F: FnOnce(&dyn HirPrinterSupport, hir_map::Map<'_>) -> A, -{ - match *ppmode { - PpHirMode::Normal => { - let annotation = NoAnn { sess: tcx.sess, tcx: Some(tcx) }; - f(&annotation, tcx.hir()) - } - PpHirMode::Identified => { - let annotation = IdentifiedAnnotation { sess: tcx.sess, tcx: Some(tcx) }; - f(&annotation, tcx.hir()) - } - PpHirMode::Typed => { - abort_on_err(tcx.analysis(()), tcx.sess); - - let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) }; - tcx.dep_graph.with_ignore(|| f(&annotation, tcx.hir())) - } - } -} - -trait Sess { - /// Provides a uniform interface for re-extracting a reference to a - /// `Session`. - fn sess(&self) -> &Session; -} - -trait AstPrinterSupport: pprust_ast::PpAnn + Sess {} -trait HirPrinterSupport: pprust_hir::PpAnn + Sess {} - struct NoAnn<'hir> { - sess: &'hir Session, tcx: Option>, } -impl<'hir> Sess for NoAnn<'hir> { - fn sess(&self) -> &Session { - self.sess - } -} - -impl<'tcx> AstPrinterSupport for NoAnn<'tcx> {} -impl<'hir> HirPrinterSupport for NoAnn<'hir> {} - impl<'hir> pprust_ast::PpAnn for NoAnn<'hir> {} impl<'hir> pprust_hir::PpAnn for NoAnn<'hir> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { @@ -111,18 +33,9 @@ fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) } struct IdentifiedAnnotation<'hir> { - sess: &'hir Session, tcx: Option>, } -impl<'hir> Sess for IdentifiedAnnotation<'hir> { - fn sess(&self) -> &Session { - self.sess - } -} - -impl<'hir> AstPrinterSupport for IdentifiedAnnotation<'hir> {} - impl<'hir> pprust_ast::PpAnn for IdentifiedAnnotation<'hir> { fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { if let pprust_ast::AnnNode::Expr(_) = node { @@ -161,8 +74,6 @@ fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { } } -impl<'hir> HirPrinterSupport for IdentifiedAnnotation<'hir> {} - impl<'hir> pprust_hir::PpAnn for IdentifiedAnnotation<'hir> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { if let Some(ref tcx) = self.tcx { @@ -212,14 +123,6 @@ struct HygieneAnnotation<'a> { sess: &'a Session, } -impl<'a> Sess for HygieneAnnotation<'a> { - fn sess(&self) -> &Session { - self.sess - } -} - -impl<'a> AstPrinterSupport for HygieneAnnotation<'a> {} - impl<'a> pprust_ast::PpAnn for HygieneAnnotation<'a> { fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { @@ -247,14 +150,6 @@ struct TypedAnnotation<'tcx> { maybe_typeck_results: Cell>>, } -impl<'tcx> Sess for TypedAnnotation<'tcx> { - fn sess(&self) -> &Session { - self.tcx.sess - } -} - -impl<'tcx> HirPrinterSupport for TypedAnnotation<'tcx> {} - impl<'tcx> pprust_hir::PpAnn for TypedAnnotation<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { let old_maybe_typeck_results = self.maybe_typeck_results.get(); @@ -320,7 +215,7 @@ pub enum PrintExtra<'tcx> { impl<'tcx> PrintExtra<'tcx> { fn with_krate(&self, f: F) -> R where - F: FnOnce(&ast::Crate) -> R + F: FnOnce(&ast::Crate) -> R, { match self { PrintExtra::AfterParsing { krate, .. } => f(krate), @@ -345,23 +240,26 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { let out = match ppm { Source(s) => { - // Silently ignores an identified node. - call_with_pp_support_ast(&s, sess, None, move |annotation| { - debug!("pretty printing source code {:?}", s); - let sess = annotation.sess(); - let parse = &sess.parse_sess; - let is_expanded = ppm.needs_ast_map(); - ex.with_krate(|krate| - pprust_ast::print_crate( - sess.source_map(), - krate, - src_name, - src, - annotation, - is_expanded, - parse.edition, - &sess.parse_sess.attr_id_generator, - ) + debug!("pretty printing source code {:?}", s); + let annotation: Box = match s { + Normal => Box::new(NoAnn { tcx: None }), + Expanded => Box::new(NoAnn { tcx: Some(ex.tcx()) }), + Identified => Box::new(IdentifiedAnnotation { tcx: None }), + ExpandedIdentified => Box::new(IdentifiedAnnotation { tcx: Some(ex.tcx()) }), + ExpandedHygiene => Box::new(HygieneAnnotation { sess }), + }; + let parse = &sess.parse_sess; + let is_expanded = ppm.needs_ast_map(); + ex.with_krate(|krate| { + pprust_ast::print_crate( + sess.source_map(), + krate, + src_name, + src, + &*annotation, + is_expanded, + parse.edition, + &sess.parse_sess.attr_id_generator, ) }) } @@ -373,13 +271,38 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { debug!("pretty-printing expanded AST"); format!("{:#?}", ex.tcx().resolver_for_lowering(()).borrow().1) } - Hir(s) => call_with_pp_support_hir(&s, ex.tcx(), move |annotation, hir_map| { + Hir(s) => { debug!("pretty printing HIR {:?}", s); - let sess = annotation.sess(); - let sm = sess.source_map(); - let attrs = |id| hir_map.attrs(id); - pprust_hir::print_crate(sm, hir_map.root_module(), src_name, src, &attrs, annotation) - }), + let tcx = ex.tcx(); + let f = |annotation: &dyn pprust_hir::PpAnn| { + let sm = sess.source_map(); + let hir_map = tcx.hir(); + let attrs = |id| hir_map.attrs(id); + pprust_hir::print_crate( + sm, + hir_map.root_module(), + src_name, + src, + &attrs, + annotation, + ) + }; + match s { + PpHirMode::Normal => { + let annotation = NoAnn { tcx: Some(tcx) }; + f(&annotation) + } + PpHirMode::Identified => { + let annotation = IdentifiedAnnotation { tcx: Some(tcx) }; + f(&annotation) + } + PpHirMode::Typed => { + abort_on_err(tcx.analysis(()), tcx.sess); + let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) }; + tcx.dep_graph.with_ignore(|| f(&annotation)) + } + } + } HirTree => { debug!("pretty printing HIR tree"); format!("{:#?}", ex.tcx().hir().krate()) From ba58e3213db9626381f5fd014e569f7d2335fa03 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Oct 2023 08:58:27 +1100 Subject: [PATCH 50/58] Rename some `'hir` lifetimes as `'tcx`. Because they all end up within a `TyCtxt`. --- compiler/rustc_driver_impl/src/pretty.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 4145b5baeb0..daba78612f5 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -19,12 +19,12 @@ pub use self::PpSourceMode::*; use crate::abort_on_err; -struct NoAnn<'hir> { - tcx: Option>, +struct NoAnn<'tcx> { + tcx: Option>, } -impl<'hir> pprust_ast::PpAnn for NoAnn<'hir> {} -impl<'hir> pprust_hir::PpAnn for NoAnn<'hir> { +impl<'tcx> pprust_ast::PpAnn for NoAnn<'tcx> {} +impl<'tcx> pprust_hir::PpAnn for NoAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { if let Some(tcx) = self.tcx { pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested) @@ -32,11 +32,11 @@ fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) } } -struct IdentifiedAnnotation<'hir> { - tcx: Option>, +struct IdentifiedAnnotation<'tcx> { + tcx: Option>, } -impl<'hir> pprust_ast::PpAnn for IdentifiedAnnotation<'hir> { +impl<'tcx> pprust_ast::PpAnn for IdentifiedAnnotation<'tcx> { fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { if let pprust_ast::AnnNode::Expr(_) = node { s.popen(); @@ -74,7 +74,7 @@ fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { } } -impl<'hir> pprust_hir::PpAnn for IdentifiedAnnotation<'hir> { +impl<'tcx> pprust_hir::PpAnn for IdentifiedAnnotation<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { if let Some(ref tcx) = self.tcx { pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested) From b65227a9ee38cd21e0b4ffbec7542d2638b661a2 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Oct 2023 09:31:43 +1100 Subject: [PATCH 51/58] Make `needs_analysis` true for `PpHirMode::Typed`. This avoids the need for a bespoke `tcx.analysis()` call. --- compiler/rustc_driver_impl/src/pretty.rs | 1 - compiler/rustc_session/src/config.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index daba78612f5..87e3af2f9d9 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -297,7 +297,6 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { f(&annotation) } PpHirMode::Typed => { - abort_on_err(tcx.analysis(()), tcx.sess); let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) }; tcx.dep_graph.with_ignore(|| f(&annotation)) } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 056986a8144..2a6f5994c49 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3144,7 +3144,7 @@ pub fn needs_hir(&self) -> bool { pub fn needs_analysis(&self) -> bool { use PpMode::*; - matches!(*self, Mir | MirCFG | ThirTree | ThirFlat) + matches!(*self, Hir(PpHirMode::Typed) | Mir | MirCFG | ThirTree | ThirFlat) } } From 2e2924f2631bebbf2e0f5f40e26bf5d8d032c6d4 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 11 Oct 2023 09:47:15 +1100 Subject: [PATCH 52/58] Split and rename the annotation structs. `NoAnn` and `IdentifiedAnnotation` impl both `pprust_ast::PpAnn` and `pprust_hir::PpAnn`, which is a bit confusing, because the optional `tcx` is only needed for the HIR cases. (Currently the `tcx` is unnecessarily provided in the `expanded` AST cases.) This commit splits each one into `Ast` and `Hir` versions, which makes things clear about where the `tcx` is needed. The commit also renames all the traits so they consistently end with `Ann`. --- compiler/rustc_driver_impl/src/pretty.rs | 63 ++++++++++++++---------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs index 87e3af2f9d9..8c6fee83013 100644 --- a/compiler/rustc_driver_impl/src/pretty.rs +++ b/compiler/rustc_driver_impl/src/pretty.rs @@ -19,24 +19,27 @@ pub use self::PpSourceMode::*; use crate::abort_on_err; -struct NoAnn<'tcx> { - tcx: Option>, +struct AstNoAnn; + +impl pprust_ast::PpAnn for AstNoAnn {} + +struct HirNoAnn<'tcx> { + tcx: TyCtxt<'tcx>, } -impl<'tcx> pprust_ast::PpAnn for NoAnn<'tcx> {} -impl<'tcx> pprust_hir::PpAnn for NoAnn<'tcx> { +impl<'tcx> pprust_hir::PpAnn for HirNoAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { - if let Some(tcx) = self.tcx { - pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested) - } + pprust_hir::PpAnn::nested( + &(&self.tcx.hir() as &dyn hir::intravisit::Map<'_>), + state, + nested, + ) } } -struct IdentifiedAnnotation<'tcx> { - tcx: Option>, -} +struct AstIdentifiedAnn; -impl<'tcx> pprust_ast::PpAnn for IdentifiedAnnotation<'tcx> { +impl pprust_ast::PpAnn for AstIdentifiedAnn { fn pre(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { if let pprust_ast::AnnNode::Expr(_) = node { s.popen(); @@ -74,11 +77,17 @@ fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { } } -impl<'tcx> pprust_hir::PpAnn for IdentifiedAnnotation<'tcx> { +struct HirIdentifiedAnn<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> pprust_hir::PpAnn for HirIdentifiedAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { - if let Some(ref tcx) = self.tcx { - pprust_hir::PpAnn::nested(&(&tcx.hir() as &dyn hir::intravisit::Map<'_>), state, nested) - } + pprust_hir::PpAnn::nested( + &(&self.tcx.hir() as &dyn hir::intravisit::Map<'_>), + state, + nested, + ) } fn pre(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { @@ -119,11 +128,11 @@ fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) { } } -struct HygieneAnnotation<'a> { +struct AstHygieneAnn<'a> { sess: &'a Session, } -impl<'a> pprust_ast::PpAnn for HygieneAnnotation<'a> { +impl<'a> pprust_ast::PpAnn for AstHygieneAnn<'a> { fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { match node { pprust_ast::AnnNode::Ident(&Ident { name, span }) => { @@ -145,12 +154,12 @@ fn post(&self, s: &mut pprust_ast::State<'_>, node: pprust_ast::AnnNode<'_>) { } } -struct TypedAnnotation<'tcx> { +struct HirTypedAnn<'tcx> { tcx: TyCtxt<'tcx>, maybe_typeck_results: Cell>>, } -impl<'tcx> pprust_hir::PpAnn for TypedAnnotation<'tcx> { +impl<'tcx> pprust_hir::PpAnn for HirTypedAnn<'tcx> { fn nested(&self, state: &mut pprust_hir::State<'_>, nested: pprust_hir::Nested) { let old_maybe_typeck_results = self.maybe_typeck_results.get(); if let pprust_hir::Nested::Body(id) = nested { @@ -242,11 +251,11 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { Source(s) => { debug!("pretty printing source code {:?}", s); let annotation: Box = match s { - Normal => Box::new(NoAnn { tcx: None }), - Expanded => Box::new(NoAnn { tcx: Some(ex.tcx()) }), - Identified => Box::new(IdentifiedAnnotation { tcx: None }), - ExpandedIdentified => Box::new(IdentifiedAnnotation { tcx: Some(ex.tcx()) }), - ExpandedHygiene => Box::new(HygieneAnnotation { sess }), + Normal => Box::new(AstNoAnn), + Expanded => Box::new(AstNoAnn), + Identified => Box::new(AstIdentifiedAnn), + ExpandedIdentified => Box::new(AstIdentifiedAnn), + ExpandedHygiene => Box::new(AstHygieneAnn { sess }), }; let parse = &sess.parse_sess; let is_expanded = ppm.needs_ast_map(); @@ -289,15 +298,15 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) { }; match s { PpHirMode::Normal => { - let annotation = NoAnn { tcx: Some(tcx) }; + let annotation = HirNoAnn { tcx }; f(&annotation) } PpHirMode::Identified => { - let annotation = IdentifiedAnnotation { tcx: Some(tcx) }; + let annotation = HirIdentifiedAnn { tcx }; f(&annotation) } PpHirMode::Typed => { - let annotation = TypedAnnotation { tcx, maybe_typeck_results: Cell::new(None) }; + let annotation = HirTypedAnn { tcx, maybe_typeck_results: Cell::new(None) }; tcx.dep_graph.with_ignore(|| f(&annotation)) } } From 2b4c33817a5aaecabf4c6598d41e190080ec119e Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 6 Oct 2023 18:15:46 +1100 Subject: [PATCH 53/58] Remove unneeded `pub`s. --- compiler/rustc_driver_impl/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 4074d57433e..7a45ac10f0b 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -545,7 +545,7 @@ pub enum Compilation { } impl Compilation { - pub fn and_then Compilation>(self, next: F) -> Compilation { + fn and_then Compilation>(self, next: F) -> Compilation { match self { Compilation::Stop => Compilation::Stop, Compilation::Continue => next(), @@ -657,7 +657,7 @@ fn show_md_content_with_pager(content: &str, color: ColorConfig) { } } -pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Compilation { +fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Compilation { if sess.opts.unstable_opts.link_only { if let Input::File(file) = &sess.io.input { let outputs = compiler.build_output_filenames(sess, &[]); @@ -698,7 +698,7 @@ pub fn try_process_rlink(sess: &Session, compiler: &interface::Compiler) -> Comp } } -pub fn list_metadata( +fn list_metadata( handler: &EarlyErrorHandler, sess: &Session, metadata_loader: &dyn MetadataLoader, @@ -1184,7 +1184,7 @@ fn print_flag_list( /// /// So with all that in mind, the comments below have some more detail about the /// contortions done here to get things to work out correctly. -pub fn handle_options(handler: &EarlyErrorHandler, args: &[String]) -> Option { +fn handle_options(handler: &EarlyErrorHandler, args: &[String]) -> Option { if args.is_empty() { // user did not write `-v` nor `-Z unstable-options`, so do not // include that extra information. @@ -1283,9 +1283,9 @@ pub fn catch_with_exit_code(f: impl FnOnce() -> interface::Result<()>) -> i32 { } } -pub static ICE_PATH: OnceLock> = OnceLock::new(); +static ICE_PATH: OnceLock> = OnceLock::new(); -pub fn ice_path() -> &'static Option { +fn ice_path() -> &'static Option { ICE_PATH.get_or_init(|| { if !rustc_feature::UnstableFeatures::from_environment(None).is_nightly_build() { return None; @@ -1394,7 +1394,7 @@ pub fn install_ice_hook(bug_report_url: &'static str, extra_info: fn(&Handler)) /// /// When `install_ice_hook` is called, this function will be called as the panic /// hook. -pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) { +fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str, extra_info: fn(&Handler)) { let fallback_bundle = rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false); let emitter = Box::new(rustc_errors::emitter::EmitterWriter::stderr( From 6b2c6c7fd3860cc9e6fde3077b889abbd6a30892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 11 Oct 2023 23:20:41 +0000 Subject: [PATCH 54/58] Detect ruby-style closure in parser When parsing a closure without a body that is surrounded by a block, suggest moving the opening brace after the closure head. Fix #116608. --- .../rustc_parse/src/parser/diagnostics.rs | 59 +++++++++++++++++++ compiler/rustc_parse/src/parser/expr.rs | 18 +++++- .../missing_block_in_fn_call.fixed | 10 ++++ .../missing_block_in_fn_call.rs | 10 ++++ .../missing_block_in_fn_call.stderr | 38 ++++++++++++ .../missing_block_in_let_binding.rs | 6 ++ .../missing_block_in_let_binding.stderr | 16 +++++ .../ruby_style_closure_parse_error.fixed | 9 +++ .../ruby_style_closure_parse_error.rs | 9 +++ .../ruby_style_closure_parse_error.stderr | 36 +++++++++++ .../or-patterns-syntactic-fail.stderr | 8 ++- tests/ui/parser/issues/issue-32505.rs | 1 + tests/ui/parser/issues/issue-32505.stderr | 18 +++++- 13 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed create mode 100644 tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs create mode 100644 tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr create mode 100644 tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs create mode 100644 tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr create mode 100644 tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed create mode 100644 tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs create mode 100644 tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 06b1b1523ed..59498978948 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -827,6 +827,65 @@ pub fn maybe_suggest_struct_literal( None } + pub(super) fn recover_closure_body( + &mut self, + mut err: DiagnosticBuilder<'a, ErrorGuaranteed>, + before: token::Token, + prev: token::Token, + token: token::Token, + lo: Span, + decl_hi: Span, + ) -> PResult<'a, P> { + err.span_label(lo.to(decl_hi), "while parsing the body of this closure"); + match before.kind { + token::OpenDelim(Delimiter::Brace) + if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => + { + // `{ || () }` should have been `|| { () }` + err.multipart_suggestion( + "you might have meant to open the body of the closure, instead of enclosing \ + the closure in a block", + vec![ + (before.span, String::new()), + (prev.span.shrink_to_hi(), " {".to_string()), + ], + Applicability::MaybeIncorrect, + ); + err.emit(); + self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]); + } + token::OpenDelim(Delimiter::Parenthesis) + if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => + { + // We are within a function call or tuple, we can emit the error + // and recover. + self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis), &token::Comma]); + + err.multipart_suggestion_verbose( + "you might have meant to open the body of the closure", + vec![ + (prev.span.shrink_to_hi(), " {".to_string()), + (self.token.span.shrink_to_lo(), "}".to_string()), + ], + Applicability::MaybeIncorrect, + ); + err.emit(); + } + _ if !matches!(token.kind, token::OpenDelim(Delimiter::Brace)) => { + // We don't have a heuristic to correctly identify where the block + // should be closed. + err.multipart_suggestion_verbose( + "you might have meant to open the body of the closure", + vec![(prev.span.shrink_to_hi(), " {".to_string())], + Applicability::HasPlaceholders, + ); + return Err(err); + } + _ => return Err(err), + } + Ok(self.mk_expr_err(lo.to(self.token.span))) + } + /// Eats and discards tokens until one of `kets` is encountered. Respects token trees, /// passes through any errors encountered. Used for error recovery. pub(super) fn eat_to_tokens(&mut self, kets: &[&TokenKind]) { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 91bb2d9eb66..7172b567a34 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2209,6 +2209,7 @@ fn parse_simple_block(&mut self) -> PResult<'a, P> { fn parse_expr_closure(&mut self) -> PResult<'a, P> { let lo = self.token.span; + let before = self.prev_token.clone(); let binder = if self.check_keyword(kw::For) { let lo = self.token.span; let lifetime_defs = self.parse_late_bound_lifetime_defs()?; @@ -2239,7 +2240,12 @@ fn parse_expr_closure(&mut self) -> PResult<'a, P> { FnRetTy::Default(_) => { let restrictions = self.restrictions - Restrictions::STMT_EXPR - Restrictions::ALLOW_LET; - self.parse_expr_res(restrictions, None)? + let prev = self.prev_token.clone(); + let token = self.token.clone(); + match self.parse_expr_res(restrictions, None) { + Ok(expr) => expr, + Err(err) => self.recover_closure_body(err, before, prev, token, lo, decl_hi)?, + } } _ => { // If an explicit return type is given, require a block to appear (RFC 968). @@ -2459,10 +2465,16 @@ fn parse_expr_cond(&mut self) -> PResult<'a, P> { /// Parses a `let $pat = $expr` pseudo-expression. fn parse_expr_let(&mut self, restrictions: Restrictions) -> PResult<'a, P> { let is_recovered = if !restrictions.contains(Restrictions::ALLOW_LET) { - Some(self.sess.emit_err(errors::ExpectedExpressionFoundLet { + let err = errors::ExpectedExpressionFoundLet { span: self.token.span, reason: ForbiddenLetReason::OtherForbidden, - })) + }; + if self.prev_token.kind == token::BinOp(token::Or) { + // This was part of a closure, the that part of the parser recover. + return Err(err.into_diagnostic(&self.sess.span_diagnostic)); + } else { + Some(self.sess.emit_err(err)) + } } else { None }; diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed new file mode 100644 index 00000000000..b81515cda9a --- /dev/null +++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.fixed @@ -0,0 +1,10 @@ +// run-rustfix +fn main() { + let _ = vec![1, 2, 3].into_iter().map(|x| { + let y = x; //~ ERROR expected expression, found `let` statement + y + }); + let _: () = foo(); //~ ERROR mismatched types +} + +fn foo() {} diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs new file mode 100644 index 00000000000..e47ad562fb0 --- /dev/null +++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.rs @@ -0,0 +1,10 @@ +// run-rustfix +fn main() { + let _ = vec![1, 2, 3].into_iter().map(|x| + let y = x; //~ ERROR expected expression, found `let` statement + y + ); + let _: () = foo; //~ ERROR mismatched types +} + +fn foo() {} diff --git a/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr new file mode 100644 index 00000000000..fbb7e0e4df5 --- /dev/null +++ b/tests/ui/expr/malformed_closure/missing_block_in_fn_call.stderr @@ -0,0 +1,38 @@ +error: expected expression, found `let` statement + --> $DIR/missing_block_in_fn_call.rs:4:9 + | +LL | let _ = vec![1, 2, 3].into_iter().map(|x| + | --- while parsing the body of this closure +LL | let y = x; + | ^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +help: you might have meant to open the body of the closure + | +LL ~ let _ = vec![1, 2, 3].into_iter().map(|x| { +LL | let y = x; +LL | y +LL ~ }); + | + +error[E0308]: mismatched types + --> $DIR/missing_block_in_fn_call.rs:7:17 + | +LL | let _: () = foo; + | -- ^^^ expected `()`, found fn item + | | + | expected due to this +... +LL | fn foo() {} + | -------- function `foo` defined here + | + = note: expected unit type `()` + found fn item `fn() {foo}` +help: use parentheses to call this function + | +LL | let _: () = foo(); + | ++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs new file mode 100644 index 00000000000..1ee215d150d --- /dev/null +++ b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.rs @@ -0,0 +1,6 @@ +fn main() { + let x = |x| + let y = x; //~ ERROR expected expression, found `let` statement + let _ = () + (); + y +} diff --git a/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr new file mode 100644 index 00000000000..d4640fba96c --- /dev/null +++ b/tests/ui/expr/malformed_closure/missing_block_in_let_binding.stderr @@ -0,0 +1,16 @@ +error: expected expression, found `let` statement + --> $DIR/missing_block_in_let_binding.rs:3:9 + | +LL | let x = |x| + | --- while parsing the body of this closure +LL | let y = x; + | ^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +help: you might have meant to open the body of the closure + | +LL | let x = |x| { + | + + +error: aborting due to previous error + diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed new file mode 100644 index 00000000000..8014dc87c08 --- /dev/null +++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.fixed @@ -0,0 +1,9 @@ +// run-rustfix +fn main() { + let _ = vec![1, 2, 3].into_iter().map(|x| { + let y = x; //~ ERROR expected expression, found `let` statement + y + }); + let _: () = foo(); //~ ERROR mismatched types +} +fn foo() {} diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs new file mode 100644 index 00000000000..9e4aca888ad --- /dev/null +++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.rs @@ -0,0 +1,9 @@ +// run-rustfix +fn main() { + let _ = vec![1, 2, 3].into_iter().map({|x| + let y = x; //~ ERROR expected expression, found `let` statement + y + }); + let _: () = foo; //~ ERROR mismatched types +} +fn foo() {} diff --git a/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr new file mode 100644 index 00000000000..5fc48d73b14 --- /dev/null +++ b/tests/ui/expr/malformed_closure/ruby_style_closure_parse_error.stderr @@ -0,0 +1,36 @@ +error: expected expression, found `let` statement + --> $DIR/ruby_style_closure_parse_error.rs:4:9 + | +LL | let _ = vec![1, 2, 3].into_iter().map({|x| + | --- while parsing the body of this closure +LL | let y = x; + | ^^^ + | + = note: only supported directly in conditions of `if` and `while` expressions +help: you might have meant to open the body of the closure, instead of enclosing the closure in a block + | +LL - let _ = vec![1, 2, 3].into_iter().map({|x| +LL + let _ = vec![1, 2, 3].into_iter().map(|x| { + | + +error[E0308]: mismatched types + --> $DIR/ruby_style_closure_parse_error.rs:7:17 + | +LL | let _: () = foo; + | -- ^^^ expected `()`, found fn item + | | + | expected due to this +LL | } +LL | fn foo() {} + | -------- function `foo` defined here + | + = note: expected unit type `()` + found fn item `fn() {foo}` +help: use parentheses to call this function + | +LL | let _: () = foo(); + | ++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr b/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr index c16a90368e1..e09194d5d39 100644 --- a/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr +++ b/tests/ui/or-patterns/or-patterns-syntactic-fail.stderr @@ -2,9 +2,15 @@ error: expected identifier, found `:` --> $DIR/or-patterns-syntactic-fail.rs:11:19 | LL | let _ = |A | B: E| (); - | ^ expected identifier + | ---- ^ expected identifier + | | + | while parsing the body of this closure | = note: type ascription syntax has been removed, see issue #101728 +help: you might have meant to open the body of the closure + | +LL | let _ = |A | { B: E| (); + | + error: top-level or-patterns are not allowed in function parameters --> $DIR/or-patterns-syntactic-fail.rs:18:13 diff --git a/tests/ui/parser/issues/issue-32505.rs b/tests/ui/parser/issues/issue-32505.rs index f31c00e5cc3..d95e7dc7d9e 100644 --- a/tests/ui/parser/issues/issue-32505.rs +++ b/tests/ui/parser/issues/issue-32505.rs @@ -1,5 +1,6 @@ pub fn test() { foo(|_|) //~ ERROR expected expression, found `)` + //~^ ERROR cannot find function `foo` in this scope } fn main() { } diff --git a/tests/ui/parser/issues/issue-32505.stderr b/tests/ui/parser/issues/issue-32505.stderr index cdd779a93ef..27ad2c3e5be 100644 --- a/tests/ui/parser/issues/issue-32505.stderr +++ b/tests/ui/parser/issues/issue-32505.stderr @@ -2,7 +2,21 @@ error: expected expression, found `)` --> $DIR/issue-32505.rs:2:12 | LL | foo(|_|) - | ^ expected expression + | ---^ expected expression + | | + | while parsing the body of this closure + | +help: you might have meant to open the body of the closure + | +LL | foo(|_| {}) + | ++ -error: aborting due to previous error +error[E0425]: cannot find function `foo` in this scope + --> $DIR/issue-32505.rs:2:5 + | +LL | foo(|_|) + | ^^^ not found in this scope +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0425`. From e7618756c067081c4d347b1ad5f500af0bb534a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Thu, 12 Oct 2023 22:20:17 +0000 Subject: [PATCH 55/58] On type error involving closure, avoid ICE When we encounter a type error involving a closure, we try to typeck prior closure invocations to see if they influenced the current expected type. When trying to do so, ensure that the closure was defined in our current scope. Fix #116658. --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 3 +- ...ype-mismatch-closure-from-another-scope.rs | 23 ++++++++++ ...mismatch-closure-from-another-scope.stderr | 44 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs create mode 100644 tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 9b7f8f80310..c762e684480 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -2036,7 +2036,8 @@ fn label_fn_like( } let typeck = self.typeck_results.borrow(); for (rcvr, args) in call_finder.calls { - if let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id) + if rcvr.hir_id.owner == typeck.hir_owner + && let Some(rcvr_ty) = typeck.node_type_opt(rcvr.hir_id) && let ty::Closure(call_def_id, _) = rcvr_ty.kind() && def_id == *call_def_id && let Some(idx) = expected_idx diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs new file mode 100644 index 00000000000..157383fede1 --- /dev/null +++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.rs @@ -0,0 +1,23 @@ +fn test() { + let x = match **x { //~ ERROR + Some(&a) if { panic!() } => {} + }; + let mut p = &x; + + { + let mut closure = expect_sig(|p, y| *p = y); + closure(&mut p, &y); //~ ERROR + //~^ ERROR + } + + deref(p); //~ ERROR +} + +fn expect_sig(f: F) -> F +where + F: FnMut(&mut &i32, &i32), +{ + f +} + +fn main() {} diff --git a/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr new file mode 100644 index 00000000000..1470c32d7de --- /dev/null +++ b/tests/ui/unboxed-closures/unboxed-closures-type-mismatch-closure-from-another-scope.stderr @@ -0,0 +1,44 @@ +error[E0425]: cannot find value `x` in this scope + --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:2:21 + | +LL | let x = match **x { + | ^ not found in this scope + +error[E0425]: cannot find value `y` in this scope + --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:9:26 + | +LL | closure(&mut p, &y); + | ^ help: a local variable with a similar name exists: `p` + +error[E0308]: mismatched types + --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:9:17 + | +LL | closure(&mut p, &y); + | ------- ^^^^^^ expected `&mut &i32`, found `&mut &()` + | | + | arguments to this function are incorrect + | + = note: expected mutable reference `&mut &i32` + found mutable reference `&mut &()` +note: closure parameter defined here + --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:8:39 + | +LL | let mut closure = expect_sig(|p, y| *p = y); + | ^ + +error[E0425]: cannot find function `deref` in this scope + --> $DIR/unboxed-closures-type-mismatch-closure-from-another-scope.rs:13:5 + | +LL | deref(p); + | ^^^^^ not found in this scope + | +help: use the `.` operator to call the method `Deref::deref` on `&&()` + | +LL - deref(p); +LL + p.deref(); + | + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0308, E0425. +For more information about an error, try `rustc --explain E0308`. From 49aa5a23ca6509b5693cb01b244048a427d1a874 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Fri, 13 Oct 2023 13:43:00 -0600 Subject: [PATCH 56/58] Revert "Invoke `backtrace-rs` buildscript in `std` buildscript" This reverts commit 93677276bc495e78f74536385a16201d465fd523 because it caused issues for projects building the standard library with non-cargo build systems. --- Cargo.lock | 1 - library/std/Cargo.toml | 4 ---- library/std/build.rs | 8 -------- 3 files changed, 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f2a918d463..8bd9b605ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5082,7 +5082,6 @@ version = "0.0.0" dependencies = [ "addr2line", "alloc", - "cc", "cfg-if", "compiler_builtins", "core", diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index feb6274d029..965132bdedb 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -36,10 +36,6 @@ object = { version = "0.32.0", default-features = false, optional = true, featur rand = { version = "0.8.5", default-features = false, features = ["alloc"] } rand_xorshift = "0.3.0" -[build-dependencies] -# Dependency of the `backtrace` crate's build script -cc = "1.0.67" - [target.'cfg(any(all(target_family = "wasm", target_os = "unknown"), target_os = "xous", all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies] dlmalloc = { version = "0.2.4", features = ['rustc-dep-of-std'] } diff --git a/library/std/build.rs b/library/std/build.rs index 49354d5a58c..046ac488b1f 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -1,11 +1,5 @@ use std::env; -// backtrace-rs requires a feature check on Android targets, so -// we need to run its build.rs as well. -#[allow(unused_extern_crates)] -#[path = "../backtrace/build.rs"] -mod backtrace_build_rs; - fn main() { println!("cargo:rerun-if-changed=build.rs"); let target = env::var("TARGET").expect("TARGET was not set"); @@ -65,6 +59,4 @@ fn main() { } println!("cargo:rustc-env=STD_ENV_ARCH={}", env::var("CARGO_CFG_TARGET_ARCH").unwrap()); println!("cargo:rustc-cfg=backtrace_in_libstd"); - - backtrace_build_rs::main(); } From 3209d2d46e4ce3a8216c0a54fb9dd8baafe73586 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 9 Sep 2023 15:56:42 -0400 Subject: [PATCH 57/58] Correct documentation for `atomic_from_ptr` * Remove duplicate alignment note that mentioned `AtomicBool` with other types * Update safety requirements about when non-atomic operations are allowed --- library/core/src/sync/atomic.rs | 43 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index cf1fbe2d389..3cb8f9f4241 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -346,9 +346,17 @@ pub const fn new(v: bool) -> AtomicBool { /// /// # Safety /// - /// * `ptr` must be aligned to `align_of::()` (note that on some platforms this can be bigger than `align_of::()`). + /// * `ptr` must be aligned to `align_of::()` (note that on some platforms this can + /// be bigger than `align_of::()`). /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`. - /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`. + /// * Non-atomic accesses to the value behind `ptr` must have a happens-before relationship + /// with atomic accesses via the returned value (or vice-versa). + /// * In other words, time periods where the value is accessed atomically may not overlap + /// with periods where the value is accessed non-atomically. + /// * This requirement is trivially satisfied if `ptr` is never used non-atomically for the + /// duration of lifetime `'a`. Most use cases should be able to follow this guideline. + /// * This requirement is also trivially satisfied if all accesses (atomic or not) are done + /// from the same thread. /// /// [valid]: crate::ptr#safety #[unstable(feature = "atomic_from_ptr", issue = "108652")] @@ -1140,9 +1148,19 @@ pub const fn new(p: *mut T) -> AtomicPtr { /// /// # Safety /// - /// * `ptr` must be aligned to `align_of::>()` (note that on some platforms this can be bigger than `align_of::<*mut T>()`). + /// * `ptr` must be aligned to `align_of::>()` (note that on some platforms this + /// can be bigger than `align_of::<*mut T>()`). /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`. - /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`. + /// * Non-atomic accesses to the value behind `ptr` must have a happens-before relationship + /// with atomic accesses via the returned value (or vice-versa). + /// * In other words, time periods where the value is accessed atomically may not overlap + /// with periods where the value is accessed non-atomically. + /// * This requirement is trivially satisfied if `ptr` is never used non-atomically for the + /// duration of lifetime `'a`. Most use cases should be able to follow this guideline. + /// * This requirement is also trivially satisfied if all accesses (atomic or not) are done + /// from the same thread. + /// * This method should not be used to create overlapping or mixed-size atomic accesses, as + /// these are not supported by the memory model. /// /// [valid]: crate::ptr#safety #[unstable(feature = "atomic_from_ptr", issue = "108652")] @@ -2111,10 +2129,21 @@ pub const fn new(v: $int_type) -> Self { /// /// # Safety /// - /// * `ptr` must be aligned to `align_of::()` (note that on some platforms this can be bigger than `align_of::()`). - #[doc = concat!(" * `ptr` must be aligned to `align_of::<", stringify!($atomic_type), ">()` (note that on some platforms this can be bigger than `align_of::<", stringify!($int_type), ">()`).")] + #[doc = concat!(" * `ptr` must be aligned to \ + `align_of::<", stringify!($atomic_type), ">()` (note that on some platforms this \ + can be bigger than `align_of::<", stringify!($int_type), ">()`).")] /// * `ptr` must be [valid] for both reads and writes for the whole lifetime `'a`. - /// * The value behind `ptr` must not be accessed through non-atomic operations for the whole lifetime `'a`. + /// * Non-atomic accesses to the value behind `ptr` must have a happens-before + /// relationship with atomic accesses via the returned value (or vice-versa). + /// * In other words, time periods where the value is accessed atomically may not + /// overlap with periods where the value is accessed non-atomically. + /// * This requirement is trivially satisfied if `ptr` is never used non-atomically + /// for the duration of lifetime `'a`. Most use cases should be able to follow + /// this guideline. + /// * This requirement is also trivially satisfied if all accesses (atomic or not) are + /// done from the same thread. + /// * This method should not be used to create overlapping or mixed-size atomic + /// accesses, as these are not supported by the memory model. /// /// [valid]: crate::ptr#safety #[unstable(feature = "atomic_from_ptr", issue = "108652")] From 227c844b167743c65750808eed65b498c0e03cee Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Sat, 9 Sep 2023 16:09:32 -0400 Subject: [PATCH 58/58] Stabilize 'atomic_from_ptr', move const gate to 'const_atomic_from_ptr' --- library/core/src/sync/atomic.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index 3cb8f9f4241..1271dcc78e0 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -319,7 +319,7 @@ pub const fn new(v: bool) -> AtomicBool { /// # Examples /// /// ``` - /// #![feature(atomic_from_ptr, pointer_is_aligned)] + /// #![feature(pointer_is_aligned)] /// use std::sync::atomic::{self, AtomicBool}; /// use std::mem::align_of; /// @@ -359,8 +359,8 @@ pub const fn new(v: bool) -> AtomicBool { /// from the same thread. /// /// [valid]: crate::ptr#safety - #[unstable(feature = "atomic_from_ptr", issue = "108652")] - #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")] + #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")] pub const unsafe fn from_ptr<'a>(ptr: *mut bool) -> &'a AtomicBool { // SAFETY: guaranteed by the caller unsafe { &*ptr.cast() } @@ -1121,7 +1121,7 @@ pub const fn new(p: *mut T) -> AtomicPtr { /// # Examples /// /// ``` - /// #![feature(atomic_from_ptr, pointer_is_aligned)] + /// #![feature(pointer_is_aligned)] /// use std::sync::atomic::{self, AtomicPtr}; /// use std::mem::align_of; /// @@ -1163,8 +1163,8 @@ pub const fn new(p: *mut T) -> AtomicPtr { /// these are not supported by the memory model. /// /// [valid]: crate::ptr#safety - #[unstable(feature = "atomic_from_ptr", issue = "108652")] - #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")] + #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")] pub const unsafe fn from_ptr<'a>(ptr: *mut *mut T) -> &'a AtomicPtr { // SAFETY: guaranteed by the caller unsafe { &*ptr.cast() } @@ -2101,7 +2101,7 @@ pub const fn new(v: $int_type) -> Self { /// # Examples /// /// ``` - /// #![feature(atomic_from_ptr, pointer_is_aligned)] + /// #![feature(pointer_is_aligned)] #[doc = concat!($extra_feature, "use std::sync::atomic::{self, ", stringify!($atomic_type), "};")] /// use std::mem::align_of; /// @@ -2146,8 +2146,8 @@ pub const fn new(v: $int_type) -> Self { /// accesses, as these are not supported by the memory model. /// /// [valid]: crate::ptr#safety - #[unstable(feature = "atomic_from_ptr", issue = "108652")] - #[rustc_const_unstable(feature = "atomic_from_ptr", issue = "108652")] + #[stable(feature = "atomic_from_ptr", since = "CURRENT_RUSTC_VERSION")] + #[rustc_const_unstable(feature = "const_atomic_from_ptr", issue = "108652")] pub const unsafe fn from_ptr<'a>(ptr: *mut $int_type) -> &'a $atomic_type { // SAFETY: guaranteed by the caller unsafe { &*ptr.cast() }