Skip to content

Commit cfc6c4b

Browse files
committed
Handle field attributes in suggestions
1 parent 5c80b35 commit cfc6c4b

6 files changed

+131
-44
lines changed

clippy_lints/src/inconsistent_struct_constructor.rs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ use clippy_config::Conf;
22
use clippy_config::types::InitializerSuggestionApplicability;
33
use clippy_utils::diagnostics::span_lint_and_sugg;
44
use clippy_utils::fulfill_or_allowed;
5-
use clippy_utils::source::{snippet, snippet_opt};
5+
use clippy_utils::source::snippet_opt;
66
use rustc_data_structures::fx::FxHashMap;
77
use rustc_errors::Applicability;
8-
use rustc_hir::{self as hir, ExprKind, StructTailExpr};
8+
use rustc_hir::{self as hir, ExprKind};
99
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty::TyCtxt;
1011
use rustc_session::impl_lint_pass;
12+
use rustc_span::Span;
1113
use rustc_span::symbol::Symbol;
12-
use std::fmt::{self, Write as _};
1314

1415
declare_clippy_lint! {
1516
/// ### What it does
@@ -80,7 +81,7 @@ impl_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTO
8081

8182
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
8283
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
83-
let ExprKind::Struct(qpath, fields, base) = expr.kind else {
84+
let ExprKind::Struct(_, fields, _) = expr.kind else {
8485
return;
8586
};
8687
let applicability = if fields.iter().all(|f| f.is_shorthand) {
@@ -107,32 +108,15 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
107108
return;
108109
}
109110

110-
let mut ordered_fields: Vec<_> = fields.to_vec();
111-
ordered_fields.sort_unstable_by_key(|id| def_order_map[&id.ident.name]);
112-
113-
let mut fields_snippet = String::new();
114-
let (last_field, fields) = ordered_fields.split_last().unwrap();
115-
for field in fields {
116-
let _: fmt::Result = write!(fields_snippet, "{}, ", snippet_opt(cx, field.span).unwrap());
117-
}
118-
fields_snippet.push_str(&snippet_opt(cx, last_field.span).unwrap());
119-
120-
let base_snippet = if let StructTailExpr::Base(base) = base {
121-
format!(", ..{}", snippet(cx, base.span, ".."))
122-
} else {
123-
String::new()
124-
};
125-
126-
let sugg = format!(
127-
"{} {{ {fields_snippet}{base_snippet} }}",
128-
snippet(cx, qpath.span(), ".."),
129-
);
111+
let span = field_with_attrs_span(cx.tcx, fields.first().unwrap())
112+
.with_hi(field_with_attrs_span(cx.tcx, fields.last().unwrap()).hi());
113+
let sugg = suggestion(cx, fields, &def_order_map);
130114

131115
if !fulfill_or_allowed(cx, INCONSISTENT_STRUCT_CONSTRUCTOR, Some(ty_hir_id)) {
132116
span_lint_and_sugg(
133117
cx,
134118
INCONSISTENT_STRUCT_CONSTRUCTOR,
135-
expr.span,
119+
span,
136120
"struct constructor field order is inconsistent with struct definition field order",
137121
"try",
138122
sugg,
@@ -157,3 +141,45 @@ fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map
157141

158142
true
159143
}
144+
145+
fn suggestion<'tcx>(
146+
cx: &LateContext<'_>,
147+
fields: &'tcx [hir::ExprField<'tcx>],
148+
def_order_map: &FxHashMap<Symbol, usize>,
149+
) -> String {
150+
let ws = fields
151+
.windows(2)
152+
.map(|w| {
153+
let w0_span = field_with_attrs_span(cx.tcx, &w[0]);
154+
let w1_span = field_with_attrs_span(cx.tcx, &w[1]);
155+
let span = w0_span.with_hi(w1_span.lo()).with_lo(w0_span.hi());
156+
snippet_opt(cx, span).unwrap()
157+
})
158+
.collect::<Vec<_>>();
159+
160+
let mut fields = fields.to_vec();
161+
fields.sort_unstable_by_key(|field| def_order_map[&field.ident.name]);
162+
let field_snippets = fields
163+
.iter()
164+
.map(|field| snippet_opt(cx, field_with_attrs_span(cx.tcx, field)).unwrap())
165+
.collect::<Vec<_>>();
166+
167+
assert_eq!(field_snippets.len(), ws.len() + 1);
168+
169+
let mut sugg = String::new();
170+
for i in 0..field_snippets.len() {
171+
sugg += &field_snippets[i];
172+
if i < ws.len() {
173+
sugg += &ws[i];
174+
}
175+
}
176+
sugg
177+
}
178+
179+
fn field_with_attrs_span(tcx: TyCtxt<'_>, field: &hir::ExprField<'_>) -> Span {
180+
if let Some(attr) = tcx.hir().attrs(field.hir_id).first() {
181+
field.span.with_lo(attr.span.lo())
182+
} else {
183+
field.span
184+
}
185+
}

tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.fixed

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,25 @@ fn main() {
1717

1818
Foo { x, y, z: z };
1919

20-
Foo { x, z: z, ..Default::default() };
20+
Foo {
21+
x,
22+
z: z,
23+
..Default::default()
24+
};
25+
}
26+
27+
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1859261645
28+
mod field_attributes {
29+
struct HirId;
30+
struct BodyVisitor {
31+
macro_unsafe_blocks: Vec<HirId>,
32+
expn_depth: u32,
33+
}
34+
fn check_body(condition: bool) {
35+
BodyVisitor {
36+
macro_unsafe_blocks: Vec::new(),
37+
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
38+
expn_depth: if condition { 1 } else { 0 },
39+
};
40+
}
2141
}

tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,19 @@ fn main() {
2323
..Default::default()
2424
};
2525
}
26+
27+
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1859261645
28+
mod field_attributes {
29+
struct HirId;
30+
struct BodyVisitor {
31+
macro_unsafe_blocks: Vec<HirId>,
32+
expn_depth: u32,
33+
}
34+
fn check_body(condition: bool) {
35+
BodyVisitor {
36+
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
37+
expn_depth: if condition { 1 } else { 0 },
38+
macro_unsafe_blocks: Vec::new(),
39+
};
40+
}
41+
}
Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
11
error: struct constructor field order is inconsistent with struct definition field order
2-
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:18:5
2+
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:18:11
33
|
44
LL | Foo { y, x, z: z };
5-
| ^^^^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z: z }`
5+
| ^^^^^^^^^^ help: try: `x, y, z: z`
66
|
77
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]`
99

