Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit de028e2

Browse files
committed
Implemented suspicious_to_owned lint to check if to_owned is called on a Cow.
This is done because `to_owned` is very similarly named to `into_owned`, but the effect of calling those two methods is completely different. This creates confusion (stemming from the ambiguity of the 'owned' term in the context of `Cow`s) and might not be what the writer intended.
1 parent 602bec2 commit de028e2

File tree

9 files changed

+202
-1
lines changed

9 files changed

+202
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4082,6 +4082,7 @@ Released 2018-09-13
40824082
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
40834083
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
40844084
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
4085+
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
40854086
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
40864087
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
40874088
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
207207
LintId::of(methods::STRING_EXTEND_CHARS),
208208
LintId::of(methods::SUSPICIOUS_MAP),
209209
LintId::of(methods::SUSPICIOUS_SPLITN),
210+
LintId::of(methods::SUSPICIOUS_TO_OWNED),
210211
LintId::of(methods::UNINIT_ASSUMED_INIT),
211212
LintId::of(methods::UNIT_HASH),
212213
LintId::of(methods::UNNECESSARY_FILTER_MAP),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ store.register_lints(&[
358358
methods::STRING_EXTEND_CHARS,
359359
methods::SUSPICIOUS_MAP,
360360
methods::SUSPICIOUS_SPLITN,
361+
methods::SUSPICIOUS_TO_OWNED,
361362
methods::UNINIT_ASSUMED_INIT,
362363
methods::UNIT_HASH,
363364
methods::UNNECESSARY_FILTER_MAP,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
2424
LintId::of(loops::MUT_RANGE_BOUND),
2525
LintId::of(methods::NO_EFFECT_REPLACE),
2626
LintId::of(methods::SUSPICIOUS_MAP),
27+
LintId::of(methods::SUSPICIOUS_TO_OWNED),
2728
LintId::of(multi_assignments::MULTI_ASSIGNMENTS),
2829
LintId::of(mut_key::MUTABLE_KEY_TYPE),
2930
LintId::of(octal_escapes::OCTAL_ESCAPES),

clippy_lints/src/methods/mod.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ mod str_splitn;
7878
mod string_extend_chars;
7979
mod suspicious_map;
8080
mod suspicious_splitn;
81+
mod suspicious_to_owned;
8182
mod uninit_assumed_init;
8283
mod unit_hash;
8384
mod unnecessary_filter_map;
@@ -2053,6 +2054,55 @@ declare_clippy_lint! {
20532054
"replace `.iter().count()` with `.len()`"
20542055
}
20552056

2057+
declare_clippy_lint! {
2058+
/// ### What it does
2059+
/// Checks for the usage of `_.to_owned()`, on a `Cow<'_, _>`.
2060+
///
2061+
/// ### Why is this bad?
2062+
/// Calling `to_owned()` on a `Cow` creates a clone of the `Cow`
2063+
/// itself, without taking ownership of the `Cow` contents (i.e.
2064+
/// it's equivalent to calling `Cow::clone`).
2065+
/// The similarly named `into_owned` method, on the other hand,
2066+
/// clones the `Cow` contents, effectively turning any `Cow::Borrowed`
2067+
/// into a `Cow::Owned`.
2068+
///
2069+
/// Given the potential ambiguity, consider replacing `to_owned`
2070+
/// with `clone` for better readability or, if getting a `Cow::Owned`
2071+
/// was the original intent, using `into_owned` instead.
2072+
///
2073+
/// ### Example
2074+
/// ```rust
2075+
/// # use std::borrow::Cow;
2076+
/// let s = "Hello world!";
2077+
/// let cow = Cow::Borrowed(s);
2078+
///
2079+
/// let data = cow.to_owned();
2080+
/// assert!(matches!(data, Cow::Borrowed(_)))
2081+
/// ```
2082+
/// Use instead:
2083+
/// ```rust
2084+
/// # use std::borrow::Cow;
2085+
/// let s = "Hello world!";
2086+
/// let cow = Cow::Borrowed(s);
2087+
///
2088+
/// let data = cow.clone();
2089+
/// assert!(matches!(data, Cow::Borrowed(_)))
2090+
/// ```
2091+
/// or
2092+
/// ```rust
2093+
/// # use std::borrow::Cow;
2094+
/// let s = "Hello world!";
2095+
/// let cow = Cow::Borrowed(s);
2096+
///
2097+
/// let data = cow.into_owned();
2098+
/// assert!(matches!(data, String))
2099+
/// ```
2100+
#[clippy::version = "1.65.0"]
2101+
pub SUSPICIOUS_TO_OWNED,
2102+
suspicious,
2103+
"calls to `to_owned` on a `Cow<'_, _>` might not do what they are expected"
2104+
}
2105+
20562106
declare_clippy_lint! {
20572107
/// ### What it does
20582108
/// Checks for calls to [`splitn`]
@@ -3075,6 +3125,7 @@ impl_lint_pass!(Methods => [
30753125
FROM_ITER_INSTEAD_OF_COLLECT,
30763126
INSPECT_FOR_EACH,
30773127
IMPLICIT_CLONE,
3128+
SUSPICIOUS_TO_OWNED,
30783129
SUSPICIOUS_SPLITN,
30793130
MANUAL_STR_REPEAT,
30803131
EXTEND_WITH_DRAIN,
@@ -3553,7 +3604,12 @@ impl Methods {
35533604
}
35543605
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
35553606
},
3556-
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
3607+
("to_owned", []) => {
3608+
if !suspicious_to_owned::check(cx, expr, recv) {
3609+
implicit_clone::check(cx, name, expr, recv);
3610+
}
3611+
},
3612+
("to_os_string" | "to_path_buf" | "to_vec", []) => {
35573613
implicit_clone::check(cx, name, expr, recv);
35583614
},
35593615
("unwrap", []) => {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_diag_trait_item;
3+
use clippy_utils::source::snippet_with_context;
4+
use if_chain::if_chain;
5+
use rustc_errors::Applicability;
6+
use rustc_hir as hir;
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty;
9+
use rustc_span::sym;
10+
11+
use super::SUSPICIOUS_TO_OWNED;
12+
13+
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool {
14+
if_chain! {
15+
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
16+
if is_diag_trait_item(cx, method_def_id, sym::ToOwned);
17+
let input_type = cx.typeck_results().expr_ty(expr);
18+
if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind();
19+
if cx.tcx.is_diagnostic_item(sym::Cow, adt.did());
20+
then {
21+
let mut app = Applicability::MaybeIncorrect;
22+
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
23+
span_lint_and_sugg(
24+
cx,
25+
SUSPICIOUS_TO_OWNED,
26+
expr.span,
27+
&format!("this `to_owned` call clones the {0} itself and does not cause the {0} contents to become owned", input_type),
28+
"consider using, depending on intent",
29+
format!("{0}.clone()` or `{0}.into_owned()", recv_snip),
30+
app,
31+
);
32+
return true;
33+
}
34+
}
35+
false
36+
}

tests/compile-test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[
393393
"search_is_some.rs",
394394
"single_component_path_imports_nested_first.rs",
395395
"string_add.rs",
396+
"suspicious_to_owned.rs",
396397
"toplevel_ref_arg_non_rustfix.rs",
397398
"unit_arg.rs",
398399
"unnecessary_clone.rs",

tests/ui/suspicious_to_owned.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#![warn(clippy::suspicious_to_owned)]
2+
#![warn(clippy::implicit_clone)]
3+
#![allow(clippy::redundant_clone)]
4+
use std::borrow::Cow;
5+
use std::ffi::CStr;
6+
7+
fn main() {
8+
let moo = "Moooo";
9+
let c_moo = b"Moooo\0";
10+
let c_moo_ptr = c_moo.as_ptr() as *const i8;
11+
let moos = ['M', 'o', 'o'];
12+
let moos_vec = moos.to_vec();
13+
14+
// we expect this to be linted
15+
let cow = Cow::Borrowed(moo);
16+
let _ = cow.to_owned();
17+
// we expect no lints for this
18+
let cow = Cow::Borrowed(moo);
19+
let _ = cow.into_owned();
20+
// we expect no lints for this
21+
let cow = Cow::Borrowed(moo);
22+
let _ = cow.clone();
23+
24+
// we expect this to be linted
25+
let cow = Cow::Borrowed(&moos);
26+
let _ = cow.to_owned();
27+
// we expect no lints for this
28+
let cow = Cow::Borrowed(&moos);
29+
let _ = cow.into_owned();
30+
// we expect no lints for this
31+
let cow = Cow::Borrowed(&moos);
32+
let _ = cow.clone();
33+
34+
// we expect this to be linted
35+
let cow = Cow::Borrowed(&moos_vec);
36+
let _ = cow.to_owned();
37+
// we expect no lints for this
38+
let cow = Cow::Borrowed(&moos_vec);
39+
let _ = cow.into_owned();
40+
// we expect no lints for this
41+
let cow = Cow::Borrowed(&moos_vec);
42+
let _ = cow.clone();
43+
44+
// we expect this to be linted
45+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
46+
let _ = cow.to_owned();
47+
// we expect no lints for this
48+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
49+
let _ = cow.into_owned();
50+
// we expect no lints for this
51+
let cow = unsafe { CStr::from_ptr(c_moo_ptr) }.to_string_lossy();
52+
let _ = cow.clone();
53+
54+
// we expect no lints for these
55+
let _ = moo.to_owned();
56+
let _ = c_moo.to_owned();
57+
let _ = moos.to_owned();
58+
59+
// we expect implicit_clone lints for these
60+
let _ = String::from(moo).to_owned();
61+
let _ = moos_vec.to_owned();
62+
}

tests/ui/suspicious_to_owned.stderr

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned
2+
--> $DIR/suspicious_to_owned.rs:16:13
3+
|
4+
LL | let _ = cow.to_owned();
5+
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
6+
|
7+
= note: `-D clippy::suspicious-to-owned` implied by `-D warnings`
8+
9+
error: this `to_owned` call clones the std::borrow::Cow<[char; 3]> itself and does not cause the std::borrow::Cow<[char; 3]> contents to become owned
10+
--> $DIR/suspicious_to_owned.rs:26:13
11+
|
12+
LL | let _ = cow.to_owned();
13+
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
14+
15+
error: this `to_owned` call clones the std::borrow::Cow<std::vec::Vec<char>> itself and does not cause the std::borrow::Cow<std::vec::Vec<char>> contents to become owned
16+
--> $DIR/suspicious_to_owned.rs:36:13
17+
|
18+
LL | let _ = cow.to_owned();
19+
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
20+
21+
error: this `to_owned` call clones the std::borrow::Cow<str> itself and does not cause the std::borrow::Cow<str> contents to become owned
22+
--> $DIR/suspicious_to_owned.rs:46:13
23+
|
24+
LL | let _ = cow.to_owned();
25+
| ^^^^^^^^^^^^^^ help: consider using, depending on intent: `cow.clone()` or `cow.into_owned()`
26+
27+
error: implicitly cloning a `String` by calling `to_owned` on its dereferenced type
28+
--> $DIR/suspicious_to_owned.rs:60:13
29+
|
30+
LL | let _ = String::from(moo).to_owned();
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `String::from(moo).clone()`
32+
|
33+
= note: `-D clippy::implicit-clone` implied by `-D warnings`
34+
35+
error: implicitly cloning a `Vec` by calling `to_owned` on its dereferenced type
36+
--> $DIR/suspicious_to_owned.rs:61:13
37+
|
38+
LL | let _ = moos_vec.to_owned();
39+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `moos_vec.clone()`
40+
41+
error: aborting due to 6 previous errors
42+

0 commit comments

Comments
 (0)