|
1 | 1 | use super::implicit_clone::is_clone_like;
|
2 | 2 | use super::unnecessary_iter_cloned::{self, is_into_iter};
|
3 | 3 | use clippy_config::msrvs::{self, Msrv};
|
4 |
| -use clippy_utils::diagnostics::span_lint_and_sugg; |
5 |
| -use clippy_utils::source::snippet_opt; |
| 4 | +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
| 5 | +use clippy_utils::source::{snippet, snippet_opt}; |
6 | 6 | use clippy_utils::ty::{
|
7 | 7 | get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
|
8 | 8 | };
|
9 | 9 | use clippy_utils::visitors::find_all_ret_expressions;
|
10 |
| -use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; |
| 10 | +use clippy_utils::{ |
| 11 | + fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, match_def_path, paths, return_ty, |
| 12 | +}; |
11 | 13 | use rustc_errors::Applicability;
|
12 | 14 | use rustc_hir::def::{DefKind, Res};
|
13 | 15 | use rustc_hir::def_id::DefId;
|
@@ -52,6 +54,9 @@ pub fn check<'tcx>(
|
52 | 54 | if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) {
|
53 | 55 | return;
|
54 | 56 | }
|
| 57 | + if check_string_from_utf8(cx, expr, receiver) { |
| 58 | + return; |
| 59 | + } |
55 | 60 | check_other_call_arg(cx, expr, method_name, receiver);
|
56 | 61 | }
|
57 | 62 | } else {
|
@@ -240,6 +245,65 @@ fn check_into_iter_call_arg(
|
240 | 245 | false
|
241 | 246 | }
|
242 | 247 |
|
| 248 | +/// Checks for `&String::from_utf8(bytes.{to_vec,to_owned,...}()).unwrap()` coercing to `&str`, |
| 249 | +/// which can be written as just `std::str::from_utf8(bytes).unwrap()`. |
| 250 | +fn check_string_from_utf8<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, receiver: &'tcx Expr<'tcx>) -> bool { |
| 251 | + if let Some((call, arg)) = skip_addr_of_ancestors(cx, expr) |
| 252 | + && !arg.span.from_expansion() |
| 253 | + && let ExprKind::Call(callee, _) = call.kind |
| 254 | + && fn_def_id(cx, call).is_some_and(|did| match_def_path(cx, did, &paths::STRING_FROM_UTF8)) |
| 255 | + && let Some(unwrap_call) = get_parent_expr(cx, call) |
| 256 | + && let ExprKind::MethodCall(unwrap_method_name, ..) = unwrap_call.kind |
| 257 | + && matches!(unwrap_method_name.ident.name, sym::unwrap | sym::expect) |
| 258 | + && let Some(ref_string) = get_parent_expr(cx, unwrap_call) |
| 259 | + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = ref_string.kind |
| 260 | + && let adjusted_ty = cx.typeck_results().expr_ty_adjusted(ref_string) |
| 261 | + // `&...` creates a `&String`, so only actually lint if this coerces to a `&str` |
| 262 | + && matches!(adjusted_ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) |
| 263 | + { |
| 264 | + span_lint_and_then( |
| 265 | + cx, |
| 266 | + UNNECESSARY_TO_OWNED, |
| 267 | + ref_string.span, |
| 268 | + "allocating a new `String` only to create a temporary `&str` from it", |
| 269 | + |diag| { |
| 270 | + let arg_suggestion = format!( |
| 271 | + "{borrow}{recv_snippet}", |
| 272 | + recv_snippet = snippet(cx, receiver.span.source_callsite(), ".."), |
| 273 | + borrow = if cx.typeck_results().expr_ty(receiver).is_ref() { |
| 274 | + "" |
| 275 | + } else { |
| 276 | + // If not already a reference, prefix with a borrow so that it can coerce to one |
| 277 | + "&" |
| 278 | + } |
| 279 | + ); |
| 280 | + |
| 281 | + diag.multipart_suggestion( |
| 282 | + "convert from `&[u8]` to `&str` directly", |
| 283 | + vec![ |
| 284 | + // `&String::from_utf8(bytes.to_vec()).unwrap()` |
| 285 | + // ^^^^^^^^^^^^^^^^^ |
| 286 | + (callee.span, "core::str::from_utf8".into()), |
| 287 | + // `&String::from_utf8(bytes.to_vec()).unwrap()` |
| 288 | + // ^ |
| 289 | + ( |
| 290 | + ref_string.span.shrink_to_lo().to(unwrap_call.span.shrink_to_lo()), |
| 291 | + String::new(), |
| 292 | + ), |
| 293 | + // `&String::from_utf8(bytes.to_vec()).unwrap()` |
| 294 | + // ^^^^^^^^^^^^^^ |
| 295 | + (arg.span, arg_suggestion), |
| 296 | + ], |
| 297 | + Applicability::MachineApplicable, |
| 298 | + ); |
| 299 | + }, |
| 300 | + ); |
| 301 | + true |
| 302 | + } else { |
| 303 | + false |
| 304 | + } |
| 305 | +} |
| 306 | + |
243 | 307 | /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
|
244 | 308 | /// call of a `to_owned`-like function is unnecessary.
|
245 | 309 | fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
|
|
0 commit comments