Skip to content

[unnecessary_to_owned]: catch to_owned on byte slice to create temporary &str #11656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2024
Merged
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
70 changes: 67 additions & 3 deletions clippy_lints/src/methods/unnecessary_to_owned.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use super::implicit_clone::is_clone_like;
use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{
get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
};
use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
use clippy_utils::{
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, match_def_path, paths, return_ty,
};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -52,6 +54,9 @@ pub fn check<'tcx>(
if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
return;
}
if check_string_from_utf8(cx, expr, receiver) {
return;
}
check_other_call_arg(cx, expr, method_name, receiver);
}
} else {
Expand Down Expand Up @@ -240,6 +245,65 @@ fn check_into_iter_call_arg(
false
}

/// Checks for `&String::from_utf8(bytes.{to_vec,to_owned,...}()).unwrap()` coercing to `&str`,
/// which can be written as just `std::str::from_utf8(bytes).unwrap()`.
fn check_string_from_utf8<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, receiver: &'tcx Expr<'tcx>) -> bool {
if let Some((call, arg)) = skip_addr_of_ancestors(cx, expr)
&& !arg.span.from_expansion()
&& let ExprKind::Call(callee, _) = call.kind
&& fn_def_id(cx, call).is_some_and(|did| match_def_path(cx, did, &paths::STRING_FROM_UTF8))
&& let Some(unwrap_call) = get_parent_expr(cx, call)
&& let ExprKind::MethodCall(unwrap_method_name, ..) = unwrap_call.kind
&& matches!(unwrap_method_name.ident.name, sym::unwrap | sym::expect)
&& let Some(ref_string) = get_parent_expr(cx, unwrap_call)
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = ref_string.kind
&& let adjusted_ty = cx.typeck_results().expr_ty_adjusted(ref_string)
// `&...` creates a `&String`, so only actually lint if this coerces to a `&str`
&& matches!(adjusted_ty.kind(), ty::Ref(_, ty, _) if ty.is_str())
{
span_lint_and_then(
cx,
UNNECESSARY_TO_OWNED,
ref_string.span,
"allocating a new `String` only to create a temporary `&str` from it",
|diag| {
let arg_suggestion = format!(
"{borrow}{recv_snippet}",
recv_snippet = snippet(cx, receiver.span.source_callsite(), ".."),
borrow = if cx.typeck_results().expr_ty(receiver).is_ref() {
""
} else {
// If not already a reference, prefix with a borrow so that it can coerce to one
"&"
}
);

diag.multipart_suggestion(
"convert from `&[u8]` to `&str` directly",
vec![
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^^^^^^^^^^^^^^^^^
(callee.span, "core::str::from_utf8".into()),
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^
(
ref_string.span.shrink_to_lo().to(unwrap_call.span.shrink_to_lo()),
String::new(),
),
// `&String::from_utf8(bytes.to_vec()).unwrap()`
// ^^^^^^^^^^^^^^
(arg.span, arg_suggestion),
],
Applicability::MachineApplicable,
);
},
);
true
} else {
false
}
}

