Skip to content

Commit eb01aed

Browse files
committed
add lint for recreation of an entire struct
This lint makes Clippy warn about situations where an owned struct is essentially recreated by moving all its fields into a new instance of the struct. Clippy will suggest to simply use the original struct. The lint is not machine-applicable because the source struct may have been partially moved.
1 parent 0b1bf37 commit eb01aed

6 files changed

+228
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5692,6 +5692,7 @@ Released 2018-09-13
56925692
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
56935693
[`redundant_guards`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_guards
56945694
[`redundant_locals`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_locals
5695+
[`redundant_owned_struct_recreation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_owned_struct_recreation
56955696
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
56965697
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
56975698
[`redundant_pub_crate`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pub_crate

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
619619
crate::redundant_else::REDUNDANT_ELSE_INFO,
620620
crate::redundant_field_names::REDUNDANT_FIELD_NAMES_INFO,
621621
crate::redundant_locals::REDUNDANT_LOCALS_INFO,
622+
crate::redundant_owned_struct_recreation::REDUNDANT_OWNED_STRUCT_RECREATION_INFO,
622623
crate::redundant_pub_crate::REDUNDANT_PUB_CRATE_INFO,
623624
crate::redundant_slicing::DEREF_BY_SLICING_INFO,
624625
crate::redundant_slicing::REDUNDANT_SLICING_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ mod redundant_closure_call;
298298
mod redundant_else;
299299
mod redundant_field_names;
300300
mod redundant_locals;
301+
mod redundant_owned_struct_recreation;
301302
mod redundant_pub_crate;
302303
mod redundant_slicing;
303304
mod redundant_static_lifetimes;
@@ -706,6 +707,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
706707
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
707708
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
708709
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
710+
store.register_late_pass(|_| Box::new(redundant_owned_struct_recreation::RedundantOwnedStructRecreation));
709711
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
710712
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
711713
let format_args = format_args_storage.clone();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#![allow(unused)]
2+
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
3+
use clippy_utils::source::snippet;
4+
use rustc_data_structures::fx::FxHashMap;
5+
use rustc_errors::Applicability;
6+
7+
use rustc_hir::{self as hir, ExprKind, Path, PathSegment, QPath};
8+
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::declare_lint_pass;
11+
use std::fmt::{self, Write as _};
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for struct literals that recreate a struct we already have an owned
16+
/// version of.
17+
///
18+
/// ### Why is this bad?
19+
/// This is essentially a no-op as all members are being moved into the new
20+
/// struct and the old one becomes inaccessible.
21+
///
22+
/// ### Example
23+
/// ```no_run
24+
/// fn redundant () {
25+
/// struct Foo { a: i32, b: String }
26+
///
27+
/// let foo = Foo { a: 42, b: "Hello, Foo!".into() };
28+
///
29+
/// let bar = Foo { a: foo.a, b: foo.b };
30+
///
31+
/// println!("redundant: bar = Foo {{ a: {}, b: \"{}\" }}", bar.a, bar.b);
32+
/// }
33+
/// ```
34+
///
35+
/// The struct literal in the assignment to ``bar`` could be replaced with
36+
/// ``let bar = foo``.
37+
///
38+
/// Empty structs are ignored by the lint.
39+
#[clippy::version = "pre 1.29.0"]
40+
pub REDUNDANT_OWNED_STRUCT_RECREATION,
41+
//pedantic,
42+
correctness,
43+
"recreation of owned structs from identical parts"
44+
}
45+
46+
declare_lint_pass!(RedundantOwnedStructRecreation => [REDUNDANT_OWNED_STRUCT_RECREATION]);
47+
48+
impl<'tcx> LateLintPass<'tcx> for RedundantOwnedStructRecreation {
49+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
50+
if !expr.span.from_expansion()
51+
&& let ExprKind::Struct(qpath, fields, base) = expr.kind
52+
{
53+
let ty = cx.typeck_results().expr_ty(expr);
54+
55+
// Conditions that must be satisfied to trigger the lint:
56+
// - source of the assignment must be a struct
57+
// - type of struct in expr must match
58+
// - names of assignee struct fields (lhs) must match the names of the assigned on (rhs)
59+
// - all fields musts be assigned (no Default)
60+
61+
if base.is_some() {
62+
// one or more fields assigned from `..Default`
63+
return;
64+
}
65+
66+
let matching = fields.iter().filter(|f| !f.is_shorthand).try_fold(
67+
None,
68+
|seen_path, f| -> Result<Option<&'tcx Path<'tcx>>, ()> {
69+
// fields are assigned from expression
70+
if let ExprKind::Field(expr, ident) = f.expr.kind
71+
// expression type matches
72+
&& ty == cx.typeck_results().expr_ty(expr)
73+
// field name matches
74+
&& f.ident == ident
75+
&& let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind
76+
{
77+
match seen_path {
78+
None => {
79+
// first field assignment
80+
Ok(Some(path))
81+
},
82+
Some(p) if p.res == path.res => {
83+
// subsequent field assignment, same origin struct as before
84+
Ok(seen_path)
85+
},
86+
Some(_) => {
87+
// subsequent field assignment, different origin struct as before
88+
Err(())
89+
},
90+
}
91+
} else {
92+
// source of field assignment not an expression
93+
Err(())
94+
}
95+
},
96+
);
97+
98+
let Ok(Some(path)) = matching else { return };
99+
100+
let sugg = format!("{}", snippet(cx, path.span, ".."),);
101+
102+
span_lint_and_sugg(
103+
cx,
104+
REDUNDANT_OWNED_STRUCT_RECREATION,
105+
expr.span,
106+
"recreation of owned struct from identical parts",
107+
"try replacing it with",
108+
sugg,
109+
// the lint may be incomplete due to partial moves
110+
// of struct fields
111+
Applicability::MaybeIncorrect,
112+
);
113+
}
114+
}
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//@no-rustfix
2+
#![warn(clippy::redundant_owned_struct_recreation)]
3+
#![allow(dead_code, unused_variables)]
4+
5+
fn recreate() {
6+
struct Recreated<'s> {
7+
a: i32,
8+
b: &'s str,
9+
}
10+
11+
fn recreate_immediate(r: Recreated) -> Recreated {
12+
Recreated { a: r.a, b: r.b }
13+
}
14+
15+
fn recreate_through_binding(r: Recreated) -> Recreated {
16+
let b = r.b;
17+
Recreated { a: r.a, b }
18+
}
19+
20+
let b = String::from("Answer");
21+
let r = recreate_immediate(Recreated { a: 42, b: &b });
22+
let r = recreate_through_binding(Recreated { a: 42, b: &b });
23+
}
24+
25+
struct One;
26+
struct Two;
27+
struct Three {
28+
one: One,
29+
two: Two,
30+
}
31+
32+
fn recreate_immediate(xy: Three) -> Three {
33+
Three {
34+
one: xy.one,
35+
two: xy.two,
36+
}
37+
}
38+
39+
fn recreate_through_binding(xy: Three) -> Three {
40+
let two = xy.two;
41+
// Here ``xy`` is subject to a partial move so we cannot
42+
// emit a machine applicable fixup suggestion.
43+
Three { one: xy.one, two }
44+
}
45+
46+
impl Three {
47+
fn recreate_through_member(self) -> Self {
48+
Self {
49+
one: self.one,
50+
two: self.two,
51+
}
52+
}
53+
}
54+
55+
fn ignore_struct_with_default() {
56+
#[derive(Default)]
57+
struct WithDefault {
58+
a: usize,
59+
b: u8,
60+
}
61+
62+
let one = WithDefault { a: 1337usize, b: 42u8 };
63+
// This *must not* trigger the lint.
64+
let two = WithDefault {
65+
a: one.a,
66+
..Default::default()
67+
};
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: recreation of owned struct from identical parts
2+
--> tests/ui/redundant_owned_struct_recreation.rs:12:9
3+
|
4+
LL | Recreated { a: r.a, b: r.b }
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing it with: `r`
6+
|
7+
= note: `-D clippy::redundant-owned-struct-recreation` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::redundant_owned_struct_recreation)]`
9+
10+
error: recreation of owned struct from identical parts
11+
--> tests/ui/redundant_owned_struct_recreation.rs:17:9
12+
|
13+
LL | Recreated { a: r.a, b }
14+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing it with: `r`
15+
16+
error: recreation of owned struct from identical parts
17+
--> tests/ui/redundant_owned_struct_recreation.rs:33:5
18+
|
19+
LL | / Three {
20+
LL | | one: xy.one,
21+
LL | | two: xy.two,
22+
LL | | }
23+
| |_____^ help: try replacing it with: `xy`
24+
25+
error: recreation of owned struct from identical parts
26+
--> tests/ui/redundant_owned_struct_recreation.rs:43:5
27+
|
28+
LL | Three { one: xy.one, two }
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try replacing it with: `xy`
30+
31+
error: recreation of owned struct from identical parts
32+
--> tests/ui/redundant_owned_struct_recreation.rs:48:9
33+
|
34+
LL | / Self {
35+
LL | | one: self.one,
36+
LL | | two: self.two,
37+
LL | | }
38+
| |_________^ help: try replacing it with: `self`
39+
40+
error: aborting due to 5 previous errors
41+

0 commit comments

Comments
 (0)