Skip to content

Commit 726c15b

Browse files
committed
option-if-let-else - factor out some common code in eager_or_lazy.rs
1 parent 52e5633 commit 726c15b

10 files changed

+346
-209
lines changed

clippy_lints/src/methods/mod.rs

+13-44
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ use rustc_span::source_map::Span;
2525
use rustc_span::symbol::{sym, SymbolStr};
2626

2727
use crate::consts::{constant, Constant};
28+
use crate::utils::eager_or_lazy::is_lazyness_candidate;
2829
use crate::utils::usage::mutated_variables;
2930
use crate::utils::{
3031
contains_ty, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro,
31-
is_copy, is_ctor_or_promotable_const_function, is_expn_of, is_type_diagnostic_item, iter_input_pats,
32-
last_path_segment, match_def_path, match_qpath, match_trait_method, match_type, match_var, method_calls,
33-
method_chain_args, paths, remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability,
34-
snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
35-
span_lint_and_then, sugg, walk_ptrs_ty, walk_ptrs_ty_depth, SpanlessEq,
32+
is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath,
33+
match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, remove_blocks, return_ty,
34+
single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
35+
span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty,
36+
walk_ptrs_ty_depth, SpanlessEq,
3637
};
3738

3839
declare_clippy_lint! {
@@ -1454,17 +1455,17 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
14541455
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]),
14551456
["unwrap_or_else", "map"] => {
14561457
if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]) {
1457-
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or");
1458+
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or");
14581459
}
14591460
},
14601461
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
14611462
["and_then", ..] => {
1462-
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "and");
1463+
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "and");
14631464
bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]);
14641465
bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]);
14651466
},
14661467
["or_else", ..] => {
1467-
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "or");
1468+
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "or");
14681469
bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]);
14691470
},
14701471
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
@@ -1508,9 +1509,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
15081509
["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
15091510
["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
15101511
["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
1511-
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or"),
1512-
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "get_or_insert"),
1513-
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "ok_or"),
1512+
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
1513+
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
1514+
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
15141515
_ => {},
15151516
}
15161517

