From c973b5051072d339c21a3e9493ff0fabac8f8f97 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Fri, 18 Sep 2026 23:47:48 -0400 Subject: [PATCH 1/7] Avoid generating overlapping assignments in DSE --- .../src/dead_store_elimination.rs | 7 +++++ ...eadStoreElimination-final.panic-abort.diff | 2 +- ...adStoreElimination-final.panic-unwind.diff | 2 +- ...eadStoreElimination-final.panic-abort.diff | 16 ++++++++++ ...adStoreElimination-final.panic-unwind.diff | 16 ++++++++++ .../dead-store-elimination/call_arg_copy.rs | 29 ++++++++++++++----- 6 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-abort.diff create mode 100644 tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-unwind.diff diff --git a/compiler/rustc_mir_transform/src/dead_store_elimination.rs b/compiler/rustc_mir_transform/src/dead_store_elimination.rs index 9b0e2a0663eca..7c41217f402ff 100644 --- a/compiler/rustc_mir_transform/src/dead_store_elimination.rs +++ b/compiler/rustc_mir_transform/src/dead_store_elimination.rs @@ -63,6 +63,13 @@ fn eliminate<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) -> bool { loc, ); + // The logic in LivenessTransferFunction isn't quite what we need; it ignores call + // destinations that are just locals because they are killed by the call, which makes + // it eligible to be moved-from in the argument list. That's backwards. + if !destination.is_indirect() { + state.insert(destination.local); + } + for (index, arg) in args.iter().map(|a| &a.node).enumerate().rev() { if let Operand::Copy(place) = *arg && !place.is_indirect() diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff index 012dd7d88a5f0..2c4e7ae552844 100644 --- a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-abort.diff @@ -5,7 +5,7 @@ let mut _0: (); bb0: { - _1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind unreachable]; + _1[_2] = identity::(copy _2) -> [return: bb1, unwind unreachable]; } bb1: { diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff index fcd0ae43e2897..86f9b93130d72 100644 --- a/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.move_index.DeadStoreElimination-final.panic-unwind.diff @@ -5,7 +5,7 @@ let mut _0: (); bb0: { - _1[_2] = passthrough_usize(copy _2) -> [return: bb1, unwind continue]; + _1[_2] = identity::(copy _2) -> [return: bb1, unwind continue]; } bb1: { diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-abort.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-abort.diff new file mode 100644 index 0000000000000..d4d49d0fb79e7 --- /dev/null +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-abort.diff @@ -0,0 +1,16 @@ +- // MIR for `ret_is_arg` before DeadStoreElimination-final ++ // MIR for `ret_is_arg` after DeadStoreElimination-final + + fn ret_is_arg(_1: [u64; 5]) -> [u64; 5] { + let mut _0: [u64; 5]; + + bb0: { + _1 = identity::<[u64; 5]>(copy _1) -> [return: bb1, unwind unreachable]; + } + + bb1: { + _0 = copy _1; + return; + } + } + diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-unwind.diff b/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-unwind.diff new file mode 100644 index 0000000000000..d4d49d0fb79e7 --- /dev/null +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.ret_is_arg.DeadStoreElimination-final.panic-unwind.diff @@ -0,0 +1,16 @@ +- // MIR for `ret_is_arg` before DeadStoreElimination-final ++ // MIR for `ret_is_arg` after DeadStoreElimination-final + + fn ret_is_arg(_1: [u64; 5]) -> [u64; 5] { + let mut _0: [u64; 5]; + + bb0: { + _1 = identity::<[u64; 5]>(copy _1) -> [return: bb1, unwind unreachable]; + } + + bb1: { + _0 = copy _1; + return; + } + } + diff --git a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs index 00a9a49c2abb8..1a7f9a1494dbe 100644 --- a/tests/mir-opt/dead-store-elimination/call_arg_copy.rs +++ b/tests/mir-opt/dead-store-elimination/call_arg_copy.rs @@ -6,6 +6,7 @@ #![feature(custom_mir)] #![allow(internal_features)] +use std::convert::identity; use std::intrinsics::mir::*; #[inline(never)] @@ -40,22 +41,17 @@ fn move_packed(packed: Packed) { } } -#[inline(never)] -fn passthrough_usize(a: usize) -> usize { - a -} - // EMIT_MIR call_arg_copy.move_index.DeadStoreElimination-final.diff #[custom_mir(dialect = "analysis")] fn move_index(a: [usize; 10], b: usize) { // CHECK-LABEL: fn move_index( - // CHECK: = passthrough_usize(copy _2) + // CHECK: = identity::(copy _2) mir! { { // The index is used again after the operand is evaluated to - // evaluate the destionation place, so the argument cannot be turned + // evaluate the destination place, so the argument cannot be turned // into a move. - Call(a[b] = passthrough_usize(b), ReturnTo(ret), UnwindContinue()) + Call(a[b] = identity(b), ReturnTo(ret), UnwindContinue()) } ret = { Return() @@ -63,8 +59,25 @@ fn move_index(a: [usize; 10], b: usize) { } } +// EMIT_MIR call_arg_copy.ret_is_arg.DeadStoreElimination-final.diff +#[custom_mir(dialect = "runtime")] +fn ret_is_arg(x: [u64; 5]) -> [u64; 5] { + // CHECK-LABEL: fn ret_is_arg(_1 + // CHECK: _1 = identity::<[u64; 5]>(copy _1) -> + mir! { + { + Call(x = identity(x), ReturnTo(bb1), UnwindUnreachable()) + } + bb1 = { + RET = x; + Return() + } + } +} + fn main() { move_simple(1); move_packed(Packed { x: 0, y: 1 }); move_index([0; _], 1); + ret_is_arg([0; 5]); } From 5e86855354b3dde36c3de271cf600ce6b9d7e46d Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Mon, 21 Sep 2026 17:13:27 +0600 Subject: [PATCH 2/7] emit the constant pattern note for raw identifier bindings `is_const_pat_that_looks_like_binding` matched the pattern snippet against a plain identifier so the `#` in `let r#x = 0` rejected it and the binding lost both the label pointing at `const x` and the `introduce a variable instead` suggestion. strip a leading `r#` before the check and render the const name with `to_ident_string` in the label so a keyword name prints as `r#fn`. fixes https://github.com/rust-lang/rust/issues/162949 --- .../src/thir/pattern/check_match.rs | 15 ++++-- tests/ui/consts/const-pattern-irrefutable.rs | 14 ++++++ .../consts/const-pattern-irrefutable.stderr | 48 ++++++++++++++++--- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 3414ee751585c..5b4f7a79d4bac 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -678,10 +678,13 @@ impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { if let Some(def_id) = is_const_pat_that_looks_like_binding(self.tcx, pat) { let span = self.tcx.def_span(def_id); - let variable = self.tcx.item_name(def_id).to_string(); + let name = self.tcx.item_name(def_id); // When we encounter a constant as the binding name, point at the `const` definition. - interpreted_as_const = Some(InterpretedAsConst { span, variable: variable.clone() }); - interpreted_as_const_sugg = Some(InterpretedAsConstSugg { span: pat.span, variable }); + interpreted_as_const = + Some(InterpretedAsConst { span, variable: name.to_ident_string() }); + // The suggested name is suffixed, so it is never a keyword and never needs `r#`. + interpreted_as_const_sugg = + Some(InterpretedAsConstSugg { span: pat.span, variable: name.to_string() }); } else if let PatKind::Constant { .. } = pat.kind && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span) { @@ -1231,7 +1234,11 @@ fn is_const_pat_that_looks_like_binding<'tcx>(tcx: TyCtxt<'tcx>, pat: &Pat<'tcx> // `::` namespace separators or other non-identifier characters. if let ty::AliasConstKind::Free { def_id } = pat.extra.as_deref()?.expanded_const? && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(pat.span) - && snippet.chars().all(|c| c.is_alphanumeric() || c == '_') + && snippet + .strip_prefix("r#") + .unwrap_or(&snippet) + .chars() + .all(|c| c.is_alphanumeric() || c == '_') { Some(def_id) } else { diff --git a/tests/ui/consts/const-pattern-irrefutable.rs b/tests/ui/consts/const-pattern-irrefutable.rs index e131fd27d532c..f0f5fc90d5195 100644 --- a/tests/ui/consts/const-pattern-irrefutable.rs +++ b/tests/ui/consts/const-pattern-irrefutable.rs @@ -13,6 +13,12 @@ use foo::d; const a: u8 = 2; //~^ NOTE missing patterns are not covered because `a` is interpreted as a constant pattern, not a new variable +const g: u8 = 2; +//~^ NOTE missing patterns are not covered because `g` is interpreted as a constant pattern, not a new variable + +const r#fn: u8 = 2; +//~^ NOTE missing patterns are not covered because `r#fn` is interpreted as a constant pattern, not a new variable + #[derive(PartialEq)] struct S { foo: u8, @@ -41,5 +47,13 @@ fn main() { //~| HELP introduce a variable instead foo: 1, }; + let r#g = 4; + //~^ ERROR refutable pattern in local binding + //~| NOTE patterns `0_u8..=1_u8` and `3_u8..=u8::MAX` not covered + //~| HELP introduce a variable instead + let r#fn = 4; + //~^ ERROR refutable pattern in local binding + //~| NOTE patterns `0_u8..=1_u8` and `3_u8..=u8::MAX` not covered + //~| HELP introduce a variable instead fn f() {} // Check that the `NOTE`s still work with an item here (cf. issue #35115). } diff --git a/tests/ui/consts/const-pattern-irrefutable.stderr b/tests/ui/consts/const-pattern-irrefutable.stderr index f52ebc313339c..a59b17e41c799 100644 --- a/tests/ui/consts/const-pattern-irrefutable.stderr +++ b/tests/ui/consts/const-pattern-irrefutable.stderr @@ -1,5 +1,5 @@ error[E0005]: refutable pattern in local binding - --> $DIR/const-pattern-irrefutable.rs:26:9 + --> $DIR/const-pattern-irrefutable.rs:32:9 | LL | const a: u8 = 2; | ----------- missing patterns are not covered because `a` is interpreted as a constant pattern, not a new variable @@ -16,7 +16,7 @@ LL | let a_var = 4; | ++++ error[E0005]: refutable pattern in local binding - --> $DIR/const-pattern-irrefutable.rs:30:9 + --> $DIR/const-pattern-irrefutable.rs:36:9 | LL | pub const b: u8 = 2; | --------------- missing patterns are not covered because `b` is interpreted as a constant pattern, not a new variable @@ -34,7 +34,7 @@ LL + let b_var = 4; | error[E0005]: refutable pattern in local binding - --> $DIR/const-pattern-irrefutable.rs:34:9 + --> $DIR/const-pattern-irrefutable.rs:40:9 | LL | pub const d: (u8, u8) = (2, 1); | --------------------- missing patterns are not covered because `d` is interpreted as a constant pattern, not a new variable @@ -51,7 +51,7 @@ LL | let d_var = (4, 4); | ++++ error[E0005]: refutable pattern in local binding - --> $DIR/const-pattern-irrefutable.rs:38:9 + --> $DIR/const-pattern-irrefutable.rs:44:9 | LL | const e: S = S { | ---------- missing patterns are not covered because `e` is interpreted as a constant pattern, not a new variable @@ -62,7 +62,7 @@ LL | let e = S { = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html note: `S` defined here - --> $DIR/const-pattern-irrefutable.rs:17:8 + --> $DIR/const-pattern-irrefutable.rs:23:8 | LL | struct S { | ^ @@ -72,6 +72,42 @@ help: introduce a variable instead LL | let e_var = S { | ++++ -error: aborting due to 4 previous errors +error[E0005]: refutable pattern in local binding + --> $DIR/const-pattern-irrefutable.rs:50:9 + | +LL | const g: u8 = 2; + | ----------- missing patterns are not covered because `g` is interpreted as a constant pattern, not a new variable +... +LL | let r#g = 4; + | ^^^ patterns `0_u8..=1_u8` and `3_u8..=u8::MAX` not covered + | + = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant + = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html + = note: the matched value is of type `u8` +help: introduce a variable instead + | +LL - let r#g = 4; +LL + let g_var = 4; + | + +error[E0005]: refutable pattern in local binding + --> $DIR/const-pattern-irrefutable.rs:54:9 + | +LL | const r#fn: u8 = 2; + | -------------- missing patterns are not covered because `r#fn` is interpreted as a constant pattern, not a new variable +... +LL | let r#fn = 4; + | ^^^^ patterns `0_u8..=1_u8` and `3_u8..=u8::MAX` not covered + | + = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant + = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html + = note: the matched value is of type `u8` +help: introduce a variable instead + | +LL - let r#fn = 4; +LL + let fn_var = 4; + | + +error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0005`. From 919170b08cc014c8e85709dd80efeed2bac74562 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 21 Sep 2026 12:24:34 +0200 Subject: [PATCH 3/7] remove unnecessary restriction with next-solver --- .../src/region_infer/opaque_types/mod.rs | 55 +++++++++---------- ...tderr => lifetime_mismatch.current.stderr} | 8 +-- .../lifetime_mismatch.rs | 7 ++- ...f-uses-in-one-fn-lifetimes.current.stderr} | 4 +- .../multiple-def-uses-in-one-fn-lifetimes.rs | 5 +- 5 files changed, 42 insertions(+), 37 deletions(-) rename tests/ui/type-alias-impl-trait/{lifetime_mismatch.stderr => lifetime_mismatch.current.stderr} (84%) rename tests/ui/type-alias-impl-trait/{multiple-def-uses-in-one-fn-lifetimes.stderr => multiple-def-uses-in-one-fn-lifetimes.current.stderr} (78%) diff --git a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs index ad15c5750b092..069f6b598e19c 100644 --- a/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/opaque_types/mod.rs @@ -362,11 +362,11 @@ fn compute_definition_site_hidden_types_from_defining_uses<'tcx>( DefinitionSiteHiddenType::new_error(tcx, guar) }); - // Sometimes, when the hidden type is an inference variable, it can happen that - // the hidden type becomes the opaque type itself. In this case, this was an opaque - // usage of the opaque type and we can ignore it. This check is mirrored in typeck's - // writeback. if !rcx.infcx.tcx.use_typing_mode_post_typeck_until_borrowck() { + // Sometimes, when the hidden type is an inference variable, it can happen that + // the hidden type becomes the opaque type itself. In this case, this was an opaque + // usage of the opaque type and we can ignore it. This check is mirrored in typeck's + // writeback. if let &ty::Alias(_, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) = hidden_type.ty.skip_binder().kind() && def_id == opaque_type_key.def_id.to_def_id() @@ -374,32 +374,31 @@ fn compute_definition_site_hidden_types_from_defining_uses<'tcx>( { continue; } - } - // Check that all opaque types have the same region parameters if they have the same - // non-region parameters. This is necessary because within the new solver we perform - // various query operations modulo regions, and thus could unsoundly select some impls - // that don't hold. - // - // FIXME(-Znext-solver): This isn't necessary after all. We can remove this check again. - if let Some((prev_decl_key, prev_span)) = decls_modulo_regions.insert( - rcx.infcx.tcx.erase_and_anonymize_regions(opaque_type_key), - (opaque_type_key, hidden_type.span), - ) && let Some((arg1, arg2)) = std::iter::zip( - prev_decl_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), - opaque_type_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), - ) - .find(|(arg1, arg2)| arg1 != arg2) - { - errors.push(DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam( - LifetimeMismatchOpaqueParam { - arg: arg1, - prev: arg2, - span: prev_span, - prev_span: hidden_type.span, - }, - )); + // Check that all opaque types have the same region parameters if they have the same + // non-region parameters. This is necessary because within the new solver we perform + // various query operations modulo regions, and thus could unsoundly select some impls + // that don't hold. + if let Some((prev_decl_key, prev_span)) = decls_modulo_regions.insert( + rcx.infcx.tcx.erase_and_anonymize_regions(opaque_type_key), + (opaque_type_key, hidden_type.span), + ) && let Some((arg1, arg2)) = std::iter::zip( + prev_decl_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), + opaque_type_key.iter_captured_args(infcx.tcx).map(|(_, arg)| arg), + ) + .find(|(arg1, arg2)| arg1 != arg2) + { + errors.push(DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam( + LifetimeMismatchOpaqueParam { + arg: arg1, + prev: arg2, + span: prev_span, + prev_span: hidden_type.span, + }, + )); + } } + add_hidden_type(tcx, hidden_types, opaque_type_key.def_id, hidden_type); } } diff --git a/tests/ui/type-alias-impl-trait/lifetime_mismatch.stderr b/tests/ui/type-alias-impl-trait/lifetime_mismatch.current.stderr similarity index 84% rename from tests/ui/type-alias-impl-trait/lifetime_mismatch.stderr rename to tests/ui/type-alias-impl-trait/lifetime_mismatch.current.stderr index 719748edc011f..83ed7ae17902c 100644 --- a/tests/ui/type-alias-impl-trait/lifetime_mismatch.stderr +++ b/tests/ui/type-alias-impl-trait/lifetime_mismatch.current.stderr @@ -1,5 +1,5 @@ error: opaque type used twice with different lifetimes - --> $DIR/lifetime_mismatch.rs:7:5 + --> $DIR/lifetime_mismatch.rs:10:5 | LL | (x, y) | ^^^^^^ @@ -8,13 +8,13 @@ LL | (x, y) | lifetime `'b` previously used here | note: if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types - --> $DIR/lifetime_mismatch.rs:7:5 + --> $DIR/lifetime_mismatch.rs:10:5 | LL | (x, y) | ^^^^^^ error: opaque type used twice with different lifetimes - --> $DIR/lifetime_mismatch.rs:15:5 + --> $DIR/lifetime_mismatch.rs:18:5 | LL | (i, j) | ^^^^^^ @@ -23,7 +23,7 @@ LL | (i, j) | lifetime `'y` previously used here | note: if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types - --> $DIR/lifetime_mismatch.rs:15:5 + --> $DIR/lifetime_mismatch.rs:18:5 | LL | (i, j) | ^^^^^^ diff --git a/tests/ui/type-alias-impl-trait/lifetime_mismatch.rs b/tests/ui/type-alias-impl-trait/lifetime_mismatch.rs index b58840718a354..4a8320e922a2e 100644 --- a/tests/ui/type-alias-impl-trait/lifetime_mismatch.rs +++ b/tests/ui/type-alias-impl-trait/lifetime_mismatch.rs @@ -1,3 +1,6 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver=globally +//@[next] check-pass #![feature(type_alias_impl_trait)] type Foo<'a> = impl Sized; @@ -5,7 +8,7 @@ type Foo<'a> = impl Sized; #[define_opaque(Foo)] fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> (Foo<'a>, Foo<'b>) { (x, y) - //~^ ERROR opaque type used twice with different lifetimes + //[current]~^ ERROR opaque type used twice with different lifetimes } type Bar<'a, 'b> = impl std::fmt::Debug; @@ -13,7 +16,7 @@ type Bar<'a, 'b> = impl std::fmt::Debug; #[define_opaque(Bar)] fn bar<'x, 'y>(i: &'x i32, j: &'y i32) -> (Bar<'x, 'y>, Bar<'y, 'x>) { (i, j) - //~^ ERROR opaque type used twice with different lifetimes + //[current]~^ ERROR opaque type used twice with different lifetimes } fn main() { diff --git a/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.stderr b/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.current.stderr similarity index 78% rename from tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.stderr rename to tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.current.stderr index 03f2b1c532a00..fb67f6ffa44e4 100644 --- a/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.stderr +++ b/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.current.stderr @@ -1,5 +1,5 @@ error: opaque type used twice with different lifetimes - --> $DIR/multiple-def-uses-in-one-fn-lifetimes.rs:7:5 + --> $DIR/multiple-def-uses-in-one-fn-lifetimes.rs:10:5 | LL | (i, j) | ^^^^^^ @@ -8,7 +8,7 @@ LL | (i, j) | lifetime `'y` previously used here | note: if all non-lifetime generic parameters are the same, but the lifetime parameters differ, it is not possible to differentiate the opaque types - --> $DIR/multiple-def-uses-in-one-fn-lifetimes.rs:7:5 + --> $DIR/multiple-def-uses-in-one-fn-lifetimes.rs:10:5 | LL | (i, j) | ^^^^^^ diff --git a/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.rs b/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.rs index 39e4912ae3af6..f1c0bc190108b 100644 --- a/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.rs +++ b/tests/ui/type-alias-impl-trait/multiple-def-uses-in-one-fn-lifetimes.rs @@ -1,3 +1,6 @@ +//@ revisions: current next +//@[next] compile-flags: -Znext-solver=globally +//@[next] check-pass #![feature(type_alias_impl_trait)] type Foo<'a, 'b> = impl std::fmt::Debug; @@ -5,7 +8,7 @@ type Foo<'a, 'b> = impl std::fmt::Debug; #[define_opaque(Foo)] fn foo<'x, 'y>(i: &'x i32, j: &'y i32) -> (Foo<'x, 'y>, Foo<'y, 'x>) { (i, j) - //~^ ERROR opaque type used twice with different lifetimes + //[current]~^ ERROR opaque type used twice with different lifetimes } fn main() {} From db6e110a114382fd2f1912354272c2f51d01ddca Mon Sep 17 00:00:00 2001 From: Shunpoco Date: Sun, 19 Oct 2025 10:03:06 +0100 Subject: [PATCH 4/7] visit_nested_body and visit_fn for LateLint should return earlier if typeck is tainted We started to check typeck result's tainted_by_errors in check_pat for LateLint, But ideally the check should be in a better place which all lints profit from it. --- compiler/rustc_lint/src/builtin.rs | 5 +---- compiler/rustc_lint/src/late.rs | 21 +++++++++++++++++-- tests/crashes/138361.rs | 6 ------ ...ng-constant-evaluation-cause-dead-code.rs} | 0 ...onstant-evaluation-cause-dead-code.stderr} | 4 ++-- ...ng-constant-evaluation-cause-ice-in-sty.rs | 5 +++++ ...onstant-evaluation-cause-ice-in-sty.stderr | 17 +++++++++++++++ 7 files changed, 44 insertions(+), 14 deletions(-) delete mode 100644 tests/crashes/138361.rs rename tests/ui/consts/{do-not-ice-long-constant-evaluation-in-for-loop.rs => long-constant-evaluation-cause-dead-code.rs} (100%) rename tests/ui/consts/{do-not-ice-long-constant-evaluation-in-for-loop.stderr => long-constant-evaluation-cause-dead-code.stderr} (78%) create mode 100644 tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.rs create mode 100644 tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.stderr diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 2d571d8b8e13d..d4f213833aa48 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -148,10 +148,7 @@ declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]); impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns { fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { - // The result shouldn't be tainted, otherwise it will cause ICE. - if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind - && cx.typeck_results().tainted_by_errors.is_none() - { + if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind { let variant = cx .typeck_results() .pat_ty(pat) diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index ac185c65aa127..93ce4118c0ab7 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -99,7 +99,15 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas // The body and typeck results are also set in `visit_fn`. // Only fetch the results if this is for a new body. if old_enclosing_body != Some(body_id) && !self.actually_rustdoc { - self.context.typeck_results = Some(self.context.tcx.typeck_body(body_id)); + let typeck_results = self.context.tcx.typeck_body(body_id); + + // To avoid ICE, skip visiting the body if the typeck_results is tainted. + if typeck_results.tainted_by_errors.is_some() { + self.context.enclosing_body = old_enclosing_body; + self.context.typeck_results = old_typeck_results; + return; + } + self.context.typeck_results = Some(typeck_results); } let body = self.context.tcx.hir_body(body_id); @@ -189,7 +197,16 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas let old_enclosing_body = self.context.enclosing_body.replace(body_id); let old_typeck_results = self.context.typeck_results; if !self.actually_rustdoc { - self.context.typeck_results = Some(self.context.tcx.typeck_body(body_id)); + let typeck_results = self.context.tcx.typeck_body(body_id); + + // To avoid ICE, skip visiting the body if the typeck_results is tainted. + if typeck_results.tainted_by_errors.is_some() { + self.context.enclosing_body = old_enclosing_body; + self.context.typeck_results = old_typeck_results; + return; + } + + self.context.typeck_results = Some(typeck_results); } let body = self.context.tcx.hir_body(body_id); lint_callback!(self, check_fn, fk, decl, body, span, id); diff --git a/tests/crashes/138361.rs b/tests/crashes/138361.rs deleted file mode 100644 index 8661ed374744a..0000000000000 --- a/tests/crashes/138361.rs +++ /dev/null @@ -1,6 +0,0 @@ -//@ known-bug: #138361 - -fn main() { - [0; loop{}]; - std::mem::transmute(4) -} diff --git a/tests/ui/consts/do-not-ice-long-constant-evaluation-in-for-loop.rs b/tests/ui/consts/long-constant-evaluation-cause-dead-code.rs similarity index 100% rename from tests/ui/consts/do-not-ice-long-constant-evaluation-in-for-loop.rs rename to tests/ui/consts/long-constant-evaluation-cause-dead-code.rs diff --git a/tests/ui/consts/do-not-ice-long-constant-evaluation-in-for-loop.stderr b/tests/ui/consts/long-constant-evaluation-cause-dead-code.stderr similarity index 78% rename from tests/ui/consts/do-not-ice-long-constant-evaluation-in-for-loop.stderr rename to tests/ui/consts/long-constant-evaluation-cause-dead-code.stderr index c28f37bc16705..08160f8c78166 100644 --- a/tests/ui/consts/do-not-ice-long-constant-evaluation-in-for-loop.stderr +++ b/tests/ui/consts/long-constant-evaluation-cause-dead-code.stderr @@ -1,5 +1,5 @@ error: constant evaluation is taking a long time - --> $DIR/do-not-ice-long-constant-evaluation-in-for-loop.rs:10:14 + --> $DIR/long-constant-evaluation-cause-dead-code.rs:10:14 | LL | [(); loop {}]; | ^^^^^^^ @@ -7,7 +7,7 @@ LL | [(); loop {}]; = note: this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. If your compilation actually takes a long time, you can safely allow the lint help: the constant being evaluated - --> $DIR/do-not-ice-long-constant-evaluation-in-for-loop.rs:10:14 + --> $DIR/long-constant-evaluation-cause-dead-code.rs:10:14 | LL | [(); loop {}]; | ^^^^^^^ diff --git a/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.rs b/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.rs new file mode 100644 index 0000000000000..04d0f7ee2de84 --- /dev/null +++ b/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.rs @@ -0,0 +1,5 @@ +// The test confirms ICE-138361 is fixed. +fn main() { + [0; loop{}]; //~ ERROR constant evaluation is taking a long time + std::mem::transmute(4) +} diff --git a/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.stderr b/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.stderr new file mode 100644 index 0000000000000..58c62d61adaab --- /dev/null +++ b/tests/ui/consts/long-constant-evaluation-cause-ice-in-sty.stderr @@ -0,0 +1,17 @@ +error: constant evaluation is taking a long time + --> $DIR/long-constant-evaluation-cause-ice-in-sty.rs:3:7 + | +LL | [0; loop{}]; + | ^^^^^^ + | + = note: this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint +help: the constant being evaluated + --> $DIR/long-constant-evaluation-cause-ice-in-sty.rs:3:7 + | +LL | [0; loop{}]; + | ^^^^^^ + = note: `#[deny(long_running_const_eval)]` on by default + +error: aborting due to 1 previous error + From 28b5293debd90e0ad9b8ccb937c03e499cfc2170 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 21 Sep 2026 17:16:37 +0200 Subject: [PATCH 5/7] add field_projections fixme --- .../src/traits/select/candidate_assembly.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index 8509be2fb6dec..120f83c14b5c7 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -1494,6 +1494,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { obligation: &PolyTraitObligation<'tcx>, candidates: &mut SelectionCandidateSet<'tcx>, ) { + // FIXME(field_projections): We should not use `evaluate_obligation` in + // the trait solver. Doing so means we don't track overflow and cycles properly + // encountering query cycles instead. if let ty::Adt(def, args) = obligation.predicate.self_ty().skip_binder().kind() && let Some(FieldInfo { base, ty, .. }) = def.field_representing_type_info(self.tcx(), args) From 16ede108f08f85dbbac01dc4cc3318f092d1893f Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:42:42 +0200 Subject: [PATCH 6/7] library: prune allowed lints --- library/alloc/src/alloc.rs | 1 - library/alloc/src/lib.rs | 1 - library/alloctests/lib.rs | 1 - library/core/src/alloc/mod.rs | 2 +- library/core/src/lib.rs | 1 - library/core/src/num/int_macros.rs | 3 --- library/core/src/panic/panic_info.rs | 2 +- library/core/src/str/mod.rs | 1 - library/std/src/alloc.rs | 1 - library/std/src/env.rs | 2 +- library/std/src/lib.rs | 31 +++++++++++++--------------- library/std/src/num/f32.rs | 2 +- library/std/src/num/f64.rs | 2 +- 13 files changed, 19 insertions(+), 31 deletions(-) diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs index 0846063cbc175..f921bc292568d 100644 --- a/library/alloc/src/alloc.rs +++ b/library/alloc/src/alloc.rs @@ -663,7 +663,6 @@ pub const fn handle_alloc_error(layout: Layout) -> ! { #[cfg(not(no_global_oom_handling))] #[doc(hidden)] -#[allow(unused_attributes)] #[unstable(feature = "alloc_internals", issue = "none")] pub mod __alloc_error_handler { // called via generated `__rust_alloc_error_handler` if there is no diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index acdc22ac5b331..664e3a4b1e218 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -58,7 +58,6 @@ #![allow(unused_features)] #![allow(incomplete_features)] -#![allow(unused_attributes)] #![expect(clippy::partialeq_ne_impl, reason = "we need to implement ne for a lot of alloc types")] #![stable(feature = "alloc", since = "1.36.0")] #![doc( diff --git a/library/alloctests/lib.rs b/library/alloctests/lib.rs index 3c26ecf9875e2..50c58092bd41a 100644 --- a/library/alloctests/lib.rs +++ b/library/alloctests/lib.rs @@ -1,5 +1,4 @@ #![cfg(test)] -#![allow(unused_attributes)] #![unstable(feature = "alloctests", issue = "none")] #![no_std] // Lints: diff --git a/library/core/src/alloc/mod.rs b/library/core/src/alloc/mod.rs index 6069c468f96e4..9acd3dd12a64c 100644 --- a/library/core/src/alloc/mod.rs +++ b/library/core/src/alloc/mod.rs @@ -15,7 +15,7 @@ pub use self::layout::Layout; note = "Name does not follow std convention, use LayoutError", suggestion = "LayoutError" )] -#[allow(deprecated, deprecated_in_future)] +#[allow(deprecated)] pub use self::layout::LayoutErr; #[stable(feature = "alloc_layout_error", since = "1.50.0")] pub use self::layout::LayoutError; diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index c215560122022..d7d8d32ed88e7 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -367,7 +367,6 @@ pub mod primitive; unused_imports, unsafe_op_in_unsafe_fn, ambiguous_glob_reexports, - deprecated_in_future, unreachable_pub, // FIXME: stdach is a submodule so clippy lints should be fixed (and ideally enforced) there clippy::all, diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index bb37a734824fa..4cbd012e0021b 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2485,7 +2485,6 @@ macro_rules! int_impl { #[rustc_const_stable(feature = "const_int_methods", since = "1.32.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[allow(unused_attributes)] #[inline] pub const fn wrapping_abs(self) -> Self { if self.is_negative() { @@ -2978,7 +2977,6 @@ macro_rules! int_impl { #[rustc_const_stable(feature = "const_int_methods", since = "1.32.0")] #[must_use = "this returns the result of the operation, \ without modifying the original"] - #[allow(unused_attributes)] pub const fn overflowing_neg(self) -> (Self, bool) { if intrinsics::unlikely(self == Self::MIN) { (Self::MIN, true) @@ -3630,7 +3628,6 @@ macro_rules! int_impl { /// ``` #[stable(feature = "rust1", since = "1.0.0")] #[rustc_const_stable(feature = "const_int_methods", since = "1.32.0")] - #[allow(unused_attributes)] #[must_use = "this returns the result of the operation, \ without modifying the original"] #[inline] diff --git a/library/core/src/panic/panic_info.rs b/library/core/src/panic/panic_info.rs index ee22521cb014c..a5448b39ad7c0 100644 --- a/library/core/src/panic/panic_info.rs +++ b/library/core/src/panic/panic_info.rs @@ -106,7 +106,7 @@ impl<'a> PanicInfo<'a> { /// [`std::panic::PanicHookInfo::payload`]: ../../std/panic/struct.PanicHookInfo.html#method.payload #[deprecated(since = "1.81.0", note = "this never returns anything useful")] #[stable(feature = "panic_hooks", since = "1.10.0")] - #[allow(deprecated, deprecated_in_future)] + #[allow(deprecated)] pub fn payload(&self) -> &(dyn crate::any::Any + Send) { struct NoPayload; &NoPayload diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index cc96e51d83393..99eb935cb2715 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -510,7 +510,6 @@ impl str { #[rustc_const_stable(feature = "str_as_bytes", since = "1.39.0")] #[must_use] #[inline(always)] - #[allow(unused_attributes)] pub const fn as_bytes(&self) -> &[u8] { // SAFETY: const sound because we transmute two types with the same layout unsafe { mem::transmute(self) } diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index 558ab0f2fc66d..8e0143c137b5e 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -444,7 +444,6 @@ pub fn rust_oom(layout: Layout) -> ! { #[cfg(not(test))] #[doc(hidden)] -#[allow(unused_attributes)] #[unstable(feature = "alloc_internals", issue = "none")] pub mod __default_lib_allocator { use super::Layout; diff --git a/library/std/src/env.rs b/library/std/src/env.rs index 762ad071e967c..07645debe82be 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -592,7 +592,7 @@ impl fmt::Display for JoinPathsError { #[stable(feature = "env", since = "1.0.0")] impl Error for JoinPathsError { - #[allow(deprecated, deprecated_in_future)] + #[allow(deprecated)] fn description(&self) -> &str { self.inner.description() } diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 3f853df52bc53..acc8cdfc8281d 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -244,7 +244,6 @@ #![warn(unreachable_pub)] #![warn(missing_debug_implementations)] #![allow(explicit_outlives_requirements)] -#![allow(unused_lifetimes)] #![allow(internal_features)] #![deny(implicit_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] @@ -539,24 +538,24 @@ pub use core::future; #[stable(feature = "core_hint", since = "1.27.0")] pub use core::hint; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::i8; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::i16; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::i32; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::i64; #[stable(feature = "i128", since = "1.26.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::i128; #[stable(feature = "rust1", since = "1.0.0")] pub use core::intrinsics; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::isize; #[stable(feature = "rust1", since = "1.0.0")] pub use core::iter; @@ -577,24 +576,24 @@ pub use core::range; #[stable(feature = "rust1", since = "1.0.0")] pub use core::result; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::u8; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::u16; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::u32; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::u64; #[stable(feature = "i128", since = "1.26.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::u128; #[unstable(feature = "unsafe_binders", issue = "130516")] pub use core::unsafe_binder; #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::usize; #[stable(feature = "rust1", since = "1.0.0")] @@ -740,7 +739,7 @@ pub mod alloc; mod panicking; #[path = "../../backtrace/src/lib.rs"] -#[allow(dead_code, unused_attributes, implicit_provenance_casts, unsafe_op_in_unsafe_fn)] +#[allow(implicit_provenance_casts, unsafe_op_in_unsafe_fn)] #[allow(clippy::len_zero, clippy::needless_borrow, clippy::filter_map_next)] // FIXME mod backtrace_rs; @@ -760,12 +759,10 @@ pub use core::concat_bytes; #[unstable(feature = "derive_macro_global_path", issue = "154645")] pub use core::derive; #[stable(feature = "matches_macro", since = "1.42.0")] -#[allow(deprecated, deprecated_in_future)] pub use core::matches; #[stable(feature = "core_primitive", since = "1.43.0")] pub use core::primitive; #[stable(feature = "todo_macro", since = "1.40.0")] -#[allow(deprecated, deprecated_in_future)] pub use core::todo; // Re-export built-in macros defined through core. #[stable(feature = "builtin_macro_prelude", since = "1.38.0")] @@ -776,7 +773,7 @@ pub use core::{ }; // Re-export macros defined in core. #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future)] +#[expect(deprecated, reason = "`try! macro")] pub use core::{ assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, r#try, unimplemented, unreachable, write, writeln, diff --git a/library/std/src/num/f32.rs b/library/std/src/num/f32.rs index b89c71dd1abe1..5792d236b4e89 100644 --- a/library/std/src/num/f32.rs +++ b/library/std/src/num/f32.rs @@ -13,7 +13,7 @@ #![allow(missing_docs)] #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::f32::{ DIGITS, EPSILON, INFINITY, MANTISSA_DIGITS, MAX, MAX_10_EXP, MAX_EXP, MIN, MIN_10_EXP, MIN_EXP, MIN_POSITIVE, NAN, NEG_INFINITY, RADIX, consts, diff --git a/library/std/src/num/f64.rs b/library/std/src/num/f64.rs index c3f5d8cca6014..8ca38cc0be69e 100644 --- a/library/std/src/num/f64.rs +++ b/library/std/src/num/f64.rs @@ -13,7 +13,7 @@ #![allow(missing_docs)] #[stable(feature = "rust1", since = "1.0.0")] -#[allow(deprecated, deprecated_in_future, clippy::legacy_numeric_constants)] +#[allow(deprecated, clippy::legacy_numeric_constants)] pub use core::f64::{ DIGITS, EPSILON, INFINITY, MANTISSA_DIGITS, MAX, MAX_10_EXP, MAX_EXP, MIN, MIN_10_EXP, MIN_EXP, MIN_POSITIVE, NAN, NEG_INFINITY, RADIX, consts, From 9fecc5aefe59d203ed2111b3cdf2c3f26138347b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 22 Sep 2026 08:52:22 +1000 Subject: [PATCH 7/7] Clean up diagnostic hashing `DiagInner` impls `PartialEq` and `Hash`, as you'd expect for storing it in a hash table. But there's a couple of strange things. - We only store the hash value of the `DiagInner` to do deduplication, not the `DiagInner` itself, which means the `PartialEq` impl is unused. - The `Hash` impl only considers some of the fields. Some of the ignored fields are clearly deliberate (there are comments) but for some it is unclear if it is deliberate. This commit: - Removes the unused `PartialEq` impl. - Inlines and removes `keys` now that it's not needed for `PartialEq`. - Uses struct deconstruction to ensure no fields can be accidentally ignored. I have preserved existing behaviour by assuming that all the ignored fields are supposed to be ignored. - Renames `hash` as an inherent method `dedup_hash` to indicate that it's not a typical hash function, and simplifies it to just return `Hash128` instead of being generic. - Replaces the unnecessary `collect` on `args` with `as_slice`. - Improves the comment on `emitted_diagnostics`. --- compiler/rustc_errors/src/diagnostic.rs | 71 ++++++++++--------------- compiler/rustc_errors/src/lib.rs | 13 ++--- 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs index 9a12d6d53b6e6..729569fd5f5d1 100644 --- a/compiler/rustc_errors/src/diagnostic.rs +++ b/compiler/rustc_errors/src/diagnostic.rs @@ -1,13 +1,15 @@ use std::borrow::Cow; use std::fmt::{self, Debug}; -use std::hash::{Hash, Hasher}; +use std::hash::Hash; use std::ops::{Deref, DerefMut}; use std::panic; use std::path::PathBuf; use std::thread::panicking; use rustc_ast::attr::version::RustcVersion; -use rustc_error_messages::{DiagArgMap, DiagArgName, DiagArgValue, IntoDiagArg}; +use rustc_data_structures::stable_hash::StableHasher; +use rustc_error_messages::{DiagArgMap, DiagArgName, IntoDiagArg}; +use rustc_hashes::Hash128; use rustc_lint_defs::{Applicability, LintExpectationId}; use rustc_macros::{Decodable, Encodable}; use rustc_span::{DUMMY_SP, Span, Spanned, Symbol}; @@ -305,46 +307,31 @@ impl DiagInner { } } - /// Fields used for Hash, and PartialEq trait. - fn keys( - &self, - ) -> ( - &Level, - &[(DiagMessage, Style)], - &Option, - &MultiSpan, - &[Subdiag], - &Suggestions, - Vec<(&DiagArgName, &DiagArgValue)>, - &Option, - ) { - ( - &self.level, - &self.messages, - &self.code, - &self.span, - &self.children, - &self.suggestions, - self.args.iter().collect(), - // omit self.sort_span - &self.is_lint, - // omit self.emitted_at - ) - } -} - -impl Hash for DiagInner { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.keys().hash(state); - } -} - -impl PartialEq for DiagInner { - fn eq(&self, other: &Self) -> bool { - self.keys() == other.keys() + /// Hash used to determine if two diagnostics are the same. Used by + /// `DiagCtxtInner::emitted_diagnostics`. Some fields are ignored for the hash. + pub(crate) fn dedup_hash(&self) -> Hash128 { + // Deconstruct to ensure all fields are considered. + let DiagInner { + level, + messages, + code, + lint_id: _, // ignore + span, + children, + suggestions, + args, + sort_span: _, // ignore + is_lint, + long_ty_path: _, // ignore + emitted_at: _, // ignore + } = self; + + let hashed_parts = + (level, messages, code, span, children, suggestions, args.as_slice(), is_lint); + + let mut hasher = StableHasher::new(); + hashed_parts.hash(&mut hasher); + hasher.finish() } } diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 30791a51f7644..68e10802e21c5 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -325,8 +325,10 @@ struct DiagCtxtInner { emitted_diagnostic_codes: FxIndexSet, /// This set contains a hash of every diagnostic that has been emitted by - /// this `DiagCtxt`. These hashes is used to avoid emitting the same error - /// twice. + /// this `DiagCtxt`. These hashes are used to avoid emitting the same error + /// twice. (Because we don't store the diagnostics themselves, two + /// different diagnostics with the same hash value will be considered + /// equivalent. Such collisions should be vanishingly rare...) emitted_diagnostics: FxHashSet, /// We only want to emit `recursion_depth_exceeding_limit` once per @@ -1301,12 +1303,7 @@ impl DiagCtxtInner { self.emitted_diagnostic_codes.insert(code); } - let already_emitted = { - let mut hasher = StableHasher::new(); - diagnostic.hash(&mut hasher); - let diagnostic_hash = hasher.finish(); - !self.emitted_diagnostics.insert(diagnostic_hash) - }; + let already_emitted = !self.emitted_diagnostics.insert(diagnostic.dedup_hash()); let is_error = diagnostic.is_error(); let is_lint = diagnostic.is_lint.is_some();