Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6258,6 +6258,7 @@ Released 2018-09-13
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
[`cloned_ref_to_slice_refs`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_ref_to_slice_refs
[`clones_into_boxed_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#clones_into_boxed_slices
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::methods::CHARS_NEXT_CMP_INFO,
crate::methods::CLEAR_WITH_DRAIN_INFO,
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
crate::methods::CLONES_INTO_BOXED_SLICES_INFO,
crate::methods::CLONE_ON_COPY_INFO,
crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
Expand Down
144 changes: 144 additions & 0 deletions clippy_lints/src/methods/clones_into_boxed_slices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::sym;
use clippy_utils::ty::peel_and_count_ty_refs;
use rustc_ast::{BorrowKind, UnOp};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_span::{Span, Symbol};

use super::CLONES_INTO_BOXED_SLICES;

// Shows the lint with a suggestion using the given parts
// Assures that the inner argument is correctly ref'd/deref'd in the suggestion based on needs_ref
fn show_lint(
cx: &LateContext<'_>,
full_span: Span,
mut inner: &Expr<'_>,
needs_ref: bool,
suggestion: (Option<&str>, &str, Option<&str>),
degrade_app_to: Option<Applicability>,
) {
let mut applicability = degrade_app_to.unwrap_or(Applicability::MachineApplicable);

while let ExprKind::AddrOf(BorrowKind::Ref, _, expr) | ExprKind::Unary(UnOp::Deref, expr) = inner.kind {
inner = expr;
}

let mut sugg = Sugg::hir_with_context(cx, inner, full_span.ctxt(), suggestion.1, &mut applicability);

let inner_ty = cx.typeck_results().expr_ty(inner);
let (inner_ty_peeled, ref_count, _) = peel_and_count_ty_refs(inner_ty);
let mut ref_count = ref_count as i128;
if needs_ref {
if ty_is_slice_like(cx, inner_ty_peeled) {
ref_count -= 1;
} else {
// Inner argument is in some kind of Rc-like object, so it should be addr_deref'd to get a reference
// to the underlying slice
sugg = sugg.addr_deref();
}
}
for _ in 0..ref_count {
sugg = sugg.deref();
}
for _ in 0..-ref_count {
sugg = sugg.addr();
}

span_lint_and_sugg(
cx,
CLONES_INTO_BOXED_SLICES,
full_span,
"clone into boxed slice",
"use",
format!(
"Box::from({}{}{})",
suggestion.0.unwrap_or_default(),
sugg,
suggestion.2.unwrap_or_default()
),
applicability,
);
}

// Is the given type a slice, path, or one of the str types
fn ty_is_slice_like(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
ty.is_slice() || ty.is_str() || matches!(ty.opt_diag_name(&cx.tcx), Some(sym::cstr_type | sym::Path | sym::OsStr))
}

// Shows the lint with a suggestion that depends on the types of the inner argument and the
// resulting Box
pub(super) fn check(
cx: &LateContext<'_>,
first_method_ty: Ty<'_>,
second_method_name: Symbol,
full_span: Span,
arg: &Expr<'_>,
) {
let first_ty_diag_name = first_method_ty.opt_diag_name(cx);
if (second_method_name == sym::into_boxed_c_str && first_ty_diag_name != Some(sym::cstring_type))
|| (second_method_name == sym::into_boxed_os_str && first_ty_diag_name != Some(sym::OsString))
|| (second_method_name == sym::into_boxed_path && first_ty_diag_name != Some(sym::PathBuf))
|| (second_method_name == sym::into_boxed_str && !first_method_ty.is_lang_item(cx, LangItem::String))
|| (second_method_name == sym::into_boxed_slice && first_ty_diag_name != Some(sym::Vec))
{
return;
}

let arg_ty = cx.typeck_results().expr_ty(arg);
let inner_ty = arg_ty.peel_refs();
if ty_is_slice_like(cx, inner_ty) {
if second_method_name == sym::into_boxed_path && !inner_ty.is_diag_item(cx, sym::Path) {
// PathBuf's from(...) can convert from other str types,
// so Path::new(...) must be used to assure resulting Box is the correct type
show_lint(
cx,
full_span,
arg,
true,
(Some("Path::new("), "...", Some(")")),
Some(Applicability::MaybeIncorrect),
);
} else if let ExprKind::Unary(UnOp::Deref, deref_inner) = arg.kind
&& cx
.typeck_results()
.expr_ty(deref_inner)
.is_lang_item(cx, LangItem::OwnedBox)
{
// Special case when inner argument is already in a Box: just use Box::clone
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
CLONES_INTO_BOXED_SLICES,
full_span,
"clone into boxed slice",
"use",
format!(
"{}.clone()",
snippet_with_applicability(cx, deref_inner.span, "...", &mut applicability)
),
applicability,
);
} else {
// Inner type is a ref to a slice, so it can be directly used in the suggestion
show_lint(cx, full_span, arg, true, (None, "...", None), None);
}
// For all the following the inner type is owned, so they have to be converted to a
// reference first for the suggestion
} else if inner_ty.is_lang_item(cx, LangItem::String) {
show_lint(cx, full_span, arg, false, (None, "(...)", Some(".as_str()")), None);
} else if inner_ty.is_diag_item(cx, sym::cstring_type) {
show_lint(cx, full_span, arg, false, (None, "(...)", Some(".as_c_str()")), None);
} else if inner_ty.is_diag_item(cx, sym::PathBuf) {
show_lint(cx, full_span, arg, false, (None, "(...)", Some(".as_path()")), None);
} else if inner_ty.is_diag_item(cx, sym::Vec) {
show_lint(cx, full_span, arg, false, (Some("&"), "(...)", Some("[..]")), None);
} else if inner_ty.is_diag_item(cx, sym::OsString) {
show_lint(cx, full_span, arg, false, (None, "(...)", Some(".as_os_str()")), None);
}
}
64 changes: 63 additions & 1 deletion clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod clear_with_drain;
mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod clones_into_boxed_slices;
mod collapsible_str_replace;
mod double_ended_iterator_last;
mod drain_collect;
Expand Down Expand Up @@ -156,7 +157,7 @@ use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::{contains_return, iter_input_pats, peel_blocks, sym};
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::{self as hir, Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
use rustc_hir::{self as hir, Expr, ExprKind, Node, QPath, Stmt, StmtKind, TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::TraitRef;
use rustc_session::impl_lint_pass;
Expand Down Expand Up @@ -4714,6 +4715,27 @@ declare_clippy_lint! {
"filtering `std::io::Lines` with `filter_map()`, `flat_map()`, or `flatten()` might cause an infinite loop"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for clones that are immediately converted into boxed slices instead of using `Box::from(...)`.
///
/// ### Why is this bad?
/// Using `Box::from(...)` is more concise and avoids creating an unnecessary temporary object.
///
/// ### Example
/// ```no_run
/// let boxed: Box<str> = "example".to_string().into_boxed_str();
/// ```
/// Use instead:
/// ```no_run
/// let boxed: Box<str> = Box::from("example");
/// ```
#[clippy::version = "1.93.0"]
pub CLONES_INTO_BOXED_SLICES,
perf,
"Cloning then converting into boxed slice instead of using Box::from"
}

#[expect(clippy::struct_excessive_bools)]
pub struct Methods {
avoid_breaking_exported_api: bool,
Expand Down Expand Up @@ -4897,6 +4919,7 @@ impl_lint_pass!(Methods => [
REDUNDANT_ITER_CLONED,
UNNECESSARY_OPTION_MAP_OR_ELSE,
LINES_FILTER_MAP_OK,
CLONES_INTO_BOXED_SLICES,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -5279,6 +5302,45 @@ impl Methods {
(sym::hash, [arg]) => {
unit_hash::check(cx, expr, recv, arg);
},
(
second_name @ (sym::into_boxed_c_str
| sym::into_boxed_os_str
| sym::into_boxed_path
| sym::into_boxed_slice
| sym::into_boxed_str),
_,
) if self.msrv.meets(cx, msrvs::CLONES_INTO_BOXED_SLICES) => {
let first_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let full_span = expr.span.to(recv.span);
if let Some((
sym::clone
| sym::to_os_string
| sym::to_owned
| sym::to_path_buf
| sym::to_string
| sym::to_vec,
left,
_,
_,
_,
)) = method_call(recv)
{
clones_into_boxed_slices::check(cx, first_ty, second_name, full_span, left);
} else if let ExprKind::Call(
Expr {
hir_id: _,
kind: ExprKind::Path(QPath::TypeRelative(_, call_path)),
span: _,
},
args,
) = recv.kind
&& call_path.ident.name == sym::from
//&& cx.ty_based_def(recv).opt_parent(cx).is_diag_item(cx, sym::From)
&& cx.typeck_results().expr_ty(&args[0]).is_ref()
{
clones_into_boxed_slices::check(cx, first_ty, second_name, full_span, &args[0]);
}
},
(sym::is_empty, []) => {
match method_call(recv) {
Some((prev_method @ (sym::as_bytes | sym::bytes), prev_recv, [], _, _)) => {
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ msrv_aliases! {
1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT }
1,15,0 { MAYBE_BOUND_IN_WHERE }
1,13,0 { QUESTION_MARK_OPERATOR }
1,7,0 { CLONES_INTO_BOXED_SLICES }
}

/// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic
Expand Down
5 changes: 5 additions & 0 deletions clippy_utils/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ generate! {
inspect,
int_roundings,
into,
into_boxed_c_str,
into_boxed_os_str,
into_boxed_path,
into_boxed_slice,
into_boxed_str,
into_bytes,
into_ok,
into_owned,
Expand Down
Loading