From d71b418ac511ee604af9320e401a7ff3fd115482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sun, 16 Aug 2020 20:47:50 +0200 Subject: [PATCH] Moved to submodule, don't trigger if map_unwrap_or does --- clippy_lints/src/methods/mod.rs | 136 +++--------------- .../src/methods/unnecessary_lazy_eval.rs | 113 +++++++++++++++ 2 files changed, 132 insertions(+), 117 deletions(-) create mode 100644 clippy_lints/src/methods/unnecessary_lazy_eval.rs diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 70a6b1f50..bfc89a747 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3,6 +3,7 @@ mod inefficient_to_string; mod manual_saturating_arithmetic; mod option_map_unwrap_or; mod unnecessary_filter_map; +mod unnecessary_lazy_eval; use std::borrow::Cow; use std::fmt; @@ -1436,17 +1437,18 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ["expect", ..] => lint_expect(cx, expr, arg_lists[0]), ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), ["unwrap_or_else", "map"] => { - lint_lazy_eval(cx, expr, arg_lists[0], true, "unwrap_or"); - lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]); + if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]) { + unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or"); + } }, ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]), ["and_then", ..] => { - lint_lazy_eval(cx, expr, arg_lists[0], false, "and"); + unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "and"); bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]); bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]); }, ["or_else", ..] => { - lint_lazy_eval(cx, expr, arg_lists[0], false, "or"); + unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "or"); bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]); }, ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), @@ -1490,9 +1492,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]), ["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false), ["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true), - ["unwrap_or_else", ..] => lint_lazy_eval(cx, expr, arg_lists[0], true, "unwrap_or"), - ["get_or_insert_with", ..] => lint_lazy_eval(cx, expr, arg_lists[0], true, "get_or_insert"), - ["ok_or_else", ..] => lint_lazy_eval(cx, expr, arg_lists[0], true, "ok_or"), + ["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or"), + ["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "get_or_insert"), + ["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "ok_or"), _ => {}, } @@ -2708,119 +2710,13 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map } } -/// lint use of `_else(simple closure)` for `Option`s and `Result`s that can be -/// replaced with `(return value of simple closure)` -fn lint_lazy_eval<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - args: &'tcx [hir::Expr<'_>], - allow_variant_calls: bool, - simplify_using: &str, -) { - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type)); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(result_type)); - - if !is_option && !is_result { - return; - } - - // Return true if the expression is an accessor of any of the arguments - fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool { - params.iter().any(|arg| { - if_chain! { - if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind; - if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind; - if let [p, ..] = path.segments; - then { - ident.name == p.ident.name - } else { - false - } - } - }) - } - - fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool { - paths.iter().any(|candidate| match_qpath(path, candidate)) - } - - fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool { - match expr.kind { - // Closures returning literals can be unconditionally simplified - hir::ExprKind::Lit(_) => true, - - hir::ExprKind::Index(ref object, ref index) => { - // arguments are not being indexed into - if !expr_uses_argument(object, params) { - // arguments are not used as index - !expr_uses_argument(index, params) - } else { - false - } - }, - - // Reading fields can be simplified if the object is not an argument of the closure - hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params), - - // Paths can be simplified if the root is not the argument, this also covers None - hir::ExprKind::Path(_) => !expr_uses_argument(expr, params), - - // Calls to Some, Ok, Err can be considered literals if they don't derive an argument - hir::ExprKind::Call(ref func, ref args) => if_chain! { - if variant_calls; // Disable lint when rules conflict with bind_instead_of_map - if let hir::ExprKind::Path(ref path) = func.kind; - if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]); - then { - // Recursively check all arguments - args.iter().all(|arg| can_simplify(arg, params, variant_calls)) - } else { - false - } - }, - - // For anything more complex than the above, a closure is probably the right solution, - // or the case is handled by an other lint - _ => false, - } - } - - if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind { - let body = cx.tcx.hir().body(eid); - let ex = &body.value; - let params = &body.params; - - if can_simplify(ex, params, allow_variant_calls) { - let msg = if is_option { - "unnecessary closure used to substitute value for `Option::None`" - } else { - "unnecessary closure used to substitute value for `Result::Err`" - }; - - span_lint_and_sugg( - cx, - UNNECESSARY_LAZY_EVALUATION, - expr.span, - msg, - &format!("Use `{}` instead", simplify_using), - format!( - "{0}.{1}({2})", - snippet(cx, args[0].span, ".."), - simplify_using, - snippet(cx, ex.span, ".."), - ), - Applicability::MachineApplicable, - ); - } - } -} - /// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s fn lint_map_unwrap_or_else<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>], unwrap_args: &'tcx [hir::Expr<'_>], -) { +) -> bool { // lint if the caller of `map()` is an `Option` let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(option_type)); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(result_type)); @@ -2832,10 +2728,10 @@ fn lint_map_unwrap_or_else<'tcx>( let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { - return; + return false; } } else { - return; + return false; } // lint message @@ -2865,9 +2761,15 @@ fn lint_map_unwrap_or_else<'tcx>( map_snippet, unwrap_snippet, ), ); + true } else if same_span && multiline { span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); - }; + true + } else { + false + } + } else { + false } } diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs new file mode 100644 index 000000000..0dbedc491 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -0,0 +1,113 @@ +use crate::utils::{match_qpath, span_lint_and_sugg, snippet, is_type_diagnostic_item}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::UNNECESSARY_LAZY_EVALUATION; + +/// lint use of `_else(simple closure)` for `Option`s and `Result`s that can be +/// replaced with `(return value of simple closure)` +pub(super) fn lint<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + args: &'tcx [hir::Expr<'_>], + allow_variant_calls: bool, + simplify_using: &str, +) { + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type)); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(result_type)); + + if !is_option && !is_result { + return; + } + + // Return true if the expression is an accessor of any of the arguments + fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool { + params.iter().any(|arg| { + if_chain! { + if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind; + if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind; + if let [p, ..] = path.segments; + then { + ident.name == p.ident.name + } else { + false + } + } + }) + } + + fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool { + paths.iter().any(|candidate| match_qpath(path, candidate)) + } + + fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool { + match expr.kind { + // Closures returning literals can be unconditionally simplified + hir::ExprKind::Lit(_) => true, + + hir::ExprKind::Index(ref object, ref index) => { + // arguments are not being indexed into + if !expr_uses_argument(object, params) { + // arguments are not used as index + !expr_uses_argument(index, params) + } else { + false + } + }, + + // Reading fields can be simplified if the object is not an argument of the closure + hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params), + + // Paths can be simplified if the root is not the argument, this also covers None + hir::ExprKind::Path(_) => !expr_uses_argument(expr, params), + + // Calls to Some, Ok, Err can be considered literals if they don't derive an argument + hir::ExprKind::Call(ref func, ref args) => if_chain! { + if variant_calls; // Disable lint when rules conflict with bind_instead_of_map + if let hir::ExprKind::Path(ref path) = func.kind; + if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]); + then { + // Recursively check all arguments + args.iter().all(|arg| can_simplify(arg, params, variant_calls)) + } else { + false + } + }, + + // For anything more complex than the above, a closure is probably the right solution, + // or the case is handled by an other lint + _ => false, + } + } + + if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind { + let body = cx.tcx.hir().body(eid); + let ex = &body.value; + let params = &body.params; + + if can_simplify(ex, params, allow_variant_calls) { + let msg = if is_option { + "unnecessary closure used to substitute value for `Option::None`" + } else { + "unnecessary closure used to substitute value for `Result::Err`" + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_LAZY_EVALUATION, + expr.span, + msg, + &format!("Use `{}` instead", simplify_using), + format!( + "{0}.{1}({2})", + snippet(cx, args[0].span, ".."), + simplify_using, + snippet(cx, ex.span, ".."), + ), + Applicability::MachineApplicable, + ); + } + } +}