Skip to content

Commit ffcd129

Browse files
authored
{flat_,}map_identity: recognize (tuple) struct de- and restructuring (#15261)
Follow-up of #15229, as described in #15229 (comment) -- it turned out to be not that difficult after all! changelog: [`map_identity`,`flat_map_identity`]: also recognize (tuple) struct de- and resctructuring r? @y21
2 parents e42586d + eea4d6d commit ffcd129

File tree

4 files changed

+200
-13
lines changed

4 files changed

+200
-13
lines changed

clippy_utils/src/lib.rs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub use self::hir_utils::{
8484
use core::mem;
8585
use core::ops::ControlFlow;
8686
use std::collections::hash_map::Entry;
87-
use std::iter::{once, repeat_n};
87+
use std::iter::{once, repeat_n, zip};
8888
use std::sync::{Mutex, MutexGuard, OnceLock};
8989

9090
use itertools::Itertools;
@@ -582,7 +582,7 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
582582
return false;
583583
}
584584

585-
for (x1, x2) in s1.iter().zip(s2.iter()) {
585+
for (x1, x2) in zip(&s1, &s2) {
586586
if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() {
587587
return false;
588588
}
@@ -1898,6 +1898,8 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
18981898
/// * `|x| { return x; }`
18991899
/// * `|(x, y)| (x, y)`
19001900
/// * `|[x, y]| [x, y]`
1901+
/// * `|Foo(bar, baz)| Foo(bar, baz)`
1902+
/// * `|Foo { bar, baz }| Foo { bar, baz }`
19011903
///
19021904
/// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
19031905
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
@@ -1942,6 +1944,8 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
19421944
/// * `x` is the identity representation of `x`
19431945
/// * `(x, y)` is the identity representation of `(x, y)`
19441946
/// * `[x, y]` is the identity representation of `[x, y]`
1947+
/// * `Foo(bar, baz)` is the identity representation of `Foo(bar, baz)`
1948+
/// * `Foo { bar, baz }` is the identity representation of `Foo { bar, baz }`
19451949
///
19461950
/// Note that `by_hir` is used to determine bindings are checked by their `HirId` or by their name.
19471951
/// This can be useful when checking patterns in `let` bindings or `match` arms.
@@ -1958,6 +1962,9 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
19581962
return false;
19591963
}
19601964

1965+
// NOTE: we're inside a (function) body, so this won't ICE
1966+
let qpath_res = |qpath, hir| cx.typeck_results().qpath_res(qpath, hir);
1967+
19611968
match (pat.kind, expr.kind) {
19621969
(PatKind::Binding(_, id, _, _), _) if by_hir => {
19631970
path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty()
@@ -1968,16 +1975,36 @@ pub fn is_expr_identity_of_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<
19681975
(PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup))
19691976
if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() =>
19701977
{
1971-
pats.iter()
1972-
.zip(tup)
1973-
.all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1978+
zip(pats, tup).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1979+
},
1980+
(PatKind::Slice(before, None, after), ExprKind::Array(arr)) if before.len() + after.len() == arr.len() => {
1981+
zip(before.iter().chain(after), arr).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
19741982
},
1975-
(PatKind::Slice(before, slice, after), ExprKind::Array(arr))
1976-
if slice.is_none() && before.len() + after.len() == arr.len() =>
1983+
(PatKind::TupleStruct(pat_ident, field_pats, dotdot), ExprKind::Call(ident, fields))
1984+
if dotdot.as_opt_usize().is_none() && field_pats.len() == fields.len() =>
19771985
{
1978-
(before.iter().chain(after))
1979-
.zip(arr)
1980-
.all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr, by_hir))
1986+
// check ident
1987+
if let ExprKind::Path(ident) = &ident.kind
1988+
&& qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
1989+
// check fields
1990+
&& zip(field_pats, fields).all(|(pat, expr)| is_expr_identity_of_pat(cx, pat, expr,by_hir))
1991+
{
1992+
true
1993+
} else {
1994+
false
1995+
}
1996+
},
1997+
(PatKind::Struct(pat_ident, field_pats, false), ExprKind::Struct(ident, fields, hir::StructTailExpr::None))
1998+
if field_pats.len() == fields.len() =>
1999+
{
2000+
// check ident
2001+
qpath_res(&pat_ident, pat.hir_id) == qpath_res(ident, expr.hir_id)
2002+
// check fields
2003+
&& field_pats.iter().all(|field_pat| {
2004+
fields.iter().any(|field| {
2005+
field_pat.ident == field.ident && is_expr_identity_of_pat(cx, field_pat.pat, field.expr, by_hir)
2006+
})
2007+
})
19812008
},
19822009
_ => false,
19832010
}

tests/ui/map_identity.fixed

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::map_identity)]
2-
#![allow(clippy::needless_return)]
2+
#![allow(clippy::needless_return, clippy::disallowed_names)]
33

