| use rustc::lint::*; |
| use rustc::hir::*; |
| use utils::{CLONE_PATH, OPTION_PATH}; |
| use utils::{is_adjusted, match_path, match_trait_method, match_type, snippet, span_help_and_lint, walk_ptrs_ty, |
| walk_ptrs_ty_depth}; |
| |
| /// **What it does:** This lint checks for mapping clone() over an iterator. |
| /// |
| /// **Why is this bad?** It makes the code less readable. |
| /// |
| /// **Known problems:** None |
| /// |
| /// **Example:** `x.map(|e| e.clone());` |
| declare_lint! { |
| pub MAP_CLONE, Warn, |
| "using `.map(|x| x.clone())` to clone an iterator or option's contents (recommends \ |
| `.cloned()` instead)" |
| } |
| |
| #[derive(Copy, Clone)] |
| pub struct MapClonePass; |
| |
| impl LateLintPass for MapClonePass { |
| fn check_expr(&mut self, cx: &LateContext, expr: &Expr) { |
| // call to .map() |
| if let ExprMethodCall(name, _, ref args) = expr.node { |
| if name.node.as_str() == "map" && args.len() == 2 { |
| match args[1].node { |
| ExprClosure(_, ref decl, ref blk) => { |
| if_let_chain! { |
| [ |
| // just one expression in the closure |
| blk.stmts.is_empty(), |
| let Some(ref closure_expr) = blk.expr, |
| // nothing special in the argument, besides reference bindings |
| // (e.g. .map(|&x| x) ) |
| let Some(arg_ident) = get_arg_name(&*decl.inputs[0].pat), |
| // the method is being called on a known type (option or iterator) |
| let Some(type_name) = get_type_name(cx, expr, &args[0]) |
| ], { |
| // look for derefs, for .map(|x| *x) |
| if only_derefs(cx, &*closure_expr, arg_ident) && |
| // .cloned() only removes one level of indirection, don't lint on more |
| walk_ptrs_ty_depth(cx.tcx.pat_ty(&*decl.inputs[0].pat)).1 == 1 |
| { |
| span_help_and_lint(cx, MAP_CLONE, expr.span, &format!( |
| "you seem to be using .map() to clone the contents of an {}, consider \ |
| using `.cloned()`", type_name), |
| &format!("try\n{}.cloned()", snippet(cx, args[0].span, ".."))); |
| } |
| // explicit clone() calls ( .map(|x| x.clone()) ) |
| else if let ExprMethodCall(clone_call, _, ref clone_args) = closure_expr.node { |
| if clone_call.node.as_str() == "clone" && |
| clone_args.len() == 1 && |
| match_trait_method(cx, closure_expr, &["core", "clone", "Clone"]) && |
| expr_eq_ident(&clone_args[0], arg_ident) |
| { |
| span_help_and_lint(cx, MAP_CLONE, expr.span, &format!( |
| "you seem to be using .map() to clone the contents of an {}, consider \ |
| using `.cloned()`", type_name), |
| &format!("try\n{}.cloned()", snippet(cx, args[0].span, ".."))); |
| } |
| } |
| } |
| } |
| } |
| ExprPath(_, ref path) => { |
| if match_path(path, &CLONE_PATH) { |
| let type_name = get_type_name(cx, expr, &args[0]).unwrap_or("_"); |
| span_help_and_lint(cx, |
| MAP_CLONE, |
| expr.span, |
| &format!("you seem to be using .map() to clone the contents of an \ |
| {}, consider using `.cloned()`", |
| type_name), |
| &format!("try\n{}.cloned()", snippet(cx, args[0].span, ".."))); |
| } |
| } |
| _ => (), |
| } |
| } |
| } |
| } |
| } |
| |
| fn expr_eq_ident(expr: &Expr, id: Ident) -> bool { |
| match expr.node { |
| ExprPath(None, ref path) => { |
| let arg_segment = [PathSegment { |
| identifier: id, |
| parameters: PathParameters::none(), |
| }]; |
| !path.global && path.segments[..] == arg_segment |
| } |
| _ => false, |
| } |
| } |
| |
| fn get_type_name(cx: &LateContext, expr: &Expr, arg: &Expr) -> Option<&'static str> { |
| if match_trait_method(cx, expr, &["core", "iter", "Iterator"]) { |
| Some("iterator") |
| } else if match_type(cx, walk_ptrs_ty(cx.tcx.expr_ty(arg)), &OPTION_PATH) { |
| Some("Option") |
| } else { |
| None |
| } |
| } |
| |
| fn get_arg_name(pat: &Pat) -> Option<Ident> { |
| match pat.node { |
| PatKind::Ident(_, ident, None) => Some(ident.node), |
| PatKind::Ref(ref subpat, _) => get_arg_name(subpat), |
| _ => None, |
| } |
| } |
| |
| fn only_derefs(cx: &LateContext, expr: &Expr, id: Ident) -> bool { |
| match expr.node { |
| ExprUnary(UnDeref, ref subexpr) if !is_adjusted(cx, subexpr) => only_derefs(cx, subexpr, id), |
| _ => expr_eq_ident(expr, id), |
| } |
| } |
| |
| impl LintPass for MapClonePass { |
| fn get_lints(&self) -> LintArray { |
| lint_array!(MAP_CLONE) |
| } |
| } |