| 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 { |
| check_inconsistent_struct_field_initializers: bool, |
| } |
| |
| impl InconsistentStructConstructor { |
| pub fn new(conf: &'static Conf) -> Self { |
| Self { |
| check_inconsistent_struct_field_initializers: conf.check_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.check_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<Symbol, usize>) -> 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<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 |
| } |
| } |