1
Fork 0

Make inconsistent_struct_constructor "all fields are shorthand" requirement configurable (#13737)

Fixes #11846.

This PR has three commits:
- The first commit adds an `initializer-suggestions` configuration to
control suggestion applicability when initializers are present. The
following are the options:
  - "none": do not suggest
- "maybe-incorrect": suggest, but do not apply suggestions with `--fix`
  - "machine-applicable": suggest and apply suggestions with `--fix`
- The second commit fixes suggestions to handle field attributes
(problem [noticed by
@samueltardieu](https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1859261645)).
- The third commit adds `initializer-suggestions = "machine-applicable"`
to Clippy's `clippy.toml` and applies the suggestions. (Nothing seems to
break.)

---

changelog: make `inconsistent_struct_constructor` "all fields are
shorthand" requirement configurable
This commit is contained in:
Timo 2024-12-27 22:05:03 +00:00 committed by GitHub
commit a8968e5dd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 450 additions and 97 deletions

View file

@ -6252,6 +6252,7 @@ Released 2018-09-13
[`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold
[`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability
[`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
[`lint-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-inconsistent-struct-field-initializers
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold
[`matches-for-let-else`]: https://doc.rust-lang.org/clippy/lint_configuration.html#matches-for-let-else
[`max-fn-params-bools`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-fn-params-bools

View file

@ -582,6 +582,33 @@ The maximum size of the `Err`-variant in a `Result` returned from a function
* [`result_large_err`](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err)
## `lint-inconsistent-struct-field-initializers`
Whether to suggest reordering constructor fields when initializers are present.
Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
suggested code would compile, it can change semantics if the initializer expressions have side effects. The
following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
```rust
struct MyStruct {
vector: Vec<u32>,
length: usize
}
fn main() {
let vector = vec![1,2,3];
MyStruct { length: vector.len(), vector};
}
```
[from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
**Default Value:** `false`
---
**Affected lints:**
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
## `literal-representation-threshold`
The lower bound for linting decimal literals

View file

@ -1,5 +1,7 @@
avoid-breaking-exported-api = false
lint-inconsistent-struct-field-initializers = true
[[disallowed-methods]]
path = "rustc_lint::context::LintContext::lint"
reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead"

View file

@ -532,6 +532,26 @@ define_Conf! {
/// The maximum size of the `Err`-variant in a `Result` returned from a function
#[lints(result_large_err)]
large_error_threshold: u64 = 128,
/// Whether to suggest reordering constructor fields when initializers are present.
///
/// Warnings produced by this configuration aren't necessarily fixed by just reordering the fields. Even if the
/// suggested code would compile, it can change semantics if the initializer expressions have side effects. The
/// following example [from rust-clippy#11846] shows how the suggestion can run into borrow check errors:
///
/// ```rust
/// struct MyStruct {
/// vector: Vec<u32>,
/// length: usize
/// }
/// fn main() {
/// let vector = vec![1,2,3];
/// MyStruct { length: vector.len(), vector};
/// }
/// ```
///
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
#[lints(inconsistent_struct_constructor)]
lint_inconsistent_struct_field_initializers: bool = false,
/// The lower bound for linting decimal literals
#[lints(decimal_literal_representation)]
literal_representation_threshold: u64 = 16384,

View file

@ -179,8 +179,8 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
#[expect(clippy::drain_collect)]
fields.push(ClippyConf {
name,
lints: lints.drain(..).collect(),
attrs: &conf[attrs_start..attrs_end],
lints: lints.drain(..).collect(),
field: conf[field_start..i].trim_end(),
});
attrs_start = i;
@ -191,8 +191,8 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
#[expect(clippy::drain_collect)]
fields.push(ClippyConf {
name,
lints: lints.drain(..).collect(),
attrs: &conf[attrs_start..attrs_end],
lints: lints.drain(..).collect(),
field: conf[field_start..i].trim_end(),
});
attrs_start = i;
@ -220,8 +220,8 @@ fn fmt_conf(check: bool) -> Result<(), Error> {
}
fields.push(ClippyConf {
name,
lints,
attrs: &conf[attrs_start..attrs_end],
lints,
field: conf[field_start..].trim_end(),
});

View file

@ -428,8 +428,8 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering {
// Makes a note of the current item for comparison with the next.
cur_t = Some(CurItem {
order: module_level_order,
item,
order: module_level_order,
name: get_item_name(item),
});
}

View file

@ -797,8 +797,8 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
parser.into_offset_iter(),
&doc,
Fragments {
fragments: &fragments,
doc: &doc,
fragments: &fragments,
},
))
}

View file

@ -678,12 +678,12 @@ fn find_insert_calls<'tcx>(
map: contains_expr.map,
key: contains_expr.key,
ctxt: expr.span.ctxt(),
edits: Vec::new(),
is_map_used: false,
allow_insert_closure: true,
can_use_entry: true,
in_tail_pos: true,
is_single_insert: true,
is_map_used: false,
edits: Vec::new(),
loops: Vec::new(),
locals: HirIdSet::default(),
};

View file

@ -243,11 +243,11 @@ fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds
&& !predicates.is_empty()
{
Some(ImplTraitBound {
span: bound.span(),
predicates,
trait_def_id,
args: path.args.map_or([].as_slice(), |p| p.args),
constraints: path.args.map_or([].as_slice(), |p| p.constraints),
trait_def_id,
span: bound.span(),
})
} else {
None

View file

@ -1,19 +1,21 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::fulfill_or_allowed;
use clippy_utils::source::snippet;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind, StructTailExpr};
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::symbol::Symbol;
use std::fmt::{self, Write as _};
declare_clippy_lint! {
/// ### What it does
/// Checks for struct constructors where all fields are shorthand and
/// the order of the field init shorthand in the constructor is inconsistent
/// with the order in the struct definition.
/// Checks for struct constructors where the order of the field
/// init in the constructor is inconsistent with the order in the
/// struct definition.
///
/// ### Why is this bad?
/// Since the order of fields in a constructor doesn't affect the
@ -59,16 +61,37 @@ declare_clippy_lint! {
#[clippy::version = "1.52.0"]
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
pedantic,
"the order of the field init shorthand is inconsistent with the order in the struct definition"
"the order of the field init is inconsistent with the order in the struct definition"
}
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
pub struct InconsistentStructConstructor {
lint_inconsistent_struct_field_initializers: bool,
}
impl InconsistentStructConstructor {
pub fn new(conf: &'static Conf) -> Self {
Self {
lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers,
}
}
}
impl_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if let ExprKind::Struct(qpath, fields, base) = expr.kind
&& fields.iter().all(|f| f.is_shorthand)
&& !expr.span.from_expansion()
let ExprKind::Struct(_, fields, _) = expr.kind else {
return;
};
let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand);
let applicability = if all_fields_are_shorthand {
Applicability::MachineApplicable
} else if self.lint_inconsistent_struct_field_initializers {
Applicability::MaybeIncorrect
} else {
return;
};
if !expr.span.from_expansion()
&& let ty = cx.typeck_results().expr_ty(expr)
&& let Some(adt_def) = ty.ty_adt_def()
&& adt_def.is_struct()
@ -85,36 +108,24 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
let _: fmt::Result = write!(fields_snippet, "{ident}, ");
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let StructTailExpr::Base(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!(
"{} {{ {fields_snippet}{base_snippet} }}",
snippet(cx, qpath.span(), ".."),
);
let span = field_with_attrs_span(cx.tcx, fields.first().unwrap())
.with_hi(field_with_attrs_span(cx.tcx, fields.last().unwrap()).hi());
if !fulfill_or_allowed(cx, INCONSISTENT_STRUCT_CONSTRUCTOR, Some(ty_hir_id)) {
span_lint_and_sugg(
span_lint_and_then(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
span,
"struct constructor field order is inconsistent with struct definition field order",
"try",
sugg,
Applicability::MachineApplicable,
|diag| {
let msg = if all_fields_are_shorthand {
"try"
} else {
"if the field evaluation order doesn't matter, try"
};
let sugg = suggestion(cx, fields, &def_order_map);
diag.span_suggestion(span, msg, sugg, applicability);
},
);
}
}
@ -135,3 +146,45 @@ fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map
true
}
fn suggestion<'tcx>(
cx: &LateContext<'_>,
fields: &'tcx [hir::ExprField<'tcx>],
def_order_map: &FxHashMap<Symbol, usize>,
) -> String {
let ws = fields
.windows(2)
.map(|w| {
let w0_span = field_with_attrs_span(cx.tcx, &w[0]);
let w1_span = field_with_attrs_span(cx.tcx, &w[1]);
let span = w0_span.between(w1_span);
snippet(cx, span, " ")
})
.collect::<Vec<_>>();
let mut fields = fields.to_vec();
fields.sort_unstable_by_key(|field| def_order_map[&field.ident.name]);
let field_snippets = fields
.iter()
.map(|field| snippet(cx, field_with_attrs_span(cx.tcx, field), ".."))
.collect::<Vec<_>>();
assert_eq!(field_snippets.len(), ws.len() + 1);
let mut sugg = String::new();
for i in 0..field_snippets.len() {
sugg += &field_snippets[i];
if i < ws.len() {
sugg += &ws[i];
}
}
sugg
}
fn field_with_attrs_span(tcx: TyCtxt<'_>, field: &hir::ExprField<'_>) -> Span {
if let Some(attr) = tcx.hir().attrs(field.hir_id).first() {
field.span.with_lo(attr.span.lo())
} else {
field.span
}
}

