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

wrong_self_convention: to_ convention respects Copy types #6924

Merged
merged 3 commits into from
Mar 20, 2021
Merged
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
39 changes: 24 additions & 15 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,18 @@ declare_clippy_lint! {
/// **What it does:** Checks for methods with certain name prefixes and which
/// doesn't match how self is taken. The actual rules are:
///
/// |Prefix |Postfix |`self` taken |
/// |-------|------------|----------------------|
/// |`as_` | none |`&self` or `&mut self`|
/// |`from_`| none | none |
/// |`into_`| none |`self` |
/// |`is_` | none |`&self` or none |
/// |`to_` | `_mut` |`&mut &self` |
/// |`to_` | not `_mut` |`&self` |
/// |Prefix |Postfix |`self` taken | `self` type |
/// |-------|------------|-----------------------|--------------|
/// |`as_` | none |`&self` or `&mut self` | any |
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm wondering if it wouldn't be more clear to have n/a instead of none where the Postfix doesn't matter.

Copy link
Contributor

Choose a reason for hiding this comment

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

I personally don't think an abbreviation of "not applicable" is clearer than "none" when there is no suffix, but that may be my dislike of needless abbreviations.

/// |`from_`| none | none | any |
/// |`into_`| none |`self` | any |
/// |`is_` | none |`&self` or none | any |
/// |`to_` | `_mut` |`&mut self` | any |
/// |`to_` | not `_mut` |`self` | `Copy` |
/// |`to_` | not `_mut` |`&self` | not `Copy` |
///
/// Please find more info here:
/// https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
///
/// **Why is this bad?** Consistency breeds readability. If you follow the
/// conventions, your users won't be surprised that they, e.g., need to supply a
Expand Down Expand Up @@ -1837,10 +1841,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
let item = cx.tcx.hir().expect_item(parent);
let self_ty = cx.tcx.type_of(item.def_id);

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

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

then {
if cx.access_levels.is_exported(impl_item.hir_id()) {
// if this impl block implements a trait, lint in trait definition instead
if !implements_trait && cx.access_levels.is_exported(impl_item.hir_id()) {
// check missing trait implementations
for method_config in &TRAIT_METHODS {
if name == method_config.method_name &&
Expand Down Expand Up @@ -1891,11 +1893,17 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
item.vis.node.is_pub(),
self_ty,
first_arg_ty,
first_arg.pat.span
first_arg.pat.span,
false
);
}
}

// if this impl block implements a trait, lint in trait definition instead
if implements_trait {
return;
}

if let hir::ImplItemKind::Fn(_, _) = impl_item.kind {
let ret_ty = return_ty(cx, impl_item.hir_id());

Expand Down Expand Up @@ -1947,7 +1955,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
false,
self_ty,
first_arg_ty,
first_arg_span
first_arg_span,
true
);
}
}
Expand Down
74 changes: 42 additions & 32 deletions clippy_lints/src/methods/wrong_self_convention.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::methods::SelfKind;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_copy;
use rustc_lint::LateContext;
use rustc_middle::ty::TyS;
use rustc_span::source_map::Span;
Expand All @@ -9,32 +10,40 @@ use super::WRONG_PUB_SELF_CONVENTION;
use super::WRONG_SELF_CONVENTION;

#[rustfmt::skip]
const CONVENTIONS: [(&[Convention], &[SelfKind]); 8] = [
const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [
(&[Convention::Eq("new")], &[SelfKind::No]),
(&[Convention::StartsWith("as_")], &[SelfKind::Ref, SelfKind::RefMut]),
(&[Convention::StartsWith("from_")], &[SelfKind::No]),
(&[Convention::StartsWith("into_")], &[SelfKind::Value]),
(&[Convention::StartsWith("is_")], &[SelfKind::Ref, SelfKind::No]),
(&[Convention::Eq("to_mut")], &[SelfKind::RefMut]),
(&[Convention::StartsWith("to_"), Convention::EndsWith("_mut")], &[SelfKind::RefMut]),
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut")], &[SelfKind::Ref]),

// Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types).
// Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]),
(&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), Convention::ImplementsTrait(false)], &[SelfKind::Value]),
];