@@ -1714,37 +1715,6 @@ fn lint_or_fun_call<'tcx>(
17141715
name: &str,
17151716
args: &'tcx [hir::Expr<'_>],
17161717
) {
1717-
// Searches an expression for method calls or function calls that aren't ctors
1718-
struct FunCallFinder<'a, 'tcx> {
1719-
cx: &'a LateContext<'tcx>,
1720-
found: bool,
1721-
}
1722-
1723-
impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
1724-
type Map = Map<'tcx>;
1725-
1726-
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
1727-
let call_found = match &expr.kind {
1728-
// ignore enum and struct constructors
1729-
hir::ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
1730-
hir::ExprKind::MethodCall(..) => true,
1731-
_ => false,
1732-
};
1733-
1734-
if call_found {
1735-
self.found |= true;
1736-
}
1737-
1738-
if !self.found {
1739-
intravisit::walk_expr(self, expr);
1740-
}
1741-
}
1742-
1743-
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
1744-
intravisit::NestedVisitorMap::None
1745-
}
1746-
}
1747-
17481718
/// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`.
17491719
fn check_unwrap_or_default(
17501720
cx: &LateContext<'_>,
@@ -1825,8 +1795,7 @@ fn lint_or_fun_call<'tcx>(
18251795
if_chain! {
18261796
if know_types.iter().any(|k| k.2.contains(&name));
18271797

1828-
let mut finder = FunCallFinder { cx: &cx, found: false };
1829-
if { finder.visit_expr(&arg); finder.found };
1798+
if is_lazyness_candidate(cx, arg);
18301799
if !contains_return(&arg);
18311800

18321801
let self_ty = cx.typeck_results().expr_ty(self_expr);

clippy_lints/src/methods/unnecessary_lazy_eval.rs

+9-67
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,17 @@
1-
use crate::utils::{is_type_diagnostic_item, match_qpath, snippet, span_lint_and_sugg};
2-
use if_chain::if_chain;
1+
use crate::utils::{eager_or_lazy, usage};
2+
use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_sugg};
33
use rustc_errors::Applicability;
44
use rustc_hir as hir;
55
use rustc_lint::LateContext;
66

77
use super::UNNECESSARY_LAZY_EVALUATIONS;
88

9-
// Return true if the expression is an accessor of any of the arguments
10-
fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool {
11-
params.iter().any(|arg| {
12-
if_chain! {
13-
if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind;
14-
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind;
15-
if let [p, ..] = path.segments;
16-
then {
17-
ident.name == p.ident.name
18-
} else {
19-
false
20-
}
21-
}
22-
})
23-
}
24-
25-
fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool {
26-
paths.iter().any(|candidate| match_qpath(path, candidate))
27-
}
28-
29-
fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool {
30-
match expr.kind {
31-
// Closures returning literals can be unconditionally simplified
32-
hir::ExprKind::Lit(_) => true,
33-
34-
hir::ExprKind::Index(ref object, ref index) => {
35-
// arguments are not being indexed into
36-
if expr_uses_argument(object, params) {
37-
false
38-
} else {
39-
// arguments are not used as index
40-
!expr_uses_argument(index, params)
41-
}
42-
},
43-
44-
// Reading fields can be simplified if the object is not an argument of the closure
45-
hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params),
46-
47-
// Paths can be simplified if the root is not the argument, this also covers None
48-
hir::ExprKind::Path(_) => !expr_uses_argument(expr, params),
49-
50-
// Calls to Some, Ok, Err can be considered literals if they don't derive an argument
51-
hir::ExprKind::Call(ref func, ref args) => if_chain! {
52-
if variant_calls; // Disable lint when rules conflict with bind_instead_of_map
53-
if let hir::ExprKind::Path(ref path) = func.kind;
54-
if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]);
55-
then {
56-
// Recursively check all arguments
57-
args.iter().all(|arg| can_simplify(arg, params, variant_calls))
58-
} else {
59-
false
60-
}
61-
},
62-
63-
// For anything more complex than the above, a closure is probably the right solution,
64-
// or the case is handled by an other lint
65-
_ => false,
66-
}
67-
}
68-
699
/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
7010
/// replaced with `<fn>(return value of simple closure)`
7111
pub(super) fn lint<'tcx>(
7212
cx: &LateContext<'tcx>,
7313
expr: &'tcx hir::Expr<'_>,
7414
args: &'tcx [hir::Expr<'_>],
75-
allow_variant_calls: bool,
7615
simplify_using: &str,
7716
) {
7817
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type));
@@ -81,10 +20,13 @@ pub(super) fn lint<'tcx>(
8120
if is_option || is_result {
8221
if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind {
8322
let body = cx.tcx.hir().body(eid);
84-
let ex = &body.value;
85-
let params = &body.params;
23+
let body_expr = &body.value;
24+
25+
if usage::BindingUsageFinder::are_params_used(cx, body) {
26+
return;
27+
}
8628

87-
if can_simplify(ex, params, allow_variant_calls) {
29+
if eager_or_lazy::is_eagerness_candidate(cx, body_expr) {
8830
let msg = if is_option {
8931
"unnecessary closure used to substitute value for `Option::None`"
9032
} else {
@@ -101,7 +43,7 @@ pub(super) fn lint<'tcx>(
10143
"{0}.{1}({2})",
10244
snippet(cx, args[0].span, ".."),
10345
simplify_using,
104-
snippet(cx, ex.span, ".."),
46+
snippet(cx, body_expr.span, ".."),
10547
),
10648
Applicability::MachineApplicable,
10749
);

clippy_lints/src/option_if_let_else.rs

+9-35
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::utils;
2+
use crate::utils::eager_or_lazy;
23
use crate::utils::sugg::Sugg;
34
use crate::utils::{match_type, paths, span_lint_and_sugg};
45
use if_chain::if_chain;
56

67
use rustc_errors::Applicability;
7-
use rustc_hir::def::{DefKind, Res};
88
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
9-
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, Path, QPath, UnOp};
9+
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, UnOp};
1010
use rustc_lint::{LateContext, LateLintPass};
1111
use rustc_middle::hir::map::Map;
1212
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -193,39 +193,13 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
193193
)
194194
}
195195

