add owned_cow lint (#13948)

Closes #13697.

---

changelog: add [`owned_cow`] lint
This commit is contained in:
Timo 2025-02-20 14:53:45 +00:00 committed by GitHub
commit bbf65f008d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 315 additions and 96 deletions

View file

@ -745,6 +745,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::types::BOX_COLLECTION_INFO,
crate::types::LINKEDLIST_INFO,
crate::types::OPTION_OPTION_INFO,
crate::types::OWNED_COW_INFO,
crate::types::RC_BUFFER_INFO,
crate::types::RC_MUTEX_INFO,
crate::types::REDUNDANT_ALLOCATION_INFO,

View file

@ -2,6 +2,7 @@ mod borrowed_box;
mod box_collection;
mod linked_list;
mod option_option;
mod owned_cow;
mod rc_buffer;
mod rc_mutex;
mod redundant_allocation;
@ -355,13 +356,63 @@ declare_clippy_lint! {
"usage of `Rc<Mutex<T>>`"
}
declare_clippy_lint! {
/// ### What it does
/// Detects needlessly owned `Cow` types.
///
/// ### Why is this bad?
/// The borrowed types are usually more flexible, in that e.g. a
/// `Cow<'_, str>` can accept both `&str` and `String` while
/// `Cow<'_, String>` can only accept `&String` and `String`. In
/// particular, `&str` is more general, because it allows for string
/// literals while `&String` can only be borrowed from a heap-owned
/// `String`).
///
/// ### Known Problems
/// The lint does not check for usage of the type. There may be external
/// interfaces that require the use of an owned type.
///
/// At least the `CString` type also has a different API than `CStr`: The
/// former has an `as_bytes` method which the latter calls `to_bytes`.
/// There is no guarantee that other types won't gain additional methods
/// leading to a similar mismatch.
///
/// In addition, the lint only checks for the known problematic types
/// `String`, `Vec<_>`, `CString`, `OsString` and `PathBuf`. Custom types
/// that implement `ToOwned` will not be detected.
///
/// ### Example
/// ```no_run
/// let wrogn: std::borrow::Cow<'_, Vec<u8>>;
/// ```
/// Use instead:
/// ```no_run
/// let right: std::borrow::Cow<'_, [u8]>;
/// ```
#[clippy::version = "1.85.0"]
pub OWNED_COW,
style,
"needlessly owned Cow type"
}
pub struct Types {
vec_box_size_threshold: u64,
type_complexity_threshold: u64,
avoid_breaking_exported_api: bool,
}
impl_lint_pass!(Types => [BOX_COLLECTION, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]);
impl_lint_pass!(Types => [
BOX_COLLECTION,
VEC_BOX,
OPTION_OPTION,
LINKEDLIST,
BORROWED_BOX,
REDUNDANT_ALLOCATION,
RC_BUFFER,
RC_MUTEX,
TYPE_COMPLEXITY,
OWNED_COW
]);
impl<'tcx> LateLintPass<'tcx> for Types {
fn check_fn(
@ -561,6 +612,7 @@ impl Types {
triggered |= option_option::check(cx, hir_ty, qpath, def_id);
triggered |= linked_list::check(cx, hir_ty, def_id);
triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
triggered |= owned_cow::check(cx, qpath, def_id);
if triggered {
return;
@ -612,6 +664,14 @@ impl Types {
QPath::LangItem(..) => {},
}
},
TyKind::Path(ref qpath) => {
let res = cx.qpath_res(qpath, hir_ty.hir_id);
if let Some(def_id) = res.opt_def_id()
&& self.is_type_change_allowed(context)
{
owned_cow::check(cx, qpath, def_id);
}
},
TyKind::Ref(lt, ref mut_ty) => {
context.is_nested_call = true;
if !borrowed_box::check(cx, hir_ty, lt, mut_ty) {

View file

@ -0,0 +1,66 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{self as hir};
use rustc_lint::LateContext;
use rustc_span::{Span, sym};
pub(super) fn check(cx: &LateContext<'_>, qpath: &hir::QPath<'_>, def_id: DefId) -> bool {
if cx.tcx.is_diagnostic_item(sym::Cow, def_id)
&& let hir::QPath::Resolved(_, path) = qpath
&& let [.., last_seg] = path.segments
&& let Some(args) = last_seg.args
&& let [_lt, carg] = args.args
&& let hir::GenericArg::Type(cty) = carg
&& let Some((span, repl)) = replacement(cx, cty.as_unambig_ty())
{
span_lint_and_sugg(
cx,
super::OWNED_COW,
span,
"needlessly owned Cow type",
"use",
repl,
Applicability::Unspecified,
);
return true;
}
false
}
fn replacement(cx: &LateContext<'_>, cty: &hir::Ty<'_>) -> Option<(Span, String)> {
if clippy_utils::is_path_lang_item(cx, cty, hir::LangItem::String) {
return Some((cty.span, "str".into()));
}
if clippy_utils::is_path_diagnostic_item(cx, cty, sym::Vec) {
return if let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = cty.kind
&& let [.., last_seg] = path.segments
&& let Some(args) = last_seg.args
&& let [t, ..] = args.args
&& let Some(snip) = snippet_opt(cx, t.span())
{
Some((cty.span, format!("[{snip}]")))
} else {
None
};
}
if clippy_utils::is_path_diagnostic_item(cx, cty, sym::cstring_type) {
return Some((
cty.span,
(if clippy_utils::is_no_std_crate(cx) {
"core::ffi::CStr"
} else {
"std::ffi::CStr"
})
.into(),
));
}
// Neither OsString nor PathBuf are available outside std
for (diag, repl) in [(sym::OsString, "std::ffi::OsStr"), (sym::PathBuf, "std::path::Path")] {
if clippy_utils::is_path_diagnostic_item(cx, cty, diag) {
return Some((cty.span, repl.into()));
}
}
None
}