View file

@ -649,7 +649,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn));
store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf)));
store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback));
store.register_late_pass(|_| Box::new(inconsistent_struct_constructor::InconsistentStructConstructor));
store.register_late_pass(move |_| {
Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new(
conf,
))
});
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf)));

View file

@ -38,8 +38,8 @@ pub(super) fn check<'tcx>(
cx,
label,
inner_labels: label.into_iter().collect(),
is_finite: false,
loop_depth: 0,
is_finite: false,
};
loop_visitor.visit_block(loop_block);

View file

@ -220,11 +220,11 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
// `check_stmt_post` on `(Late)LintPass`, which we'd need to detect when we're leaving a macro span
let mut vis = BodyVisitor {
macro_unsafe_blocks: Vec::new(),
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
expn_depth: if body.value.span.from_expansion() { 1 } else { 0 },
macro_unsafe_blocks: Vec::new(),
lint: self,
cx
cx,
lint: self
};
vis.visit_body(body);
}

View file

@ -470,14 +470,14 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
captured_ids: HirIdSet,
) -> Option<Vec<IterFunction>> {
let mut visitor = IterFunctionVisitor {
uses: Vec::new(),
target: id,
seen_other: false,
cx,
current_mutably_captured_ids: HirIdSet::default(),
illegal_mutable_capture_ids: captured_ids,
current_mutably_captured_ids: HirIdSet::default(),
cx,
uses: Vec::new(),
hir_id_uses_map: FxHashMap::default(),
current_statement_hir_id: None,
seen_other: false,
target: id,
};
visitor.visit_block(block);
if visitor.seen_other {

View file

@ -297,8 +297,8 @@ fn parse_iter_usage<'tcx>(
{
Some(IterUsage {
kind: IterUsageKind::NextTuple,
span: e.span,
unwrap_kind: None,
span: e.span,
})
} else {
None

View file

@ -124,30 +124,30 @@ pub(super) fn check(
match lit.node {
ast::LitKind::Bool(false) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Or, Replacement {
method_name: "any",
has_args: true,
has_generic_return: false,
method_name: "any",
});
},
ast::LitKind::Bool(true) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::And, Replacement {
method_name: "all",
has_args: true,
has_generic_return: false,
method_name: "all",
});
},
ast::LitKind::Int(Pu128(0), _) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Add, Replacement {
method_name: "sum",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "sum",
});
},
ast::LitKind::Int(Pu128(1), _) => {
check_fold_with_op(cx, expr, acc, fold_span, hir::BinOpKind::Mul, Replacement {
method_name: "product",
has_args: false,
has_generic_return: needs_turbofish(cx, expr),
method_name: "product",
});
},
_ => (),