196-
/// Is the expr pure (is it free from side-effects)?
197-
/// If yes, we can use `map_or`, else we must use `map_or_else` to preserve the behavior.
198-
/// This implementation can be improved and identify more pure patterns.
199-
fn is_pure(expr: &Expr<'_>) -> bool {
200-
match expr.kind {
201-
ExprKind::Lit(..) | ExprKind::Path(..) => true,
202-
ExprKind::AddrOf(_, _, addr_of_expr) => is_pure(addr_of_expr),
203-
ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| is_pure(expr)),
204-
ExprKind::Struct(_, fields, expr) => {
205-
fields.iter().all(|f| is_pure(f.expr)) && expr.map_or(true, |e| is_pure(e))
206-
},
207-
ExprKind::Call(
208-
&Expr {
209-
kind:
210-
ExprKind::Path(QPath::Resolved(
211-
_,
212-
Path {
213-
res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
214-
..
215-
},
216-
)),
217-
..
218-
},
219-
args,
220-
) => args.iter().all(|expr| is_pure(expr)),
221-
_ => false,
222-
}
223-
}
224-
225196
/// If this expression is the option if let/else construct we're detecting, then
226197
/// this function returns an `OptionIfLetElseOccurence` struct with details if
227198
/// this construct is found, or None if this construct is not found.
228-
fn detect_option_if_let_else(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OptionIfLetElseOccurence> {
199+
fn detect_option_if_let_else<'tcx>(
200+
cx: &'_ LateContext<'tcx>,
201+
expr: &'_ Expr<'tcx>,
202+
) -> Option<OptionIfLetElseOccurence> {
229203
if_chain! {
230204
if !utils::in_macro(expr.span); // Don't lint macros, because it behaves weirdly
231205
if let ExprKind::Match(cond_expr, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind;
@@ -240,7 +214,7 @@ fn detect_option_if_let_else(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Op
240214
let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" };
241215
let some_body = extract_body_from_arm(&arms[0])?;
242216
let none_body = extract_body_from_arm(&arms[1])?;
243-
let method_sugg = if is_pure(none_body) { "map_or" } else { "map_or_else" };
217+
let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { "map_or" } else { "map_or_else" };
244218
let capture_name = id.name.to_ident_string();
245219
let wrap_braces = should_wrap_in_braces(cx, expr);
246220
let (as_ref, as_mut) = match &cond_expr.kind {
@@ -266,8 +240,8 @@ fn detect_option_if_let_else(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Op
266240
}
267241
}
268242

269-
impl<'a> LateLintPass<'a> for OptionIfLetElse {
270-
fn check_expr(&mut self, cx: &LateContext<'a>, expr: &Expr<'_>) {
243+
impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse {
244+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
271245
if let Some(detection) = detect_option_if_let_else(cx, expr) {
272246
span_lint_and_sugg(
273247
cx,
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
2+
//!
3+
//! Things to consider:
4+
//! - has the expression side-effects?
5+
//! - is the expression computationally expensive?
6+
//!
7+
//! See lints:
8+
//! - unnecessary-lazy-evaluations
9+
//! - or-fun-call
10+
//! - option-if-let-else
11+
12+
use crate::utils::is_ctor_or_promotable_const_function;
13+
use rustc_hir::def::{DefKind, Res};
14+
15+
use rustc_hir::intravisit;
16+
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
17+
18+
use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
19+
use rustc_lint::LateContext;
20+
use rustc_middle::hir::map::Map;
21+
22+
/// Is the expr pure (is it free from side-effects)?
23+
/// This function is named so to stress that it isn't exhaustive and returns FNs.
24+
fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
25+
match expr.kind {
26+
ExprKind::Lit(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
27+
ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
28+
ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)),
29+
ExprKind::Struct(_, fields, expr) => {
30+
fields.iter().all(|f| identify_some_pure_patterns(f.expr))
31+
&& expr.map_or(true, |e| identify_some_pure_patterns(e))
32+
},
33+
ExprKind::Call(
34+
&Expr {
35+
kind:
36+
ExprKind::Path(QPath::Resolved(
37+
_,
38+
Path {
39+
res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
40+
..
41+
},
42+
)),
43+
..
44+
},
45+
args,
46+
) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
47+
ExprKind::Block(
48+
&Block {
49+
stmts,
50+
expr: Some(expr),
51+
..
52+
},
53+
_,
54+
) => stmts.is_empty() && identify_some_pure_patterns(expr),
55+
_ => false,
56+
}
57+
}
58+
59+
/// Identify some potentially computationally expensive patterns.
60+
/// This function is named so to stress that its implementation is non-exhaustive.
61+
/// It returns FNs and FPs.
62+
fn identify_some_potentially_expensive_patterns<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
63+
// Searches an expression for method calls or function calls that aren't ctors
64+
struct FunCallFinder<'a, 'tcx> {
65+
cx: &'a LateContext<'tcx>,
66+
found: bool,
67+
}
68+
69+
impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
70+
type Map = Map<'tcx>;
71+
72+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
73+
let call_found = match &expr.kind {
74+
// ignore enum and struct constructors
75+
ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
76+
ExprKind::MethodCall(..) => true,
77+
_ => false,
78+
};
79+
80+
if call_found {
81+
self.found |= true;
82+
}
83+
84+
if !self.found {
85+
intravisit::walk_expr(self, expr);
86+
}
87+
}
88+
89+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
90+
NestedVisitorMap::None
91+
}
92+
}
93+
94+
let mut finder = FunCallFinder { cx, found: false };
95+
finder.visit_expr(expr);
96+
finder.found
97+
}
98+
99+
pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
100+
!identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
101+
}
102+
103+
pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
104+
identify_some_potentially_expensive_patterns(cx, expr)
105+
}

0 commit comments

Comments
 (0)