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 all 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
47 changes: 28 additions & 19 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 Expand Up @@ -2339,10 +2348,10 @@ impl SelfKind {
#[must_use]
fn description(self) -> &'static str {
match self {
Self::Value => "self by value",
Self::Ref => "self by reference",
Self::RefMut => "self by mutable reference",
Self::No => "no self",
Self::Value => "`self` by value",
Self::Ref => "`self` by reference",
Self::RefMut => "`self` by mutable reference",
Self::No => "no `self`",
}
}
}
Expand Down
87 changes: 50 additions & 37 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,43 +10,58 @@ 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,
}
}
}

impl fmt::Display for Convention {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match *self {
Self::Eq(this) => this.fmt(f),
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::Eq(this) => format!("`{}`", this).fmt(f),
Self::StartsWith(this) => format!("`{}*`", this).fmt(f),
Self::EndsWith(this) => format!("`*{}`", this).fmt(f),
Self::NotEndsWith(this) => format!("`~{}`", this).fmt(f),
Self::IsSelfTypeCopy(is_true) => {
format!("`self` type is{} `Copy`", if is_true { "" } else { " not" }).fmt(f)
},
Self::ImplementsTrait(is_true) => {
let (negation, s_suffix) = if is_true { ("", "s") } else { (" does not", "") };
format!("Method{} implement{} a trait", negation, s_suffix).fmt(f)
},
}
}
}
Expand All @@ -57,47 +73,44 @@ 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(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])
format!("methods called {}", &conventions[0])
}
};

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/def_id_nocore.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: methods called `as_*` usually take self by reference or self by mutable reference
error: methods called `as_*` usually take `self` by reference or `self` by mutable reference
--> $DIR/def_id_nocore.rs:26:19
|
LL | pub fn as_ref(self) -> &'static str {
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
}
}
}
Loading