View file

@ -349,11 +349,11 @@ fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) {
for (i, stmt) in loop_block.stmts.iter().enumerate() {
with_if_expr(stmt, |if_expr, cond, then_block, else_expr| {
let data = &LintData {
stmt_idx: i,
if_expr,
if_cond: cond,
if_block: then_block,
else_expr,
stmt_idx: i,
loop_block,
};
if needless_continue_in_else(else_expr, label) {

View file

@ -270,10 +270,10 @@ impl SimilarNamesNameVisitor<'_, '_, '_> {
return;
}
self.0.names.push(ExistingName {
exemptions: get_exemptions(interned_name).unwrap_or(&[]),
interned: ident.name,
span: ident.span,
len: count,
exemptions: get_exemptions(interned_name).unwrap_or(&[]),
});
}

View file

@ -47,6 +47,7 @@ impl ArithmeticSideEffects {
Self {
allowed_binary,
allowed_unary,
const_span: None,
disallowed_int_methods: [
sym::saturating_div,
sym::wrapping_div,
@ -55,7 +56,6 @@ impl ArithmeticSideEffects {
]
.into_iter()
.collect(),
const_span: None,
expr_span: None,
}
}

View file

@ -143,11 +143,11 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> {
self.searcher = Some(PathbufPushSearcher {
local_id: id,
lhs_is_let: true,
name: name.name,
let_ty_span: local.ty.map(|ty| ty.span),
err_span: local.span,
init_val: *init_expr,
arg: None,
name: name.name,
err_span: local.span,
});
}
}
@ -165,10 +165,10 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> {
local_id: id,
lhs_is_let: false,
let_ty_span: None,
name: name.ident.name,
err_span: expr.span,
init_val: *right,
arg: None,
name: name.ident.name,
err_span: expr.span,
});
}
}

