Skip to content

Commit fe01ddc

Browse files
committed
Auto merge of #6769 - Y-Nak:inconsistent-struct-constructor, r=matthiaskrgr
Inconsistent struct constructor fixes: #6352 r? `@matthiaskrgr` I added the lint that checks for the struct constructors where the order of the field init shorthands is inconsistent with that in the struct definition. changelog: Add style lint: `inconsistent_struct_constructor`
2 parents 728f397 + bfdf0fa commit fe01ddc

7 files changed

+288
-2
lines changed

CHANGELOG.md

+1
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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?** Since the order of fields in a constructor doesn't affect the
17+
/// resulted instance as the below example indicates,
18+
///
19+
/// ```rust
20+
/// #[derive(Debug, PartialEq, Eq)]
21+
/// struct Foo {
22+
/// x: i32,
23+
/// y: i32,
24+
/// }
25+
/// let x = 1;
26+
/// let y = 2;
27+
///
28+
/// // This assertion never fails.
29+
/// assert_eq!(Foo { x, y }, Foo { y, x });
30+
/// ```
31+
///
32+
/// inconsistent order means nothing and just decreases readability and consistency.
33+
///
34+
/// **Known problems:** None.
35+
///
36+
/// **Example:**
37+
///
38+
/// ```rust
39+
/// struct Foo {
40+
/// x: i32,
41+
/// y: i32,
42+
/// }
43+
/// let x = 1;
44+
/// let y = 2;
45+
/// Foo { y, x };
46+
/// ```
47+
///
48+
/// Use instead:
49+
/// ```rust
50+
/// # struct Foo {
51+
/// # x: i32,
52+
/// # y: i32,
53+
/// # }
54+
/// # let x = 1;
55+
/// # let y = 2;
56+
/// Foo { x, y };
57+
/// ```
58+
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
59+
style,
60+
"the order of the field init shorthand is inconsistent with the order in the struct definition"
61+
}
62+
63+
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
64+
65+
impl LateLintPass<'_> for InconsistentStructConstructor {
66+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
67+
if_chain! {
68+
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
69+
if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
70+
let ty = cx.tcx.type_of(def_id);
71+
if let Some(adt_def) = ty.ty_adt_def();
72+
if adt_def.is_struct();
73+
if let Some(variant) = adt_def.variants.iter().next();
74+
if fields.iter().all(|f| f.is_shorthand);
75+
then {
76+
let mut def_order_map = FxHashMap::default();
77+
for (idx, field) in variant.fields.iter().enumerate() {
78+
def_order_map.insert(field.ident.name, idx);
79+
}
80+
81+
if is_consistent_order(fields, &def_order_map) {
82+
return;
83+
}
84+
85+
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
86+
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
87+
88+
let mut fields_snippet = String::new();
89+
let (last_ident, idents) = ordered_fields.split_last().unwrap();
90+
for ident in idents {
91+
fields_snippet.push_str(&format!("{}, ", ident));
92+
}
93+
fields_snippet.push_str(&last_ident.to_string());
94+
95+
let base_snippet = if let Some(base) = base {
96+
format!(", ..{}", snippet(cx, base.span, ".."))
97+
} else {
98+
String::new()
99+
};
100+
101+
let sugg = format!("{} {{ {}{} }}",
102+
snippet(cx, qpath.span(), ".."),
103+
fields_snippet,
104+
base_snippet,
105+
);
106+
107+
span_lint_and_sugg(
108+
cx,
109+
INCONSISTENT_STRUCT_CONSTRUCTOR,
110+
expr.span,
111+
"inconsistent struct constructor",
112+
"try",
113+
sugg,
114+
Applicability::MachineApplicable,
115+
)
116+
}
117+
}
118+
}
119+
}
120+
121+
// Check whether the order of the fields in the constructor is consistent with the order in the
122+
// definition.
123+
fn is_consistent_order<'tcx>(fields: &'tcx [hir::Field<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
124+
let mut cur_idx = usize::MIN;
125+
for f in fields {
126+
let next_idx = def_order_map[&f.ident.name];
127+
if cur_idx > next_idx {
128+
return false;
129+
}
130+
cur_idx = next_idx;
131+
}
132+
133+
true
134+
}

clippy_lints/src/lib.rs

+5
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(|| {
@@ -1486,6 +1489,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14861489
LintId::of(&identity_op::IDENTITY_OP),
14871490
LintId::of(&if_let_mutex::IF_LET_MUTEX),
14881491
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
1492+
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
14891493
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
14901494
LintId::of(&infinite_iter::INFINITE_ITER),
14911495
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),

clippy_lints/src/unnecessary_sort_by.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
212212
if !expr_borrows(cx, left_expr) {
213213
return Some(LintTrigger::SortByKey(SortByKeyDetection {
214214
vec_name,
215-
unstable,
216215
closure_arg,
217216
closure_body,
218-
reverse
217+
reverse,
218+
unstable,
219219
}));
220220
}
221221
}
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() {}
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() {}
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)