enum Convention {
Eq(&'static str),
StartsWith(&'static str),
EndsWith(&'static str),
NotEndsWith(&'static str),
IsSelfTypeCopy(bool),
ImplementsTrait(bool),
}

impl Convention {
#[must_use]
fn check(&self, other: &str) -> bool {
fn check<'tcx>(&self, cx: &LateContext<'tcx>, self_ty: &'tcx TyS<'tcx>, other: &str, is_trait_def: bool) -> bool {
match *self {
Self::Eq(this) => this == other,
Self::StartsWith(this) => other.starts_with(this) && this != other,
Self::EndsWith(this) => other.ends_with(this) && this != other,
Self::NotEndsWith(this) => !Self::EndsWith(this).check(other),
Self::NotEndsWith(this) => !Self::EndsWith(this).check(cx, self_ty, other, is_trait_def),
Self::IsSelfTypeCopy(is_true) => is_true == is_copy(cx, self_ty),
Self::ImplementsTrait(is_true) => is_true == is_trait_def,
}
}
}
Expand All @@ -46,6 +55,10 @@ impl fmt::Display for Convention {
Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)),
Self::EndsWith(this) => '*'.fmt(f).and_then(|_| this.fmt(f)),
Self::NotEndsWith(this) => '~'.fmt(f).and_then(|_| this.fmt(f)),
Self::IsSelfTypeCopy(is_true) => format!("self type is {} Copy", if is_true { "" } else { "not" }).fmt(f),
Self::ImplementsTrait(is_true) => {
format!("Method {} implement a trait", if is_true { "" } else { "do not" }).fmt(f)
},
}
}
}
Expand All @@ -57,45 +70,42 @@ pub(super) fn check<'tcx>(
self_ty: &'tcx TyS<'tcx>,
first_arg_ty: &'tcx TyS<'tcx>,
first_arg_span: Span,
is_trait_item: bool,
) {
let lint = if is_pub {
WRONG_PUB_SELF_CONVENTION
} else {
WRONG_SELF_CONVENTION
};
if let Some((conventions, self_kinds)) = &CONVENTIONS
.iter()
.find(|(convs, _)| convs.iter().all(|conv| conv.check(item_name)))
{
if let Some((conventions, self_kinds)) = &CONVENTIONS.iter().find(|(convs, _)| {
convs
.iter()
.all(|conv| conv.check(cx, self_ty, item_name, is_trait_item))
}) {
if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
let suggestion = {
if conventions.len() > 1 {
let special_case = {
// Don't mention `NotEndsWith` when there is also `StartsWith` convention present
if conventions.len() == 2 {
match conventions {
[Convention::StartsWith(starts_with), Convention::NotEndsWith(_)]
| [Convention::NotEndsWith(_), Convention::StartsWith(starts_with)] => {
Some(format!("methods called `{}`", Convention::StartsWith(starts_with)))
},
_ => None,
}
} else {
None
}
};

if let Some(suggestion) = special_case {
suggestion
} else {
let s = conventions
// Don't mention `NotEndsWith` when there is also `StartsWith` convention present
let cut_ends_with_conv = conventions.iter().any(|conv| matches!(conv, Convention::StartsWith(_)))
&& conventions
.iter()
.map(|c| format!("`{}`", &c.to_string()))
.collect::<Vec<_>>()
.join(" and ");
.any(|conv| matches!(conv, Convention::NotEndsWith(_)));

let s = conventions
.iter()
.filter_map(|conv| {
if (cut_ends_with_conv && matches!(conv, Convention::NotEndsWith(_)))
|| matches!(conv, Convention::ImplementsTrait(_))
{
None
} else {
Some(format!("`{}`", &conv.to_string()))
}
})
.collect::<Vec<_>>()
.join(" and ");

format!("methods called like this: ({})", &s)
}
format!("methods with the following characteristics: ({})", &s)
} else {
format!("methods called `{}`", &conventions[0])
}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/use_self.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ mod lifetimes {

mod issue2894 {
trait IntoBytes {
fn to_bytes(&self) -> Vec<u8>;
fn to_bytes(self) -> Vec<u8>;
}

// This should not be linted
Copy link
Contributor

Choose a reason for hiding this comment

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

If this comment is correct, then why the #[allow(..)] below?

impl IntoBytes for u8 {
fn to_bytes(&self) -> Vec<u8> {
vec![*self]
fn to_bytes(self) -> Vec<u8> {
vec![self]
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/use_self.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ mod lifetimes {

mod issue2894 {
trait IntoBytes {
fn to_bytes(&self) -> Vec<u8>;
fn to_bytes(self) -> Vec<u8>;
}

// This should not be linted
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here. I get that it's probably a different lint that should not trigger, but it still feels wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the comment says that use_self shouldn't trigger.
I added the #[allow(clippy::wrong_self_convention)] to suppress the warning that occurred there, after current changes. I wasn't sure whether I should adjust the self convention there.
Agree it's better to adjust that uitest to the proper self convention, which I did in the next commit.

impl IntoBytes for u8 {
fn to_bytes(&self) -> Vec<u8> {
vec![*self]
fn to_bytes(self) -> Vec<u8> {
vec![self]
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tests/ui/wrong_self_convention.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,35 @@ mod issue6307 {
fn to_mut(&mut self);
}
}

mod issue6727 {
trait ToU64 {
fn to_u64(self) -> u64;
fn to_u64_v2(&self) -> u64;
}

#[derive(Clone, Copy)]
struct FooCopy;

impl ToU64 for FooCopy {
fn to_u64(self) -> u64 {
1
}
// trigger lint
fn to_u64_v2(&self) -> u64 {
1
}
}

struct FooNoCopy;

impl ToU64 for FooNoCopy {
// trigger lint
fn to_u64(self) -> u64 {
2
}
fn to_u64_v2(&self) -> u64 {
2
}
}
}
36 changes: 18 additions & 18 deletions tests/ui/wrong_self_convention.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ LL | fn is_i32(self) {}
|
= help: consider choosing a less ambiguous name

error: methods called `to_*` usually take self by reference
error: methods with the following characteristics: (`to_*` and `self type is not Copy`) usually take self by reference
--> $DIR/wrong_self_convention.rs:42:15
|
LL | fn to_i32(self) {}
Expand Down Expand Up @@ -79,7 +79,7 @@ LL | pub fn is_i64(self) {}
|
= help: consider choosing a less ambiguous name

error: methods called `to_*` usually take self by reference
error: methods with the following characteristics: (`to_*` and `self type is not Copy`) usually take self by reference
--> $DIR/wrong_self_convention.rs:49:19
|
LL | pub fn to_i64(self) {}
Expand Down Expand Up @@ -119,14 +119,6 @@ LL | fn is_i32(self) {}
|
= help: consider choosing a less ambiguous name

error: methods called `to_*` usually take self by reference
--> $DIR/wrong_self_convention.rs:102:19
|
LL | fn to_i32(self) {}
| ^^^^
|
= help: consider choosing a less ambiguous name

error: methods called `from_*` usually take no self
--> $DIR/wrong_self_convention.rs:104:21
|
Expand Down Expand Up @@ -159,14 +151,6 @@ LL | fn is_i32(self);
|
= help: consider choosing a less ambiguous name

error: methods called `to_*` usually take self by reference
--> $DIR/wrong_self_convention.rs:126:19
|
LL | fn to_i32(self);
| ^^^^
|
= help: consider choosing a less ambiguous name

error: methods called `from_*` usually take no self
--> $DIR/wrong_self_convention.rs:128:21
|
Expand All @@ -191,5 +175,21 @@ LL | fn from_i32(self);
|
= help: consider choosing a less ambiguous name

error: methods with the following characteristics: (`to_*` and `self type is Copy`) usually take self by value
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking once more at the resulting error message, this gave me pause: "`self type is Copy`" should probably read "`self` type is `Copy`" (note the double space and the backticks).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, thanks!

--> $DIR/wrong_self_convention.rs:181:22
|
LL | fn to_u64_v2(&self) -> u64 {
| ^^^^^
|
= help: consider choosing a less ambiguous name

error: methods with the following characteristics: (`to_*` and `self type is not Copy`) usually take self by reference
--> $DIR/wrong_self_convention.rs:190:19
|
LL | fn to_u64(self) -> u64 {
| ^^^^
|
= help: consider choosing a less ambiguous name

error: aborting due to 24 previous errors

4 changes: 2 additions & 2 deletions tests/ui/wrong_self_conventions_mut.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: methods called `to_*` usually take self by reference
error: methods with the following characteristics: (`to_*` and `self type is not Copy`) usually take self by reference
--> $DIR/wrong_self_conventions_mut.rs:15:24
|
LL | pub fn to_many(&mut self) -> Option<&mut [T]> {
Expand All @@ -7,7 +7,7 @@ LL | pub fn to_many(&mut self) -> Option<&mut [T]> {
= note: `-D clippy::wrong-self-convention` implied by `-D warnings`
= help: consider choosing a less ambiguous name

error: methods called like this: (`to_*` and `*_mut`) usually take self by mutable reference
error: methods with the following characteristics: (`to_*` and `*_mut`) usually take self by mutable reference
--> $DIR/wrong_self_conventions_mut.rs:23:28
|
LL | pub fn to_many_mut(&self) -> Option<&[T]> {
Expand Down