Skip to content

Commit 346936a

Browse files
authored
Merge pull request #1513 from sinkuu/identical_conversion
Add identity_conversion lint
2 parents 1ab7f36 + 1b1b41a commit 346936a

File tree

6 files changed

+185
-2
lines changed

6 files changed

+185
-2
lines changed
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use rustc::lint::*;
2+
use rustc::hir::*;
3+
use syntax::ast::NodeId;
4+
use utils::{in_macro, match_def_path, match_trait_method, same_tys, snippet, span_lint_and_then};
5+
use utils::{opt_def_id, paths, resolve_node};
6+
7+
/// **What it does:** Checks for always-identical `Into`/`From` conversions.
8+
///
9+
/// **Why is this bad?** Redundant code.
10+
///
11+
/// **Known problems:** None.
12+
///
13+
/// **Example:**
14+
/// ```rust
15+
/// // format!() returns a `String`
16+
/// let s: String = format!("hello").into();
17+
/// ```
18+
declare_lint! {
19+
pub IDENTITY_CONVERSION,
20+
Warn,
21+
"using always-identical `Into`/`From` conversions"
22+
}
23+
24+
#[derive(Default)]
25+
pub struct IdentityConversion {
26+
try_desugar_arm: Vec<NodeId>,
27+
}
28+
29+
impl LintPass for IdentityConversion {
30+
fn get_lints(&self) -> LintArray {
31+
lint_array!(IDENTITY_CONVERSION)
32+
}
33+
}
34+
35+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IdentityConversion {
36+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
37+
if in_macro(e.span) {
38+
return;
39+
}
40+
41+
if Some(&e.id) == self.try_desugar_arm.last() {
42+
return;
43+
}
44+
45+
match e.node {
46+
ExprMatch(_, ref arms, MatchSource::TryDesugar) => {
47+
let e = match arms[0].body.node {
48+
ExprRet(Some(ref e)) | ExprBreak(_, Some(ref e)) => e,
49+
_ => return,
50+
};
51+
if let ExprCall(_, ref args) = e.node {
52+
self.try_desugar_arm.push(args[0].id);
53+
} else {
54+
return;
55+
}
56+
},
57+
58+
ExprMethodCall(ref name, .., ref args) => {
59+
if match_trait_method(cx, e, &paths::INTO[..]) && &*name.name.as_str() == "into" {
60+
let a = cx.tables.expr_ty(e);
61+
let b = cx.tables.expr_ty(&args[0]);
62+
if same_tys(cx, a, b) {
63+
let sugg = snippet(cx, args[0].span, "<expr>").into_owned();
64+
span_lint_and_then(cx, IDENTITY_CONVERSION, e.span, "identical conversion", |db| {
65+
db.span_suggestion(e.span, "consider removing `.into()`", sugg);
66+
});
67+
}
68+
}
69+
},
70+
71+
ExprCall(ref path, ref args) => if let ExprPath(ref qpath) = path.node {
72+
if let Some(def_id) = opt_def_id(resolve_node(cx, qpath, path.hir_id)) {
73+
if match_def_path(cx.tcx, def_id, &paths::FROM_FROM[..]) {
74+
let a = cx.tables.expr_ty(e);
75+
let b = cx.tables.expr_ty(&args[0]);
76+
if same_tys(cx, a, b) {
77+
let sugg = snippet(cx, args[0].span, "<expr>").into_owned();
78+
let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
79+
span_lint_and_then(cx, IDENTITY_CONVERSION, e.span, "identical conversion", |db| {
80+
db.span_suggestion(e.span, &sugg_msg, sugg);
81+
});
82+
}
83+
}
84+
}
85+
},
86+
87+
_ => {},
88+
}
89+
}
90+
91+
fn check_expr_post(&mut self, _: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
92+
if Some(&e.id) == self.try_desugar_arm.last() {
93+
self.try_desugar_arm.pop();
94+
}
95+
}
96+
}

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ pub mod eval_order_dependence;
9393
pub mod format;
9494
pub mod formatting;
9595
pub mod functions;
96+
pub mod identity_conversion;
9697
pub mod identity_op;
9798
pub mod if_let_redundant_pattern_matching;
9899
pub mod if_not_else;
@@ -331,6 +332,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
331332
reg.register_late_lint_pass(box bytecount::ByteCount);
332333
reg.register_late_lint_pass(box infinite_iter::Pass);
333334
reg.register_late_lint_pass(box invalid_ref::InvalidRef);
335+
reg.register_late_lint_pass(box identity_conversion::IdentityConversion::default());
334336

