Skip to content

Commit 428844e

Browse files
committed
Make "all fields are shorthand" requirement configurable
1 parent d49501c commit 428844e

File tree

11 files changed

+191
-21
lines changed

11 files changed

+191
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6201,6 +6201,7 @@ Released 2018-09-13
62016201
[`excessive-nesting-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#excessive-nesting-threshold
62026202
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
62036203
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
6204+
[`initializer-suggestions`]: https://doc.rust-lang.org/clippy/lint_configuration.html#initializer-suggestions
62046205
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
62056206
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
62066207
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else

book/src/lint_configuration.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,36 @@ A list of paths to types that should be treated as if they do not contain interi
562562
* [`mutable_key_type`](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type)
563563

564564

565+
## `initializer-suggestions`
566+
Suggestion behavior when initializers are present. Options are:
567+
568+
- "none": do not suggest
569+
- "maybe-incorrect": suggest, but do not apply suggestions with `--fix`
570+
- "machine-applicable": suggest and apply suggestions with `--fix`
571+
572+
The following example [due to @ronnodas] shows why "maybe-incorrect" may be the right choice.
573+
Swapping the fields in the constructor produces incompilable code:
574+
575+
```rust
576+
struct MyStruct {
577+
vector: Vec<u32>,
578+
length: usize
579+
}
580+
fn main() {
581+
let vector = vec![1,2,3];
582+
MyStruct { length: vector.len(), vector};
583+
}
584+
```
585+
586+
[due to @ronnodas]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
587+
588+
**Default Value:** `"none"`
589+
590+
---
591+
**Affected lints:**
592+
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
593+
594+
565595
## `large-error-threshold`
566596
The maximum size of the `Err`-variant in a `Result` returned from a function
567597

clippy_config/src/conf.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::ClippyConfiguration;
22
use crate::types::{
3-
DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering,
4-
SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind,
5-
SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
3+
DisallowedPath, InitializerSuggestionApplicability, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour,
4+
Rename, SourceItemOrdering, SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings,
5+
SourceItemOrderingModuleItemKind, SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds,
66
};
77
use clippy_utils::msrvs::Msrv;
88
use rustc_errors::Applicability;
@@ -526,6 +526,29 @@ define_Conf! {
526526
/// A list of paths to types that should be treated as if they do not contain interior mutability
527527
#[lints(borrow_interior_mutable_const, declare_interior_mutable_const, ifs_same_cond, mutable_key_type)]
528528
ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()]),
529+
/// Suggestion behavior when initializers are present. Options are:
530+
///
531+
/// - "none": do not suggest
532+
/// - "maybe-incorrect": suggest, but do not apply suggestions with `--fix`
533+
/// - "machine-applicable": suggest and apply suggestions with `--fix`
534+
///
535+
/// The following example [due to @ronnodas] shows why "maybe-incorrect" may be the right choice.
536+
/// Swapping the fields in the constructor produces incompilable code:
537+
///
538+
/// ```rust
539+
/// struct MyStruct {
540+
/// vector: Vec<u32>,
541+
/// length: usize
542+
/// }
543+
/// fn main() {
544+
/// let vector = vec![1,2,3];
545+
/// MyStruct { length: vector.len(), vector};
546+
/// }
547+
/// ```
548+
///
549+
/// [due to @ronnodas]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
550+
#[lints(inconsistent_struct_constructor)]
551+
initializer_suggestions: InitializerSuggestionApplicability = InitializerSuggestionApplicability::None,
529552
/// The maximum size of the `Err`-variant in a `Result` returned from a function
530553
#[lints(result_large_err)]
531554
large_error_threshold: u64 = 128,

clippy_config/src/types.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clippy_utils::def_path_def_ids;
2+
use rustc_errors::Applicability;
23
use rustc_hir::def_id::DefIdMap;
34
use rustc_middle::ty::TyCtxt;
45
use serde::de::{self, Deserializer, Visitor};
@@ -46,6 +47,24 @@ pub fn create_disallowed_map(
4647
.collect()
4748
}
4849

50+
#[derive(Clone, Copy, Deserialize, Serialize)]
51+
#[serde(rename_all = "kebab-case")]
52+
pub enum InitializerSuggestionApplicability {
53+
None,
54+
MaybeIncorrect,
55+
MachineApplicable,
56+
}
57+
58+
impl InitializerSuggestionApplicability {
59+
pub fn to_applicability(self) -> Option<Applicability> {
60+
match self {
61+
InitializerSuggestionApplicability::None => None,
62+
InitializerSuggestionApplicability::MaybeIncorrect => Some(Applicability::MaybeIncorrect),
63+
InitializerSuggestionApplicability::MachineApplicable => Some(Applicability::MachineApplicable),
64+
}
65+
}
66+
}
67+
4968
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
5069
pub enum MatchLintBehaviour {
5170
AllTypes,

clippy_lints/src/inconsistent_struct_constructor.rs

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1+
use clippy_config::Conf;
2+
use clippy_config::types::InitializerSuggestionApplicability;
13
use clippy_utils::diagnostics::span_lint_and_sugg;
24
use clippy_utils::fulfill_or_allowed;
3-
use clippy_utils::source::snippet;
5+
use clippy_utils::source::{snippet, snippet_opt};
46
use rustc_data_structures::fx::FxHashMap;
57
use rustc_errors::Applicability;
68
use rustc_hir::{self as hir, ExprKind};
79
use rustc_lint::{LateContext, LateLintPass};
8-
use rustc_session::declare_lint_pass;
10+
use rustc_session::impl_lint_pass;
911
use rustc_span::symbol::Symbol;
1012
use std::fmt::{self, Write as _};
1113

1214
declare_clippy_lint! {
1315
/// ### What it does
14-
/// Checks for struct constructors where all fields are shorthand and
15-
/// the order of the field init shorthand in the constructor is inconsistent
16-
/// with the order in the struct definition.
16+
/// Checks for struct constructors where the order of the field
17+
/// init in the constructor is inconsistent with the order in the
18+
/// struct definition.
1719
///
1820
/// ### Why is this bad?
1921
/// Since the order of fields in a constructor doesn't affect the
@@ -59,16 +61,36 @@ declare_clippy_lint! {
5961
#[clippy::version = "1.52.0"]
6062
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
6163
pedantic,
62-
"the order of the field init shorthand is inconsistent with the order in the struct definition"
64+
"the order of the field init is inconsistent with the order in the struct definition"
6365
}
6466

65-
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
67+
pub struct InconsistentStructConstructor {
68+
initializer_suggestions: InitializerSuggestionApplicability,
69+
}
70+
71+
impl InconsistentStructConstructor {
72+
pub fn new(conf: &'static Conf) -> Self {
73+
Self {
74+
initializer_suggestions: conf.initializer_suggestions,
75+
}
76+
}
77+
}
78+
79+
impl_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
6680

6781
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
6882
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
69-
if let ExprKind::Struct(qpath, fields, base) = expr.kind
70-
&& fields.iter().all(|f| f.is_shorthand)
71-
&& !expr.span.from_expansion()
83+
let ExprKind::Struct(qpath, fields, base) = expr.kind else {
84+
return;
85+
};
86+
let applicability = if fields.iter().all(|f| f.is_shorthand) {
87+
Applicability::MachineApplicable
88+
} else if let Some(applicability) = self.initializer_suggestions.to_applicability() {
89+
applicability
90+
} else {
91+
return;
92+
};
93+
if !expr.span.from_expansion()
7294
&& let ty = cx.typeck_results().expr_ty(expr)
7395
&& let Some(adt_def) = ty.ty_adt_def()
7496
&& adt_def.is_struct()
@@ -85,15 +107,15 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
85107
return;
86108
}
87109

88-
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
89-
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
110+
let mut ordered_fields: Vec<_> = fields.to_vec();
111+
ordered_fields.sort_unstable_by_key(|id| def_order_map[&id.ident.name]);
90112

91113
let mut fields_snippet = String::new();
92-
let (last_ident, idents) = ordered_fields.split_last().unwrap();
93-
for ident in idents {
94-
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
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());
95117
}
96-
fields_snippet.push_str(&last_ident.to_string());
118+
fields_snippet.push_str(&snippet_opt(cx, last_field.span).unwrap());
97119

98120
let base_snippet = if let Some(base) = base {
99121
format!(", ..{}", snippet(cx, base.span, ".."))
@@ -114,7 +136,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
114136
"struct constructor field order is inconsistent with struct definition field order",
115137
"try",
116138
sugg,
117-
Applicability::MachineApplicable,
139+
applicability,
118140
);
119141
}
120142
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
646646
store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn));
647647
store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf)));
648648
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
649-
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
649+
store.register_late_pass(move |_| {
650+
Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new(
651+
conf,
652+
))
653+
});
650654
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
651655
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
652656
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf)));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
initializer-suggestions = "machine-applicable"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![warn(clippy::inconsistent_struct_constructor)]
2+
#![allow(clippy::redundant_field_names)]
3+
#![allow(clippy::unnecessary_operation)]
4+
#![allow(clippy::no_effect)]
5+
6+
#[derive(Default)]
7+
struct Foo {
8+
x: i32,
9+
y: i32,
10+
z: i32,
11+
}
12+
13+
fn main() {
14+
let x = 1;
15+
let y = 1;
16+
let z = 1;
17+
18+
Foo { x, y, z: z };
19+
20+
Foo { x, z: z, ..Default::default() };
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![warn(clippy::inconsistent_struct_constructor)]
2+
#![allow(clippy::redundant_field_names)]
3+
#![allow(clippy::unnecessary_operation)]
4+
#![allow(clippy::no_effect)]
5+
6+
#[derive(Default)]
7+
struct Foo {
8+
x: i32,
9+
y: i32,
10+
z: i32,
11+
}
12+
13+
fn main() {
14+
let x = 1;
15+
let y = 1;
16+
let z = 1;
17+
18+
Foo { y, x, z: z };
19+
20+
Foo {
21+
z: z,
22+
x,
23+
..Default::default()
24+
};
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
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
3+
|
4+
LL | Foo { y, x, z: z };
5+
| ^^^^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z: z }`
6+
|
7+
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]`
9+
10+
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
12+
|
13+
LL | / Foo {
14+
LL | | z: z,
15+
LL | | x,
16+
LL | | ..Default::default()
17+
LL | | };
18+
| |_____^ help: try: `Foo { x, z: z, ..Default::default() }`
19+
20+
error: aborting due to 2 previous errors
21+

0 commit comments

Comments
 (0)