Skip to content

Commit d230389

Browse files
committed
New lint: inconsistent_struct_constructor
1 parent 67087a1 commit d230389

File tree

6 files changed

+270
-0
lines changed

6 files changed

+270
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,6 +2109,7 @@ Released 2018-09-13
21092109
[`implicit_saturating_sub`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_saturating_sub
21102110
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
21112111
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
2112+
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
21122113
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
21132114
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
21142115
[`inefficient_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#inefficient_to_string
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use rustc_data_structures::fx::FxHashMap;
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{self as hir, ExprKind};
4+
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_session::{declare_lint_pass, declare_tool_lint};
6+
use rustc_span::symbol::Symbol;
7+
8+
use if_chain::if_chain;
9+
10+
use crate::utils::{snippet, span_lint_and_sugg};
11+
12+
declare_clippy_lint! {
13+
/// **What it does:** Checks for struct constructors where the order of the field init
14+
/// shorthand in the constructor is inconsistent with the order in the struct definition.
15+
///
16+
/// **Why is this bad?** It decreases readability and consistency.
17+
///
18+
/// **Known problems:** None.
19+
///
20+
/// **Example:**
21+
///
22+
/// ```rust
23+
/// struct Foo {
24+
/// x: i32,
25+
/// y: i32,
26+
/// }
27+
/// let x = 1;
28+
/// let y = 2;
29+
/// Foo { y, x };
30+
/// ```
31+
///
32+
/// Use instead:
33+
/// ```rust
34+
/// # struct Foo {
35+
/// # x: i32,
36+
/// # y: i32,
37+
/// # }
38+
/// # let x = 1;
39+
/// # let y = 2;
40+
/// Foo { x, y };
41+
/// ```
42+
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
43+
style,
44+
"the order of the field init shorthand is inconsistent with the order in the struct definition"
45+
}
46+
47+
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
48+
49+
impl LateLintPass<'_> for InconsistentStructConstructor {
50+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
51+
if_chain! {
52+
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
53+
if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
54+
let ty = cx.tcx.type_of(def_id);
55+
if let Some(adt_def) = ty.ty_adt_def();
56+
if adt_def.is_struct();
57+
if let Some(variant) = adt_def.variants.iter().next();
58+
if fields.iter().all(|f| f.is_shorthand);
59+
then {
60+
let mut def_order_map = FxHashMap::default();
61+
for (idx, field) in variant.fields.iter().enumerate() {
62+
def_order_map.insert(field.ident.name, idx);
63+
}
64+
65+
if is_consistent_order(fields, &def_order_map) {
66+
return;
67+
}
68+
69+
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
70+
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
71+
72+
let mut fields_snippet = String::new();
73+
let (last_ident, idents) = ordered_fields.split_last().unwrap();
74+
for ident in idents {
75+
fields_snippet.push_str(&format!("{}, ", ident));
76+
}
77+
fields_snippet.push_str(&format!("{}", last_ident));
78+
79+
let base_snippet = if let Some(base) = base {
80+
format!(", ..{}", snippet(cx, base.span, ".."))
81+
} else {
82+
"".to_string()
83+
};
84+
85+
let sugg = format!("{} {{ {}{} }}",
86+
snippet(cx, qpath.span(), ".."),
87+
fields_snippet,
88+
base_snippet,
89+
);
90+
91+
span_lint_and_sugg(
92+
cx,
93+
INCONSISTENT_STRUCT_CONSTRUCTOR,
94+
expr.span,
95+
"inconsistent struct constructor",
96+
"try",
97+
sugg,
98+
Applicability::MachineApplicable,
99+
)
100+
}
101+
}
102+
}
103+
}
104+
105+
// Check whether the order of the fields in the constructor is consistent with the order in the
106+
// definition.
107+
fn is_consistent_order<'tcx>(fields: &'tcx [hir::Field<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
108+
let mut cur_idx = usize::MIN;
109+
for f in fields {
110+
let next_idx = def_order_map[&f.ident.name];
111+
if cur_idx > next_idx {
112+
return false;
113+
}
114+
cur_idx = next_idx;
115+
}
116+
117+
true
118+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ mod if_let_some_result;
221221
mod if_not_else;
222222
mod implicit_return;
223223
mod implicit_saturating_sub;
224+
mod inconsistent_struct_constructor;
224225
mod indexing_slicing;
225226
mod infinite_iter;
226227
mod inherent_impl;
@@ -656,6 +657,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
656657
&if_not_else::IF_NOT_ELSE,
657658
&implicit_return::IMPLICIT_RETURN,
658659
&implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
660+
&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
659661
&indexing_slicing::INDEXING_SLICING,
660662
&indexing_slicing::OUT_OF_BOUNDS_INDEXING,
661663
&infinite_iter::INFINITE_ITER,
@@ -1036,6 +1038,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10361038
store.register_late_pass(|| box implicit_return::ImplicitReturn);
10371039
store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
10381040
store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback);
1041+
store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor);
10391042

