1- use clippy_utils:: diagnostics:: span_lint_and_sugg;
1+ use clippy_config:: Conf ;
2+ use clippy_utils:: diagnostics:: span_lint_and_then;
23use clippy_utils:: fulfill_or_allowed;
34use clippy_utils:: source:: snippet;
45use rustc_data_structures:: fx:: FxHashMap ;
56use rustc_errors:: Applicability ;
6- use rustc_hir:: { self as hir, ExprKind , StructTailExpr } ;
7+ use rustc_hir:: { self as hir, ExprKind } ;
78use rustc_lint:: { LateContext , LateLintPass } ;
8- use rustc_session:: declare_lint_pass;
9+ use rustc_middle:: ty:: TyCtxt ;
10+ use rustc_session:: impl_lint_pass;
11+ use rustc_span:: Span ;
912use rustc_span:: symbol:: Symbol ;
10- use std:: fmt:: { self , Write as _} ;
1113
1214declare_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,37 @@ 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+ lint_inconsistent_struct_field_initializers : bool ,
69+ }
70+
71+ impl InconsistentStructConstructor {
72+ pub fn new ( conf : & ' static Conf ) -> Self {
73+ Self {
74+ lint_inconsistent_struct_field_initializers : conf. lint_inconsistent_struct_field_initializers ,
75+ }
76+ }
77+ }
78+
79+ impl_lint_pass ! ( InconsistentStructConstructor => [ INCONSISTENT_STRUCT_CONSTRUCTOR ] ) ;
6680
6781impl < ' 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 ( _, fields, _) = expr. kind else {
84+ return ;
85+ } ;
86+ let all_fields_are_shorthand = fields. iter ( ) . all ( |f| f. is_shorthand ) ;
87+ let applicability = if all_fields_are_shorthand {
88+ Applicability :: MachineApplicable
89+ } else if self . lint_inconsistent_struct_field_initializers {
90+ Applicability :: MaybeIncorrect
91+ } else {
92+ return ;
93+ } ;
94+ if !expr. span . from_expansion ( )
7295 && let ty = cx. typeck_results ( ) . expr_ty ( expr)
7396 && let Some ( adt_def) = ty. ty_adt_def ( )
7497 && adt_def. is_struct ( )
@@ -85,36 +108,24 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor {
85108 return ;
86109 }
87110
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] ) ;
90-
91- 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}, " ) ;
95- }
96- fields_snippet. push_str ( & last_ident. to_string ( ) ) ;
97-
98- let base_snippet = if let StructTailExpr :: Base ( base) = base {
99- format ! ( ", ..{}" , snippet( cx, base. span, ".." ) )
100- } else {
101- String :: new ( )
102- } ;
103-
104- let sugg = format ! (
105- "{} {{ {fields_snippet}{base_snippet} }}" ,
106- snippet( cx, qpath. span( ) , ".." ) ,
107- ) ;
111+ let span = field_with_attrs_span ( cx. tcx , fields. first ( ) . unwrap ( ) )
112+ . with_hi ( field_with_attrs_span ( cx. tcx , fields. last ( ) . unwrap ( ) ) . hi ( ) ) ;
108113
109114 if !fulfill_or_allowed ( cx, INCONSISTENT_STRUCT_CONSTRUCTOR , Some ( ty_hir_id) ) {
110- span_lint_and_sugg (
115+ span_lint_and_then (
111116 cx,
112117 INCONSISTENT_STRUCT_CONSTRUCTOR ,
113- expr . span ,
118+ span,
114119 "struct constructor field order is inconsistent with struct definition field order" ,
115- "try" ,
116- sugg,
117- Applicability :: MachineApplicable ,
120+ |diag| {
121+ let msg = if all_fields_are_shorthand {
122+ "try"
123+ } else {
124+ "if the field evaluation order doesn't matter, try"
125+ } ;
126+ let sugg = suggestion ( cx, fields, & def_order_map) ;
127+ diag. span_suggestion ( span, msg, sugg, applicability) ;
128+ } ,
118129 ) ;
119130 }
120131 }
@@ -135,3 +146,45 @@ fn is_consistent_order<'tcx>(fields: &'tcx [hir::ExprField<'tcx>], def_order_map
135146
136147 true
137148}
149+
150+ fn suggestion < ' tcx > (
151+ cx : & LateContext < ' _ > ,
152+ fields : & ' tcx [ hir:: ExprField < ' tcx > ] ,
153+ def_order_map : & FxHashMap < Symbol , usize > ,
154+ ) -> String {
155+ let ws = fields
156+ . windows ( 2 )
157+ . map ( |w| {
158+ let w0_span = field_with_attrs_span ( cx. tcx , & w[ 0 ] ) ;
159+ let w1_span = field_with_attrs_span ( cx. tcx , & w[ 1 ] ) ;
160+ let span = w0_span. between ( w1_span) ;
161+ snippet ( cx, span, " " )
162+ } )
163+ . collect :: < Vec < _ > > ( ) ;
164+
165+ let mut fields = fields. to_vec ( ) ;
166+ fields. sort_unstable_by_key ( |field| def_order_map[ & field. ident . name ] ) ;
167+ let field_snippets = fields
168+ . iter ( )
169+ . map ( |field| snippet ( cx, field_with_attrs_span ( cx. tcx , field) , ".." ) )
170+ . collect :: < Vec < _ > > ( ) ;
171+
172+ assert_eq ! ( field_snippets. len( ) , ws. len( ) + 1 ) ;
173+
174+ let mut sugg = String :: new ( ) ;
175+ for i in 0 ..field_snippets. len ( ) {
176+ sugg += & field_snippets[ i] ;
177+ if i < ws. len ( ) {
178+ sugg += & ws[ i] ;
179+ }
180+ }
181+ sugg
182+ }
183+
184+ fn field_with_attrs_span ( tcx : TyCtxt < ' _ > , field : & hir:: ExprField < ' _ > ) -> Span {
185+ if let Some ( attr) = tcx. hir ( ) . attrs ( field. hir_id ) . first ( ) {
186+ field. span . with_lo ( attr. span . lo ( ) )
187+ } else {
188+ field. span
189+ }
190+ }
0 commit comments