View file

@ -282,9 +282,9 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> {
}
{
let mut apa = AuxParamsAttr {
first_bind_ident: ident,
first_block_hir_id: self.ap.curr_block_hir_id,
first_block_span: self.ap.curr_block_span,
first_bind_ident: ident,
first_method_span: {
let expr_or_init = expr_or_init(self.cx, expr);
if let hir::ExprKind::MethodCall(_, local_expr, _, span) = expr_or_init.kind {
@ -395,8 +395,8 @@ impl Default for AuxParamsAttr {
counter: 0,
has_expensive_expr_after_last_attr: false,
first_block_hir_id: HirId::INVALID,
first_bind_ident: Ident::empty(),
first_block_span: DUMMY_SP,
first_bind_ident: Ident::empty(),
first_method_span: DUMMY_SP,
first_stmt_span: DUMMY_SP,
last_bind_ident: Ident::empty(),

View file

@ -384,9 +384,9 @@ impl<'tcx> IndexBinding<'_, 'tcx> {
fn is_used_after_swap(&mut self, idx_ident: Ident) -> bool {
let mut v = IndexBindingVisitor {
found_used: false,
suggest_span: self.suggest_span,
idx: idx_ident,
suggest_span: self.suggest_span,
found_used: false,
};
for stmt in self.block.stmts {

View file

@ -388,8 +388,8 @@ impl<'tcx> LateLintPass<'tcx> for Types {
self.check_fn_decl(cx, decl, CheckTyContext {
is_in_trait_impl,
is_exported,
in_body: matches!(fn_kind, FnKind::Closure),
is_exported,
..CheckTyContext::default()
});
}

View file

@ -120,8 +120,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
let mut visitor = AsyncFnVisitor {
cx,
found_await: false,
async_depth: 0,
await_in_async_block: None,
async_depth: 0,
};
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
if !visitor.found_await {
@ -129,9 +129,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
// The actual linting happens in `check_crate_post`, once we've found all
// uses of local async functions that do require asyncness to pass typeck
self.unused_async_fns.push(UnusedAsyncFn {
await_in_async_block: visitor.await_in_async_block,
fn_span: span,
def_id,
fn_span: span,
await_in_async_block: visitor.await_in_async_block,
});
}
}

View file

@ -245,9 +245,9 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> {
let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
let mut delegate = MutationVisitor {
tcx: self.cx.tcx,
is_mutated: false,
local_id: unwrap_info.local_id,
tcx: self.cx.tcx,
};
let vis = ExprUseVisitor::for_clippy(self.cx, cond.hir_id.owner.def_id, &mut delegate);
@ -397,8 +397,8 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap {
}
let mut v = UnwrappableVariablesVisitor {
cx,
unwrappables: Vec::new(),
cx,
};
walk_fn(&mut v, kind, decl, body.id(), fn_id);

View file

@ -166,8 +166,8 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
local_id: id,
init,
lhs_is_let: true,
name: name.name,
let_ty_span: local.ty.map(|ty| ty.span),
name: name.name,
err_span: local.span,
found: 0,
last_push_expr: init_expr.hir_id,
@ -206,8 +206,8 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
&& name.ident.as_str() == "push"
{
self.searcher = Some(VecPushSearcher {
found: searcher.found + 1,
err_span: searcher.err_span.to(stmt.span),
found: searcher.found + 1,
last_push_expr: expr.hir_id,
..searcher
});

View file

@ -248,8 +248,8 @@ impl Write {
pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
Self {
format_args,
allow_print_in_tests: conf.allow_print_in_tests,
in_debug_impl: false,
allow_print_in_tests: conf.allow_print_in_tests,
}
}
}

View file

@ -300,8 +300,8 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus
};
let mut vis = ExitPointFinder {
cx,
state: ExitPointState::WalkUpTo(spawn_expr.hir_id),
cx,
};
if let Break(ExitCallFound) = vis.visit_block(block) {
// Visitor found an unconditional `exit()` call, so don't lint.

View file

@ -196,8 +196,8 @@ impl<'hir> IfOrIfLet<'hir> {
if let ExprKind::DropTemps(new_cond) = cond.kind {
return Some(Self {
cond: new_cond,
r#else,
then,
r#else,
});
}
if let ExprKind::Let(..) = cond.kind {

View file

@ -788,8 +788,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
Self {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
path_check: PathCheck::default(),
s: FxHasher::default(),
path_check: PathCheck::default(),
}
}

View file

@ -1243,9 +1243,9 @@ pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'
let mut v = V {
cx,
allow_closure: true,
loops: Vec::new(),
locals: HirIdSet::default(),
allow_closure: true,
captures: HirIdMap::default(),
};
v.visit_expr(expr);

View file

@ -32,8 +32,8 @@ impl<'a, 'b, 'tcx> PossibleBorrowerVisitor<'a, 'b, 'tcx> {
) -> Self {
Self {
possible_borrower: TransitiveRelation::default(),
cx,
body,
cx,
possible_origin,
}
}

View file

@ -94,8 +94,8 @@ impl ClippyWarning {
Some(Self {
name,
diag,
url,
krate: krate.to_string(),
url,
})
}

View file

@ -574,12 +574,12 @@ impl LintMetadata {
id_location: None,
group: "deprecated",
level: "none",
version,
docs: format!(
"### What it does\n\n\
Nothing. This lint has been deprecated\n\n\
### Deprecation reason\n\n{reason}.\n",
),
version,
applicability: Applicability::Unspecified,
}
}

View file

@ -0,0 +1 @@
lint-inconsistent-struct-field-initializers = true

View file

@ -0,0 +1,79 @@
#![warn(clippy::inconsistent_struct_constructor)]
#![allow(clippy::redundant_field_names)]
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::no_effect)]
#[derive(Default)]
struct Foo {
x: i32,
y: i32,
z: i32,
}
fn main() {
let x = 1;
let y = 1;
let z = 1;
Foo { x, y, z: z };
Foo {
x,
z: z,
..Default::default()
};
}
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1859261645
mod field_attributes {
struct HirId;
struct BodyVisitor {
macro_unsafe_blocks: Vec<HirId>,
expn_depth: u32,
}
fn check_body(condition: bool) {
BodyVisitor {
macro_unsafe_blocks: Vec::new(),
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
expn_depth: if condition { 1 } else { 0 },
};
}
}
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1874539800
mod cfgs_between_fields {
#[allow(clippy::non_minimal_cfg)]
fn cfg_all() {
struct S {
a: i32,
b: i32,
#[cfg(all())]
c: i32,
d: i32,
}
let s = S {
a: 3,
b: 2,
#[cfg(all())]
c: 1,
d: 0,
};
}
fn cfg_any() {
struct S {
a: i32,
b: i32,
#[cfg(any())]
c: i32,
d: i32,
}
let s = S {
a: 3,
#[cfg(any())]
c: 1,
b: 2,
d: 0,
};
}
}

View file

@ -0,0 +1,79 @@
#![warn(clippy::inconsistent_struct_constructor)]
#![allow(clippy::redundant_field_names)]
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::no_effect)]
#[derive(Default)]
struct Foo {
x: i32,
y: i32,
z: i32,
}
fn main() {
let x = 1;
let y = 1;
let z = 1;
Foo { y, x, z: z };
Foo {
z: z,
x,
..Default::default()
};
}
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1859261645
mod field_attributes {
struct HirId;
struct BodyVisitor {
macro_unsafe_blocks: Vec<HirId>,
expn_depth: u32,
}
fn check_body(condition: bool) {
BodyVisitor {
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
expn_depth: if condition { 1 } else { 0 },
macro_unsafe_blocks: Vec::new(),
};
}
}
// https://github.com/rust-lang/rust-clippy/pull/13737#discussion_r1874539800
mod cfgs_between_fields {
#[allow(clippy::non_minimal_cfg)]
fn cfg_all() {
struct S {
a: i32,
b: i32,
#[cfg(all())]
c: i32,
d: i32,
}
let s = S {
d: 0,
#[cfg(all())]
c: 1,
b: 2,
a: 3,
};
}
fn cfg_any() {
struct S {
a: i32,
b: i32,
#[cfg(any())]
c: i32,
d: i32,
}
let s = S {
d: 0,
#[cfg(any())]
c: 1,
b: 2,
a: 3,
};
}
}

View file

@ -0,0 +1,77 @@
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:18:11
|
LL | Foo { y, x, z: z };
| ^^^^^^^^^^ help: if the field evaluation order doesn't matter, try: `x, y, z: z`
|
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]`
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:21:9
|
LL | / z: z,
LL | | x,
| |_________^
|
help: if the field evaluation order doesn't matter, try
|
LL ~ x,
LL ~ z: z,
|
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:36:13
|
LL | / #[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
LL | | expn_depth: if condition { 1 } else { 0 },
LL | | macro_unsafe_blocks: Vec::new(),
| |___________________________________________^
|
help: if the field evaluation order doesn't matter, try
|
LL ~ macro_unsafe_blocks: Vec::new(),
LL + #[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
LL ~ expn_depth: if condition { 1 } else { 0 },
|
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:55:13
|
LL | / d: 0,
LL | | #[cfg(all())]
LL | | c: 1,
LL | | b: 2,
LL | | a: 3,
| |________________^
|
help: if the field evaluation order doesn't matter, try
|
LL ~ a: 3,
LL + b: 2,
LL + #[cfg(all())]
LL + c: 1,
LL ~ d: 0,
|
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui-toml/toml_inconsistent_struct_constructor/conf_inconsistent_struct_constructor.rs:72:13
|
LL | / d: 0,
LL | | #[cfg(any())]
LL | | c: 1,
LL | | b: 2,
LL | | a: 3,
| |________________^
|
help: if the field evaluation order doesn't matter, try
|
LL ~ a: 3,
LL + #[cfg(any())]
LL + c: 1,
LL + b: 2,
LL ~ d: 0,
|
error: aborting due to 5 previous errors

View file

@ -46,6 +46,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
future-size-threshold
ignore-interior-mutability
large-error-threshold
lint-inconsistent-struct-field-initializers
literal-representation-threshold
matches-for-let-else
max-fn-params-bools
@ -134,6 +135,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
future-size-threshold
ignore-interior-mutability
large-error-threshold
lint-inconsistent-struct-field-initializers
literal-representation-threshold
matches-for-let-else
max-fn-params-bools
@ -222,6 +224,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
future-size-threshold
ignore-interior-mutability
large-error-threshold
lint-inconsistent-struct-field-initializers
literal-representation-threshold
matches-for-let-else
max-fn-params-bools

View file

@ -60,7 +60,11 @@ mod with_base {
let z = 1;
// Should lint.
Foo { x, z, ..Default::default() };
Foo {
x,
z,
..Default::default()
};
// Should NOT lint because the order is consistent with the definition.
Foo {

View file

@ -1,21 +1,24 @@
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui/inconsistent_struct_constructor.rs:36:9
--> tests/ui/inconsistent_struct_constructor.rs:36:15
|
LL | Foo { y, x, z };
| ^^^^^^^^^^^^^^^ help: try: `Foo { x, y, z }`
| ^^^^^^^ help: try: `x, y, z`
|
= note: `-D clippy::inconsistent-struct-constructor` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::inconsistent_struct_constructor)]`
error: struct constructor field order is inconsistent with struct definition field order
--> tests/ui/inconsistent_struct_constructor.rs:63:9
--> tests/ui/inconsistent_struct_constructor.rs:64:13
|
LL | / Foo {
LL | | z,
LL | / z,
LL | | x,
LL | | ..Default::default()
LL | | };
| |_________^ help: try: `Foo { x, z, ..Default::default() }`
| |_____________^
|
help: try
|
LL ~ x,
LL ~ z,
|
error: aborting due to 2 previous errors