]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/rc_clone_in_vec_init.rs
Auto merge of #8799 - Alexendoo:lintcheck-common, r=giraffate
[rust.git] / clippy_lints / src / rc_clone_in_vec_init.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::higher::VecArgs;
3 use clippy_utils::last_path_segment;
4 use clippy_utils::macros::{root_macro_call_first_node, MacroCall};
5 use rustc_hir::{Expr, ExprKind, QPath, TyKind};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8 use rustc_span::{sym, Symbol};
9
10 declare_clippy_lint! {
11     /// ### What it does
12     /// Checks for `Arc::new` or `Rc::new` in `vec![elem; len]`
13     ///
14     /// ### Why is this bad?
15     /// This will create `elem` once and clone it `len` times - doing so with `Arc` or `Rc`
16     /// is a bit misleading, as it will create references to the same pointer, rather
17     /// than different instances.
18     ///
19     /// ### Example
20     /// ```rust
21     /// let v = vec![std::sync::Arc::new("some data".to_string()); 100];
22     /// // or
23     /// let v = vec![std::rc::Rc::new("some data".to_string()); 100];
24     /// ```
25     /// Use instead:
26     /// ```rust
27     ///
28     /// // Initialize each value separately:
29     /// let mut data = Vec::with_capacity(100);
30     /// for _ in 0..100 {
31     ///     data.push(std::rc::Rc::new("some data".to_string()));
32     /// }
33     ///
34     /// // Or if you want clones of the same reference,
35     /// // Create the reference beforehand to clarify that
36     /// // it should be cloned for each value
37     /// let data = std::rc::Rc::new("some data".to_string());
38     /// let v = vec![data; 100];
39     /// ```
40     #[clippy::version = "1.62.0"]
41     pub RC_CLONE_IN_VEC_INIT,
42     suspicious,
43     "initializing `Arc` or `Rc` in `vec![elem; len]`"
44 }
45 declare_lint_pass!(RcCloneInVecInit => [RC_CLONE_IN_VEC_INIT]);
46
47 impl LateLintPass<'_> for RcCloneInVecInit {
48     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
49         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; };
51         let Some(symbol) = new_reference_call(cx, elem) else { return; };
52
53         emit_lint(cx, symbol, &macro_call);
54     }
55 }
56
57 fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, macro_call: &MacroCall) {
58     let symbol_name = symbol.as_str();
59
60     span_lint_and_then(
61         cx,
62         RC_CLONE_IN_VEC_INIT,
63         macro_call.span,
64         &format!("calling `{symbol_name}::new` in `vec![elem; len]`"),
65         |diag| {
66             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");
71         },
72     );
73 }
74
75 /// Checks whether the given `expr` is a call to `Arc::new` or `Rc::new`
76 fn new_reference_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
77     if_chain! {
78         if let ExprKind::Call(func, _args) = expr.kind;
79         if let ExprKind::Path(ref func_path @ QPath::TypeRelative(ty, _)) = func.kind;
80         if let TyKind::Path(ref ty_path) = ty.kind;
81         if let Some(def_id) = cx.qpath_res(ty_path, ty.hir_id).opt_def_id();
82         if last_path_segment(func_path).ident.name == sym::new;
83
84         then {
85             return cx.tcx.get_diagnostic_name(def_id).filter(|symbol| symbol == &sym::Arc || symbol == &sym::Rc);
86         }
87     }
88
89     None
90 }