Skip to content

Commit 1d3c539

Browse files
committed
Auto merge of rust-lang#6924 - mgacek8:issue6727_copy_types, r=llogiq
wrong_self_convention: `to_` convention respects `Copy` types fixes rust-lang#6727 changelog: wrong_self_convention: `to_` convention respects `Copy` types
2 parents 0d2e2b5 + 1f2d016 commit 1d3c539

8 files changed

+157
-103
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,18 @@ declare_clippy_lint! {
192192
/// **What it does:** Checks for methods with certain name prefixes and which
193193
/// doesn't match how self is taken. The actual rules are:
194194
///
195-
/// |Prefix |Postfix |`self` taken |
196-
/// |-------|------------|----------------------|
197-
/// |`as_` | none |`&self` or `&mut self`|
198-
/// |`from_`| none | none |
199-
/// |`into_`| none |`self` |
200-
/// |`is_` | none |`&self` or none |
201-
/// |`to_` | `_mut` |`&mut &self` |
202-
/// |`to_` | not `_mut` |`&self` |
195+
/// |Prefix |Postfix |`self` taken | `self` type |
196+
/// |-------|------------|-----------------------|--------------|
197+
/// |`as_` | none |`&self` or `&mut self` | any |
198+
/// |`from_`| none | none | any |
199+
/// |`into_`| none |`self` | any |
200+
/// |`is_` | none |`&self` or none | any |
201+
/// |`to_` | `_mut` |`&mut self` | any |
202+
/// |`to_` | not `_mut` |`self` | `Copy` |
203+
/// |`to_` | not `_mut` |`&self` | not `Copy` |
204+
///
205+
/// Please find more info here:
206+
/// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
203207
///
204208
/// **Why is this bad?** Consistency breeds readability. If you follow the
205209
/// conventions, your users won't be surprised that they, e.g., need to supply a
@@ -1836,10 +1840,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
18361840
let item = cx.tcx.hir().expect_item(parent);
18371841
let self_ty = cx.tcx.type_of(item.def_id);
18381842

1839-
// if this impl block implements a trait, lint in trait definition instead
1840-
if let hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind {
1841-
return;
1842-
}
1843+
let implements_trait = matches!(item.kind, hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }));
18431844

18441845
if_chain! {
18451846
if let hir::ImplItemKind::Fn(ref sig, id) = impl_item.kind;
@@ -1854,7 +1855,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
18541855
if let Some(first_arg_ty) = first_arg_ty;
18551856

18561857
then {
1857-
if cx.access_levels.is_exported(impl_item.hir_id()) {
1858+
// if this impl block implements a trait, lint in trait definition instead
1859+
if !implements_trait && cx.access_levels.is_exported(impl_item.hir_id()) {
18581860
// check missing trait implementations
18591861
for method_config in &TRAIT_METHODS {
18601862
if name == method_config.method_name &&
@@ -1890,11 +1892,17 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
18901892
item.vis.node.is_pub(),
18911893
self_ty,
18921894
first_arg_ty,
1893-
first_arg.pat.span
1895+
first_arg.pat.span,
1896+
false
18941897
);
18951898
}
18961899
}
18971900

1901+
// if this impl block implements a trait, lint in trait definition instead
1902+
if implements_trait {
1903+
return;
1904+
}
1905+
18981906
if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
18991907
let ret_ty = return_ty(cx, impl_item.hir_id());
19001908

@@ -1946,7 +1954,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
19461954
false,
19471955
self_ty,
19481956
first_arg_ty,
1949-
first_arg_span
1957+
first_arg_span,
1958+
true
19501959
);
19511960
}
19521961
}
@@ -2338,10 +2347,10 @@ impl SelfKind {
23382347
#[must_use]
23392348
fn description(self) -> &'static str {
23402349
match self {
2341-
Self::Value => "self by value",
2342-
Self::Ref => "self by reference",
2343-
Self::RefMut => "self by mutable reference",
2344-
Self::No => "no self",
2350+
Self::Value => "`self` by value",
2351+
Self::Ref => "`self` by reference",
2352+
Self::RefMut => "`self` by mutable reference",
2353+
Self::No => "no `self`",
23452354
}
23462355
}
23472356
}

