about summary refs log tree commit diff
path: root/compiler/rustc_passes/src
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-09-04 10:01:54 +1000
committerGitHub <noreply@github.com>2025-09-04 10:01:54 +1000
commit3a6ae1167f56516fc0de1d304fcb9163cea0cb40 (patch)
tree8146219299de27bbdc1b03225d2458645082f880 /compiler/rustc_passes/src
parentd71a9b6bcf9aae816291c560432b728f7d7f3494 (diff)
parent86085b4f65fae0ebc456202584e7e04b58a871f3 (diff)
downloadrust-3a6ae1167f56516fc0de1d304fcb9163cea0cb40.tar.gz
rust-3a6ae1167f56516fc0de1d304fcb9163cea0cb40.zip
Rollup merge of #145827 - estebank:issue-51976, r=jackh726
On unused binding or binding not present in all patterns, suggest potential typo of unit struct/variant or const

When encountering an or-pattern with a binding not available in all patterns, look for consts and unit struct/variants that have similar names as the binding to detect typos.

```
error[E0408]: variable `Ban` is not bound in all patterns
  --> $DIR/binding-typo.rs:22:9
   |
LL |         (Foo, _) | (Ban, Foo) => {}
   |         ^^^^^^^^    --- variable not in all patterns
   |         |
   |         pattern doesn't bind `Ban`
   |
help: you might have meant to use the similarly named unit variant `Bar`
   |
LL -         (Foo, _) | (Ban, Foo) => {}
LL +         (Foo, _) | (Bar, Foo) => {}
   |
```

For items that are not in the immedate scope, suggest the full path for them:

```
error[E0408]: variable `Non` is not bound in all patterns
  --> $DIR/binding-typo-2.rs:51:16
   |
LL |         (Non | Some(_))=> {}
   |          ---   ^^^^^^^ pattern doesn't bind `Non`
   |          |
   |          variable not in all patterns
   |
help: you might have meant to use the similarly named unit variant `None`
   |
LL -         (Non | Some(_))=> {}
LL +         (core::option::Option::None | Some(_))=> {}
   |
```

When encountering a typo in a pattern that gets interpreted as an unused binding, look for unit struct/variant of the same type as the binding:

```
error: unused variable: `Non`
  --> $DIR/binding-typo-2.rs:36:9
   |
LL |         Non => {}
   |         ^^^
   |
help: if this is intentional, prefix it with an underscore
   |
LL |         _Non => {}
   |         +
help: you might have meant to pattern match on the similarly named variant `None`
   |
LL -         Non => {}
LL +         std::prelude::v1::None => {}
   |
```

 Suggest constant on unused binding in a pattern

```
error: unused variable: `Batery`
  --> $DIR/binding-typo-2.rs:110:9
   |
LL |         Batery => {}
   |         ^^^^^^
   |
help: if this is intentional, prefix it with an underscore
   |
LL |         _Batery => {}
   |         +
help: you might have meant to pattern match on the similarly named constant `Battery`
   |
LL |         Battery => {}
   |            +
```

Fix rust-lang/rust#51976.
Diffstat (limited to 'compiler/rustc_passes/src')
-rw-r--r--compiler/rustc_passes/src/errors.rs18
-rw-r--r--compiler/rustc_passes/src/liveness.rs50
2 files changed, 67 insertions, 1 deletions
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 680e2a26d84..23dcabef1a1 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1367,6 +1367,22 @@ pub(crate) struct UnusedVarRemoveFieldSugg {
 #[note]
 pub(crate) struct UnusedVarAssignedOnly {
     pub name: String,
+    #[subdiagnostic]
+    pub typo: Option<PatternTypo>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    passes_unused_var_typo,
+    style = "verbose",
+    applicability = "machine-applicable"
+)]
+pub(crate) struct PatternTypo {
+    #[suggestion_part(code = "{code}")]
+    pub span: Span,
+    pub code: String,
+    pub item_name: String,
+    pub kind: String,
 }
 
 #[derive(LintDiagnostic)]
@@ -1434,6 +1450,8 @@ pub(crate) struct UnusedVariableTryPrefix {
     #[subdiagnostic]
     pub sugg: UnusedVariableSugg,
     pub name: String,
+    #[subdiagnostic]
+    pub typo: Option<PatternTypo>,
 }
 
 #[derive(Subdiagnostic)]
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index ae16a51bc69..1b2ffb5b3db 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -95,8 +95,10 @@ use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet, find_attr};
 use rustc_index::IndexVec;
 use rustc_middle::query::Providers;
 use rustc_middle::span_bug;
+use rustc_middle::ty::print::with_no_trimmed_paths;
 use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt};
 use rustc_session::lint;
+use rustc_span::edit_distance::find_best_match_for_name;
 use rustc_span::{BytePos, Span, Symbol};
 use tracing::{debug, instrument};
 
@@ -1688,6 +1690,51 @@ impl<'tcx> Liveness<'_, 'tcx> {
             let is_assigned =
                 if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) };
 
+            let mut typo = None;
+            for (hir_id, _, span) in &hir_ids_and_spans {
+                let ty = self.typeck_results.node_type(*hir_id);
+                if let ty::Adt(adt, _) = ty.peel_refs().kind() {
+                    let name = Symbol::intern(&name);
+                    let adt_def = self.ir.tcx.adt_def(adt.did());
+                    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, _)))
+                        })
+                    {
+                        typo = Some(errors::PatternTypo {
+                            span: *span,
+                            code: with_no_trimmed_paths!(self.ir.tcx.def_path_str(variant.def_id)),
+                            kind: self.ir.tcx.def_descr(variant.def_id).to_string(),
+                            item_name: variant.name.to_string(),
+                        });
+                    }
+                }
+            }
+            if typo.is_none() {
+                for (hir_id, _, span) in &hir_ids_and_spans {
+                    let ty = self.typeck_results.node_type(*hir_id);
+                    // Look for consts of the same type with similar names as well, not just unit
+                    // structs and variants.
+                    for def_id in self.ir.tcx.hir_body_owners() {
+                        if let DefKind::Const = self.ir.tcx.def_kind(def_id)
+                            && self.ir.tcx.type_of(def_id).instantiate_identity() == ty
+                        {
+                            typo = Some(errors::PatternTypo {
+                                span: *span,
+                                code: with_no_trimmed_paths!(self.ir.tcx.def_path_str(def_id)),
+                                kind: "constant".to_string(),
+                                item_name: self.ir.tcx.item_name(def_id).to_string(),
+                            });
+                        }
+                    }
+                }
+            }
             if is_assigned {
                 self.ir.tcx.emit_node_span_lint(
                     lint::builtin::UNUSED_VARIABLES,
@@ -1696,7 +1743,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
                         .into_iter()
                         .map(|(_, _, ident_span)| ident_span)
                         .collect::<Vec<_>>(),
-                    errors::UnusedVarAssignedOnly { name },
+                    errors::UnusedVarAssignedOnly { name, typo },
                 )
             } else if can_remove {
                 let spans = hir_ids_and_spans
@@ -1788,6 +1835,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
                             name,
                             sugg,
                             string_interp: suggestions,
+                            typo,
                         },
                     );
                 }