Skip to content

Commit c1b04e6

Browse files
committed
Added code that does a lint
1 parent 7427065 commit c1b04e6

File tree

5 files changed

+169
-0
lines changed

5 files changed

+169
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,7 @@ Released 2018-09-13
15081508
[`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone
15091509
[`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry
15101510
[`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten
1511+
[`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity
15111512
[`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or
15121513
[`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref
15131514
[`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ mod main_recursion;
252252
mod manual_async_fn;
253253
mod manual_non_exhaustive;
254254
mod map_clone;
255+
mod map_identity;
255256
mod map_unit_fn;
256257
mod match_on_vec_items;
257258
mod matches;
@@ -631,6 +632,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
631632
&manual_async_fn::MANUAL_ASYNC_FN,
632633
&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
633634
&map_clone::MAP_CLONE,
635+
&map_identity::MAP_IDENTITY,
634636
&map_unit_fn::OPTION_MAP_UNIT_FN,
635637
&map_unit_fn::RESULT_MAP_UNIT_FN,
636638
&match_on_vec_items::MATCH_ON_VEC_ITEMS,
@@ -1080,6 +1082,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10801082
});
10811083
store.register_early_pass(|| box unnested_or_patterns::UnnestedOrPatterns);
10821084
store.register_late_pass(|| box macro_use::MacroUseImports::default());
1085+
store.register_late_pass(|| box map_identity::MapIdentity);
10831086

10841087
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10851088
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1295,6 +1298,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12951298
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
12961299
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
12971300
LintId::of(&map_clone::MAP_CLONE),
1301+
LintId::of(&map_identity::MAP_IDENTITY),
12981302
LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN),
12991303
LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN),
13001304
LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
@@ -1492,6 +1496,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14921496
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
14931497
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
14941498
LintId::of(&map_clone::MAP_CLONE),
1499+
LintId::of(&map_identity::MAP_IDENTITY),
14951500
LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
14961501
LintId::of(&matches::MATCH_OVERLAPPING_ARM),
14971502
LintId::of(&matches::MATCH_REF_PATS),

clippy_lints/src/map_identity.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use crate::utils::{
2+
is_adjusted, is_type_diagnostic_item, match_trait_method, match_var, paths, remove_blocks, span_lint_and_sugg,
3+
};
4+
use if_chain::if_chain;
5+
use rustc_errors::Applicability;
6+
use rustc_lint::{LateLintPass, LateContext};
7+
use rustc_session::{declare_lint_pass, declare_tool_lint};
8+
use rustc_hir::*;
9+
10+
declare_clippy_lint! {
11+
/// **What it does:** Checks for instances `iterator.map(f)` where `f` is the identity function.
12+
///
13+
/// **Why is this bad?** It can be written more concisely as `iterator`, without the call to `map`.
14+
///
15+
/// **Known problems:** None.
16+
///
17+
/// **Example:**
18+
///
19+
/// ```rust
20+
/// // example code where clippy issues a warning
21+
/// ```
22+
/// Use instead:
23+
/// ```rust
24+
/// // example code which does not raise clippy warning
25+
/// ```
26+
pub MAP_IDENTITY,
27+
style,
28+
"default lint description"
29+
}
30+
31+
declare_lint_pass!(MapIdentity => [MAP_IDENTITY]);
32+
33+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MapIdentity {
34+
fn check_expr(&mut self, cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
35+
// println!("start");
36+
if expr.span.from_expansion() {
37+
return;
38+
}
39+
40+
if_chain! {
41+
if let Some(func) = get_map_argument(cx, expr);
42+
if let Some(body) = get_body(cx, func);
43+
if is_identity_function(cx, body);
44+
then {
45+
span_lint_and_sugg(
46+
cx,
47+
MAP_IDENTITY,
48+
expr.span,
49+
"Unnecessary map of the identity function",
50+
"Remove the `map` call",
51+
String::new(),
52+
Applicability::MachineApplicable
53+
)
54+
}
55+
}
56+
}
57+
}
58+
59+
60+
/// Returns the function passed into iterator.map() if the expression is a method call to
61+
/// iterator.map(). Otherwise, returns None.
62+
fn get_map_argument<'a>(cx: &LateContext<'_, '_>, expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> {
63+
if_chain! {
64+
if let ExprKind::MethodCall(ref method, _, ref args) = expr.kind;
65+
if args.len() == 2 && method.ident.as_str() == "map";
66+
let caller_ty = cx.tables.expr_ty(&args[0]);
67+
if match_trait_method(cx, expr, &paths::ITERATOR)
68+
|| is_type_diagnostic_item(cx, caller_ty, sym!(result_type))
69+
|| is_type_diagnostic_item(cx, caller_ty, sym!(option_type));
70+
then {
71+
Some(&args[1])
72+
} else {
73+
None
74+
}
75+
}
76+
}
77+
78+
/// Determines if a function is the identity function (in a naive way)
79+
fn is_identity_function(cx: &LateContext<'_, '_>, func: &Body<'_>) -> bool {
80+
let params = func.params;
81+
let body = remove_blocks(&func.value);
82+
83+
// if there's less/more than one parameter, then it is not the identity function
84+
if params.len() != 1 {
85+
return false;
86+
}
87+
88+
match body.kind {
89+
ExprKind::Path(QPath::Resolved(None, _)) => match_expr_param(cx, body, params[0].pat),
90+
ExprKind::Ret(Some(ref ret_val)) => match_expr_param(cx, ret_val, params[0].pat),
91+
ExprKind::Block(ref block, _) => {
92+
if_chain! {
93+
if block.stmts.len() == 1;
94+
if let StmtKind::Semi(ref expr) | StmtKind::Expr(ref expr) = block.stmts[0].kind;
95+
if let ExprKind::Ret(Some(ref ret_val)) = expr.kind;
96+
then {
97+
match_expr_param(cx, ret_val, params[0].pat)
98+
} else {
99+
false
100+
}
101+
}
102+
}
103+
_ => false
104+
}
105+
}
106+
107+
/// Returns an associated function/closure body from an expression
108+
fn get_body<'a>(cx: &LateContext<'a, '_>, expr: &'a Expr<'a>) -> Option<&'a Body<'a>> {
109+
match expr.kind {
110+
ExprKind::Closure(_, _, body_id, _, _) => Some(cx.tcx.hir().body(body_id)),
111+
ExprKind::Path(QPath::Resolved(_, ref path)) => path_to_body(cx, path),
112+
_ => None
113+
}
114+
}
115+
116+
/// Returns the function body associated with a path
117+
fn path_to_body<'a>(cx: &LateContext<'a, '_>, path: &'a Path<'a>) -> Option<&'a Body<'a>> {
118+
if let def::Res::Def(_, def_id) = path.res {
119+
def_id.as_local()
120+
.and_then(|local_id| cx.tcx.hir().opt_local_def_id_to_hir_id(local_id))
121+
.and_then(|hir_id| cx.tcx.hir().maybe_body_owned_by(hir_id))
122+
.map(|body_id| cx.tcx.hir().body(body_id))
123+
} else {
124+
None
125+
}
126+
}
127+
128+
/// Determines if an expression (syntactically) returns the same thing as a parameter's pattern
129+
fn match_expr_param(cx: &LateContext<'_, '_>, expr: &Expr<'_>, pat: &Pat<'_>) -> bool {
130+
if let PatKind::Binding(_, _, ident, _) = pat.kind {
131+
match_var(expr, ident.name) && !(cx.tables.hir_owner == Some(expr.hir_id.owner) && is_adjusted(cx, expr))
132+
} else {
133+
false
134+
}
135+
}

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
11441144
deprecation: None,
11451145
module: "methods",
11461146
},
1147+
Lint {
1148+
name: "map_identity",
1149+
group: "style",
1150+
desc: "default lint description",
1151+
deprecation: None,
1152+
module: "map_identity",
1153+
},
11471154
Lint {
11481155
name: "map_unwrap_or",
11491156
group: "pedantic",

tests/ui/map_identity.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::map_identity)]
2+
#![allow(clippy::needless_return)]
3+
4+
fn main() {
5+
let x: [u16; 3] = [1, 2, 3];
6+
// should lint
7+
let _: Vec<_> = x.iter().map(|x| { return x }).collect();
8+
let _: Vec<_> = x.iter().map(identity).collect();
9+
let _: Option<u8> = Some(3).map(|x| x);
10+
// should not lint
11+
let _: Vec<_> = x.iter().map(|x| 2*x).collect();
12+
let _: Vec<_> = x.iter().map(not_identity).collect();
13+
}
14+
15+
fn identity(x: &u16) -> &u16 {
16+
return x;
17+
}
18+
19+
fn not_identity(x: &u16) -> u16 {
20+
*x
21+
}

0 commit comments

Comments
 (0)