|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then;
|
2 |
| -use clippy_utils::source::snippet_with_context; |
| 2 | +use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; |
| 3 | +use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_context}; |
3 | 4 | use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
|
4 | 5 | use core::ops::ControlFlow;
|
| 6 | +use rustc_ast::{FormatArgs, FormatArgumentKind}; |
5 | 7 | use rustc_errors::Applicability;
|
6 | 8 | use rustc_hir::def::{DefKind, Res};
|
7 |
| -use rustc_hir::intravisit::{Visitor, walk_body}; |
| 9 | +use rustc_hir::intravisit::{Visitor, walk_body, walk_expr}; |
8 | 10 | use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, LetStmt, MatchSource, Node, PatKind, QPath, TyKind};
|
9 | 11 | use rustc_lint::{LateContext, LintContext};
|
10 | 12 | use rustc_middle::ty;
|
| 13 | +use rustc_span::Span; |
11 | 14 |
|
12 | 15 | use super::LET_UNIT_VALUE;
|
13 | 16 |
|
14 |
| -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { |
| 17 | +pub(super) fn check<'a, 'tcx>(cx: &LateContext<'tcx>, format_args: &'a FormatArgsStorage, local: &'tcx LetStmt<'_>) { |
15 | 18 | // skip `let () = { ... }`
|
16 | 19 | if let PatKind::Tuple(fields, ..) = local.pat.kind
|
17 | 20 | && fields.is_empty()
|
@@ -73,65 +76,112 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) {
|
73 | 76 | let mut suggestions = Vec::new();
|
74 | 77 |
|
75 | 78 | // Suggest omitting the `let` binding
|
76 |
| - if let Some(expr) = &local.init { |
77 |
| - let mut app = Applicability::MachineApplicable; |
78 |
| - let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0; |
79 |
| - suggestions.push((local.span, format!("{snip};"))); |
80 |
| - } |
| 79 | + let mut app = Applicability::MachineApplicable; |
| 80 | + let snip = snippet_with_context(cx, init.span, local.span.ctxt(), "()", &mut app).0; |
81 | 81 |
|
82 | 82 | // If this is a binding pattern, we need to add suggestions to remove any usages
|
83 | 83 | // of the variable
|
84 | 84 | if let PatKind::Binding(_, binding_hir_id, ..) = local.pat.kind
|
85 | 85 | && let Some(body_id) = cx.enclosing_body.as_ref()
|
86 |
| - { |
87 |
| - let body = cx.tcx.hir_body(*body_id); |
| 86 | + && let body = cx.tcx.hir_body(*body_id) |
| 87 | + && let mut visitor = UnitVariableCollector::new(cx, format_args, binding_hir_id) |
| 88 | + && { |
| 89 | + walk_body(&mut visitor, body); |
88 | 90 |
|
89 |
| - // Collect variable usages |
90 |
| - let mut visitor = UnitVariableCollector::new(binding_hir_id); |
91 |
| - walk_body(&mut visitor, body); |
| 91 | + let mut has_in_format_capture = false; |
| 92 | + suggestions.extend(visitor.spans.iter().filter_map(|span| match span { |
| 93 | + MaybeInFormatCapture::Yes => { |
| 94 | + has_in_format_capture = true; |
| 95 | + None |
| 96 | + }, |
| 97 | + MaybeInFormatCapture::No(span) => Some((*span, "()".to_string())), |
| 98 | + })); |
92 | 99 |
|
93 |
| - // Add suggestions for replacing variable usages |
94 |
| - suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string()))); |
| 100 | + has_in_format_capture |
| 101 | + } |
| 102 | + { |
| 103 | + suggestions.push(( |
| 104 | + init.span, |
| 105 | + format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), |
| 106 | + )); |
| 107 | + diag.multipart_suggestion( |
| 108 | + "replace variable usages with `()`", |
| 109 | + suggestions, |
| 110 | + Applicability::MachineApplicable, |
| 111 | + ); |
| 112 | + return; |
95 | 113 | }
|
96 | 114 |
|
97 |
| - // Emit appropriate diagnostic based on whether there are usages of the let binding |
98 |
| - if !suggestions.is_empty() { |
99 |
| - let message = if suggestions.len() == 1 { |
100 |
| - "omit the `let` binding" |
101 |
| - } else { |
102 |
| - "omit the `let` binding and replace variable usages with `()`" |
103 |
| - }; |
104 |
| - diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable); |
105 |
| - } |
| 115 | + suggestions.push((local.span, format!("{snip};"))); |
| 116 | + let message = if suggestions.len() == 1 { |
| 117 | + "omit the `let` binding" |
| 118 | + } else { |
| 119 | + "omit the `let` binding and replace variable usages with `()`" |
| 120 | + }; |
| 121 | + diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable); |
106 | 122 | },
|
107 | 123 | );
|
108 | 124 | }
|
109 | 125 | }
|
110 | 126 | }
|
111 | 127 |
|
112 |
| -struct UnitVariableCollector { |
| 128 | +struct UnitVariableCollector<'a, 'tcx> { |
| 129 | + cx: &'a LateContext<'tcx>, |
| 130 | + format_args: &'a FormatArgsStorage, |
113 | 131 | id: HirId,
|
114 |
| - spans: Vec<rustc_span::Span>, |
| 132 | + spans: Vec<MaybeInFormatCapture>, |
| 133 | + macro_call: Option<&'a FormatArgs>, |
| 134 | +} |
| 135 | + |
| 136 | +enum MaybeInFormatCapture { |
| 137 | + Yes, |
| 138 | + No(Span), |
115 | 139 | }
|
116 | 140 |
|
117 |
| -impl UnitVariableCollector { |
118 |
| - fn new(id: HirId) -> Self { |
119 |
| - Self { id, spans: vec![] } |
| 141 | +impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> { |
| 142 | + fn new(cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId) -> Self { |
| 143 | + Self { |
| 144 | + cx, |
| 145 | + format_args, |
| 146 | + id, |
| 147 | + spans: Vec::new(), |
| 148 | + macro_call: None, |
| 149 | + } |
120 | 150 | }
|
121 | 151 | }
|
122 | 152 |
|
123 | 153 | /**
|
124 | 154 | * Collect all instances where a variable is used based on its `HirId`.
|
125 | 155 | */
|
126 |
| -impl<'tcx> Visitor<'tcx> for UnitVariableCollector { |
| 156 | +impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> { |
127 | 157 | fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
|
| 158 | + if let Some(macro_call) = root_macro_call_first_node(self.cx, ex) |
| 159 | + && is_format_macro(self.cx, macro_call.def_id) |
| 160 | + && let Some(format_args) = self.format_args.get(self.cx, ex, macro_call.expn) |
| 161 | + { |
| 162 | + let parent_macro_call = self.macro_call; |
| 163 | + self.macro_call = Some(format_args); |
| 164 | + walk_expr(self, ex); |
| 165 | + self.macro_call = parent_macro_call; |
| 166 | + return; |
| 167 | + } |
| 168 | + |
128 | 169 | if let ExprKind::Path(QPath::Resolved(None, path)) = ex.kind
|
129 | 170 | && let Res::Local(id) = path.res
|
130 | 171 | && id == self.id
|
131 | 172 | {
|
132 |
| - self.spans.push(path.span); |
| 173 | + if let Some(macro_call) = self.macro_call |
| 174 | + && macro_call.arguments.all_args().iter().any(|arg| { |
| 175 | + matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some() |
| 176 | + }) |
| 177 | + { |
| 178 | + self.spans.push(MaybeInFormatCapture::Yes); |
| 179 | + } else { |
| 180 | + self.spans.push(MaybeInFormatCapture::No(path.span)); |
| 181 | + } |
133 | 182 | }
|
134 |
| - rustc_hir::intravisit::walk_expr(self, ex); |
| 183 | + |
| 184 | + walk_expr(self, ex); |
135 | 185 | }
|
136 | 186 | }
|
137 | 187 |
|
|
0 commit comments