Skip to content
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

add owned_cow lint #13948

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -5891,6 +5891,7 @@ Released 2018-09-13
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
[`overly_complex_bool_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#overly_complex_bool_expr
[`owned_cow`]: https://rust-lang.github.io/rust-clippy/master/index.html#owned_cow
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
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 @@ -704,6 +704,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
crate::too_owned::OWNED_COW_INFO,
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ mod temporary_assignment;
mod tests_outside_test_module;
mod to_digit_is_some;
mod to_string_trait_impl;
mod too_owned;
mod trailing_empty_array;
mod trait_bounds;
mod transmute;
Expand Down Expand Up @@ -970,5 +971,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
store.register_late_pass(|_| Box::new(too_owned::TooOwned));
// add lints here, do not remove this comment, it's used in `new_lint`
}
92 changes: 92 additions & 0 deletions clippy_lints/src/too_owned.rs
llogiq marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::sym;

declare_clippy_lint! {
/// ### What it does
/// Detects needlessly owned `Cow` types.
///
/// ### Why is this bad?
/// The borrowed types are usually more flexible (e.g. `&str` vs. `&String`).
llogiq marked this conversation as resolved.
Show resolved Hide resolved
///
/// ### Example
/// ```no_run
/// let wrogn: std::borrow::Cow<'_, String>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 ___________________
< looks like a typo >
 -------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a deliberate misspelling. Spelling "wrong" wrongly is a humourous way of normalizing making errors, learning from them and fixing them.

/// ```
/// Use instead:
/// ```no_run
/// let right: std::borrow::Cow<'_, str>;
/// ```
#[clippy::version = "1.85.0"]
pub OWNED_COW,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just here to say that I love the lint name 🐄

style,
"needlessly owned Cow type"
}

declare_lint_pass!(TooOwned => [OWNED_COW]);

impl LateLintPass<'_> for TooOwned {
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) {
llogiq marked this conversation as resolved.
Show resolved Hide resolved
if let hir::TyKind::Path(qpath) = ty.kind
&& let Some(cow_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::Cow, 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a span checks like !is_external_macro(...) to avoid cases where the user can't do anything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The types checks already appear to do that.

{
span_lint_and_sugg(
cx,
OWNED_COW,
span,
"needlessly owned Cow type",
"use",
repl,
Applicability::MachineApplicable,
llogiq marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
}

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
}
17 changes: 17 additions & 0 deletions tests/ui/too_owned.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![warn(clippy::cmp_owned)]

use std::borrow::Cow;
use std::ffi::{CString, OsString};
use std::path::PathBuf;

fn main() {
let x: Cow<'static, str> = Cow::Owned(String::from("Hi!"));
let y: Cow<'_, [u8]> = Cow::Owned(vec![]);
let z: Cow<'_, [_]> = Cow::Owned(vec![2_i32]);
let o: Cow<'_, std::ffi::OsStr> = Cow::Owned(OsString::new());
let c: Cow<'_, std::ffi::CStr> = Cow::Owned(CString::new("").unwrap());
let p: Cow<'_, std::path::Path> = Cow::Owned(PathBuf::new());

// false positive: borrowed type
let b: Cow<'_, str> = Cow::Borrowed("Hi!");
}
17 changes: 17 additions & 0 deletions tests/ui/too_owned.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![warn(clippy::too_owned)]
llogiq marked this conversation as resolved.
Show resolved Hide resolved

use std::borrow::Cow;
use std::ffi::{CString, OsString};
use std::path::PathBuf;

fn main() {
let x: Cow<'static, String> = Cow::Owned(String::from("Hi!"));
let y: Cow<'_, Vec<u8>> = Cow::Owned(vec![]);
let z: Cow<'_, Vec<_>> = Cow::Owned(vec![2_i32]);
let o: Cow<'_, OsString> = Cow::Owned(OsString::new());
let c: Cow<'_, CString> = Cow::Owned(CString::new("").unwrap());
let p: Cow<'_, PathBuf> = Cow::Owned(PathBuf::new());

// false positive: borrowed type
let b: Cow<'_, str> = Cow::Borrowed("Hi!");
}
50 changes: 50 additions & 0 deletions tests/ui/too_owned.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
error: unknown lint: `clippy::too_owned`
--> tests/ui/too_owned.rs:1:9
|
LL | #![warn(clippy::too_owned)]
| ^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_owned`
|
= note: `-D unknown-lints` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(unknown_lints)]`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:8:25
|
LL | let x: Cow<'static, String> = Cow::Owned(String::from("Hi!"));
| ^^^^^^ help: use: `str`
|
= note: `-D clippy::owned-cow` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::owned_cow)]`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:9:20
|
LL | let y: Cow<'_, Vec<u8>> = Cow::Owned(vec![]);
| ^^^^^^^ help: use: `[u8]`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:10:20
|
LL | let z: Cow<'_, Vec<_>> = Cow::Owned(vec![2_i32]);
| ^^^^^^ help: use: `[_]`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:11:20
|
LL | let o: Cow<'_, OsString> = Cow::Owned(OsString::new());
| ^^^^^^^^ help: use: `std::ffi::OsStr`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:12:20
|
LL | let c: Cow<'_, CString> = Cow::Owned(CString::new("").unwrap());
| ^^^^^^^ help: use: `std::ffi::CStr`

error: needlessly owned Cow type
--> tests/ui/too_owned.rs:13:20
|
LL | let p: Cow<'_, PathBuf> = Cow::Owned(PathBuf::new());
| ^^^^^^^ help: use: `std::path::Path`

error: aborting due to 7 previous errors

Loading