335337
reg.register_lint_group("clippy_restrictions", vec![
336338
arithmetic::FLOAT_ARITHMETIC,
@@ -431,6 +433,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
431433
formatting::SUSPICIOUS_ELSE_FORMATTING,
432434
functions::NOT_UNSAFE_PTR_ARG_DEREF,
433435
functions::TOO_MANY_ARGUMENTS,
436+
identity_conversion::IDENTITY_CONVERSION,
434437
identity_op::IDENTITY_OP,
435438
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
436439
infinite_iter::INFINITE_ITER,

clippy_lints/src/utils/paths.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ pub const DOUBLE_ENDED_ITERATOR: [&'static str; 4] = ["core", "iter", "traits",
2626
pub const DROP: [&'static str; 3] = ["core", "mem", "drop"];
2727
pub const FMT_ARGUMENTS_NEWV1: [&'static str; 4] = ["core", "fmt", "Arguments", "new_v1"];
2828
pub const FMT_ARGUMENTV1_NEW: [&'static str; 4] = ["core", "fmt", "ArgumentV1", "new"];
29+
pub const FROM_FROM: [&'static str; 4] = ["core", "convert", "From", "from"];
2930
pub const HASH: [&'static str; 2] = ["hash", "Hash"];
3031
pub const HASHMAP: [&'static str; 5] = ["std", "collections", "hash", "map", "HashMap"];
3132
pub const HASHMAP_ENTRY: [&'static str; 5] = ["std", "collections", "hash", "map", "Entry"];
3233
pub const HASHSET: [&'static str; 5] = ["std", "collections", "hash", "set", "HashSet"];
3334
pub const INIT: [&'static str; 4] = ["core", "intrinsics", "", "init"];
35+
pub const INTO: [&'static str; 3] = ["core", "convert", "Into"];
3436
pub const INTO_ITERATOR: [&'static str; 4] = ["core", "iter", "traits", "IntoIterator"];
3537
pub const IO_PRINT: [&'static str; 4] = ["std", "io", "stdio", "_print"];
3638
pub const IO_READ: [&'static str; 3] = ["std", "io", "Read"];

clippy_lints/src/vec.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ fn check_vec_macro<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, vec_args: &higher::VecA
6767
.eval(len)
6868
.is_ok()
6969
{
70-
format!("&[{}; {}]", snippet(cx, elem.span, "elem"), snippet(cx, len.span, "len")).into()
70+
format!("&[{}; {}]", snippet(cx, elem.span, "elem"), snippet(cx, len.span, "len"))
7171
} else {
7272
return;
7373
}
7474
},
7575
higher::VecArgs::Vec(args) => if let Some(last) = args.iter().last() {
7676
let span = args[0].span.to(last.span);
7777

78-
format!("&[{}]", snippet(cx, span, "..")).into()
78+
format!("&[{}]", snippet(cx, span, ".."))
7979
} else {
8080
"&[]".into()
8181
},

tests/ui/identity_conversion.rs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![deny(identity_conversion)]
2+
3+
fn test_generic<T: Copy>(val: T) -> T {
4+
let _ = T::from(val);
5+
val.into()
6+
}
7+
8+
fn test_generic2<T: Copy + Into<i32> + Into<U>, U: From<T>>(val: T) {
9+
// ok
10+
let _: i32 = val.into();
11+
let _: U = val.into();
12+
let _ = U::from(val);
13+
}
14+
15+
fn test_questionmark() -> Result<(), ()> {
16+
{
17+
let _: i32 = 0i32.into();
18+
Ok(Ok(()))
19+
}??;
20+
Ok(())
21+
}
22+
23+
fn main() {
24+
test_generic(10i32);
25+
test_generic2::<i32, i32>(10i32);
26+
test_questionmark().unwrap();
27+
28+
let _: String = "foo".into();
29+
let _: String = From::from("foo");
30+
let _ = String::from("foo");
31+
#[allow(identity_conversion)]
32+
{
33+
let _: String = "foo".into();
34+
let _ = String::from("foo");
35+
}
36+
37+
let _: String = "foo".to_string().into();
38+
let _: String = From::from("foo".to_string());
39+
let _ = String::from("foo".to_string());
40+
}

tests/ui/identity_conversion.stderr

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: identical conversion
2+
--> $DIR/identity_conversion.rs:4:13
3+
|
4+
4 | let _ = T::from(val);
5+
| ^^^^^^^^^^^^ help: consider removing `T::from()`: `val`
6+
|
7+
note: lint level defined here
8+
--> $DIR/identity_conversion.rs:1:9
9+
|
10+
1 | #![deny(identity_conversion)]
11+
| ^^^^^^^^^^^^^^^^^^^
12+
13+
error: identical conversion
14+
--> $DIR/identity_conversion.rs:5:5
15+
|
16+
5 | val.into()
17+
| ^^^^^^^^^^ help: consider removing `.into()`: `val`
18+
19+
error: identical conversion
20+
--> $DIR/identity_conversion.rs:17:22
21+
|
22+
17 | let _: i32 = 0i32.into();
23+
| ^^^^^^^^^^^ help: consider removing `.into()`: `0i32`
24+
25+
error: identical conversion
26+
--> $DIR/identity_conversion.rs:37:21
27+
|
28+
37 | let _: String = "foo".to_string().into();
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into()`: `"foo".to_string()`
30+
31+
error: identical conversion
32+
--> $DIR/identity_conversion.rs:38:21
33+
|
34+
38 | let _: String = From::from("foo".to_string());
35+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `From::from()`: `"foo".to_string()`
36+
37+
error: identical conversion
38+
--> $DIR/identity_conversion.rs:39:13
39+
|
40+
39 | let _ = String::from("foo".to_string());
41+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `String::from()`: `"foo".to_string()`
42+

0 commit comments

Comments
 (0)