| 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, impl_lint_pass}; | |
| use rustc_middle::ty::TyCtxt; | |
| 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" | |
| } | |
| impl_lint_pass!(InconsistentStructConstructor => [ | |
| INCONSISTENT_STRUCT_CONSTRUCTOR, | |
| ]); | |
| 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<'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 | |
| } | |
| } |