|
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<'tcx>(cx: &LateContext<'tcx>, format_args: &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,111 @@ 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 | 86 | {
|
87 | 87 | let body = cx.tcx.hir_body(*body_id);
|
88 |
| - |
89 |
| - // Collect variable usages |
90 |
| - let mut visitor = UnitVariableCollector::new(binding_hir_id); |
| 88 | + let mut visitor = UnitVariableCollector::new(cx, format_args, binding_hir_id); |
91 | 89 | walk_body(&mut visitor, body);
|
92 | 90 |
|
93 |
| - // Add suggestions for replacing variable usages |
94 |
| - suggestions.extend(visitor.spans.into_iter().map(|span| (span, "()".to_string()))); |
95 |
| - } |
| 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 | + })); |
96 | 99 |
|
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); |
| 100 | + if has_in_format_capture { |
| 101 | + suggestions.push(( |
| 102 | + init.span, |
| 103 | + format!("();\n{}", reindent_multiline(&snip, false, indent_of(cx, local.span))), |
| 104 | + )); |
| 105 | + diag.multipart_suggestion( |
| 106 | + "replace variable usages with `()`", |
| 107 | + suggestions, |
| 108 | + Applicability::MachineApplicable, |
| 109 | + ); |
| 110 | + return; |
| 111 | + } |
105 | 112 | }
|
| 113 | + |
| 114 | + suggestions.push((local.span, format!("{snip};"))); |
| 115 | + let message = if suggestions.len() == 1 { |
| 116 | + "omit the `let` binding" |
| 117 | + } else { |
| 118 | + "omit the `let` binding and replace variable usages with `()`" |
| 119 | + }; |
| 120 | + diag.multipart_suggestion(message, suggestions, Applicability::MachineApplicable); |
106 | 121 | },
|
107 | 122 | );
|
108 | 123 | }
|
109 | 124 | }
|
110 | 125 | }
|
111 | 126 |
|
112 |
| -struct UnitVariableCollector { |
| 127 | +struct UnitVariableCollector<'a, 'tcx> { |
| 128 | + cx: &'a LateContext<'tcx>, |
| 129 | + format_args: &'a FormatArgsStorage, |
113 | 130 | id: HirId,
|
114 |
| - spans: Vec<rustc_span::Span>, |
| 131 | + spans: Vec<MaybeInFormatCapture>, |
| 132 | + macro_call: Option<&'a FormatArgs>, |
115 | 133 | }
|
116 | 134 |
|
117 |
| -impl UnitVariableCollector { |
118 |
| - fn new(id: HirId) -> Self { |
119 |
| - Self { id, spans: vec![] } |
| 135 | +enum MaybeInFormatCapture { |
| 136 | + Yes, |
| 137 | + No(Span), |
| 138 | +} |
| 139 | + |
| 140 | +impl<'a, 'tcx> UnitVariableCollector<'a, 'tcx> { |
| 141 | + fn new(cx: &'a LateContext<'tcx>, format_args: &'a FormatArgsStorage, id: HirId) -> Self { |
| 142 | + Self { |
| 143 | + cx, |
| 144 | + format_args, |
| 145 | + id, |
| 146 | + spans: Vec::new(), |
| 147 | + macro_call: None, |
| 148 | + } |
120 | 149 | }
|
121 | 150 | }
|
122 | 151 |
|
123 | 152 | /**
|
124 | 153 | * Collect all instances where a variable is used based on its `HirId`.
|
125 | 154 | */
|
126 |
| -impl<'tcx> Visitor<'tcx> for UnitVariableCollector { |
| 155 | +impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> { |
127 | 156 | fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result {
|
| 157 | + if let Some(macro_call) = root_macro_call_first_node(self.cx, ex) |
| 158 | + && is_format_macro(self.cx, macro_call.def_id) |
| 159 | + && let Some(format_args) = self.format_args.get(self.cx, ex, macro_call.expn) |
| 160 | + { |
| 161 | + let parent_macro_call = self.macro_call; |
| 162 | + self.macro_call = Some(format_args); |
| 163 | + walk_expr(self, ex); |
| 164 | + self.macro_call = parent_macro_call; |
| 165 | + return; |
| 166 | + } |
| 167 | + |
128 | 168 | if let ExprKind::Path(QPath::Resolved(None, path)) = ex.kind
|
129 | 169 | && let Res::Local(id) = path.res
|
130 | 170 | && id == self.id
|
131 | 171 | {
|
132 |
| - self.spans.push(path.span); |
| 172 | + if let Some(macro_call) = self.macro_call |
| 173 | + && macro_call.arguments.all_args().iter().any(|arg| { |
| 174 | + matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some() |
| 175 | + }) |
| 176 | + { |
| 177 | + self.spans.push(MaybeInFormatCapture::Yes); |
| 178 | + } else { |
| 179 | + self.spans.push(MaybeInFormatCapture::No(path.span)); |
| 180 | + } |
133 | 181 | }
|
134 |
| - rustc_hir::intravisit::walk_expr(self, ex); |
| 182 | + |
| 183 | + walk_expr(self, ex); |
135 | 184 | }
|
136 | 185 | }
|
137 | 186 |
|
|
0 commit comments