|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | 2 | use clippy_utils::source::snippet_with_context; |
3 | | -use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; |
| 3 | +use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source, is_local_used}; |
4 | 4 | use core::ops::ControlFlow; |
5 | 5 | use rustc_errors::Applicability; |
6 | 6 | use rustc_hir::def::{DefKind, Res}; |
| 7 | +use rustc_hir::intravisit::{walk_body, Visitor}; |
7 | 8 | use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind}; |
8 | 9 | use rustc_lint::{LateContext, LintContext}; |
9 | 10 | use rustc_middle::lint::{in_external_macro, is_from_async_await}; |
10 | 11 | use rustc_middle::ty; |
11 | 12 |
|
12 | 13 | use super::LET_UNIT_VALUE; |
13 | 14 |
|
14 | | -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { |
| 15 | +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { |
15 | 16 | // skip `let () = { ... }` |
16 | 17 | if let PatKind::Tuple(fields, ..) = local.pat.kind |
17 | 18 | && fields.is_empty() |
@@ -75,12 +76,53 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { |
75 | 76 | let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0; |
76 | 77 | diag.span_suggestion(local.span, "omit the `let` binding", format!("{snip};"), app); |
77 | 78 | } |
| 79 | + |
| 80 | + if let PatKind::Binding(_, binding_hir_id, ident, ..) = local.pat.kind |
| 81 | + && let Some(body_id) = cx.enclosing_body.as_ref() |
| 82 | + && let body = cx.tcx.hir().body(*body_id) |
| 83 | + && is_local_used(cx, body, binding_hir_id) |
| 84 | + { |
| 85 | + let identifier = ident.as_str(); |
| 86 | + let mut visitor = UnitVariableCollector::new(binding_hir_id); |
| 87 | + walk_body(&mut visitor, body); |
| 88 | + visitor.spans.into_iter().for_each(|span| { |
| 89 | + let msg = |
| 90 | + format!("variable `{identifier}` of type `()` can be replaced with explicit `()`"); |
| 91 | + diag.span_suggestion(span, msg, "()", Applicability::MachineApplicable); |
| 92 | + }); |
| 93 | + } |
78 | 94 | }, |
79 | 95 | ); |
80 | 96 | } |
81 | 97 | } |
82 | 98 | } |
83 | 99 |
|
| 100 | +struct UnitVariableCollector { |
| 101 | + id: HirId, |
| 102 | + spans: Vec<rustc_span::Span>, |
| 103 | +} |
| 104 | + |
| 105 | +impl UnitVariableCollector { |
| 106 | + fn new(id: HirId) -> Self { |
| 107 | + Self { id, spans: vec![] } |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +/** |
| 112 | + * Collect all instances where a variable is used based on its `HirId`. |
| 113 | + */ |
| 114 | +impl<'tcx> Visitor<'tcx> for UnitVariableCollector { |
| 115 | + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { |
| 116 | + if let ExprKind::Path(QPath::Resolved(None, path)) = ex.kind |
| 117 | + && let Res::Local(id) = path.res |
| 118 | + && id == self.id |
| 119 | + { |
| 120 | + self.spans.push(path.span); |
| 121 | + } |
| 122 | + rustc_hir::intravisit::walk_expr(self, ex); |
| 123 | + } |
| 124 | +} |
| 125 | + |
84 | 126 | /// Checks sub-expressions which create the value returned by the given expression for whether |
85 | 127 | /// return value inference is needed. This checks through locals to see if they also need inference |
86 | 128 | /// at this point. |
|
0 commit comments