clippy_lints/src/methods/wrong_self_convention.rs

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::methods::SelfKind;
22
use clippy_utils::diagnostics::span_lint_and_help;
3+
use clippy_utils::ty::is_copy;
34
use rustc_lint::LateContext;
45
use rustc_middle::ty::TyS;
56
use rustc_span::source_map::Span;
@@ -9,43 +10,58 @@ use super::WRONG_PUB_SELF_CONVENTION;
910
use super::WRONG_SELF_CONVENTION;
1011

1112
#[rustfmt::skip]
12-
const CONVENTIONS: [(&[Convention], &[SelfKind]); 8] = [
13+
const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
1314
(&[Convention::Eq("new")], &[SelfKind::No]),
1415
(&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
1516
(&[Convention::StartsWith("from_")], &[SelfKind::No]),
1617
(&[Convention::StartsWith("into_")], &[SelfKind::Value]),
1718
(&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]),
1819
(&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
1920
(&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),
20-
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut")], &[SelfKind::Ref]),
21+
22+
// Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types).
23+
// Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
24+
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]),
25+
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), Convention::ImplementsTrait(false)], &[SelfKind::Value]),
2126
];
2227

2328
enum Convention {
2429
Eq(&'static str),
2530
StartsWith(&'static str),
2631
EndsWith(&'static str),
2732
NotEndsWith(&'static str),
33+
IsSelfTypeCopy(bool),
34+
ImplementsTrait(bool),
2835
}
2936

3037
impl Convention {
3138
#[must_use]
32-
fn check(&self, other: &str) -> bool {
39+
fn check<'tcx>(&self, cx: &LateContext<'tcx>, self_ty: &'tcx TyS<'tcx>, other: &str, is_trait_def: bool) -> bool {
3340
match *self {
3441
Self::Eq(this) => this == other,
3542
Self::StartsWith(this) => other.starts_with(this) && this != other,
3643
Self::EndsWith(this) => other.ends_with(this) && this != other,
37-
Self::NotEndsWith(this) => !Self::EndsWith(this).check(other),
44+
Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, is_trait_def),
45+
Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty),
46+
Self::ImplementsTrait(is_true) => is_true == is_trait_def,
3847
}
3948
}
4049
}
4150

4251
impl fmt::Display for Convention {
4352
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
4453
match *self {
45-
Self::Eq(this) => this.fmt(f),
46-
Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)),
47-
Self::EndsWith(this) => '*'.fmt(f).and_then(|_| this.fmt(f)),
48-
Self::NotEndsWith(this) => '~'.fmt(f).and_then(|_| this.fmt(f)),
54+
Self::Eq(this) => format!("`{}`", this).fmt(f),
55+
Self::StartsWith(this) => format!("`{}*`", this).fmt(f),
56+
Self::EndsWith(this) => format!("`*{}`", this).fmt(f),
57+
Self::NotEndsWith(this) => format!("`~{}`", this).fmt(f),
58+
Self::IsSelfTypeCopy(is_true) => {
59+
format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
60+
},
61+
Self::ImplementsTrait(is_true) => {
62+
let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
63+
format!("Method{} implement{} a trait", negation, s_suffix).fmt(f)
64+
},
4965
}
5066
}
5167
}
@@ -57,47 +73,44 @@ pub(super) fn check<'tcx>(
5773
self_ty: &'tcx TyS<'tcx>,
5874
first_arg_ty: &'tcx TyS<'tcx>,
5975
first_arg_span: Span,
76+
is_trait_item: bool,
6077
) {
6178
let lint = if is_pub {
6279
WRONG_PUB_SELF_CONVENTION
6380
} else {
6481
WRONG_SELF_CONVENTION
6582
};
66-
if let Some((conventions, self_kinds)) = &CONVENTIONS
67-
.iter()
68-
.find(|(convs, _)| convs.iter().all(|conv| conv.check(item_name)))
69-
{
83+
if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
84+
convs
85+
.iter()
86+
.all(|conv| conv.check(cx, self_ty, item_name, is_trait_item))
87+
}) {
7088
if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
7189
let suggestion = {
7290
if conventions.len() > 1 {
73-
let special_case = {
74-
// Don't mention `NotEndsWith` when there is also `StartsWith` convention present
75-
if conventions.len() == 2 {
76-
match conventions {
77-
[Convention::StartsWith(starts_with), Convention::NotEndsWith(_)]
78-
| [Convention::NotEndsWith(_), Convention::StartsWith(starts_with)] => {
79-
Some(format!("methods called `{}`", Convention::StartsWith(starts_with)))
80-
},
81-
_ => None,
82-
}
83-
} else {
84-
None
85-
}
86-
};
87-
88-
if let Some(suggestion) = special_case {
89-
suggestion
90-
} else {
91-
let s = conventions
91+
// Don't mention `NotEndsWith` when there is also `StartsWith` convention present
92+
let cut_ends_with_conv = conventions.iter().any(|conv| matches!(conv, Convention::StartsWith(_)))
93+
&& conventions
9294
.iter()
93-
.map(|c| format!("`{}`", &c.to_string()))
94-
.collect::<Vec<_>>()
95-
.join(" and ");
95+
.any(|conv| matches!(conv, Convention::NotEndsWith(_)));
96+
97+
let s = conventions
98+
.iter()
99+
.filter_map(|conv| {
100+
if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
101+
|| matches!(conv, Convention::ImplementsTrait(_))
102+
{
103+
None
104+
} else {
105+
Some(conv.to_string())
106+
}
107+
})
108+
.collect::<Vec<_>>()
109+
.join(" and ");
96110

97-
format!("methods called like this: ({})", &s)
98-
}
111+
format!("methods with the following characteristics: ({})", &s)
99112
} else {
100-
format!("methods called `{}`", &conventions[0])
113+
format!("methods called {}", &conventions[0])
101114
}
102115
};
103116

tests/ui/def_id_nocore.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: methods called `as_*` usually take self by reference or self by mutable reference
1+
error: methods called `as_*` usually take `self` by reference or `self` by mutable reference
22
--> $DIR/def_id_nocore.rs:26:19
33
|
44
LL | pub fn as_ref(self) -> &'static str {

tests/ui/use_self.fixed

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ mod lifetimes {
7575

7676
mod issue2894 {
7777
trait IntoBytes {
78-
fn to_bytes(&self) -> Vec<u8>;
78+
fn to_bytes(self) -> Vec<u8>;
7979
}
8080

8181
// This should not be linted
8282
impl IntoBytes for u8 {
83-
fn to_bytes(&self) -> Vec<u8> {
84-
vec![*self]
83+
fn to_bytes(self) -> Vec<u8> {
84+
vec![self]
8585
}
8686
}
8787
}

tests/ui/use_self.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ mod lifetimes {
7575

7676
mod issue2894 {
7777
trait IntoBytes {
78-
fn to_bytes(&self) -> Vec<u8>;
78+
fn to_bytes(self) -> Vec<u8>;
7979
}
8080

8181
// This should not be linted
8282
impl IntoBytes for u8 {
83-
fn to_bytes(&self) -> Vec<u8> {
84-
vec![*self]
83+
fn to_bytes(self) -> Vec<u8> {
84+
vec![self]
8585
}
8686
}
8787
}

tests/ui/wrong_self_convention.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,35 @@ mod issue6307 {
163163
fn to_mut(&mut self);
164164
}
165165
}
166+
167+
mod issue6727 {
168+
trait ToU64 {
169+
fn to_u64(self) -> u64;
170+
fn to_u64_v2(&self) -> u64;
171+
}
172+
173+
#[derive(Clone, Copy)]
174+
struct FooCopy;
175+
176+
impl ToU64 for FooCopy {
177+
fn to_u64(self) -> u64 {
178+
1
179+
}
180+
// trigger lint
181+
fn to_u64_v2(&self) -> u64 {
182+
1
183+
}
184+
}
185+
186+
struct FooNoCopy;
187+
188+
impl ToU64 for FooNoCopy {
189+
// trigger lint
190+
fn to_u64(self) -> u64 {
191+
2
192+
}
193+
fn to_u64_v2(&self) -> u64 {
194+
2
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)