Skip to content

Commit f3e01c4

Browse files
committed
add suggestions to rc_clone_in_vec_init
1 parent d422baa commit f3e01c4

File tree

3 files changed

+152
-18
lines changed

3 files changed

+152
-18
lines changed

clippy_lints/src/rc_clone_in_vec_init.rs

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::higher::VecArgs;
33
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};
57
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
68
use rustc_lint::{LateContext, LateLintPass};
79
use rustc_session::{declare_lint_pass, declare_tool_lint};
8-
use rustc_span::{sym, Symbol};
10+
use rustc_span::{sym, Span, Symbol};
911

1012
declare_clippy_lint! {
1113
/// ### What it does
@@ -47,27 +49,107 @@ declare_lint_pass!(RcCloneInVecInit => [RC_CLONE_IN_VEC_INIT]);
4749
impl LateLintPass<'_> for RcCloneInVecInit {
4850
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
4951
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; };
5153
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);
5261

53-
emit_lint(cx, symbol, &macro_call);
62+
emit_lint(cx, symbol, lint_span, &lint_suggestions);
5463
}
5564
}
5665

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]) {
58141
let symbol_name = symbol.as_str();
59142

60143
span_lint_and_then(
61144
cx,
62145
RC_CLONE_IN_VEC_INIT,
63-
macro_call.span,
146+
lint_span,
64147
&format!("calling `{symbol_name}::new` in `vec![elem; len]`"),
65148
|diag| {
66149
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));
71153
},
72154
);
73155
}

tests/ui/rc_clone_in_vec_init/arc.stderr

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@ LL | let v = vec![Arc::new("x".to_string()); 2];
66
|
77
= note: `-D clippy::rc-clone-in-vec-init` implied by `-D warnings`
88
= note: each element will point to the same `Arc` instance
9-
= help: if this is intentional, consider extracting the `Arc` initialization to a variable
10-
= help: or if not, initialize each element individually
9+
help: consider initializing each `Arc` element individually
10+
|
11+
LL ~ let v = {
12+
LL + let mut v = Vec::with_capacity(2);
13+
LL + (0..2).for_each(|_| v.push(Arc::new("x".to_string())));
14+
LL + v
15+
LL ~ };
16+
|
17+
help: or if this is intentional, consider extracting the `Arc` initialization to a variable
18+
|
19+
LL ~ let v = {
20+
LL + let data = Arc::new("x".to_string());
21+
LL + vec![data; 2]
22+
LL ~ };
23+
|
1124

1225
error: calling `Arc::new` in `vec![elem; len]`
1326
--> $DIR/arc.rs:11:13
@@ -23,8 +36,21 @@ LL | | ];
2336
| |_____^
2437
|
2538
= note: each element will point to the same `Arc` instance
26-
= help: if this is intentional, consider extracting the `Arc` initialization to a variable
27-
= help: or if not, initialize each element individually
39+
help: consider initializing each `Arc` element individually
40+
|
41+
LL ~ let v = {
42+
LL + let mut v = Vec::with_capacity(2);
43+
LL + (0..2).for_each(|_| v.push(std::sync::Arc::new..));
44+
LL + v
45+
LL ~ };
46+
|
47+
help: or if this is intentional, consider extracting the `Arc` initialization to a variable
48+
|
49+
LL ~ let v = {
50+
LL + let data = std::sync::Arc::new..;
51+
LL + vec![data; 2]
52+
LL ~ };
53+
|
2854

2955
error: aborting due to 2 previous errors
3056

tests/ui/rc_clone_in_vec_init/rc.stderr

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@ LL | let v = vec![Rc::new("x".to_string()); 2];
66
|
77
= note: `-D clippy::rc-clone-in-vec-init` implied by `-D warnings`
88
= note: each element will point to the same `Rc` instance
9-
= help: if this is intentional, consider extracting the `Rc` initialization to a variable
10-
= help: or if not, initialize each element individually
9+
help: consider initializing each `Rc` element individually
10+
|
11+
LL ~ let v = {
12+
LL + let mut v = Vec::with_capacity(2);
13+
LL + (0..2).for_each(|_| v.push(Rc::new("x".to_string())));
14+
LL + v
15+
LL ~ };
16+
|
17+
help: or if this is intentional, consider extracting the `Rc` initialization to a variable
18+
|
19+
LL ~ let v = {
20+
LL + let data = Rc::new("x".to_string());
21+
LL + vec![data; 2]
22+
LL ~ };
23+
|
1124

1225
error: calling `Rc::new` in `vec![elem; len]`
1326
--> $DIR/rc.rs:12:13
@@ -23,8 +36,21 @@ LL | | ];
2336
| |_____^
2437
|
2538
= note: each element will point to the same `Rc` instance
26-
= help: if this is intentional, consider extracting the `Rc` initialization to a variable
27-
= help: or if not, initialize each element individually
39+
help: consider initializing each `Rc` element individually
40+
|
41+
LL ~ let v = {
42+
LL + let mut v = Vec::with_capacity(2);
43+
LL + (0..2).for_each(|_| v.push(std::rc::Rc::new..));
44+
LL + v
45+
LL ~ };
46+
|
47+
help: or if this is intentional, consider extracting the `Rc` initialization to a variable
48+
|
49+
LL ~ let v = {
50+
LL + let data = std::rc::Rc::new..;
51+
LL + vec![data; 2]
52+
LL ~ };
53+
|
2854

2955
error: aborting due to 2 previous errors
3056

0 commit comments

Comments
 (0)