Change:
At some point between (works) and (does not work) the following stopped compiling: I have not bisected for the exact commit that broke this. I have only tried to reproduce this error using . It's possible that this is also broken for other lints. PR:
Without properly double-checking this, I'm certain that this regressed in (merged April 14) which condensed multiple diagnostics triggered by methods into a single one for readability meddling with the affairs of . I don't know how is implemented but if it only considers the first primary span of a (buffered) lint diagnostic, then one fix will be changing the relevant part of the code to consider all primary spans. label T-compiler A-lint F-lintreasons

Discussion:
<del> // This implements the dead-code warning pass. It follows middle::reachable // closely. The idea is that all reachable symbols are live, codes called // from live codes are live, and everything else is dead. </del> <ins> // This implements the dead-code warning pass. // All reachable symbols are live, code called from live code is live, code with certain lint // expectations such as `#[expect(unused)]` and `#[expect(dead_code)]` is live, and everything else // is dead. </ins> use hir::def_id::{LocalDefIdMap, LocalDefIdSet}; use itertools::Itertools;

(symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits) } <del> struct DeadVariant { </del> <ins> struct DeadItem { </ins> def_id: LocalDefId, name: Symbol, level: lint::Level,

ShouldWarnAboutField::Yes(is_positional) } <del> fn warn_multiple_dead_codes( </del> <ins> // # Panics // All `dead_codes` must have the same lint level, otherwise we will intentionally ICE. // This is because we emit a multi-spanned lint using the lint level of the `dead_codes`'s // first local def id. // Prefer calling `Self.warn_dead_code` or `Self.warn_dead_code_grouped_by_lint_level` // since those methods group by lint level before calling this method. fn lint_at_single_level( </ins> &self, dead_codes: &[LocalDefId], participle: &str,

return; }; let tcx = self.tcx; <ins> let first_hir_id = tcx.hir().local_def_id_to_hir_id(first_id); let first_lint_level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, first_hir_id).0; assert!(dead_codes.iter().skip(1).all(|id| { let hir_id = tcx.hir().local_def_id_to_hir_id(*id); let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; level == first_lint_level })); </ins> let names: Vec<_> = dead_codes.iter().map(|&def_id| tcx.item_name(def_id.to_def_id())).collect(); let spans: Vec<_> = dead_codes

} }; <del> self.tcx.emit_spanned_lint( lint, tcx.hir().local_def_id_to_hir_id(first_id), MultiSpan::from_spans(spans), diag, ); </del> <ins> self.tcx.emit_spanned_lint(lint, first_hir_id, MultiSpan::from_spans(spans), diag); </ins> } <del> fn warn_dead_fields_and_variants( </del> <ins> fn warn_multiple( </ins> &self, def_id: LocalDefId, participle: &str, <del> dead_codes: Vec<DeadVariant>, </del> <ins> dead_codes: Vec<DeadItem>, </ins> is_positional: bool, ) { let mut dead_codes = dead_codes .iter() .filter(|v| !v.name.as_str().starts_with('_')) <del> .collect::<Vec<&DeadVariant>>(); </del> <ins> .collect::<Vec<&DeadItem>>(); </ins> if dead_codes.is_empty() { return; } dead_codes.sort_by_key(|v| v.level); for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) { <del> self.warn_multiple_dead_codes( </del> <ins> self.lint_at_single_level( </ins> &group.map(|v| v.def_id).collect::<Vec<_>>(), participle, Some(def_id),

} fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) { <del> self.warn_multiple_dead_codes(&[id], participle, None, false); </del> <ins> self.lint_at_single_level(&[id], participle, None, false); </ins> } fn check_definition(&mut self, def_id: LocalDefId) {

if let hir::ItemKind::Impl(impl_item) = tcx.hir().item(item).kind { let mut dead_items = Vec::new(); for item in impl_item.items { <del> let did = item.id.owner_id.def_id; if !visitor.is_live_code(did) { dead_items.push(did) </del> <ins> let def_id = item.id.owner_id.def_id; if !visitor.is_live_code(def_id) { let name = tcx.item_name(def_id.to_def_id()); let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; dead_items.push(DeadItem { def_id, name, level }) </ins> } } <del> visitor.warn_multiple_dead_codes( &dead_items, "used", Some(item.owner_id.def_id), false, ); </del> <ins> visitor.warn_multiple(item.owner_id.def_id, "used", dead_items, false); </ins> } if !live_symbols.contains(&item.owner_id.def_id) {

// Record to group diagnostics. let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; <del> dead_variants.push(DeadVariant { def_id, name: variant.name, level }); </del> <ins> dead_variants.push(DeadItem { def_id, name: variant.name, level }); </ins> continue; }

hir_id, ) .0; <del> Some(DeadVariant { def_id, name: field.name, level }) </del> <ins> Some(DeadItem { def_id, name: field.name, level }) </ins> } else { None } }) .collect(); <del> visitor.warn_dead_fields_and_variants(def_id, "read", dead_fields, is_positional) </del> <ins> visitor.warn_multiple(def_id, "read", dead_fields, is_positional); </ins> } <del> visitor.warn_dead_fields_and_variants( item.owner_id.def_id, "constructed", dead_variants, false, ); </del> <ins> visitor.warn_multiple(item.owner_id.def_id, "constructed", dead_variants, false); </ins> } }

<ins> // check-pass // incremental #![feature(lint_reasons)] #![warn(unused)] struct OneUnused; struct TwoUnused; impl OneUnused { #[expect(unused)] fn unused() {} } impl TwoUnused { #[expect(unused)] fn unused1(){} // This unused method has `#[expect(unused)]`, so the compiler should not emit a warning. // This ui test was added after a regression in the compiler where it did not recognize multiple // `#[expect(unused)]` annotations inside of impl blocks. // issue 114416 #[expect(unused)] fn unused2(){} } fn main() { let _ = OneUnused; let _ = TwoUnused; } </ins>