From c653430714e28dea3b39118fd9f05ab8d2bc33ab Mon Sep 17 00:00:00 2001 From: Camille Gillot Date: Sun, 7 Sep 2025 02:17:13 +0000 Subject: [PATCH] Suggest unit struct and constants. --- compiler/rustc_mir_transform/messages.ftl | 2 + compiler/rustc_mir_transform/src/errors.rs | 18 +++ compiler/rustc_mir_transform/src/liveness.rs | 88 ++++++++++++- .../lint-uppercase-variables.stderr | 21 +++- tests/ui/or-patterns/binding-typo-2.rs | 16 +-- tests/ui/or-patterns/binding-typo-2.stderr | 118 ++++++++++++++++-- 6 files changed, 237 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index e8d29c414edc..71ec2db1ef00 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -104,3 +104,5 @@ mir_transform_unused_variable = unused variable: `{$name}` mir_transform_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable mir_transform_unused_variable_try_ignore = try ignoring the field + +mir_transform_unused_variable_typo = you might have meant to pattern match on the similarly named {$kind} `{$item_name}` diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 87430b8225b6..a039851681a6 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -170,6 +170,8 @@ pub(crate) struct UnusedCaptureMaybeCaptureRef { #[note] pub(crate) struct UnusedVarAssignedOnly { pub name: Symbol, + #[subdiagnostic] + pub typo: Option, } #[derive(LintDiagnostic)] @@ -235,6 +237,8 @@ pub(crate) enum UnusedVariableSugg { #[suggestion_part(code = "_{name}")] spans: Vec, name: Symbol, + #[subdiagnostic] + typo: Option, }, #[help(mir_transform_unused_variable_args_in_macro)] @@ -266,6 +270,20 @@ impl Subdiagnostic for UnusedVariableStringInterp { } } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + mir_transform_unused_variable_typo, + style = "verbose", + applicability = "maybe-incorrect" +)] +pub(crate) struct PatternTypo { + #[suggestion_part(code = "{code}")] + pub span: Span, + pub code: String, + pub item_name: Symbol, + pub kind: &'static str, +} + pub(crate) struct MustNotSupend<'a, 'tcx> { pub tcx: TyCtxt<'tcx>, pub yield_sp: Span, diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs index 83b50e7be61b..573c794f2838 100644 --- a/compiler/rustc_mir_transform/src/liveness.rs +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -1,8 +1,8 @@ use rustc_abi::FieldIdx; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; use rustc_hir::attrs::AttributeKind; -use rustc_hir::def::DefKind; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def::{CtorKind, DefKind}; +use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::find_attr; use rustc_index::IndexVec; use rustc_index::bit_set::DenseBitSet; @@ -11,11 +11,13 @@ use rustc_middle::mir::visit::{ MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor, }; use rustc_middle::mir::*; +use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_mir_dataflow::fmt::DebugWithContext; use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor}; use rustc_session::lint; use rustc_span::Span; +use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::symbol::{Symbol, kw, sym}; use crate::errors; @@ -201,6 +203,62 @@ fn maybe_suggest_literal_matching_name( finder.found } +/// Give a diagnostic when an unused variable may be a typo of a unit variant or a struct. +fn maybe_suggest_unit_pattern_typo<'tcx>( + tcx: TyCtxt<'tcx>, + body_def_id: DefId, + name: Symbol, + span: Span, + ty: Ty<'tcx>, +) -> Option { + if let ty::Adt(adt_def, _) = ty.peel_refs().kind() { + let variant_names: Vec<_> = adt_def + .variants() + .iter() + .filter(|v| matches!(v.ctor, Some((CtorKind::Const, _)))) + .map(|v| v.name) + .collect(); + if let Some(name) = find_best_match_for_name(&variant_names, name, None) + && let Some(variant) = adt_def + .variants() + .iter() + .find(|v| v.name == name && matches!(v.ctor, Some((CtorKind::Const, _)))) + { + return Some(errors::PatternTypo { + span, + code: with_no_trimmed_paths!(tcx.def_path_str(variant.def_id)), + kind: tcx.def_descr(variant.def_id), + item_name: variant.name, + }); + } + } + + // Look for consts of the same type with similar names as well, + // not just unit structs and variants. + let constants = tcx + .hir_body_owners() + .filter(|&def_id| { + matches!(tcx.def_kind(def_id), DefKind::Const) + && tcx.type_of(def_id).instantiate_identity() == ty + && tcx.visibility(def_id).is_accessible_from(body_def_id, tcx) + }) + .collect::>(); + let names = constants.iter().map(|&def_id| tcx.item_name(def_id)).collect::>(); + if let Some(item_name) = find_best_match_for_name(&names, name, None) + && let Some(position) = names.iter().position(|&n| n == item_name) + && let Some(&def_id) = constants.get(position) + { + return Some(errors::PatternTypo { + span, + code: with_no_trimmed_paths!(tcx.def_path_str(def_id)), + kind: "constant", + item_name, + }); + } + + None +} + /// Return whether we should consider the current place as a drop guard and skip reporting. fn maybe_drop_guard<'tcx>( tcx: TyCtxt<'tcx>, @@ -855,12 +913,27 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { let from_macro = def_span.from_expansion() && introductions.iter().any(|intro| intro.span.eq_ctxt(def_span)); + let maybe_suggest_typo = || { + if let LocalKind::Arg = self.body.local_kind(local) { + None + } else { + maybe_suggest_unit_pattern_typo( + tcx, + self.body.source.def_id(), + name, + def_span, + decl.ty, + ) + } + }; + let statements = &mut self.assignments[index]; if statements.is_empty() { let sugg = if from_macro { errors::UnusedVariableSugg::NoSugg { span: def_span, name } } else { - errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name } + let typo = maybe_suggest_typo(); + errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name, typo } }; tcx.emit_node_span_lint( lint::builtin::UNUSED_VARIABLES, @@ -909,11 +982,12 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { continue; } + let typo = maybe_suggest_typo(); tcx.emit_node_span_lint( lint::builtin::UNUSED_VARIABLES, hir_id, def_span, - errors::UnusedVarAssignedOnly { name }, + errors::UnusedVarAssignedOnly { name, typo }, ); continue; } @@ -944,9 +1018,11 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> { } else if from_macro { errors::UnusedVariableSugg::NoSugg { span: def_span, name } } else if !introductions.is_empty() { - errors::UnusedVariableSugg::TryPrefix { name, spans: spans.clone() } + let typo = maybe_suggest_typo(); + errors::UnusedVariableSugg::TryPrefix { name, typo, spans: spans.clone() } } else { - errors::UnusedVariableSugg::TryPrefix { name, spans: vec![def_span] } + let typo = maybe_suggest_typo(); + errors::UnusedVariableSugg::TryPrefix { name, typo, spans: vec![def_span] } }; tcx.emit_node_span_lint( diff --git a/tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr b/tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr index b0c56003957c..09f76f86a4b1 100644 --- a/tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr +++ b/tests/ui/lint/non-snake-case/lint-uppercase-variables.stderr @@ -16,7 +16,7 @@ warning: unused variable: `Foo` --> $DIR/lint-uppercase-variables.rs:22:9 | LL | Foo => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Foo` + | ^^^ | note: the lint level is defined here --> $DIR/lint-uppercase-variables.rs:1:9 @@ -24,12 +24,29 @@ note: the lint level is defined here LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` +help: you might have meant to pattern match on the similarly named variant `Foo` + | +LL | foo::Foo::Foo => {} + | ++++++++++ +help: if this is intentional, prefix it with an underscore + | +LL | _Foo => {} + | + warning: unused variable: `Foo` --> $DIR/lint-uppercase-variables.rs:28:9 | LL | let Foo = foo::Foo::Foo; - | ^^^ help: if this is intentional, prefix it with an underscore: `_Foo` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `Foo` + | +LL | let foo::Foo::Foo = foo::Foo::Foo; + | ++++++++++ +help: if this is intentional, prefix it with an underscore + | +LL | let _Foo = foo::Foo::Foo; + | + error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo` --> $DIR/lint-uppercase-variables.rs:33:17 diff --git a/tests/ui/or-patterns/binding-typo-2.rs b/tests/ui/or-patterns/binding-typo-2.rs index 275b68a22e22..59df4459b88e 100644 --- a/tests/ui/or-patterns/binding-typo-2.rs +++ b/tests/ui/or-patterns/binding-typo-2.rs @@ -16,8 +16,8 @@ fn foo(x: (Lol, Lol)) { //~| HELP: you might have meant to use the similarly named previously used binding `Bar` //~| NOTE: pattern doesn't bind `Ban` //~| NOTE: variable not in all patterns - //~| ERROR: variable `Ban` is assigned to, but never used - //~| NOTE: consider using `_Ban` instead + //~| ERROR: unused variable: `Ban` + //~| HELP: if this is intentional, prefix it with an underscore //~| HELP: you might have meant to pattern match on the similarly named _ => {} } @@ -27,8 +27,8 @@ fn foo(x: (Lol, Lol)) { //~| HELP: you might have meant to use the similarly named unit variant `Bar` //~| NOTE: pattern doesn't bind `Ban` //~| NOTE: variable not in all patterns - //~| ERROR: variable `Ban` is assigned to, but never used - //~| NOTE: consider using `_Ban` instead + //~| ERROR: unused variable: `Ban` + //~| HELP: if this is intentional, prefix it with an underscore //~| HELP: you might have meant to pattern match on the similarly named _ => {} } @@ -73,8 +73,8 @@ fn bar(x: (Lol, Lol)) { //~| HELP: you might have meant to use the similarly named constant `Bat` //~| NOTE: pattern doesn't bind `Ban` //~| NOTE: variable not in all patterns - //~| ERROR: variable `Ban` is assigned to, but never used - //~| NOTE: consider using `_Ban` instead + //~| ERROR: unused variable: `Ban` + //~| HELP: if this is intentional, prefix it with an underscore //~| HELP: you might have meant to pattern match on the similarly named _ => {} } @@ -89,8 +89,8 @@ fn baz(x: (Lol, Lol)) { //~| HELP: you might have meant to use the similarly named constant `Bat` //~| NOTE: pattern doesn't bind `Ban` //~| NOTE: variable not in all patterns - //~| ERROR: variable `Ban` is assigned to, but never used - //~| NOTE: consider using `_Ban` instead + //~| ERROR: unused variable: `Ban` + //~| HELP: if this is intentional, prefix it with an underscore //~| HELP: you might have meant to pattern match on the similarly named _ => {} } diff --git a/tests/ui/or-patterns/binding-typo-2.stderr b/tests/ui/or-patterns/binding-typo-2.stderr index 1935d538edc6..23b570b3c23c 100644 --- a/tests/ui/or-patterns/binding-typo-2.stderr +++ b/tests/ui/or-patterns/binding-typo-2.stderr @@ -101,67 +101,165 @@ error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:14:23 | LL | (Foo, Bar) | (Ban, Foo) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ | note: the lint level is defined here --> $DIR/binding-typo-2.rs:2:9 | LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ +help: you might have meant to pattern match on the similarly named variant `Bar` + | +LL - (Foo, Bar) | (Ban, Foo) => {} +LL + (Foo, Bar) | (Lol::Bar, Foo) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | (Foo, Bar) | (_Ban, Foo) => {} + | + error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:25:21 | LL | (Foo, _) | (Ban, Foo) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `Bar` + | +LL - (Foo, _) | (Ban, Foo) => {} +LL + (Foo, _) | (Lol::Bar, Foo) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | (Foo, _) | (_Ban, Foo) => {} + | + error: unused variable: `Non` --> $DIR/binding-typo-2.rs:37:9 | LL | Non => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Non` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `None` + | +LL - Non => {} +LL + std::prelude::v1::None => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | _Non => {} + | + error: unused variable: `Non` --> $DIR/binding-typo-2.rs:44:9 | LL | Non | None => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Non` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `None` + | +LL - Non | None => {} +LL + std::prelude::v1::None | None => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | _Non | None => {} + | + error: unused variable: `Non` --> $DIR/binding-typo-2.rs:54:9 | LL | Non | Some(_) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Non` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `None` + | +LL - Non | Some(_) => {} +LL + std::prelude::v1::None | Some(_) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | _Non | Some(_) => {} + | + error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:69:21 | LL | (Foo, _) | (Ban, Foo) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `Bar` + | +LL - (Foo, _) | (Ban, Foo) => {} +LL + (Foo, _) | (Lol::Bar, Foo) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | (Foo, _) | (_Ban, Foo) => {} + | + error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:86:21 | LL | (Foo, _) | (Ban, Foo) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `Bar` + | +LL - (Foo, _) | (Ban, Foo) => {} +LL + (Foo, _) | (Lol::Bar, Foo) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | (Foo, _) | (_Ban, Foo) => {} + | + error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:98:10 | LL | (Ban, _) => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ + | +help: you might have meant to pattern match on the similarly named variant `Bar` + | +LL - (Ban, _) => {} +LL + (Lol::Bar, _) => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | (_Ban, _) => {} + | + error: unused variable: `Ban` --> $DIR/binding-typo-2.rs:104:9 | LL | Ban => {} - | ^^^ help: if this is intentional, prefix it with an underscore: `_Ban` + | ^^^ + | +help: you might have meant to pattern match on the similarly named struct `Bay` + | +LL - Ban => {} +LL + Bay => {} + | +help: if this is intentional, prefix it with an underscore + | +LL | _Ban => {} + | + error: unused variable: `Batery` --> $DIR/binding-typo-2.rs:110:9 | LL | Batery => {} - | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_Batery` + | ^^^^^^ + | +help: you might have meant to pattern match on the similarly named constant `Battery` + | +LL | Battery => {} + | + +help: if this is intentional, prefix it with an underscore + | +LL | _Batery => {} + | + error: aborting due to 16 previous errors