|
| 1 | +use std::ops::ControlFlow; |
| 2 | + |
1 | 3 | use clippy_config::Conf; |
2 | 4 | use clippy_utils::diagnostics::span_lint_and_sugg; |
3 | 5 | use clippy_utils::msrvs::{self, Msrv}; |
4 | 6 | use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; |
5 | 7 | use clippy_utils::sugg::Sugg; |
6 | 8 | use clippy_utils::visitors::is_const_evaluatable; |
7 | | -use clippy_utils::{is_in_const_context, is_mutable}; |
| 9 | +use clippy_utils::{is_in_const_context, is_mutable, sym}; |
| 10 | +use rustc_ast::Mutability; |
8 | 11 | use rustc_errors::Applicability; |
9 | | -use rustc_hir::{Expr, ExprKind}; |
| 12 | +use rustc_hir::{Expr, ExprKind, HirId, LangItem}; |
10 | 13 | use rustc_lint::{LateContext, LateLintPass}; |
| 14 | +use rustc_middle::ty; |
| 15 | +use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind, OverloadedDeref}; |
11 | 16 | use rustc_session::impl_lint_pass; |
12 | | -use rustc_span::sym; |
| 17 | +use rustc_span::Symbol; |
| 18 | + |
| 19 | +use crate::methods::is_clone_like; |
13 | 20 |
|
14 | 21 | declare_clippy_lint! { |
15 | 22 | /// ### What it does |
@@ -73,29 +80,116 @@ impl<'tcx> LateLintPass<'tcx> for ClonedRefToSliceRefs<'_> { |
73 | 80 | && let ExprKind::Array([item]) = &arr.kind |
74 | 81 |
|
75 | 82 | // check for clones |
76 | | - && let ExprKind::MethodCall(_, val, _, _) = item.kind |
77 | | - && cx.ty_based_def(item).opt_parent(cx).is_diag_item(cx, sym::Clone) |
| 83 | + && let ExprKind::MethodCall(path, recv, _, _) = item.kind |
| 84 | + && let Some(adjustment) = is_needless_clone_or_equivalent(cx, recv, path.ident.name, item.hir_id) |
78 | 85 |
|
79 | 86 | // check for immutability or purity |
80 | | - && (!is_mutable(cx, val) || is_const_evaluatable(cx, val)) |
| 87 | + && (!is_mutable(cx, recv) || is_const_evaluatable(cx, recv)) |
81 | 88 |
|
82 | 89 | // get appropriate crate for `slice::from_ref` |
83 | 90 | && let Some(builtin_crate) = clippy_utils::std_or_core(cx) |
84 | 91 | { |
85 | | - let mut sugg = Sugg::hir(cx, val, "_"); |
86 | | - if !cx.typeck_results().expr_ty(val).is_ref() { |
87 | | - sugg = sugg.addr(); |
88 | | - } |
| 92 | + let mut applicability = Applicability::MachineApplicable; |
| 93 | + let sugg = Sugg::hir_with_context(cx, recv, expr.span.ctxt(), "_", &mut applicability); |
89 | 94 |
|
90 | 95 | span_lint_and_sugg( |
91 | 96 | cx, |
92 | 97 | CLONED_REF_TO_SLICE_REFS, |
93 | 98 | expr.span, |
94 | | - format!("this call to `clone` can be replaced with `{builtin_crate}::slice::from_ref`"), |
| 99 | + format!( |
| 100 | + "unnecessary use of `{}` to create a slice from a reference", |
| 101 | + path.ident.name |
| 102 | + ), |
95 | 103 | "try", |
96 | | - format!("{builtin_crate}::slice::from_ref({sugg})"), |
97 | | - Applicability::MaybeIncorrect, |
| 104 | + format!("{builtin_crate}::slice::from_ref({adjustment}{sugg})"), |
| 105 | + applicability, |
98 | 106 | ); |
99 | 107 | } |
100 | 108 | } |
101 | 109 | } |
| 110 | + |
| 111 | +/// Checks if a method call is a needless clone or equivalent. If so, returns the necessary |
| 112 | +/// adjustments to use the method receiver directly without cloning. |
| 113 | +/// For example, in the code below: |
| 114 | +/// ```rust,no_run |
| 115 | +/// use std::path::PathBuf; |
| 116 | +/// |
| 117 | +/// let w = &PathBuf::new(); |
| 118 | +/// let b = &[w.to_path_buf()]; |
| 119 | +/// ``` |
| 120 | +/// We would replace `&[w.to_path_buf()]` with `std::slice::from_ref(&*w)`, |
| 121 | +/// hence we return `Some("&*")` as the adjustment. |
| 122 | +fn is_needless_clone_or_equivalent<'tcx>( |
| 123 | + cx: &LateContext<'tcx>, |
| 124 | + method_recv: &'tcx Expr<'tcx>, |
| 125 | + method_name: Symbol, |
| 126 | + hir_id: HirId, |
| 127 | +) -> Option<String> { |
| 128 | + let method_def = cx.ty_based_def(hir_id).opt_parent(cx)?; |
| 129 | + if !method_def.is_lang_item(cx, LangItem::Clone) && !is_clone_like(cx, method_name, method_def) { |
| 130 | + return None; |
| 131 | + } |
| 132 | + |
| 133 | + let method_ret_ty = cx.typeck_results().node_type(hir_id); |
| 134 | + let method_recv_ty = cx.typeck_results().expr_ty_adjusted(method_recv); |
| 135 | + let ty::Ref(_, method_recv_ty_inner, Mutability::Not) = method_recv_ty.kind() else { |
| 136 | + return None; |
| 137 | + }; |
| 138 | + |
| 139 | + let method_recv_adjustments = cx.typeck_results().expr_adjustments(method_recv); |
| 140 | + |
| 141 | + // The return type of the clone-like method should be the same as the inner type of the reference |
| 142 | + // being cloned, except for the following special cases: |
| 143 | + // 1. `OsString`, which is first dereferenced to `OsStr` and the borrowed as `&OsStr`. |
| 144 | + // 2. `PathBuf`, which is first dereferenced to `Path` and then borrowed as `&Path`. |
| 145 | + let adjust_target_ty = if method_ret_ty == *method_recv_ty_inner { |
| 146 | + method_ret_ty |
| 147 | + } else if let Some(after_special_case_ty_name @ (sym::OsStr | sym::Path)) = method_recv_ty_inner.opt_diag_name(cx) |
| 148 | + // Looking for the `OSString -> OSStr` or `PathBuf -> Path` adjustment in the abovementioned special cases |
| 149 | + && let [preceeding_derefs @ .., special_case, last_borrow] = method_recv_adjustments |
| 150 | + && matches!( |
| 151 | + special_case.kind, |
| 152 | + Adjust::Deref(DerefAdjustKind::Overloaded(OverloadedDeref { |
| 153 | + mutbl: Mutability::Not, |
| 154 | + .. |
| 155 | + })) |
| 156 | + ) |
| 157 | + && matches!(last_borrow.kind, Adjust::Borrow(_)) |
| 158 | + && special_case.target.is_diag_item(cx, after_special_case_ty_name) |
| 159 | + && let before_special_case_ty = preceeding_derefs |
| 160 | + .last().map_or_else(|| cx.typeck_results().expr_ty(method_recv), |a| a.target) |
| 161 | + && matches!( |
| 162 | + (before_special_case_ty.opt_diag_name(cx)?, after_special_case_ty_name), |
| 163 | + (sym::OsString, sym::OsStr) | (sym::PathBuf, sym::Path)) |
| 164 | + { |
| 165 | + before_special_case_ty |
| 166 | + } else { |
| 167 | + return None; |
| 168 | + }; |
| 169 | + |
| 170 | + // Find the number of adjustments required until `method_recv_ty_source` becomes `adjust_target_ty` |
| 171 | + let method_recv_ty_source = cx.typeck_results().expr_ty(method_recv); |
| 172 | + let adjust_count = method_recv_adjustments |
| 173 | + .iter() |
| 174 | + .enumerate() |
| 175 | + .try_fold(method_recv_ty_source, |ty, (i, a)| { |
| 176 | + if ty == adjust_target_ty { |
| 177 | + ControlFlow::Break(i) |
| 178 | + } else { |
| 179 | + ControlFlow::Continue(a.target) |
| 180 | + } |
| 181 | + }) |
| 182 | + .break_value()?; |
| 183 | + |
| 184 | + let (needs_borrow, deref_count) = if adjust_count == 0 || !method_recv_ty_source.is_ref() { |
| 185 | + (true, adjust_count) |
| 186 | + } else { |
| 187 | + (false, adjust_count - 1) |
| 188 | + }; |
| 189 | + |
| 190 | + Some(if needs_borrow { |
| 191 | + format!("&{}", "*".repeat(deref_count)) |
| 192 | + } else { |
| 193 | + "*".repeat(deref_count) |
| 194 | + }) |
| 195 | +} |
0 commit comments