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}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; declare_clippy_lint! { /// ### What it does /// 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 /// resulted instance as the below example indicates, /// /// ```no_run /// #[derive(Debug, PartialEq, Eq)] /// struct Foo { /// x: i32, /// y: i32, /// } /// let x = 1; /// let y = 2; /// /// // This assertion never fails: /// assert_eq!(Foo { x, y }, Foo { y, x }); /// ``` /// /// inconsistent order can be confusing and decreases readability and consistency. /// /// ### Example /// ```no_run /// struct Foo { /// x: i32, /// y: i32, /// } /// let x = 1; /// let y = 2; /// /// Foo { y, x }; /// ``` /// /// Use instead: /// ```no_run /// # struct Foo { /// # x: i32, /// # y: i32, /// # } /// # let x = 1; /// # let y = 2; /// Foo { x, y }; /// ``` #[clippy::version = "1.52.0"] pub INCONSISTENT_STRUCT_CONSTRUCTOR, pedantic, "the order of the field init is inconsistent with the order in the struct definition" } 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<'_>) { 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() && let Some(local_def_id) = adt_def.did().as_local() && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id) && let Some(variant) = adt_def.variants().iter().next() { let mut def_order_map = FxHashMap::default(); for (idx, field) in variant.fields.iter().enumerate() { def_order_map.insert(field.name, idx); } if is_consistent_order(fields, &def_order_map) { return; } 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_then( cx, INCONSISTENT_STRUCT_CONSTRUCTOR, span, "struct constructor field order is inconsistent with struct definition field order", |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); }, ); } } } } // Check whether the order of the fields in the constructor is consistent with the order in the // definition. fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map: &FxHashMap) -> bool { let mut cur_idx = usize::MIN; for f in fields { let next_idx = def_order_map[&f.ident.name]; if cur_idx > next_idx { return false; } cur_idx = next_idx; } true } fn suggestion<'tcx>( cx: &LateContext<'_>, fields: &'tcx [hir::ExprField<'tcx>], def_order_map: &FxHashMap, ) -> 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::>(); 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::>(); 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 } }