From 46b05279dd1c6c82ab1e850220fffb04531b9030 Mon Sep 17 00:00:00 2001 From: fru1tworld Date: Thu, 2 Apr 2026 20:15:33 +0900 Subject: [PATCH 1/8] Increase span_look_ahead limit to handle whitespace --- .../rustc_resolve/src/late/diagnostics.rs | 8 ++++-- tests/ui/generics/wrong-number-of-args.stderr | 12 ++++----- ...6-trailing-comma-in-lifetime-suggestion.rs | 18 +++++++++++++ ...ailing-comma-in-lifetime-suggestion.stderr | 25 +++++++++++++++++++ 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs create mode 100644 tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index cf048231bd607..3be653f272d9c 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -4064,6 +4064,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }; let mut spans_suggs: Vec<_> = Vec::new(); + let source_map = self.r.tcx.sess.source_map(); let build_sugg = |lt: MissingLifetime| match lt.kind { MissingLifetimeKind::Underscore => { debug_assert_eq!(lt.count, 1); @@ -4074,9 +4075,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { (lt.span.shrink_to_hi(), format!("{existing_name} ")) } MissingLifetimeKind::Comma => { - let sugg: String = std::iter::repeat_n([existing_name.as_str(), ", "], lt.count) - .flatten() + let sugg: String = std::iter::repeat_n(existing_name.as_str(), lt.count) + .intersperse(", ") .collect(); + let is_empty_brackets = + source_map.span_look_ahead(lt.span, ">", Some(50)).is_some(); + let sugg = if is_empty_brackets { sugg } else { format!("{sugg}, ") }; (lt.span.shrink_to_hi(), sugg) } MissingLifetimeKind::Brackets => { diff --git a/tests/ui/generics/wrong-number-of-args.stderr b/tests/ui/generics/wrong-number-of-args.stderr index 554d017d67e33..953cd273eb764 100644 --- a/tests/ui/generics/wrong-number-of-args.stderr +++ b/tests/ui/generics/wrong-number-of-args.stderr @@ -28,8 +28,8 @@ LL | type E = Ty<>; | help: consider introducing a named lifetime parameter | -LL | type E<'a> = Ty<'a, >; - | ++++ +++ +LL | type E<'a> = Ty<'a>; + | ++++ ++ error[E0106]: missing lifetime specifier --> $DIR/wrong-number-of-args.rs:120:22 @@ -55,12 +55,12 @@ LL | type F = Box>; | help: consider making the bound lifetime-generic with a new `'a` lifetime | -LL | type F = Box GenericLifetime<'a, >>; - | +++++++ +++ +LL | type F = Box GenericLifetime<'a>>; + | +++++++ ++ help: consider introducing a named lifetime parameter | -LL | type F<'a> = Box>; - | ++++ +++ +LL | type F<'a> = Box>; + | ++++ ++ error[E0106]: missing lifetime specifier --> $DIR/wrong-number-of-args.rs:163:43 diff --git a/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs new file mode 100644 index 0000000000000..acf93ae51cc4d --- /dev/null +++ b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.rs @@ -0,0 +1,18 @@ +// Regression test for . +// +// When suggesting lifetime parameters for empty angle brackets like `Foo<>`, +// the suggestion should not include a trailing comma (e.g., `Foo<'a>` not `Foo<'a, >`). +// When there are other generic arguments like `Foo`, the trailing comma is needed +// (e.g., `Foo<'a, T>`). + +#![crate_type = "lib"] + +struct Foo<'a>(&'a ()); + +type A = Foo<>; +//~^ ERROR missing lifetime specifier [E0106] + +struct Bar<'a, T>(&'a T); + +type B = Bar; +//~^ ERROR missing lifetime specifier [E0106] diff --git a/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr new file mode 100644 index 0000000000000..f189c259d65f9 --- /dev/null +++ b/tests/ui/lifetimes/E0106-trailing-comma-in-lifetime-suggestion.stderr @@ -0,0 +1,25 @@ +error[E0106]: missing lifetime specifier + --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:12:13 + | +LL | type A = Foo<>; + | ^ expected named lifetime parameter + | +help: consider introducing a named lifetime parameter + | +LL | type A<'a> = Foo<'a>; + | ++++ ++ + +error[E0106]: missing lifetime specifier + --> $DIR/E0106-trailing-comma-in-lifetime-suggestion.rs:17:13 + | +LL | type B = Bar; + | ^ expected named lifetime parameter + | +help: consider introducing a named lifetime parameter + | +LL | type B<'a> = Bar<'a, u8>; + | ++++ +++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0106`. From d6b280675ea24753fe5ed701c8ad119459a94a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?= Date: Tue, 9 Jul 2024 19:21:54 +0200 Subject: [PATCH 2/8] feat(core): impl Step for NonZero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement Step for NonZero unsigned integers as discussed in [libs-team#130][1]. [1]: https://github.com/rust-lang/libs-team/issues/130 `step_nonzero_impls` was adapted from `step_integer_impls` and the tests were adapted from the step tests. Signed-off-by: Jalil David Salamé Messina --- library/core/src/iter/range.rs | 146 ++++++++++++++++++++++++-- library/coretests/tests/iter/range.rs | 139 ++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 10 deletions(-) diff --git a/library/core/src/iter/range.rs b/library/core/src/iter/range.rs index 951bb5d0029f6..adc561879ebd3 100644 --- a/library/core/src/iter/range.rs +++ b/library/core/src/iter/range.rs @@ -15,6 +15,7 @@ macro_rules! unsafe_impl_trusted_step { )*}; } unsafe_impl_trusted_step![AsciiChar char i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize Ipv4Addr Ipv6Addr]; +unsafe_impl_trusted_step![NonZero NonZero NonZero NonZero NonZero NonZero]; /// Objects that have a notion of *successor* and *predecessor* operations. /// @@ -255,10 +256,8 @@ macro_rules! step_identical_methods { macro_rules! step_integer_impls { { - narrower than or same width as usize: - $( [ $u_narrower:ident $i_narrower:ident ] ),+; - wider than usize: - $( [ $u_wider:ident $i_wider:ident ] ),+; + [ $( [ $u_narrower:ident $i_narrower:ident ] ),+ ] <= usize < + [ $( [ $u_wider:ident $i_wider:ident ] ),+ ] } => { $( #[allow(unreachable_patterns)] @@ -437,20 +436,138 @@ macro_rules! step_integer_impls { #[cfg(target_pointer_width = "64")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [u64 i64], [usize isize]; - wider than usize: [u128 i128]; + [ [u8 i8], [u16 i16], [u32 i32], [u64 i64], [usize isize] ] <= usize < [ [u128 i128] ] } #[cfg(target_pointer_width = "32")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [usize isize]; - wider than usize: [u64 i64], [u128 i128]; + [ [u8 i8], [u16 i16], [u32 i32], [usize isize] ] <= usize < [ [u64 i64], [u128 i128] ] } #[cfg(target_pointer_width = "16")] step_integer_impls! { - narrower than or same width as usize: [u8 i8], [u16 i16], [usize isize]; - wider than usize: [u32 i32], [u64 i64], [u128 i128]; + [ [u8 i8], [u16 i16], [usize isize] ] <= usize < [ [u32 i32], [u64 i64], [u128 i128] ] +} + +// These are still macro-generated because the integer literals resolve to different types. +macro_rules! step_nonzero_identical_methods { + ($int:ident) => { + #[inline] + unsafe fn forward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start + n` doesn't overflow. + unsafe { Self::new_unchecked(start.get().unchecked_add(n as $int)) } + } + + #[inline] + unsafe fn backward_unchecked(start: Self, n: usize) -> Self { + // SAFETY: the caller has to guarantee that `start - n` doesn't overflow or hit zero. + unsafe { Self::new_unchecked(start.get().unchecked_sub(n as $int)) } + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn forward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::forward_checked(start, n).is_none() { + let _ = $int::MAX + 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + start.saturating_add(n as $int) + } + + #[inline] + #[allow(arithmetic_overflow)] + #[rustc_inherit_overflow_checks] + fn backward(start: Self, n: usize) -> Self { + // In debug builds, trigger a panic on overflow. + // This should optimize completely out in release builds. + if Self::backward_checked(start, n).is_none() { + let _ = $int::MIN - 1; + } + // Do saturating math (wrapping math causes UB if it wraps to Zero) + Self::new(start.get().saturating_sub(n as $int)).unwrap_or(Self::MIN) + } + + #[inline] + fn steps_between(start: &Self, end: &Self) -> (usize, Option) { + if *start <= *end { + #[allow(irrefutable_let_patterns, reason = "happens on usize or narrower")] + if let Ok(steps) = usize::try_from(end.get() - start.get()) { + (steps, Some(steps)) + } else { + (usize::MAX, None) + } + } else { + (0, None) + } + } + }; +} + +macro_rules! step_nonzero_impls { + { + [$( $narrower:ident ),+] <= usize < [$( $wider:ident ),+] + } => { + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$narrower> { + step_nonzero_identical_methods!($narrower); + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + Ok(n) => start.checked_add(n), + Err(_) => None, // if n is out of range, `unsigned_start + n` is too + } + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + match $narrower::try_from(n) { + // *_sub() is not implemented on NonZero + Ok(n) => start.get().checked_sub(n).and_then(Self::new), + Err(_) => None, // if n is out of range, `unsigned_start - n` is too + } + } + } + )+ + + $( + #[allow(unreachable_patterns)] + #[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")] + impl Step for NonZero<$wider> { + step_nonzero_identical_methods!($wider); + + #[inline] + fn forward_checked(start: Self, n: usize) -> Option { + start.checked_add(n as $wider) + } + + #[inline] + fn backward_checked(start: Self, n: usize) -> Option { + start.get().checked_sub(n as $wider).and_then(Self::new) + } + } + )+ + }; +} + +#[cfg(target_pointer_width = "64")] +step_nonzero_impls! { + [u8, u16, u32, u64, usize] <= usize < [u128] +} + +#[cfg(target_pointer_width = "32")] +step_nonzero_impls! { + [u8, u16, u32, usize] <= usize < [u64, u128] +} + +#[cfg(target_pointer_width = "16")] +step_nonzero_impls! { + [u8, u16, usize] <= usize < [u32, u64, u128] } #[unstable(feature = "step_trait", issue = "42168")] @@ -944,6 +1061,7 @@ impl Iterator for ops::Range { range_exact_iter_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.0.0. @@ -956,22 +1074,30 @@ range_exact_iter_impl! { unsafe_range_trusted_random_access_impl! { usize u8 u16 isize i8 i16 + NonZero NonZero NonZero } #[cfg(target_pointer_width = "32")] unsafe_range_trusted_random_access_impl! { u32 i32 + NonZero } #[cfg(target_pointer_width = "64")] unsafe_range_trusted_random_access_impl! { u32 i32 u64 i64 + NonZero + NonZero } range_incl_exact_iter_impl! { u8 i8 + NonZero + // Since RangeInclusive> can only be 1..=uN::MAX the length of this range is always + // <= uN::MAX, so they are always valid ExactSizeIterator unlike the ranges that include zero. + NonZero NonZero // These are incorrect per the reasoning above, // but removing them would be a breaking change as they were stabilized in Rust 1.26.0. diff --git a/library/coretests/tests/iter/range.rs b/library/coretests/tests/iter/range.rs index d5d2b8bf2b047..4a00e6f96bda8 100644 --- a/library/coretests/tests/iter/range.rs +++ b/library/coretests/tests/iter/range.rs @@ -502,3 +502,142 @@ fn test_double_ended_range() { panic!("unreachable"); } } + +macro_rules! nz { + (NonZero<$type:ident>($val:literal)) => { + ::core::num::NonZero::<$type>::new($val).unwrap() + }; + (NonZero<$type:ident>::MAX) => { + ::core::num::NonZero::<$type>::new($type::MAX).unwrap() + }; +} + +macro_rules! nonzero_array { + (NonZero<$type:ident>[$($val:literal),*]) => { + [$(nz!(NonZero<$type>($val))),*] + } +} + +macro_rules! nonzero_range { + (NonZero<$type:ident>($($left:literal)?..$($right:literal)?)) => { + nz!(NonZero<$type>($($left)?))..nz!(NonZero<$type>($($right)?)) + }; + (NonZero<$type:ident>($($left:literal)?..MAX)) => { + nz!(NonZero<$type>($($left)?))..nz!(NonZero<$type>::MAX) + }; + (NonZero<$type:ident>($($left:literal)?..=$right:literal)) => { + nz!(NonZero<$type>($($left)?))..=nz!(NonZero<$type>($right)) + }; + (NonZero<$type:ident>($($left:literal)?..=MAX)) => { + nz!(NonZero<$type>($($left)?))..=nz!(NonZero<$type>::MAX) + }; +} + +#[test] +fn test_nonzero_range() { + assert_eq!( + nonzero_range!(NonZero(1..=21)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16, 21]) + ); + assert_eq!( + nonzero_range!(NonZero(1..=20)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZero(1..20)).step_by(5).collect::>(), + nonzero_array!(NonZero[1, 6, 11, 16]) + ); + assert_eq!( + nonzero_range!(NonZero(1..21)).rev().step_by(5).collect::>(), + nonzero_array!(NonZero[20, 15, 10, 5]) + ); + assert_eq!( + nonzero_range!(NonZero(1..21)).rev().step_by(6).collect::>(), + nonzero_array!(NonZero[20, 14, 8, 2]) + ); + assert_eq!( + nonzero_range!(NonZero(200..255)).step_by(50).collect::>(), + nonzero_array!(NonZero[200, 250]) + ); + assert_eq!( + nonzero_range!(NonZero(200..5)).step_by(1).collect::>(), + nonzero_array!(NonZero[]) + ); + assert_eq!( + nonzero_range!(NonZero(200..200)).step_by(1).collect::>(), + nonzero_array!(NonZero[]) + ); + + assert_eq!(nonzero_range!(NonZero(10..20)).step_by(1).size_hint(), (10, Some(10))); + assert_eq!(nonzero_range!(NonZero(10..20)).step_by(5).size_hint(), (2, Some(2))); + assert_eq!(nonzero_range!(NonZero(1..21)).rev().step_by(5).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZero(1..21)).rev().step_by(6).size_hint(), (4, Some(4))); + assert_eq!(nonzero_range!(NonZero(20..1)).step_by(1).size_hint(), (0, Some(0))); + assert_eq!(nonzero_range!(NonZero(20..20)).step_by(1).size_hint(), (0, Some(0))); + + // ExactSizeIterator + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::from(u8::MAX)); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::from(u16::MAX)); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).len(), usize::MAX); + + // Limits (next) + let mut range = nonzero_range!(NonZero(254..=MAX)); + assert_eq!(range.next(), Some(nz!(NonZero(254)))); + assert_eq!(range.next(), Some(nz!(NonZero(255)))); + assert_eq!(range.next(), None); + + let mut range = nonzero_range!(NonZero(65534..=MAX)); + assert_eq!(range.next(), Some(nz!(NonZero(65534)))); + assert_eq!(range.next(), Some(nz!(NonZero(65535)))); + assert_eq!(range.next(), None); + + // Limits (size_hint, exclusive range) + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u8::MAX as usize - 1, Some(u8::MAX as usize - 1)) + ); + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u16::MAX as usize - 1, Some(u16::MAX as usize - 1)) + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u32::MAX as usize - 1, Some(u32::MAX as usize - 1)) + ); + #[cfg(target_pointer_width = "64")] + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (u64::MAX as usize - 1, Some(u64::MAX as usize - 1)) + ); + assert_eq!(nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), (usize::MAX, None)); + assert_eq!( + nonzero_range!(NonZero(1..MAX)).step_by(1).size_hint(), + (usize::MAX - 1, Some(usize::MAX - 1)) + ); + + // Limits (size_hint, inclusive range) + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u8::MAX as usize, Some(u8::MAX as usize)) + ); + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u16::MAX as usize, Some(u16::MAX as usize)) + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u32::MAX as usize, Some(u32::MAX as usize)) + ); + #[cfg(target_pointer_width = "64")] + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (u64::MAX as usize, Some(u64::MAX as usize)) + ); + assert_eq!(nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), (usize::MAX, None)); + assert_eq!( + nonzero_range!(NonZero(1..=MAX)).step_by(1).size_hint(), + (usize::MAX, Some(usize::MAX)) + ); +} From 13373707aefd4cf5685b3bd9987afa6a1b07c170 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Mar 2026 16:59:54 +0100 Subject: [PATCH 3/8] Remove `BuiltinLintDiag::SingleUseLifetime` --- compiler/rustc_lint/src/early/diagnostics.rs | 31 ------------ compiler/rustc_lint/src/lints.rs | 24 ---------- compiler/rustc_lint_defs/src/lib.rs | 12 ----- compiler/rustc_resolve/src/errors.rs | 24 ++++++++++ .../rustc_resolve/src/late/diagnostics.rs | 47 +++++++++++++++---- 5 files changed, 63 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 75824670d2496..dbf96a1c40492 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -8,7 +8,6 @@ use rustc_hir::lints::{AttributeLintKind, FormatWarning}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_session::lint::BuiltinLintDiag; -use tracing::debug; use crate::lints; @@ -87,36 +86,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { } .into_diag(dcx, level) } - BuiltinLintDiag::SingleUseLifetime { - param_span, - use_span, - elidable, - deletion_span, - ident, - } => { - debug!(?param_span, ?use_span, ?deletion_span); - let suggestion = if let Some(deletion_span) = deletion_span { - let (use_span, replace_lt) = if elidable { - let use_span = - self.sess.source_map().span_extend_while_whitespace(use_span); - (use_span, String::new()) - } else { - (use_span, "'_".to_owned()) - }; - debug!(?deletion_span, ?use_span); - - // issue 107998 for the case such as a wrong function pointer type - // `deletion_span` is empty and there is no need to report lifetime uses here - let deletion_span = - if deletion_span.is_empty() { None } else { Some(deletion_span) }; - Some(lints::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt }) - } else { - None - }; - - lints::SingleUseLifetime { suggestion, param_span, use_span, ident } - .into_diag(dcx, level) - } BuiltinLintDiag::NamedArgumentUsedPositionally { position_sp_to_replace, position_sp_for_msg, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index b77225db4f0eb..14dfcca0c4669 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3081,30 +3081,6 @@ pub(crate) enum UnusedImportsSugg { }, } -#[derive(Diagnostic)] -#[diag("lifetime parameter `{$ident}` only used once")] -pub(crate) struct SingleUseLifetime { - #[label("this lifetime...")] - pub param_span: Span, - #[label("...is used only here")] - pub use_span: Span, - #[subdiagnostic] - pub suggestion: Option, - - pub ident: Ident, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] -pub(crate) struct SingleUseLifetimeSugg { - #[suggestion_part(code = "")] - pub deletion_span: Option, - #[suggestion_part(code = "{replace_lt}")] - pub use_span: Span, - - pub replace_lt: String, -} - #[derive(Diagnostic)] #[diag("named argument `{$named_arg_name}` is not used by name")] pub(crate) struct NamedArgumentUsedPositionally { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 71b507f67e8f2..32e88976f589c 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -665,18 +665,6 @@ pub enum BuiltinLintDiag { test_module_span: Option, span_snippets: Vec, }, - SingleUseLifetime { - /// Span of the parameter which declares this lifetime. - param_span: Span, - /// Span of the code that should be removed when eliding this lifetime. - /// This span should include leading or trailing comma. - deletion_span: Option, - /// Span of the single use, or None if the lifetime is never used. - /// If true, the lifetime will be fully elided. - use_span: Span, - elidable: bool, - ident: Ident, - }, NamedArgumentUsedPositionally { /// Span where the named argument is used by position and will be replaced with the named /// argument name diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 43b006d72e500..5f3079cdba0de 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1697,3 +1697,27 @@ pub(crate) struct AssociatedConstElidedLifetime { #[note("cannot automatically infer `'static` because of other lifetimes in scope")] pub lifetimes_in_scope: MultiSpan, } + +#[derive(Diagnostic)] +#[diag("lifetime parameter `{$ident}` only used once")] +pub(crate) struct SingleUseLifetime { + #[label("this lifetime...")] + pub param_span: Span, + #[label("...is used only here")] + pub use_span: Span, + #[subdiagnostic] + pub suggestion: Option, + + pub ident: Ident, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] +pub(crate) struct SingleUseLifetimeSugg { + #[suggestion_part(code = "")] + pub deletion_span: Option, + #[suggestion_part(code = "{replace_lt}")] + pub use_span: Span, + + pub replace_lt: String, +} diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 7539e3c4f499f..3128e67724ca8 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -14,7 +14,7 @@ use rustc_ast_pretty::pprust::{path_to_string, where_bound_predicate_to_string}; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, + Applicability, Diag, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, pluralize, struct_span_code_err, }; use rustc_hir as hir; @@ -3647,16 +3647,47 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let deletion_span = if param.bounds.is_empty() { deletion_span() } else { None }; - self.r.lint_buffer.buffer_lint( + let tcx = self.r.tcx; + let param_ident = param.ident; + let sugg_data = deletion_span.map(|deletion_span| { + if elidable { + let use_span = + tcx.sess.source_map().span_extend_while_whitespace(use_span); + (deletion_span, use_span, String::new()) + } else { + (deletion_span, use_span, "'_".to_owned()) + } + }); + self.r.lint_buffer.dyn_buffer_lint( lint::builtin::SINGLE_USE_LIFETIMES, param.id, param.ident.span, - lint::BuiltinLintDiag::SingleUseLifetime { - param_span: param.ident.span, - use_span, - elidable, - deletion_span, - ident: param.ident, + move |dcx, level| { + let suggestion = + sugg_data.map(|(deletion_span, use_span, replace_lt)| { + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let deletion_span = if deletion_span.is_empty() { + None + } else { + Some(deletion_span) + }; + errors::SingleUseLifetimeSugg { + deletion_span, + use_span, + replace_lt, + } + }); + let param_span = param_ident.span; + debug!(?param_span, ?use_span, ?deletion_span); + + errors::SingleUseLifetime { + suggestion, + param_span, + use_span, + ident: param_ident, + } + .into_diag(dcx, level) }, ); } From 99f9ba0a33ff2ee7165df72bf7615e4a985ac6e4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Mar 2026 17:36:26 +0100 Subject: [PATCH 4/8] Remove `BuiltinLintDiag::AbsPathWithModule` --- compiler/rustc_lint/src/early/diagnostics.rs | 22 --------------- compiler/rustc_lint/src/lints.rs | 19 ------------- compiler/rustc_lint_defs/src/lib.rs | 1 - compiler/rustc_resolve/src/diagnostics.rs | 28 ++++++++++++++++---- compiler/rustc_resolve/src/errors.rs | 19 +++++++++++++ 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index dbf96a1c40492..19dc1c40e6d34 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -24,28 +24,6 @@ pub struct DecorateBuiltinLint<'sess, 'tcx> { impl<'a> Diagnostic<'a, ()> for DecorateBuiltinLint<'_, '_> { fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { match self.diagnostic { - BuiltinLintDiag::AbsPathWithModule(mod_span) => { - let (replacement, applicability) = - match self.sess.source_map().span_to_snippet(mod_span) { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = - if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - lints::AbsPathWithModule { - sugg: lints::AbsPathWithModuleSugg { - span: mod_span, - applicability, - replacement, - }, - } - .into_diag(dcx, level) - } BuiltinLintDiag::ElidedLifetimesInPaths( n, path_span, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 14dfcca0c4669..5819f2bc151f9 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3011,25 +3011,6 @@ pub(crate) struct IllFormedAttributeInput { pub docs: &'static str, } -#[derive(Diagnostic)] -#[diag( - "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" -)] -pub(crate) struct AbsPathWithModule { - #[subdiagnostic] - pub sugg: AbsPathWithModuleSugg, -} - -#[derive(Subdiagnostic)] -#[suggestion("use `crate`", code = "{replacement}")] -pub(crate) struct AbsPathWithModuleSugg { - #[primary_span] - pub span: Span, - #[applicability] - pub applicability: Applicability, - pub replacement: String, -} - #[derive(Diagnostic)] #[diag("hidden lifetime parameters in types are deprecated")] pub(crate) struct ElidedLifetimesInPaths { diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 32e88976f589c..7e51029b02bb6 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -656,7 +656,6 @@ pub enum DeprecatedSinceKind { // becomes hacky (and it gets allocated). #[derive(Debug)] pub enum BuiltinLintDiag { - AbsPathWithModule(Span), ElidedLifetimesInPaths(usize, Span, bool, Span), UnusedImports { remove_whole_use: bool, diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index cab37f5c0faa6..74f258b42b001 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -11,7 +11,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, ErrorGuaranteed, MultiSpan, SuggestionStyle, + Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, struct_span_code_err, }; use rustc_feature::BUILTIN_ATTRIBUTES; @@ -23,7 +23,6 @@ use rustc_hir::{PrimTy, Stability, StabilityLevel, find_attr}; use rustc_middle::bug; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::builtin::{ ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, @@ -510,12 +509,31 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return; } - let diag = BuiltinLintDiag::AbsPathWithModule(root_span); - self.lint_buffer.buffer_lint( + let (replacement, applicability) = + match self.tcx.sess.source_map().span_to_snippet(root_span) { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; + self.lint_buffer.dyn_buffer_lint( ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, node_id, root_span, - diag, + move |dcx, level| { + errors::AbsPathWithModule { + sugg: errors::AbsPathWithModuleSugg { + span: root_span, + applicability, + replacement, + }, + } + .into_diag(dcx, level) + }, ); } diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 5f3079cdba0de..305231a3ea0d9 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1721,3 +1721,22 @@ pub(crate) struct SingleUseLifetimeSugg { pub replace_lt: String, } + +#[derive(Diagnostic)] +#[diag( + "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" +)] +pub(crate) struct AbsPathWithModule { + #[subdiagnostic] + pub sugg: AbsPathWithModuleSugg, +} + +#[derive(Subdiagnostic)] +#[suggestion("use `crate`", code = "{replacement}")] +pub(crate) struct AbsPathWithModuleSugg { + #[primary_span] + pub span: Span, + #[applicability] + pub applicability: Applicability, + pub replacement: String, +} From 6f751ec1824d98e7b7537d0878849df155ce6116 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Mar 2026 23:25:45 +0100 Subject: [PATCH 5/8] Make `DecorateDiagCompat::Dynamic` take an `Any` reference allowing to downcast to `Session` --- compiler/rustc_errors/src/decorate_diag.rs | 28 ++++++- compiler/rustc_errors/src/diagnostic.rs | 11 --- compiler/rustc_lint/src/early.rs | 10 ++- compiler/rustc_lint/src/early/diagnostics.rs | 15 ++++ compiler/rustc_resolve/src/diagnostics.rs | 30 ++++---- .../rustc_resolve/src/late/diagnostics.rs | 73 +++++++++---------- compiler/rustc_session/src/parse.rs | 2 +- 7 files changed, 102 insertions(+), 67 deletions(-) diff --git a/compiler/rustc_errors/src/decorate_diag.rs b/compiler/rustc_errors/src/decorate_diag.rs index 61dd8f0493f91..3215914698896 100644 --- a/compiler/rustc_errors/src/decorate_diag.rs +++ b/compiler/rustc_errors/src/decorate_diag.rs @@ -1,3 +1,5 @@ +use std::any::Any; + /// This module provides types and traits for buffering lints until later in compilation. use rustc_ast::node_id::NodeId; use rustc_data_structures::fx::FxIndexMap; @@ -10,9 +12,11 @@ use crate::{Diag, DiagCtxtHandle, Diagnostic, Level}; /// We can't implement `Diagnostic` for `BuiltinLintDiag`, because decorating some of its /// variants requires types we don't have yet. So, handle that case separately. pub enum DecorateDiagCompat { + /// The third argument of the closure is a `Session`. However, due to the dependency tree, + /// we don't have access to `rustc_session` here, so we downcast it when needed. Dynamic( Box< - dyn for<'a> FnOnce(DiagCtxtHandle<'a>, Level) -> Diag<'a, ()> + dyn for<'a> FnOnce(DiagCtxtHandle<'a>, Level, &dyn Any) -> Diag<'a, ()> + DynSync + DynSend + 'static, @@ -30,7 +34,7 @@ impl std::fmt::Debug for DecorateDiagCompat { impl Diagnostic<'a, ()> + DynSync + DynSend + 'static> From for DecorateDiagCompat { #[inline] fn from(d: D) -> Self { - Self::Dynamic(Box::new(|dcx, level| d.into_diag(dcx, level))) + Self::Dynamic(Box::new(|dcx, level, _| d.into_diag(dcx, level))) } } @@ -97,6 +101,26 @@ impl LintBuffer { node_id: NodeId, span: impl Into, callback: F, + ) { + self.add_early_lint(BufferedEarlyLint { + lint_id: LintId::of(lint), + node_id, + span: Some(span.into()), + diagnostic: DecorateDiagCompat::Dynamic(Box::new(|dcx, level, _| callback(dcx, level))), + }); + } + + pub fn dyn_buffer_lint_any< + F: for<'a> FnOnce(DiagCtxtHandle<'a>, Level, &dyn Any) -> Diag<'a, ()> + + DynSend + + DynSync + + 'static, + >( + &mut self, + lint: &'static Lint, + node_id: NodeId, + span: impl Into, + callback: F, ) { self.add_early_lint(BufferedEarlyLint { lint_id: LintId::of(lint), diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 9525a45d55f1b..7acf95d77d40e 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -7,7 +7,6 @@ use std::panic; use std::path::PathBuf; use std::thread::panicking; -use rustc_data_structures::sync::{DynSend, DynSync}; use rustc_error_messages::{DiagArgMap, DiagArgName, DiagArgValue, IntoDiagArg}; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; @@ -119,16 +118,6 @@ where } } -impl<'a> Diagnostic<'a, ()> - for Box< - dyn for<'b> FnOnce(DiagCtxtHandle<'b>, Level) -> Diag<'b, ()> + DynSync + DynSend + 'static, - > -{ - fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { - self(dcx, level) - } -} - /// Type used to emit diagnostic through a closure instead of implementing the `Diagnostic` trait. pub struct DiagDecorator)>(pub F); diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 7d9c2e8327b91..635185be1cb76 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -15,9 +15,9 @@ use rustc_session::lint::LintPass; use rustc_span::{DUMMY_SP, Ident, Span}; use tracing::debug; -use crate::DecorateBuiltinLint; use crate::context::{EarlyContext, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; +use crate::{DecorateBuiltinLint, DiagAndSess}; pub(super) mod diagnostics; @@ -49,8 +49,12 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { }, ); } - DecorateDiagCompat::Dynamic(d) => { - self.context.opt_span_lint(lint_id.lint, span, d); + DecorateDiagCompat::Dynamic(callback) => { + self.context.opt_span_lint( + lint_id.lint, + span, + DiagAndSess { callback, sess: self.context.sess() }, + ); } } } diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 19dc1c40e6d34..aee16526dfc0b 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -1,5 +1,7 @@ +use std::any::Any; use std::borrow::Cow; +use rustc_data_structures::sync::DynSend; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, Level, elided_lifetime_in_path_suggestion, @@ -13,6 +15,19 @@ use crate::lints; mod check_cfg; +pub struct DiagAndSess<'sess> { + pub callback: Box< + dyn for<'b> FnOnce(DiagCtxtHandle<'b>, Level, &dyn Any) -> Diag<'b, ()> + DynSend + 'static, + >, + pub sess: &'sess Session, +} + +impl<'a> Diagnostic<'a, ()> for DiagAndSess<'_> { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> { + (self.callback)(dcx, level, self.sess) + } +} + /// This is a diagnostic struct that will decorate a `BuiltinLintDiag` /// Directly creating the lint structs is expensive, using this will only decorate the lint structs when needed. pub struct DecorateBuiltinLint<'sess, 'tcx> { diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 74f258b42b001..9e0f04e82b472 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -509,22 +509,26 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { return; } - let (replacement, applicability) = - match self.tcx.sess.source_map().span_to_snippet(root_span) { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - self.lint_buffer.dyn_buffer_lint( + self.lint_buffer.dyn_buffer_lint_any( ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, node_id, root_span, - move |dcx, level| { + move |dcx, level, sess| { + let (replacement, applicability) = match sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_to_snippet(root_span) + { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; errors::AbsPathWithModule { sugg: errors::AbsPathWithModuleSugg { span: root_span, diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 3128e67724ca8..5bd7ea502dbad 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -3641,49 +3641,48 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { match use_set { Some(LifetimeUseSet::Many) => {} Some(LifetimeUseSet::One { use_span, use_ctxt }) => { - debug!(?param.ident, ?param.ident.span, ?use_span); - - let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); + let param_ident = param.ident; let deletion_span = if param.bounds.is_empty() { deletion_span() } else { None }; - - let tcx = self.r.tcx; - let param_ident = param.ident; - let sugg_data = deletion_span.map(|deletion_span| { - if elidable { - let use_span = - tcx.sess.source_map().span_extend_while_whitespace(use_span); - (deletion_span, use_span, String::new()) - } else { - (deletion_span, use_span, "'_".to_owned()) - } - }); - self.r.lint_buffer.dyn_buffer_lint( + self.r.lint_buffer.dyn_buffer_lint_any( lint::builtin::SINGLE_USE_LIFETIMES, param.id, - param.ident.span, - move |dcx, level| { - let suggestion = - sugg_data.map(|(deletion_span, use_span, replace_lt)| { - // issue 107998 for the case such as a wrong function pointer type - // `deletion_span` is empty and there is no need to report lifetime uses here - let deletion_span = if deletion_span.is_empty() { - None - } else { - Some(deletion_span) - }; - errors::SingleUseLifetimeSugg { - deletion_span, - use_span, - replace_lt, - } - }); - let param_span = param_ident.span; - debug!(?param_span, ?use_span, ?deletion_span); - + param_ident.span, + move |dcx, level, sess| { + debug!(?param_ident, ?param_ident.span, ?use_span); + + let elidable = matches!(use_ctxt, LifetimeCtxt::Ref); + let suggestion = if let Some(deletion_span) = deletion_span { + let (use_span, replace_lt) = if elidable { + let use_span = sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_extend_while_whitespace(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + debug!(?deletion_span, ?use_span); + + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let deletion_span = if deletion_span.is_empty() { + None + } else { + Some(deletion_span) + }; + Some(errors::SingleUseLifetimeSugg { + deletion_span, + use_span, + replace_lt, + }) + } else { + None + }; errors::SingleUseLifetime { suggestion, - param_span, + param_span: param_ident.span, use_span, ident: param_ident, } diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs index 65a15dba42873..8251050b6aead 100644 --- a/compiler/rustc_session/src/parse.rs +++ b/compiler/rustc_session/src/parse.rs @@ -344,7 +344,7 @@ impl ParseSess { lint, Some(span.into()), node_id, - DecorateDiagCompat::Dynamic(Box::new(callback)), + DecorateDiagCompat::Dynamic(Box::new(|dcx, level, _| callback(dcx, level))), ) } From a08257840b8a17f6e5602959e36d7b87b8394ec1 Mon Sep 17 00:00:00 2001 From: enthropy7 <221884178+enthropy7@users.noreply.github.com> Date: Fri, 3 Apr 2026 22:36:53 +0300 Subject: [PATCH 6/8] Fix ICE in read_discriminant for enums with non-contiguous discriminants --- .../rustc_const_eval/src/interpret/discriminant.rs | 14 +++++++------- tests/ui/consts/const-eval/ub-enum.rs | 13 +++++++++++++ tests/ui/consts/const-eval/ub-enum.stderr | 13 ++++++++++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index 3a7c1ea65f3d8..c50d9db8be639 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -121,20 +121,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // discriminants are int-like. let discr_val = self.int_to_int_or_float(&tag_val, discr_layout).unwrap(); let discr_bits = discr_val.to_scalar().to_bits(discr_layout.size)?; - // Convert discriminant to variant index. Since we validated the tag against the - // layout range above, this cannot fail. + // Convert discriminant to variant index. The tag may pass the layout range + // check above but still not match any actual variant discriminant (e.g., + // non-contiguous discriminants with a wrapping valid_range). let index = match *ty.kind() { ty::Adt(adt, _) => { - adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits).unwrap() + adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits) } ty::Coroutine(def_id, args) => { let args = args.as_coroutine(); - args.discriminants(def_id, *self.tcx) - .find(|(_, var)| var.val == discr_bits) - .unwrap() + args.discriminants(def_id, *self.tcx).find(|(_, var)| var.val == discr_bits) } _ => span_bug!(self.cur_span(), "tagged layout for non-adt non-coroutine"), - }; + } + .ok_or_else(|| err_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size))))?; // Return the cast value, and the index. index.0 } diff --git a/tests/ui/consts/const-eval/ub-enum.rs b/tests/ui/consts/const-eval/ub-enum.rs index 61ab3581e3479..8feb78e0b11e8 100644 --- a/tests/ui/consts/const-eval/ub-enum.rs +++ b/tests/ui/consts/const-eval/ub-enum.rs @@ -108,4 +108,17 @@ const TEST_ICE_89765: () = { }; }; +// # Regression test for https://github.com/rust-lang/rust/issues/153758 +// Discriminants at i64::MIN and i64::MAX produce a wrapping valid_range that covers +// all values. A value like 0 passes the range check but doesn't match any variant. +#[repr(i64)] +#[derive(Copy, Clone)] +enum WideRangeDiscriminants { + A = i64::MIN, + B = i64::MAX, +} + +const BAD_WIDE_RANGE_ENUM: WideRangeDiscriminants = unsafe { mem::transmute(0_i64) }; +//~^ ERROR expected a valid enum tag + fn main() {} diff --git a/tests/ui/consts/const-eval/ub-enum.stderr b/tests/ui/consts/const-eval/ub-enum.stderr index a5ac10c7922c1..bb2c58796b1c4 100644 --- a/tests/ui/consts/const-eval/ub-enum.stderr +++ b/tests/ui/consts/const-eval/ub-enum.stderr @@ -129,6 +129,17 @@ LL | std::mem::discriminant(&*(&() as *const () as *const Never)); note: inside `discriminant::` --> $SRC_DIR/core/src/mem/mod.rs:LL:COL -error: aborting due to 14 previous errors +error[E0080]: constructing invalid value of type WideRangeDiscriminants: at ., encountered 0x0, but expected a valid enum tag + --> $DIR/ub-enum.rs:121:1 + | +LL | const BAD_WIDE_RANGE_ENUM: WideRangeDiscriminants = unsafe { mem::transmute(0_i64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ it is undefined behavior to use this value + | + = note: the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + = note: the raw bytes of the constant (size: $SIZE, align: $ALIGN) { + HEX_DUMP + } + +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0080`. From df848e78d9b2ef68b08d46aa0aea440df92d530a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 3 Apr 2026 21:48:20 +0200 Subject: [PATCH 7/8] Fix merge conflict --- compiler/rustc_lint/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index e4106cbdfb797..c76fafcde2ee1 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -129,7 +129,7 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; pub use context::{EarlyContext, LateContext, LintContext, LintStore}; -pub use early::diagnostics::{DecorateAttrLint, DecorateBuiltinLint}; +pub use early::diagnostics::{DecorateAttrLint, DecorateBuiltinLint, DiagAndSess}; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; pub use levels::LintLevelsBuilder; From d6be991fb4da0acac3d9fb2938f9ad00db999c4f Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 2 Apr 2026 20:57:18 +0000 Subject: [PATCH 8/8] llvm: Fix array ABI test to not check equality implementation LLVM has moved memcmp expansion in the pipeline, resulting in the bcmp call being expanded into loads and register comparisons, which breaks the test. Based on history, I believe the test actually intended validate that these arrays were being passed as pointer arguments, which can be done more directly. --- tests/codegen-llvm/array-equality.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/codegen-llvm/array-equality.rs b/tests/codegen-llvm/array-equality.rs index fa0475bf4809c..8e4c170e4e674 100644 --- a/tests/codegen-llvm/array-equality.rs +++ b/tests/codegen-llvm/array-equality.rs @@ -1,3 +1,6 @@ +//@ revisions: llvm-current llvm-next +//@[llvm-current] ignore-llvm-version: 23-99 +//@[llvm-next] min-llvm-version: 23 //@ compile-flags: -Copt-level=3 -Z merge-functions=disabled //@ only-x86_64 #![crate_type = "lib"] @@ -26,9 +29,18 @@ pub fn array_eq_ref(a: &[u16; 3], b: &[u16; 3]) -> bool { #[no_mangle] pub fn array_eq_value_still_passed_by_pointer(a: [u16; 9], b: [u16; 9]) -> bool { // CHECK-NEXT: start: - // CHECK: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(ptr {{.*}} dereferenceable(18) %{{.+}}, ptr {{.*}} dereferenceable(18) %{{.+}}, i64 18) - // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0 - // CHECK-NEXT: ret i1 %[[EQ]] + // CHECK-NOT: alloca + // llvm-current-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(ptr + // llvm-current-SAME: {{.*}} dereferenceable(18) %{{.+}}, ptr {{.*}} dereferenceable(18) + // llvm-current-SAME: %{{.+}}, i64 18) + // llvm-current-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0 + // llvm-current-NEXT: ret i1 %[[EQ]] + // CHECK-NOT: call + // New LLVM expands the bcmp earlier, so this becomes wide reads + icmp + // No allocas or calls, and at least one icmp + // llvm-next: icmp + // CHECK-NOT: alloca + // CHECK-NOT: call a == b }