1010
error: struct constructor field order is inconsistent with struct definition field order
11-
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:20:5
11+
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:21:9
1212
|
13-
LL | / Foo {
14-
LL | | z: z,
13+
LL | / z: z,
1514
LL | | x,
16-
LL | | ..Default::default()
17-
LL | | };
18-
| |_____^ help: try: `Foo { x, z: z, ..Default::default() }`
15+
| |_________^
16+
|
17+
help: try
18+
|
19+
LL ~ x,
20+
LL ~ z: z,
21+
|
22+
23+
error: struct constructor field order is inconsistent with struct definition field order
24+
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:36:13
25+
|
26+
LL | / #[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
27+
LL | | expn_depth: if condition { 1 } else { 0 },
28+
LL | | macro_unsafe_blocks: Vec::new(),
29+
| |___________________________________________^
30+
|
31+
help: try
32+
|
33+
LL ~ macro_unsafe_blocks: Vec::new(),
34+
LL + #[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
35+
LL ~ expn_depth: if condition { 1 } else { 0 },
36+
|
1937

20-
error: aborting due to 2 previous errors
38+
error: aborting due to 3 previous errors
2139

tests/ui/inconsistent_struct_constructor.fixed

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ mod with_base {
6060
let z = 1;
6161

6262
// Should lint.
63-
Foo { x, z, ..Default::default() };
63+
Foo {
64+
x,
65+
z,
66+
..Default::default()
67+
};
6468

6569
// Should NOT lint because the order is consistent with the definition.
6670
Foo {
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
error: struct constructor field order is inconsistent with struct definition field order
2-
--> tests/ui/inconsistent_struct_constructor.rs:36:9
2+
--> tests/ui/inconsistent_struct_constructor.rs:36:15
33
|
44
LL | Foo { y, x, z };
5-
| ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }`
5+
| ^^^^^^^ help: try: `x, y, z`
66
|
77
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]`
99

1010
error: struct constructor field order is inconsistent with struct definition field order
11-
--> tests/ui/inconsistent_struct_constructor.rs:63:9
11+
--> tests/ui/inconsistent_struct_constructor.rs:64:13
1212
|
13-
LL | / Foo {
14-
LL | | z,
13+
LL | / z,
1514
LL | | x,
16-
LL | | ..Default::default()
17-
LL | | };
18-
| |_________^ help: try: `Foo { x, z, ..Default::default() }`
15+
| |_____________^
16+
|
17+
help: try
18+
|
19+
LL ~ x,
20+
LL ~ z,
21+
|
1922

2023
error: aborting due to 2 previous errors
2124

0 commit comments

Comments
 (0)