44
fn main() {
55
let x: [u16; 3] = [1, 2, 3];
@@ -99,3 +99,65 @@ fn issue15198() {
9999
let _ = x.iter().copied();
100100
//~^ map_identity
101101
}
102+
103+
mod foo {
104+
#[derive(Clone, Copy)]
105+
pub struct Foo {
106+
pub foo: u8,
107+
pub bar: u8,
108+
}
109+
110+
#[derive(Clone, Copy)]
111+
pub struct Foo2(pub u8, pub u8);
112+
}
113+
use foo::{Foo, Foo2};
114+
115+
struct Bar {
116+
foo: u8,
117+
bar: u8,
118+
}
119+
120+
struct Bar2(u8, u8);
121+
122+
fn structs() {
123+
let x = [Foo { foo: 0, bar: 0 }];
124+
125+
let _ = x.into_iter();
126+
//~^ map_identity
127+
128+
// still lint when different paths are used for the same struct
129+
let _ = x.into_iter();
130+
//~^ map_identity
131+
132+
// don't lint: same fields but different structs
133+
let _ = x.into_iter().map(|Foo { foo, bar }| Bar { foo, bar });
134+
135+
// still lint with redundant field names
136+
#[allow(clippy::redundant_field_names)]
137+
let _ = x.into_iter();
138+
//~^ map_identity
139+
140+
// still lint with field order change
141+
let _ = x.into_iter();
142+
//~^ map_identity
143+
144+
// don't lint: switched field assignment
145+
let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: bar, bar: foo });
146+
}
147+
148+
fn tuple_structs() {
149+
let x = [Foo2(0, 0)];
150+
151+
let _ = x.into_iter();
152+
//~^ map_identity
153+
154+
// still lint when different paths are used for the same struct
155+
let _ = x.into_iter();
156+
//~^ map_identity
157+
158+
// don't lint: same fields but different structs
159+
let _ = x.into_iter().map(|Foo2(foo, bar)| Bar2(foo, bar));
160+
161+
// don't lint: switched field assignment
162+
let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(bar, foo));
163+
}

tests/ui/map_identity.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![warn(clippy::map_identity)]
2-
#![allow(clippy::needless_return)]
2+
#![allow(clippy::needless_return, clippy::disallowed_names)]
33

44
fn main() {
55
let x: [u16; 3] = [1, 2, 3];
@@ -105,3 +105,65 @@ fn issue15198() {
105105
let _ = x.iter().copied().map(|[x, y]| [x, y]);
106106
//~^ map_identity
107107
}
108+
109+
mod foo {
110+
#[derive(Clone, Copy)]
111+
pub struct Foo {
112+
pub foo: u8,
113+
pub bar: u8,
114+
}
115+
116+
#[derive(Clone, Copy)]
117+
pub struct Foo2(pub u8, pub u8);
118+
}
119+
use foo::{Foo, Foo2};
120+
121+
struct Bar {
122+
foo: u8,
123+
bar: u8,
124+
}
125+
126+
struct Bar2(u8, u8);
127+
128+
fn structs() {
129+
let x = [Foo { foo: 0, bar: 0 }];
130+
131+
let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar });
132+
//~^ map_identity
133+
134+
// still lint when different paths are used for the same struct
135+
let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar });
136+
//~^ map_identity
137+
138+
// don't lint: same fields but different structs
139+
let _ = x.into_iter().map(|Foo { foo, bar }| Bar { foo, bar });
140+
141+
// still lint with redundant field names
142+
#[allow(clippy::redundant_field_names)]
143+
let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar });
144+
//~^ map_identity
145+
146+
// still lint with field order change
147+
let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo });
148+
//~^ map_identity
149+
150+
// don't lint: switched field assignment
151+
let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: bar, bar: foo });
152+
}
153+
154+
fn tuple_structs() {
155+
let x = [Foo2(0, 0)];
156+
157+
let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar));
158+
//~^ map_identity
159+
160+
// still lint when different paths are used for the same struct
161+
let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar));
162+
//~^ map_identity
163+
164+
// don't lint: same fields but different structs
165+
let _ = x.into_iter().map(|Foo2(foo, bar)| Bar2(foo, bar));
166+
167+
// don't lint: switched field assignment
168+
let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(bar, foo));
169+
}

tests/ui/map_identity.stderr

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,41 @@ error: unnecessary map of the identity function
9393
LL | let _ = x.iter().copied().map(|[x, y]| [x, y]);
9494
| ^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
9595

96-
error: aborting due to 14 previous errors
96+
error: unnecessary map of the identity function
97+
--> tests/ui/map_identity.rs:131:26
98+
|
99+
LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo, bar });
100+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
101+
102+
error: unnecessary map of the identity function
103+
--> tests/ui/map_identity.rs:135:26
104+
|
105+
LL | let _ = x.into_iter().map(|Foo { foo, bar }| foo::Foo { foo, bar });
106+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
107+
108+
error: unnecessary map of the identity function
109+
--> tests/ui/map_identity.rs:143:26
110+
|
111+
LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { foo: foo, bar: bar });
112+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
113+
114+
error: unnecessary map of the identity function
115+
--> tests/ui/map_identity.rs:147:26
116+
|
117+
LL | let _ = x.into_iter().map(|Foo { foo, bar }| Foo { bar, foo });
118+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
119+
120+
error: unnecessary map of the identity function
121+
--> tests/ui/map_identity.rs:157:26
122+
|
123+
LL | let _ = x.into_iter().map(|Foo2(foo, bar)| Foo2(foo, bar));
124+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
125+
126+
error: unnecessary map of the identity function
127+
--> tests/ui/map_identity.rs:161:26
128+
|
129+
LL | let _ = x.into_iter().map(|Foo2(foo, bar)| foo::Foo2(foo, bar));
130+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove the call to `map`
131+
132+
error: aborting due to 20 previous errors
97133

0 commit comments

Comments
 (0)