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

Add unnecessary lazy evaluation lint #5720

Merged
merged 15 commits into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,7 @@ Released 2018-09-13
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
[`unnecessary_lazy_evaluation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluation
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
Expand Down
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&methods::UNINIT_ASSUMED_INIT,
&methods::UNNECESSARY_FILTER_MAP,
&methods::UNNECESSARY_FOLD,
&methods::UNNECESSARY_LAZY_EVALUATION,
&methods::UNWRAP_USED,
&methods::USELESS_ASREF,
&methods::WRONG_PUB_SELF_CONVENTION,
Expand Down Expand Up @@ -1360,6 +1361,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::UNINIT_ASSUMED_INIT),
LintId::of(&methods::UNNECESSARY_FILTER_MAP),
LintId::of(&methods::UNNECESSARY_FOLD),
LintId::of(&methods::UNNECESSARY_LAZY_EVALUATION),
LintId::of(&methods::USELESS_ASREF),
LintId::of(&methods::WRONG_SELF_CONVENTION),
LintId::of(&methods::ZST_OFFSET),
Expand Down Expand Up @@ -1540,6 +1542,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
LintId::of(&methods::STRING_EXTEND_CHARS),
LintId::of(&methods::UNNECESSARY_FOLD),
LintId::of(&methods::UNNECESSARY_LAZY_EVALUATION),
LintId::of(&methods::WRONG_SELF_CONVENTION),
LintId::of(&misc::TOPLEVEL_REF_ARG),
LintId::of(&misc::ZERO_PTR),
Expand Down
62 changes: 57 additions & 5 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1329,6 +1330,42 @@ declare_clippy_lint! {
"`push_str()` used with a single-character string literal as parameter"
}

declare_clippy_lint! {
/// **What it does:** As the counterpart to `or_fun_call`, this lint looks for unnecessary
/// lazily evaluated closures on `Option` and `Result`.
///
/// This lint suggests changing the following functions, when eager evaluation results in
/// simpler code:
/// - `unwrap_or_else` to `unwrap_or`
/// - `and_then` to `and`
/// - `or_else` to `or`
/// - `get_or_insert_with` to `get_or_insert`
/// - `ok_or_else` to `ok_or`
///
/// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases.
bugadani marked this conversation as resolved.
Show resolved Hide resolved
///
/// **Known problems:** It is possible, but not recommended for `Deref` and `Index` to have
/// side effects. Eagerly evaluating them can change the semantics of the program.
///
/// **Example:**
///
/// ```rust
/// // example code where clippy issues a warning
/// let opt: Option<u32> = None;
///
/// opt.unwrap_or_else(|| 42);
/// ```
/// Use instead:
/// ```rust
/// let opt: Option<u32> = None;
///
/// opt.unwrap_or(42);
/// ```
pub UNNECESSARY_LAZY_EVALUATION,
bugadani marked this conversation as resolved.
Show resolved Hide resolved
style,
"using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
}

declare_lint_pass!(Methods => [
UNWRAP_USED,
EXPECT_USED,
Expand Down Expand Up @@ -1378,6 +1415,7 @@ declare_lint_pass!(Methods => [
ZST_OFFSET,
FILETYPE_IS_FILE,
OPTION_AS_REF_DEREF,
UNNECESSARY_LAZY_EVALUATION,
]);

impl<'tcx> LateLintPass<'tcx> for Methods {
Expand All @@ -1398,13 +1436,19 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]),
["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_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
["unwrap_or_else", "map"] => {
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", ..] => {
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", ..] => {
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]),
Expand Down Expand Up @@ -1448,6 +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", ..] => 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"),
_ => {},
}

Expand Down Expand Up @@ -2664,12 +2711,13 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
}

/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
/// Return true if lint triggered
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));
Expand All @@ -2681,10 +2729,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
Expand Down Expand Up @@ -2714,10 +2762,14 @@ fn lint_map_unwrap_or_else<'tcx>(
map_snippet, unwrap_snippet,
),
);
return true;
} else if same_span && multiline {
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
};
return true;
}
}

false
}

/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
Expand Down
111 changes: 111 additions & 0 deletions clippy_lints/src/methods/unnecessary_lazy_eval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use crate::utils::{is_type_diagnostic_item, match_qpath, snippet, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;

use super::UNNECESSARY_LAZY_EVALUATION;

// 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) {
false
} else {
// arguments are not used as index
!expr_uses_argument(index, params)
}
},

// 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,
}
}

/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
/// replaced with `<fn>(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 {
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,
);
}
}
}
}
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "methods",
},
Lint {
name: "unnecessary_lazy_evaluation",
group: "style",
desc: "using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation",
deprecation: None,
module: "methods",
},
Lint {
name: "unnecessary_mut_passed",
group: "style",
Expand Down
Loading