Skip to content

Commit e230f19

Browse files
committed
Auto merge of #11521 - y21:issue9122, r=llogiq
[`map_identity`]: allow closure with type annotations Fixes #9122 `.map(|a: u32| a)` can help type inference, so we should probably allow this and not warn about "unnecessary map of the identity function" changelog: [`map_identity`]: allow closure with type annotations
2 parents cd477d4 + bba155e commit e230f19

File tree

6 files changed

+66
-31
lines changed

6 files changed

+66
-31
lines changed

clippy_lints/src/methods/filter_map_identity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::{is_expr_identity_function, is_trait_method};
2+
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
33
use rustc_errors::Applicability;
44
use rustc_hir as hir;
55
use rustc_lint::LateContext;
@@ -9,7 +9,7 @@ use rustc_span::sym;
99
use super::FILTER_MAP_IDENTITY;
1010

1111
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) {
12-
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, filter_map_arg) {
12+
if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, filter_map_arg) {
1313
span_lint_and_sugg(
1414
cx,
1515
FILTER_MAP_IDENTITY,

clippy_lints/src/methods/flat_map_identity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::{is_expr_identity_function, is_trait_method};
2+
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
33
use rustc_errors::Applicability;
44
use rustc_hir as hir;
55
use rustc_lint::LateContext;
@@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
1515
flat_map_arg: &'tcx hir::Expr<'_>,
1616
flat_map_span: Span,
1717
) {
18-
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, flat_map_arg) {
18+
if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, flat_map_arg) {
1919
span_lint_and_sugg(
2020
cx,
2121
FLAT_MAP_IDENTITY,

clippy_lints/src/methods/map_identity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::ty::is_type_diagnostic_item;
3-
use clippy_utils::{is_expr_identity_function, is_trait_method};
3+
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
44
use rustc_errors::Applicability;
55
use rustc_hir as hir;
66
use rustc_lint::LateContext;
@@ -23,7 +23,7 @@ pub(super) fn check(
2323
if is_trait_method(cx, expr, sym::Iterator)
2424
|| is_type_diagnostic_item(cx, caller_ty, sym::Result)
2525
|| is_type_diagnostic_item(cx, caller_ty, sym::Option);
26-
if is_expr_identity_function(cx, map_arg);
26+
if is_expr_untyped_identity_function(cx, map_arg);
2727
if let Some(sugg_span) = expr.span.trim_start(caller.span);
2828
then {
2929
span_lint_and_sugg(

clippy_utils/src/lib.rs

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,32 +2027,31 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
20272027
did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use))
20282028
}
20292029

2030-
/// Checks if an expression represents the identity function
2031-
/// Only examines closures and `std::convert::identity`
2032-
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2033-
/// Checks if a function's body represents the identity function. Looks for bodies of the form:
2034-
/// * `|x| x`
2035-
/// * `|x| return x`
2036-
/// * `|x| { return x }`
2037-
/// * `|x| { return x; }`
2038-
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
2039-
let id = if_chain! {
2040-
if let [param] = func.params;
2041-
if let PatKind::Binding(_, id, _, _) = param.pat.kind;
2042-
then {
2043-
id
2044-
} else {
2045-
return false;
2046-
}
2047-
};
2030+
/// Checks if a function's body represents the identity function. Looks for bodies of the form:
2031+
/// * `|x| x`
2032+
/// * `|x| return x`
2033+
/// * `|x| { return x }`
2034+
/// * `|x| { return x; }`
2035+
///
2036+
/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
2037+
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
2038+
let id = if_chain! {
2039+
if let [param] = func.params;
2040+
if let PatKind::Binding(_, id, _, _) = param.pat.kind;
2041+
then {
2042+
id
2043+
} else {
2044+
return false;
2045+
}
2046+
};
20482047

2049-
let mut expr = func.value;
2050-
loop {
2051-
match expr.kind {
2052-
#[rustfmt::skip]
2048+
let mut expr = func.value;
2049+
loop {
2050+
match expr.kind {
2051+
#[rustfmt::skip]
20532052
ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, )
20542053
| ExprKind::Ret(Some(e)) => expr = e,
2055-
#[rustfmt::skip]
2054+
#[rustfmt::skip]
20562055
ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => {
20572056
if_chain! {
20582057
if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
@@ -2064,11 +2063,41 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
20642063
}
20652064
}
20662065
},
2067-
_ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
2068-
}
2066+
_ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
20692067
}
20702068
}
2069+
}
20712070

2071+
/// This is the same as [`is_expr_identity_function`], but does not consider closures
2072+
/// with type annotations for its bindings (or similar) as identity functions:
2073+
/// * `|x: u8| x`
2074+
/// * `std::convert::identity::<u8>`
2075+
pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
2076+
match expr.kind {
2077+
ExprKind::Closure(&Closure { body, fn_decl, .. })
2078+
if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) =>
2079+
{
2080+
is_body_identity_function(cx, cx.tcx.hir().body(body))
2081+
},
2082+
ExprKind::Path(QPath::Resolved(_, path))
2083+
if path.segments.iter().all(|seg| seg.infer_args)
2084+
&& let Some(did) = path.res.opt_def_id() =>
2085+
{
2086+
match_def_path(cx, did, &paths::CONVERT_IDENTITY)
2087+
},
2088+
_ => false,
2089+
}
2090+
}
2091+
2092+
/// Checks if an expression represents the identity function
2093+
/// Only examines closures and `std::convert::identity`
2094+
///
2095+
/// NOTE: If you want to use this function to find out if a closure is unnecessary, you likely want
2096+
/// to call [`is_expr_untyped_identity_function`] instead, which makes sure that the closure doesn't
2097+
/// have type annotations. This is important because removing a closure with bindings can
2098+
/// remove type information that helped type inference before, which can then lead to compile
2099+
/// errors.
2100+
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
20722101
match expr.kind {
20732102
ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)),
20742103
_ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)),

tests/ui/map_identity.fixed

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ fn main() {
1717
});
1818
let _: Result<u32, u32> = Ok(1);
1919
let _: Result<u32, u32> = Ok(1).map_err(|a: u32| a * 42);
20+
// : u32 guides type inference
21+
let _ = Ok(1).map_err(|a: u32| a);
22+
let _ = Ok(1).map_err(std::convert::identity::<u32>);
2023
}
2124

2225
fn not_identity(x: &u16) -> u16 {

tests/ui/map_identity.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ fn main() {
1919
});
2020
let _: Result<u32, u32> = Ok(1).map_err(|a| a);
2121
let _: Result<u32, u32> = Ok(1).map_err(|a: u32| a * 42);
22+
// : u32 guides type inference
23+
let _ = Ok(1).map_err(|a: u32| a);
24+
let _ = Ok(1).map_err(std::convert::identity::<u32>);
2225
}
2326

2427
fn not_identity(x: &u16) -> u16 {

0 commit comments

Comments
 (0)