Skip to content

Commit 3af383b

Browse files
committed
Add lint clones_into_boxed_slices
1 parent 432dad4 commit 3af383b

File tree

10 files changed

+645
-2
lines changed

10 files changed

+645
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6258,6 +6258,7 @@ Released 2018-09-13
62586258
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
62596259
[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
62606260
[`cloned_ref_to_slice_refs`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_ref_to_slice_refs
6261+
[`clones_into_boxed_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#clones_into_boxed_slices
62616262
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
62626263
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
62636264
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
7878
crate::cfg_not_test::CFG_NOT_TEST_INFO,
7979
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
8080
crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO,
81+
crate::clones_into_boxed_slices::CLONES_INTO_BOXED_SLICES_INFO,
8182
crate::coerce_container_to_any::COERCE_CONTAINER_TO_ANY_INFO,
8283
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
8384
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ mod casts;
8787
mod cfg_not_test;
8888
mod checked_conversions;
8989
mod cloned_ref_to_slice_refs;
90+
mod clones_into_boxed_slices;
9091
mod coerce_container_to_any;
9192
mod cognitive_complexity;
9293
mod collapsible_if;
@@ -848,6 +849,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
848849
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
849850
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
850851
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
852+
Box::new(|_| Box::new(clones_into_boxed_slices::ClonesIntoBoxedSlices)),
851853
// add late passes here, used by `cargo dev new_lint`
852854
];
853855
store.late_passes.extend(late_lints);

clippy_utils/src/sym.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ generate! {
185185
inspect,
186186
int_roundings,
187187
into,
188+
into_boxed_c_str,
189+
into_boxed_os_str,
190+
into_boxed_path,
191+
into_boxed_slice,
192+
into_boxed_str,
188193
into_bytes,
189194
into_ok,
190195
into_owned,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#![warn(clippy::clones_into_boxed_slices)]
2+
3+
use std::borrow::ToOwned;
4+
use std::ffi::{CStr, CString, OsStr, OsString};
5+
use std::fmt::{Display, Formatter};
6+
use std::path::{Path, PathBuf};
7+
use std::rc::Rc;
8+
9+
struct Dummy {}
10+
impl Display for Dummy {
11+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
12+
write!(f, "implements display")
13+
}
14+
}
15+
16+
macro_rules! create_str {
17+
($a:expr, $b:expr) => {
18+
concat!($a, $b, "!")
19+
};
20+
}
21+
22+
macro_rules! to_string {
23+
($s:expr) => {
24+
$s.to_string()
25+
};
26+
}
27+
28+
macro_rules! in_macro {
29+
($s:expr) => {
30+
Box::from("test")
31+
//~^ clones_into_boxed_slices
32+
};
33+
}
34+
35+
fn main() {
36+
let s = "test";
37+
let _: Box<str> = Box::from(s);
38+
//~^ clones_into_boxed_slices
39+
let _: Box<str> = Box::from(s);
40+
//~^ clones_into_boxed_slices
41+
let ref_s = &s;
42+
let _: Box<str> = Box::from(*ref_s);
43+
//~^ clones_into_boxed_slices
44+
let boxed_s: Box<str> = Box::from(s);
45+
let _: Box<str> = boxed_s.clone();
46+
//~^ clones_into_boxed_slices
47+
let rc_s: Rc<str> = Rc::from(s);
48+
let _: Box<str> = Box::from(&*rc_s);
49+
//~^ clones_into_boxed_slices
50+
let _: Box<str> = Box::from(s);
51+
//~^ clones_into_boxed_slices
52+
let _: Box<str> = Box::from(&s[..2]);
53+
//~^ clones_into_boxed_slices
54+
let _: Box<str> = Box::from(s);
55+
//~^ clones_into_boxed_slices
56+
let string = String::from(s);
57+
let _: Box<str> = Box::from(string.as_str());
58+
//~^ clones_into_boxed_slices
59+
let _: Box<str> = Box::from(string.as_str());
60+
//~^ clones_into_boxed_slices
61+
let _: Box<str> = Box::from(string.as_str());
62+
//~^ clones_into_boxed_slices
63+
64+
let c_str = c"test";
65+
let _: Box<CStr> = Box::from(c_str);
66+
//~^ clones_into_boxed_slices
67+
let c_string = CString::from(c_str);
68+
let _: Box<CStr> = Box::from(c_string.as_c_str());
69+
//~^ clones_into_boxed_slices
70+
let _: Box<CStr> = Box::from(c_string.as_c_str());
71+
//~^ clones_into_boxed_slices
72+
let _: Box<CStr> = Box::from(c_str);
73+
//~^ clones_into_boxed_slices
74+
75+
let os_str = OsStr::new("test");
76+
let _: Box<OsStr> = Box::from(os_str);
77+
//~^ clones_into_boxed_slices
78+
let _: Box<OsStr> = Box::from(os_str);
79+
//~^ clones_into_boxed_slices
80+
let os_string = OsString::from(os_str);
81+
let _: Box<OsStr> = Box::from(os_string.as_os_str());
82+
//~^ clones_into_boxed_slices
83+
84+
let path = Path::new("./");
85+
let _: Box<Path> = Box::from(path);
86+
//~^ clones_into_boxed_slices
87+
let _: Box<Path> = Box::from(path);
88+
//~^ clones_into_boxed_slices
89+
let path_buf = PathBuf::from("./");
90+
let _: Box<Path> = Box::from(path_buf.as_path());
91+
//~^ clones_into_boxed_slices
92+
let _: Box<Path> = Box::from(Path::new("./"));
93+
//~^ clones_into_boxed_slices
94+
95+
//Conversions that are necessary and don't clone; don't lint
96+
let to_os_str = String::from("os_str");
97+
let _: Box<OsStr> = OsString::from(to_os_str).into_boxed_os_str();
98+
let to_path = String::from("./");
99+
let _: Box<Path> = PathBuf::from(to_path).into_boxed_path();
100+
101+
let test_vec = vec![0u32, 16u32];
102+
let _: Box<[u32]> = Box::from(&test_vec[..]);
103+
//~^ clones_into_boxed_slices
104+
let slice: &[u32] = &test_vec;
105+
let _: Box<[u32]> = Box::from(slice);
106+
//~^ clones_into_boxed_slices
107+
let _: Box<[u32]> = Box::from(slice);
108+
//~^ clones_into_boxed_slices
109+
let _: Box<[u32]> = Box::from(slice);
110+
//~^ clones_into_boxed_slices
111+
112+
let _: Box<[u32]> = test_vec.into_boxed_slice();
113+
114+
//Shouldn't lint because to_string is necessary
115+
let _: Box<str> = Dummy {}.to_string().into_boxed_str();
116+
117+
// Do lint when only inner comes from macro
118+
let _: Box<str> = Box::from(create_str!("te", "st"));
119+
//~^ clones_into_boxed_slices
120+
121+
// Don't lint when only part is in macro
122+
let _: Box<str> = to_string!("test").into_boxed_str();
123+
124+
// Don't lint here but do lint in the macro def
125+
let _: Box<str> = in_macro!("test");
126+
}

0 commit comments

Comments
 (0)