Skip to content

Commit f988463

Browse files
committed
unnecessary_option_map_or_else also uses is_expr_identity_function
1 parent 8596bc0 commit f988463

File tree

9 files changed

+143
-149
lines changed

9 files changed

+143
-149
lines changed

clippy_lints/src/matches/match_single_binding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
8282
snippet_with_context(cx, pat_span, ctxt, "..", &mut app).0
8383
),
8484
),
85-
None if is_expr_identity_of_pat(cx, arms[0].pat, ex, false) => {
85+
None if is_expr_identity_of_pat(cx, arms[0].pat, ex, cx.enclosing_body.unwrap(), false) => {
8686
span_lint_and_sugg(
8787
cx,
8888
MATCH_SINGLE_BINDING,
Lines changed: 4 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_expr_identity_function;
23
use clippy_utils::res::MaybeDef;
34
use clippy_utils::source::snippet_with_applicability;
4-
use clippy_utils::{expr_or_init, find_binding_init, peel_blocks};
55
use rustc_errors::Applicability;
6-
use rustc_hir::def::{DefKind, Res};
7-
use rustc_hir::{Body, BodyId, Closure, Expr, ExprKind, HirId, QPath};
6+
use rustc_hir::Expr;
87
use rustc_lint::LateContext;
98
use rustc_span::symbol::sym;
109

1110
use super::UNNECESSARY_OPTION_MAP_OR_ELSE;
12-
use super::utils::get_last_chain_binding_hir_id;
1311

1412
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
1513
let msg = "unused \"map closure\" when calling `Option::map_or_else` value";
@@ -27,85 +25,13 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &E
2725
);
2826
}
2927

30-
fn handle_qpath(
31-
cx: &LateContext<'_>,
32-
expr: &Expr<'_>,
33-
recv: &Expr<'_>,
34-
def_arg: &Expr<'_>,
35-
expected_hir_id: HirId,
36-
qpath: QPath<'_>,
37-
) {
38-
if let QPath::Resolved(_, path) = qpath
39-
&& let Res::Local(hir_id) = path.res
40-
&& expected_hir_id == hir_id
41-
{
42-
emit_lint(cx, expr, recv, def_arg);
43-
}
44-
}
45-
46-
fn handle_closure(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, body_id: BodyId) {
47-
let body = cx.tcx.hir_body(body_id);
48-
handle_fn_body(cx, expr, recv, def_arg, body);
49-
}
50-
51-
fn handle_fn_body(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, body: &Body<'_>) {
52-
if let Some(first_param) = body.params.first() {
53-
let body_expr = peel_blocks(body.value);
54-
match body_expr.kind {
55-
ExprKind::Path(qpath) => {
56-
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
57-
},
58-
// If this is a block (that wasn't peeled off), then it means there are statements.
59-
ExprKind::Block(block, _) => {
60-
if let Some(block_expr) = block.expr
61-
// First we ensure that this is a "binding chain" (each statement is a binding
62-
// of the previous one) and that it is a binding of the closure argument.
63-
&& let Some(last_chain_binding_id) =
64-
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
65-
&& let ExprKind::Path(qpath) = block_expr.kind
66-
{
67-
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
68-
}
69-
},
70-
_ => {},
71-
}
72-
}
73-
}
74-
7528
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Option`s.
7629
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>, map_arg: &Expr<'_>) {
7730
// lint if the caller of `map_or_else()` is an `Option`
7831
if !cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) {
7932
return;
8033
}
81-
match map_arg.kind {
82-
// If the second argument is a closure, we can check its body.
83-
ExprKind::Closure(&Closure { body, .. }) => {
84-
handle_closure(cx, expr, recv, def_arg, body);
85-
},
86-
ExprKind::Path(qpath) => {
87-
let res = cx.qpath_res(&qpath, map_arg.hir_id);
88-
match res {
89-
// Case 1: Local variable (could be a closure)
90-
Res::Local(hir_id) => {
91-
if let Some(init_expr) = find_binding_init(cx, hir_id) {
92-
let origin = expr_or_init(cx, init_expr);
93-
if let ExprKind::Closure(&Closure { body, .. }) = origin.kind {
94-
handle_closure(cx, expr, recv, def_arg, body);
95-
}
96-
}
97-
},
98-
// Case 2: Function definition
99-
Res::Def(DefKind::Fn, def_id) => {
100-
if let Some(local_def_id) = def_id.as_local()
101-
&& let Some(body) = cx.tcx.hir_maybe_body_owned_by(local_def_id)
102-
{
103-
handle_fn_body(cx, expr, recv, def_arg, body);
104-
}
105-
},
106-
_ => (),
107-
}
108-
},
109-
_ => (),
34+
if is_expr_identity_function(cx, map_arg) {
35+
emit_lint(cx, expr, recv, def_arg);
11036
}
11137
}

clippy_utils/src/lib.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ use rustc_hir::definitions::{DefPath, DefPathData};
103103
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
104104
use rustc_hir::intravisit::{Visitor, walk_expr};
105105
use rustc_hir::{
106-
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, CoroutineDesugaring,
107-
CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs,
108-
HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId,
109-
OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, TraitFn, TraitItem,
110-
TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr,
106+
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, BodyId, ByRef, Closure, ConstArgKind,
107+
CoroutineDesugaring, CoroutineKind, CoroutineSource, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy,
108+
GenericArg, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, LangItem, LetStmt, MatchSource,
109+
Mutability, Node, OwnerId, OwnerNode, Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Stmt,
110+
StmtKind, TraitFn, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, def, find_attr,
111111
};
112112
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
113113
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -1824,7 +1824,7 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
18241824
for stmt in stmts {
18251825
if let StmtKind::Let(local) = stmt.kind
18261826
&& let Some(init) = local.init
1827-
&& is_expr_identity_of_pat(cx, param_pat, init, true)
1827+
&& is_expr_identity_of_pat(cx, param_pat, init, func.id(), true)
18281828
{
18291829
param_pat = local.pat;
18301830
} else {
@@ -1833,7 +1833,7 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
18331833
}
18341834
expr = e;
18351835
},
1836-
_ => return is_expr_identity_of_pat(cx, param_pat, expr, true),
1836+
_ => return is_expr_identity_of_pat(cx, param_pat, expr, func.id(), true),
18371837
}
18381838
}
18391839
}
@@ -1847,9 +1847,16 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
18471847
///
18481848
/// Note that `by_hir` is used to determine bindings are checked by their `HirId` or by their name.
18491849
/// This can be useful when checking patterns in `let` bindings or `match` arms.
1850-
pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>, by_hir: bool) -> bool {
1850+
pub fn is_expr_identity_of_pat(
1851+
cx: &LateContext<'_>,
1852+
pat: &Pat<'_>,
1853+
expr: &Expr<'_>,
1854+
func_id: BodyId,
1855+
by_hir: bool,
1856+
) -> bool {
18511857
if cx
1852-
.typeck_results()
1858+
.tcx
1859+
.typeck_body(func_id)
18531860
.pat_binding_modes()
18541861
.get(pat.hir_id)
18551862
.is_some_and(|mode| matches!(mode.0, ByRef::Yes(_)))
@@ -1861,22 +1868,25 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
18611868
}
18621869

18631870
// NOTE: we're inside a (function) body, so this won't ICE
1864-
let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
1871+
let qpath_res = |qpath, hir| cx.tcx.typeck_body(func_id).qpath_res(qpath, hir);
18651872

18661873
match (pat.kind, expr.kind) {
18671874
(PatKind::Binding(_, id, _, _), _) if by_hir => {
1868-
expr.res_local_id() == Some(id) && cx.typeck_results().expr_adjustments(expr).is_empty()
1875+
expr.res_local_id() == Some(id) && cx.tcx.typeck_body(func_id).expr_adjustments(expr).is_empty()
18691876
},
18701877
(PatKind::Binding(_, _, ident, _), ExprKind::Path(QPath::Resolved(_, path))) => {
18711878
matches!(path.segments, [ segment] if segment.ident.name == ident.name)
18721879
},
18731880
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
18741881
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
18751882
{
1876-
over(pats, tup, |pat, expr| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1883+
over(pats, tup, |pat, expr| {
1884+
is_expr_identity_of_pat(cx, pat, expr, func_id, by_hir)
1885+
})
18771886
},
18781887
(PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
1879-
zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1888+
zip(before.iter().chain(after), arr)
1889+
.all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, func_id, by_hir))
18801890
},
18811891
(PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
18821892
if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
@@ -1885,7 +1895,7 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
18851895
if let ExprKind::Path(ident) = &ident.kind
18861896
&& qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
18871897
// check fields
1888-
&& over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,by_hir))
1898+
&& over(field_pats, fields, |pat, expr| is_expr_identity_of_pat(cx, pat, expr,func_id, by_hir))
18891899
{
18901900
true
18911901
} else {
@@ -1899,7 +1909,8 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
18991909
qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
19001910
// check fields
19011911
&& unordered_over(field_pats, fields, |field_pat, field| {
1902-
field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
1912+
field_pat.ident == field.ident &&
1913+
is_expr_identity_of_pat(cx, field_pat.pat, field.expr, func_id, by_hir)
19031914
})
19041915
},
19051916
_ => false,
@@ -1938,7 +1949,38 @@ pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>)
19381949
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
19391950
match expr.kind {
19401951
ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir_body(body)),
1941-
_ => expr.basic_res().is_diag_item(cx, sym::convert_identity),
1952+
_ if expr.basic_res().is_diag_item(cx, sym::convert_identity) => true,
1953+
1954+
ExprKind::Path(qpath) => {
1955+
let res = cx.qpath_res(&qpath, expr.hir_id);
1956+
match res {
1957+
// Case 1: Local variable (could be a closure)
1958+
Res::Local(hir_id) => {
1959+
if let Some(init_expr) = find_binding_init(cx, hir_id) {
1960+
let origin = expr_or_init(cx, init_expr);
1961+
if let ExprKind::Closure(&Closure { body, .. }) = origin.kind {
1962+
is_body_identity_function(cx, cx.tcx.hir_body(body))
1963+
} else {
1964+
false
1965+
}
1966+
} else {
1967+
false
1968+
}
1969+
},
1970+
// Case 2: Function definition
1971+
Res::Def(DefKind::Fn, def_id) => {
1972+
if let Some(local_def_id) = def_id.as_local()
1973+
&& let Some(body) = cx.tcx.hir_maybe_body_owned_by(local_def_id)
1974+
{
1975+
is_body_identity_function(cx, body)
1976+
} else {
1977+
false
1978+
}
1979+
},
1980+
_ => false,
1981+
}
1982+
},
1983+
_ => false,
19421984
}
19431985
}
19441986

tests/ui/eta.fixed

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
clippy::uninlined_format_args,
1414
clippy::useless_vec,
1515
clippy::unnecessary_map_on_constructor,
16-
clippy::needless_lifetimes
16+
clippy::needless_lifetimes,
17+
clippy::unnecessary_option_map_or_else
1718
)]
1819

1920
use std::path::{Path, PathBuf};

tests/ui/eta.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
clippy::uninlined_format_args,
1414
clippy::useless_vec,
1515
clippy::unnecessary_map_on_constructor,
16-
clippy::needless_lifetimes
16+
clippy::needless_lifetimes,
17+
clippy::unnecessary_option_map_or_else
1718
)]
1819

1920
use std::path::{Path, PathBuf};

0 commit comments

Comments
 (0)