|
1 | 1 | use clippy_utils::diagnostics::span_lint_and_then;
|
2 | 2 | use clippy_utils::higher::VecArgs;
|
3 | 3 | use clippy_utils::last_path_segment;
|
4 |
| -use clippy_utils::macros::{root_macro_call_first_node, MacroCall}; |
| 4 | +use clippy_utils::macros::root_macro_call_first_node; |
| 5 | +use clippy_utils::source::{indent_of, snippet}; |
| 6 | +use rustc_errors::{Applicability, Diagnostic}; |
5 | 7 | use rustc_hir::{Expr, ExprKind, QPath, TyKind};
|
6 | 8 | use rustc_lint::{LateContext, LateLintPass};
|
7 | 9 | use rustc_session::{declare_lint_pass, declare_tool_lint};
|
8 |
| -use rustc_span::{sym, Symbol}; |
| 10 | +use rustc_span::{sym, Span, Symbol}; |
9 | 11 |
|
10 | 12 | declare_clippy_lint! {
|
11 | 13 | /// ### What it does
|
@@ -47,27 +49,107 @@ declare_lint_pass!(RcCloneInVecInit => [RC_CLONE_IN_VEC_INIT]);
|
47 | 49 | impl LateLintPass<'_> for RcCloneInVecInit {
|
48 | 50 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
49 | 51 | let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return; };
|
50 |
| - let Some(VecArgs::Repeat(elem, _)) = VecArgs::hir(cx, expr) else { return; }; |
| 52 | + let Some(VecArgs::Repeat(elem, len)) = VecArgs::hir(cx, expr) else { return; }; |
51 | 53 | let Some(symbol) = new_reference_call(cx, elem) else { return; };
|
| 54 | + let lint_span = macro_call.span; |
| 55 | + let symbol_name = symbol.as_str(); |
| 56 | + let len_snippet = snippet(cx, len.span, ".."); |
| 57 | + let elem_snippet = elem_snippet(cx, elem, symbol_name); |
| 58 | + let indentation = indent_of(cx, lint_span).unwrap_or(0); |
| 59 | + let lint_suggestions = |
| 60 | + construct_lint_suggestions(lint_span, symbol_name, &elem_snippet, len_snippet.as_ref(), indentation); |
52 | 61 |
|
53 |
| - emit_lint(cx, symbol, ¯o_call); |
| 62 | + emit_lint(cx, symbol, lint_span, &lint_suggestions); |
54 | 63 | }
|
55 | 64 | }
|
56 | 65 |
|
57 |
| -fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, macro_call: &MacroCall) { |
| 66 | +struct LintSuggestion { |
| 67 | + span: Span, |
| 68 | + message: String, |
| 69 | + suggestion: String, |
| 70 | + applicability: Applicability, |
| 71 | +} |
| 72 | + |
| 73 | +impl LintSuggestion { |
| 74 | + fn span_suggestion(&self, diag: &mut Diagnostic) { |
| 75 | + diag.span_suggestion(self.span, &self.message, &self.suggestion, self.applicability); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +fn construct_lint_suggestions( |
| 80 | + span: Span, |
| 81 | + symbol_name: &str, |
| 82 | + elem_snippet: &str, |
| 83 | + len_snippet: &str, |
| 84 | + indentation: usize, |
| 85 | +) -> Vec<LintSuggestion> { |
| 86 | + let indentation = " ".repeat(indentation); |
| 87 | + let loop_init_suggestion = loop_init_suggestion(elem_snippet, len_snippet, &indentation); |
| 88 | + let extract_suggestion = extract_suggestion(elem_snippet, len_snippet, &indentation); |
| 89 | + |
| 90 | + vec![ |
| 91 | + LintSuggestion { |
| 92 | + span, |
| 93 | + message: format!("consider initializing each `{symbol_name}` element individually"), |
| 94 | + suggestion: loop_init_suggestion, |
| 95 | + applicability: Applicability::Unspecified, |
| 96 | + }, |
| 97 | + LintSuggestion { |
| 98 | + span, |
| 99 | + message: format!( |
| 100 | + "or if this is intentional, consider extracting the `{symbol_name}` initialization to a variable" |
| 101 | + ), |
| 102 | + suggestion: extract_suggestion, |
| 103 | + applicability: Applicability::Unspecified, |
| 104 | + }, |
| 105 | + ] |
| 106 | +} |
| 107 | + |
| 108 | +fn elem_snippet(cx: &LateContext<'_>, elem: &Expr<'_>, symbol_name: &str) -> String { |
| 109 | + let mut elem_snippet = snippet(cx, elem.span, "..").to_string(); |
| 110 | + if elem_snippet.contains('\n') { |
| 111 | + let reference_initialization = format!("{symbol_name}::new"); |
| 112 | + // This string must be found in `elem_snippet`, otherwise we won't be constructing the snippet in |
| 113 | + // the first place. |
| 114 | + let reference_initialization_end = |
| 115 | + elem_snippet.find(&reference_initialization).unwrap() + reference_initialization.len(); |
| 116 | + elem_snippet.replace_range(reference_initialization_end.., ".."); |
| 117 | + } |
| 118 | + elem_snippet |
| 119 | +} |
| 120 | + |
| 121 | +fn loop_init_suggestion(elem: &str, len: &str, indent: &str) -> String { |
| 122 | + format!( |
| 123 | + r#"{{ |
| 124 | +{indent}{indent}let mut v = Vec::with_capacity({len}); |
| 125 | +{indent}{indent}(0..{len}).for_each(|_| v.push({elem})); |
| 126 | +{indent}{indent}v |
| 127 | +{indent}}}"# |
| 128 | + ) |
| 129 | +} |
| 130 | + |
| 131 | +fn extract_suggestion(elem: &str, len: &str, indent: &str) -> String { |
| 132 | + format!( |
| 133 | + "{{ |
| 134 | +{indent}{indent}let data = {elem}; |
| 135 | +{indent}{indent}vec![data; {len}] |
| 136 | +{indent}}}" |
| 137 | + ) |
| 138 | +} |
| 139 | + |
| 140 | +fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, lint_suggestions: &[LintSuggestion]) { |
58 | 141 | let symbol_name = symbol.as_str();
|
59 | 142 |
|
60 | 143 | span_lint_and_then(
|
61 | 144 | cx,
|
62 | 145 | RC_CLONE_IN_VEC_INIT,
|
63 |
| - macro_call.span, |
| 146 | + lint_span, |
64 | 147 | &format!("calling `{symbol_name}::new` in `vec![elem; len]`"),
|
65 | 148 | |diag| {
|
66 | 149 | diag.note(format!("each element will point to the same `{symbol_name}` instance"));
|
67 |
| - diag.help(format!( |
68 |
| - "if this is intentional, consider extracting the `{symbol_name}` initialization to a variable" |
69 |
| - )); |
70 |
| - diag.help("or if not, initialize each element individually"); |
| 150 | + lint_suggestions |
| 151 | + .iter() |
| 152 | + .for_each(|suggestion| suggestion.span_suggestion(diag)); |
71 | 153 | },
|
72 | 154 | );
|
73 | 155 | }
|
|
0 commit comments