/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
/// call of a `to_owned`-like function is unnecessary.
fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"
pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
pub const STRING_FROM_UTF8: [&str; 4] = ["alloc", "string", "String", "from_utf8"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/unnecessary_to_owned.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ fn main() {
require_path(&std::path::PathBuf::from("x"));
require_str(&String::from("x"));
require_slice(&[String::from("x")]);

let slice = [0u8; 1024];
let _ref_str: &str = core::str::from_utf8(&slice).expect("not UTF-8");
let _ref_str: &str = core::str::from_utf8(b"foo").unwrap();
let _ref_str: &str = core::str::from_utf8(b"foo".as_slice()).unwrap();
// Expression is of type `&String`, can't suggest `str::from_utf8` here
let _ref_string = &String::from_utf8(b"foo".to_vec()).unwrap();
macro_rules! arg_from_macro {
() => {
b"foo".to_vec()
};
}
macro_rules! string_from_utf8_from_macro {
() => {
&String::from_utf8(b"foo".to_vec()).unwrap()
};
}
let _ref_str: &str = &String::from_utf8(arg_from_macro!()).unwrap();
let _ref_str: &str = string_from_utf8_from_macro!();
}

fn require_c_str(_: &CStr) {}
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/unnecessary_to_owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ fn main() {
require_path(&std::path::PathBuf::from("x").to_path_buf());
require_str(&String::from("x").to_string());
require_slice(&[String::from("x")].to_owned());

let slice = [0u8; 1024];
let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8");
let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap();
let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap();
// Expression is of type `&String`, can't suggest `str::from_utf8` here
let _ref_string = &String::from_utf8(b"foo".to_vec()).unwrap();
macro_rules! arg_from_macro {
() => {
b"foo".to_vec()
};
}
macro_rules! string_from_utf8_from_macro {
() => {
&String::from_utf8(b"foo".to_vec()).unwrap()
};
}
let _ref_str: &str = &String::from_utf8(arg_from_macro!()).unwrap();
let _ref_str: &str = string_from_utf8_from_macro!();
}

fn require_c_str(_: &CStr) {}
Expand Down
60 changes: 48 additions & 12 deletions tests/ui/unnecessary_to_owned.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,44 @@ error: unnecessary use of `to_owned`
LL | let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_owned());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()`

error: allocating a new `String` only to create a temporary `&str` from it
--> tests/ui/unnecessary_to_owned.rs:162:26
|
LL | let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: convert from `&[u8]` to `&str` directly
|
LL - let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8");
LL + let _ref_str: &str = core::str::from_utf8(&slice).expect("not UTF-8");
|

error: allocating a new `String` only to create a temporary `&str` from it
--> tests/ui/unnecessary_to_owned.rs:163:26
|
LL | let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: convert from `&[u8]` to `&str` directly
|
LL - let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap();
LL + let _ref_str: &str = core::str::from_utf8(b"foo").unwrap();
|

error: allocating a new `String` only to create a temporary `&str` from it
--> tests/ui/unnecessary_to_owned.rs:164:26
|
LL | let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: convert from `&[u8]` to `&str` directly
|
LL - let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap();
LL + let _ref_str: &str = core::str::from_utf8(b"foo".as_slice()).unwrap();
|

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:202:14
--> tests/ui/unnecessary_to_owned.rs:221:14
|
LL | for t in file_types.to_vec() {
| ^^^^^^^^^^^^^^^^^^^
Expand All @@ -494,64 +530,64 @@ LL + let path = match get_file_path(t) {
|

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:225:14
--> tests/ui/unnecessary_to_owned.rs:244:14
|
LL | let _ = &["x"][..].to_vec().into_iter();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().cloned()`

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:230:14
--> tests/ui/unnecessary_to_owned.rs:249:14
|
LL | let _ = &["x"][..].to_vec().into_iter();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().copied()`

error: unnecessary use of `to_string`
--> tests/ui/unnecessary_to_owned.rs:278:24
--> tests/ui/unnecessary_to_owned.rs:297:24
|
LL | Box::new(build(y.to_string()))
| ^^^^^^^^^^^^^ help: use: `y`

error: unnecessary use of `to_string`
--> tests/ui/unnecessary_to_owned.rs:387:12
--> tests/ui/unnecessary_to_owned.rs:406:12
|
LL | id("abc".to_string())
| ^^^^^^^^^^^^^^^^^ help: use: `"abc"`

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:530:37
--> tests/ui/unnecessary_to_owned.rs:549:37
|
LL | IntoFuture::into_future(foo([].to_vec(), &0));
| ^^^^^^^^^^^ help: use: `[]`

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:540:18
--> tests/ui/unnecessary_to_owned.rs:559:18
|
LL | s.remove(&a.to_vec());
| ^^^^^^^^^^^ help: replace it with: `a`

error: unnecessary use of `to_owned`
--> tests/ui/unnecessary_to_owned.rs:544:14
--> tests/ui/unnecessary_to_owned.rs:563:14
|
LL | s.remove(&"b".to_owned());
| ^^^^^^^^^^^^^^^ help: replace it with: `"b"`

error: unnecessary use of `to_string`
--> tests/ui/unnecessary_to_owned.rs:545:14
--> tests/ui/unnecessary_to_owned.rs:564:14
|
LL | s.remove(&"b".to_string());
| ^^^^^^^^^^^^^^^^ help: replace it with: `"b"`

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:550:14
--> tests/ui/unnecessary_to_owned.rs:569:14
|
LL | s.remove(&["b"].to_vec());
| ^^^^^^^^^^^^^^^ help: replace it with: `["b"].as_slice()`

error: unnecessary use of `to_vec`
--> tests/ui/unnecessary_to_owned.rs:551:14
--> tests/ui/unnecessary_to_owned.rs:570:14
|
LL | s.remove(&(&["b"]).to_vec());
| ^^^^^^^^^^^^^^^^^^ help: replace it with: `(&["b"]).as_slice()`

error: aborting due to 85 previous errors
error: aborting due to 88 previous errors