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

Implement lint for inherent to_string() method. #4259

Merged
merged 3 commits into from
Jul 17, 2019
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,8 @@ Released 2018-09-13
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
[`infallible_destructuring_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#infallible_destructuring_match
[`infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#infinite_iter
[`inherent_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string
[`inherent_to_string_shadow_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#inherent_to_string_shadow_display
[`inline_always`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_always
[`inline_fn_without_body`]: https://rust-lang.github.io/rust-clippy/master/index.html#inline_fn_without_body
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are 306 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 308 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:

Expand Down
154 changes: 154 additions & 0 deletions clippy_lints/src/inherent_to_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use if_chain::if_chain;
use rustc::hir::{ImplItem, ImplItemKind};
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};

use crate::utils::{
get_trait_def_id, implements_trait, in_macro_or_desugar, match_type, paths, return_ty, span_help_and_lint,
trait_ref_of_method, walk_ptrs_ty,
};

declare_clippy_lint! {
/// **What id does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String`.
///
/// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred.
///
/// **Known problems:** None
///
/// ** Example:**
///
/// ```rust
/// // Bad
/// pub struct A;
///
/// impl A {
/// pub fn to_string(&self) -> String {
/// "I am A".to_string()
/// }
/// }
/// ```
///
/// ```rust
/// // Good
flip1995 marked this conversation as resolved.
Show resolved Hide resolved
/// use std::fmt;
///
/// pub struct A;
///
/// impl fmt::Display for A {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "I am A")
/// }
/// }
/// ```
pub INHERENT_TO_STRING,
style,
"type implements inherent method `to_string()`, but should instead implement the `Display` trait"
}

declare_clippy_lint! {
/// **What id does:** Checks for the definition of inherent methods with a signature of `to_string(&self) -> String` and if the type implementing this method also implements the `Display` trait.
///
/// **Why is this bad?** This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`.
///
/// **Known problems:** None
///
/// ** Example:**
///
/// ```rust
/// // Bad
/// use std::fmt;
///
/// pub struct A;
///
/// impl A {
/// pub fn to_string(&self) -> String {
/// "I am A".to_string()
/// }
/// }
///
/// impl fmt::Display for A {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "I am A, too")
/// }
/// }
/// ```
///
/// ```rust
/// // Good
/// use std::fmt;
///
/// pub struct A;
///
/// impl fmt::Display for A {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "I am A")
/// }
/// }
/// ```
pub INHERENT_TO_STRING_SHADOW_DISPLAY,
correctness,
"type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait "
}

declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_SHADOW_DISPLAY]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InherentToString {
fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem) {
if in_macro_or_desugar(impl_item.span) {
return;
}

if_chain! {
// Check if item is a method, called to_string and has a parameter 'self'
if let ImplItemKind::Method(ref signature, _) = impl_item.node;
if impl_item.ident.name.as_str() == "to_string";
let decl = &signature.decl;
if decl.implicit_self.has_implicit_self();

// Check if return type is String
if match_type(cx, return_ty(cx, impl_item.hir_id), &paths::STRING);

// Filters instances of to_string which are required by a trait
if trait_ref_of_method(cx, impl_item.hir_id).is_none();

then {
show_lint(cx, impl_item);
}
}
}
}

fn show_lint(cx: &LateContext<'_, '_>, item: &ImplItem) {
let display_trait_id =
get_trait_def_id(cx, &["core", "fmt", "Display"]).expect("Failed to get trait ID of `Display`!");

// Get the real type of 'self'
let fn_def_id = cx.tcx.hir().local_def_id(item.hir_id);
let self_type = cx.tcx.fn_sig(fn_def_id).input(0);
let self_type = walk_ptrs_ty(self_type.skip_binder());

// Emit either a warning or an error
if implements_trait(cx, self_type, display_trait_id, &[]) {
span_help_and_lint(
cx,
INHERENT_TO_STRING_SHADOW_DISPLAY,
item.span,
&format!(
"type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
self_type.to_string()
),
&format!("remove the inherent method from type `{}`", self_type.to_string())
);
} else {
span_help_and_lint(
cx,
INHERENT_TO_STRING,
item.span,
&format!(
"implementation of inherent method `to_string(&self) -> String` for type `{}`",
self_type.to_string()
),
&format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
);
}
}
6 changes: 6 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ pub mod indexing_slicing;
pub mod infallible_destructuring_match;
pub mod infinite_iter;
pub mod inherent_impl;
pub mod inherent_to_string;
pub mod inline_fn_without_body;
pub mod int_plus_one;
pub mod integer_division;
Expand Down Expand Up @@ -585,6 +586,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
reg.register_late_lint_pass(box path_buf_push_overwrite::PathBufPushOverwrite);
reg.register_late_lint_pass(box checked_conversions::CheckedConversions);
reg.register_late_lint_pass(box integer_division::IntegerDivision);
reg.register_late_lint_pass(box inherent_to_string::InherentToString);

reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
arithmetic::FLOAT_ARITHMETIC,
Expand Down Expand Up @@ -725,6 +727,8 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
indexing_slicing::OUT_OF_BOUNDS_INDEXING,
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
infinite_iter::INFINITE_ITER,
inherent_to_string::INHERENT_TO_STRING,
inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
int_plus_one::INT_PLUS_ONE,
invalid_ref::INVALID_REF,
Expand Down Expand Up @@ -913,6 +917,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,
infallible_destructuring_match::INFALLIBLE_DESTRUCTURING_MATCH,
inherent_to_string::INHERENT_TO_STRING,
len_zero::LEN_WITHOUT_IS_EMPTY,
len_zero::LEN_ZERO,
let_if_seq::USELESS_LET_IF_SEQ,
Expand Down Expand Up @@ -1075,6 +1080,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
functions::NOT_UNSAFE_PTR_ARG_DEREF,
indexing_slicing::OUT_OF_BOUNDS_INDEXING,
infinite_iter::INFINITE_ITER,
inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY,
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
invalid_ref::INVALID_REF,
literal_representation::MISTYPED_LITERAL_SUFFIXES,
Expand Down
16 changes: 15 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 306] = [
pub const ALL_LINTS: [Lint; 308] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -728,6 +728,20 @@ pub const ALL_LINTS: [Lint; 306] = [
deprecation: None,
module: "infinite_iter",
},
Lint {
name: "inherent_to_string",
group: "style",
desc: "type implements inherent method `to_string()`, but should instead implement the `Display` trait",
deprecation: None,
module: "inherent_to_string",
},
Lint {
name: "inherent_to_string_shadow_display",
group: "correctness",
desc: "type implements inherent method `to_string()`, which gets shadowed by the implementation of the `Display` trait ",
deprecation: None,
module: "inherent_to_string",
},
Lint {
name: "inline_always",
group: "pedantic",
Expand Down
84 changes: 84 additions & 0 deletions tests/ui/inherent_to_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#![warn(clippy::inherent_to_string)]
#![deny(clippy::inherent_to_string_shadow_display)]

use std::fmt;

trait FalsePositive {
fn to_string(&self) -> String;
}

struct A;
struct B;
struct C;
struct D;
struct E;

impl A {
// Should be detected; emit warning
fn to_string(&self) -> String {
"A.to_string()".to_string()
}

// Should not be detected as it does not match the function signature
fn to_str(&self) -> String {
"A.to_str()".to_string()
}
}

// Should not be detected as it is a free function
fn to_string() -> String {
"free to_string()".to_string()
}

impl B {
// Should not be detected, wrong return type
fn to_string(&self) -> i32 {
42
}
}

impl C {
// Should be detected and emit error as C also implements Display
fn to_string(&self) -> String {
"C.to_string()".to_string()
}
}

impl fmt::Display for C {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "impl Display for C")
}
}

impl FalsePositive for D {
// Should not be detected, as it is a trait function
fn to_string(&self) -> String {
"impl FalsePositive for D".to_string()
}
}

impl E {
// Should not be detected, as it is not bound to an instance
fn to_string() -> String {
"E::to_string()".to_string()
}
}

fn main() {
let a = A;
a.to_string();
a.to_str();

to_string();

let b = B;
b.to_string();

let c = C;
C.to_string();

let d = D;
d.to_string();

E::to_string();
}
28 changes: 28 additions & 0 deletions tests/ui/inherent_to_string.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
error: implementation of inherent method `to_string(&self) -> String` for type `A`
--> $DIR/inherent_to_string.rs:18:5
|
LL | / fn to_string(&self) -> String {
LL | | "A.to_string()".to_string()
LL | | }
| |_____^
|
= note: `-D clippy::inherent-to-string` implied by `-D warnings`
= help: implement trait `Display` for type `A` instead

error: type `C` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`
--> $DIR/inherent_to_string.rs:42:5
|
LL | / fn to_string(&self) -> String {
LL | | "C.to_string()".to_string()
LL | | }
| |_____^
|
note: lint level defined here
--> $DIR/inherent_to_string.rs:2:9
|
LL | #![deny(clippy::inherent_to_string_shadow_display)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: remove the inherent method from type `C`

error: aborting due to 2 previous errors