10401043
let msrv = conf.msrv.as_ref().and_then(|s| {
10411044
parse_msrv(s, None, None).or_else(|| {
@@ -1485,6 +1488,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14851488
LintId::of(&identity_op::IDENTITY_OP),
14861489
LintId::of(&if_let_mutex::IF_LET_MUTEX),
14871490
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
1491+
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
14881492
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
14891493
LintId::of(&infinite_iter::INFINITE_ITER),
14901494
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
@@ -1737,6 +1741,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17371741
LintId::of(&functions::MUST_USE_UNIT),
17381742
LintId::of(&functions::RESULT_UNIT_ERR),
17391743
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
1744+
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
17401745
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
17411746
LintId::of(&len_zero::COMPARISON_TO_EMPTY),
17421747
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// run-rustfix
2+
// edition:2018
3+
#![warn(clippy::inconsistent_struct_constructor)]
4+
#![allow(clippy::redundant_field_names)]
5+
#![allow(clippy::unnecessary_operation)]
6+
#![allow(clippy::no_effect)]
7+
#![allow(dead_code)]
8+
9+
#[derive(Default)]
10+
struct Foo {
11+
x: i32,
12+
y: i32,
13+
z: i32,
14+
}
15+
16+
mod without_base {
17+
use super::Foo;
18+
19+
fn test() {
20+
let x = 1;
21+
let y = 1;
22+
let z = 1;
23+
24+
// Should lint.
25+
Foo { x, y, z };
26+
27+
// Shoule NOT lint because the order is the same as in the definition.
28+
Foo { x, y, z };
29+
30+
// Should NOT lint because z is not a shorthand init.
31+
Foo { y, x, z: z };
32+
}
33+
}
34+
35+
mod with_base {
36+
use super::Foo;
37+
38+
fn test() {
39+
let x = 1;
40+
let z = 1;
41+
42+
// Should lint.
43+
Foo { x, z, ..Default::default() };
44+
45+
// Should NOT lint because the order is consistent with the definition.
46+
Foo {
47+
x,
48+
z,
49+
..Default::default()
50+
};
51+
52+
// Should NOT lint because z is not a shorthand init.
53+
Foo {
54+
z: z,
55+
x,
56+
..Default::default()
57+
};
58+
}
59+
}
60+
61+
fn main() {}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// run-rustfix
2+
// edition:2018
3+
#![warn(clippy::inconsistent_struct_constructor)]
4+
#![allow(clippy::redundant_field_names)]
5+
#![allow(clippy::unnecessary_operation)]
6+
#![allow(clippy::no_effect)]
7+
#![allow(dead_code)]
8+
9+
#[derive(Default)]
10+
struct Foo {
11+
x: i32,
12+
y: i32,
13+
z: i32,
14+
}
15+
16+
mod without_base {
17+
use super::Foo;
18+
19+
fn test() {
20+
let x = 1;
21+
let y = 1;
22+
let z = 1;
23+
24+
// Should lint.
25+
Foo { y, x, z };
26+
27+
// Shoule NOT lint because the order is the same as in the definition.
28+
Foo { x, y, z };
29+
30+
// Should NOT lint because z is not a shorthand init.
31+
Foo { y, x, z: z };
32+
}
33+
}
34+
35+
mod with_base {
36+
use super::Foo;
37+
38+
fn test() {
39+
let x = 1;
40+
let z = 1;
41+
42+
// Should lint.
43+
Foo {
44+
z,
45+
x,
46+
..Default::default()
47+
};
48+
49+
// Should NOT lint because the order is consistent with the definition.
50+
Foo {
51+
x,
52+
z,
53+
..Default::default()
54+
};
55+
56+
// Should NOT lint because z is not a shorthand init.
57+
Foo {
58+
z: z,
59+
x,
60+
..Default::default()
61+
};
62+
}
63+
}
64+
65+
fn main() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: inconsistent struct constructor
2+
--> $DIR/inconsistent_struct_constructor.rs:25:9
3+
|
4+
LL | Foo { y, x, z };
5+
| ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }`
6+
|
7+
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
8+
9+
error: inconsistent struct constructor
10+
--> $DIR/inconsistent_struct_constructor.rs:43:9
11+
|
12+
LL | / Foo {
13+
LL | | z,
14+
LL | | x,
15+
LL | | ..Default::default()
16+
LL | | };
17+
| |_________^ help: try: `Foo { x, z, ..Default::default() }`
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)