|
| 1 | +use clippy_utils::diagnostics::span_lint_and_sugg; |
| 2 | +use clippy_utils::res::MaybeDef; |
| 3 | +use clippy_utils::source::snippet_with_applicability; |
| 4 | +use clippy_utils::sugg::Sugg; |
| 5 | +use clippy_utils::sym; |
| 6 | +use rustc_ast::{BorrowKind, UnOp}; |
| 7 | +use rustc_errors::Applicability; |
| 8 | +use rustc_hir::{Expr, ExprKind, LangItem, QPath}; |
| 9 | +use rustc_lint::{LateContext, LateLintPass}; |
| 10 | +use rustc_middle::ty::{self, Ty}; |
| 11 | +use rustc_session::declare_lint_pass; |
| 12 | +use rustc_span::Span; |
| 13 | + |
| 14 | +declare_clippy_lint! { |
| 15 | + /// ### What it does |
| 16 | + /// Checks for clones that are immediately converted into boxed slices instead of using `Box::from(...)`. |
| 17 | + /// |
| 18 | + /// ### Why is this bad? |
| 19 | + /// Using `Box::from(...)` is more concise and avoids creating an unnecessary temporary object. |
| 20 | + /// |
| 21 | + /// ### Example |
| 22 | + /// ```no_run |
| 23 | + /// "example".to_string().to_boxed_str() |
| 24 | + /// ``` |
| 25 | + /// Use instead: |
| 26 | + /// ```no_run |
| 27 | + /// Box::from("example") |
| 28 | + /// ``` |
| 29 | + #[clippy::version = "1.93.0"] |
| 30 | + pub CLONES_INTO_BOXED_SLICES, |
| 31 | + perf, |
| 32 | + "Cloning then converting into boxed slice instead of using Box::from" |
| 33 | +} |
| 34 | +declare_lint_pass!(ClonesIntoBoxedSlices => [CLONES_INTO_BOXED_SLICES]); |
| 35 | + |
| 36 | +fn count_refs(mut expr_ty: Ty<'_>) -> i64 { |
| 37 | + let mut count = 0; |
| 38 | + while let ty::Ref(_, inner, _) = expr_ty.kind() { |
| 39 | + expr_ty = *inner; |
| 40 | + count += 1; |
| 41 | + } |
| 42 | + count |
| 43 | +} |
| 44 | + |
| 45 | +// Shows the lint with a suggestion using the given parts |
| 46 | +// Assures that the inner argument is correctly ref'd/deref'd in the suggestion based on needs_ref |
| 47 | +fn show_lint( |
| 48 | + cx: &LateContext<'_>, |
| 49 | + full_span: Span, |
| 50 | + mut inner: &Expr<'_>, |
| 51 | + needs_ref: bool, |
| 52 | + sugg_prefix: Option<&str>, |
| 53 | + placeholder: &str, |
| 54 | + sugg_suffix: Option<&str>, |
| 55 | +) { |
| 56 | + let mut applicability = Applicability::MachineApplicable; |
| 57 | + |
| 58 | + while let ExprKind::AddrOf(BorrowKind::Ref, _, expr) | ExprKind::Unary(UnOp::Deref, expr) = inner.kind { |
| 59 | + inner = expr; |
| 60 | + } |
| 61 | + |
| 62 | + let mut sugg = Sugg::hir_with_context(cx, inner, full_span.ctxt(), placeholder, &mut applicability); |
| 63 | + |
| 64 | + let inner_ty = cx.typeck_results().expr_ty(inner); |
| 65 | + let mut ref_count = count_refs(inner_ty); |
| 66 | + if needs_ref { |
| 67 | + if ty_is_slice_like(cx, inner_ty.peel_refs()) { |
| 68 | + ref_count -= 1; |
| 69 | + } else { |
| 70 | + // Inner argument is in some kind of Rc-like object, so it should be addr_deref'd to get a reference |
| 71 | + // to the underlying slice |
| 72 | + sugg = sugg.addr_deref(); |
| 73 | + } |
| 74 | + } |
| 75 | + while ref_count > 0 { |
| 76 | + sugg = sugg.deref(); |
| 77 | + ref_count -= 1; |
| 78 | + } |
| 79 | + while ref_count < 0 { |
| 80 | + sugg = sugg.addr(); |
| 81 | + ref_count += 1; |
| 82 | + } |
| 83 | + |
| 84 | + span_lint_and_sugg( |
| 85 | + cx, |
| 86 | + CLONES_INTO_BOXED_SLICES, |
| 87 | + full_span, |
| 88 | + "clone into boxed slice", |
| 89 | + "use", |
| 90 | + format!( |
| 91 | + "Box::from({}{}{})", |
| 92 | + sugg_prefix.unwrap_or_default(), |
| 93 | + sugg, |
| 94 | + sugg_suffix.unwrap_or_default() |
| 95 | + ), |
| 96 | + applicability, |
| 97 | + ); |
| 98 | +} |
| 99 | + |
| 100 | +// Is the given type a slice, path, or one of the str types |
| 101 | +fn ty_is_slice_like(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { |
| 102 | + ty.is_slice() |
| 103 | + || ty.is_str() |
| 104 | + || ty.is_diag_item(cx, sym::cstr_type) |
| 105 | + || ty.is_diag_item(cx, sym::Path) |
| 106 | + || ty.is_diag_item(cx, sym::OsStr) |
| 107 | +} |
| 108 | + |
| 109 | +// Checks if an expression is one of the into_boxed_... methods preceded by a clone-like function |
| 110 | +// Then shows the lint with a suggestion that depends on the types of the inner argument and the |
| 111 | +// resulting Box |
| 112 | +impl<'tcx> LateLintPass<'tcx> for ClonesIntoBoxedSlices { |
| 113 | + fn check_expr(&mut self, cx: &LateContext<'tcx>, second_method: &'tcx Expr<'_>) { |
| 114 | + // Is the second method into_boxed_...? |
| 115 | + if let ExprKind::MethodCall(second_method_path, first_method, _, _) = second_method.kind |
| 116 | + && second_method.span.eq_ctxt(first_method.span) |
| 117 | + && [ |
| 118 | + sym::into_boxed_str, |
| 119 | + sym::into_boxed_slice, |
| 120 | + sym::into_boxed_path, |
| 121 | + sym::into_boxed_c_str, |
| 122 | + sym::into_boxed_os_str, |
| 123 | + ] |
| 124 | + .contains(&second_method_path.ident.name) |
| 125 | + { |
| 126 | + let arg = match first_method.kind { |
| 127 | + // Is the first method clone-like? |
| 128 | + ExprKind::MethodCall(first_method_path, left, _, _) |
| 129 | + if [ |
| 130 | + sym::to_owned, |
| 131 | + sym::clone, |
| 132 | + sym::to_string, |
| 133 | + sym::to_path_buf, |
| 134 | + sym::to_os_string, |
| 135 | + sym::to_vec, |
| 136 | + ] |
| 137 | + .contains(&first_method_path.ident.name) => |
| 138 | + { |
| 139 | + Some(left) |
| 140 | + }, |
| 141 | + // Also check for from(...) constructor |
| 142 | + ExprKind::Call( |
| 143 | + Expr { |
| 144 | + hir_id: _, |
| 145 | + kind: ExprKind::Path(QPath::TypeRelative(call_out_ty, call_path)), |
| 146 | + span: _, |
| 147 | + }, |
| 148 | + args, |
| 149 | + ) if call_path.ident.name == sym::from && cx.typeck_results().expr_ty(&args[0]).is_ref() => { |
| 150 | + Some(&args[0]) |
| 151 | + }, |
| 152 | + _ => None, |
| 153 | + }; |
| 154 | + |
| 155 | + if let Some(arg) = arg { |
| 156 | + let full_span = second_method.span.to(first_method.span); |
| 157 | + let arg_ty = cx.typeck_results().expr_ty(arg); |
| 158 | + let inner_ty = arg_ty.peel_refs(); |
| 159 | + if ty_is_slice_like(cx, inner_ty) { |
| 160 | + if second_method_path.ident.name == sym::into_boxed_path && !inner_ty.is_diag_item(cx, sym::Path) { |
| 161 | + // PathBuf's from(...) can convert from other str types, |
| 162 | + // so Path::new(...) must be used to assure resulting Box is the correct type |
| 163 | + show_lint(cx, full_span, arg, true, Some("Path::new("), "...", Some(")")); |
| 164 | + } else if let ExprKind::Unary(UnOp::Deref, deref_inner) = arg.kind |
| 165 | + && cx |
| 166 | + .typeck_results() |
| 167 | + .expr_ty(deref_inner) |
| 168 | + .is_lang_item(cx, LangItem::OwnedBox) |
| 169 | + { |
| 170 | + // Special case when inner argument is already in a Box: just use Box::clone |
| 171 | + let mut applicability = Applicability::MachineApplicable; |
| 172 | + span_lint_and_sugg( |
| 173 | + cx, |
| 174 | + CLONES_INTO_BOXED_SLICES, |
| 175 | + full_span, |
| 176 | + "clone into boxed slice", |
| 177 | + "use", |
| 178 | + format!( |
| 179 | + "{}.clone()", |
| 180 | + snippet_with_applicability(cx, deref_inner.span, "...", &mut applicability) |
| 181 | + ), |
| 182 | + applicability, |
| 183 | + ); |
| 184 | + } else { |
| 185 | + // Inner type is a ref to a slice, so it can be directly used in the suggestion |
| 186 | + show_lint(cx, full_span, arg, true, None, "...", None); |
| 187 | + } |
| 188 | + // For all the following the inner type is owned, so they have to be converted to a |
| 189 | + // reference first for the suggestion |
| 190 | + } else if inner_ty.is_lang_item(cx, LangItem::String) { |
| 191 | + show_lint(cx, full_span, arg, false, None, "(...)", Some(".as_str()")); |
| 192 | + } else if inner_ty.is_diag_item(cx, sym::cstring_type) { |
| 193 | + show_lint(cx, full_span, arg, false, None, "(...)", Some(".as_c_str()")); |
| 194 | + } else if inner_ty.is_diag_item(cx, sym::PathBuf) { |
| 195 | + show_lint(cx, full_span, arg, false, None, "(...)", Some(".as_path()")); |
| 196 | + } else if inner_ty.is_diag_item(cx, sym::Vec) { |
| 197 | + show_lint(cx, full_span, arg, false, Some("&"), "(...)", Some("[..]")); |
| 198 | + } else if inner_ty.is_diag_item(cx, sym::OsString) { |
| 199 | + show_lint(cx, full_span, arg, false, None, "(...)", Some(".as_os_str()")); |
| 200 | + } |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | +} |
0 commit comments