@@ -78,13 +78,16 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
78
78
}
79
79
80
80
if self_ty. span . edition ( ) . at_least_rust_2021 ( ) {
81
- let msg = "expected a type, found a trait" ;
82
- let label = "you can add the `dyn` keyword if you want a trait object" ;
83
- let mut diag =
84
- rustc_errors:: struct_span_code_err!( self . dcx( ) , self_ty. span, E0782 , "{}" , msg) ;
81
+ let mut diag = rustc_errors:: struct_span_code_err!(
82
+ self . dcx( ) ,
83
+ self_ty. span,
84
+ E0782 ,
85
+ "{}" ,
86
+ "expected a type, found a trait"
87
+ ) ;
85
88
if self_ty. span . can_be_used_for_suggestions ( )
86
89
&& !self . maybe_suggest_impl_trait ( self_ty, & mut diag)
87
- && !self . maybe_suggest_dyn_trait ( self_ty, label , sugg, & mut diag)
90
+ && !self . maybe_suggest_dyn_trait ( self_ty, sugg, & mut diag)
88
91
{
89
92
self . maybe_suggest_add_generic_impl_trait ( self_ty, & mut diag) ;
90
93
}
@@ -123,31 +126,62 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
123
126
}
124
127
}
125
128
129
+ /// For a struct or enum with an invalid bare trait object field, suggest turning
130
+ /// it into a generic type bound.
126
131
fn maybe_suggest_add_generic_impl_trait (
127
132
& self ,
128
133
self_ty : & hir:: Ty < ' _ > ,
129
134
diag : & mut Diag < ' _ > ,
130
135
) -> bool {
131
136
let tcx = self . tcx ( ) ;
132
- let msg = "you might be missing a type parameter" ;
133
- let mut sugg = vec ! [ ] ;
134
137
135
- let parent_id = tcx. hir_get_parent_item ( self_ty. hir_id ) . def_id ;
136
- let parent_item = tcx. hir_node_by_def_id ( parent_id) . expect_item ( ) ;
137
- match parent_item. kind {
138
- hir:: ItemKind :: Struct ( _, generics) | hir:: ItemKind :: Enum ( _, generics) => {
139
- sugg. push ( (
140
- generics. where_clause_span ,
141
- format ! (
142
- "<T: {}>" ,
143
- self . tcx( ) . sess. source_map( ) . span_to_snippet( self_ty. span) . unwrap( )
144
- ) ,
145
- ) ) ;
146
- sugg. push ( ( self_ty. span , "T" . to_string ( ) ) ) ;
138
+ let parent_hir_id = tcx. parent_hir_id ( self_ty. hir_id ) ;
139
+ let parent_item = tcx. hir_get_parent_item ( self_ty. hir_id ) . def_id ;
140
+
141
+ let generics = match tcx. hir_node_by_def_id ( parent_item) {
142
+ hir:: Node :: Item ( hir:: Item {
143
+ kind : hir:: ItemKind :: Struct ( variant, generics) , ..
144
+ } ) => {
145
+ if !variant. fields ( ) . iter ( ) . any ( |field| field. hir_id == parent_hir_id) {
146
+ return false ;
147
+ }
148
+ generics
147
149
}
148
- _ => { }
150
+ hir:: Node :: Item ( hir:: Item { kind : hir:: ItemKind :: Enum ( def, generics) , .. } ) => {
151
+ if !def
152
+ . variants
153
+ . iter ( )
154
+ . flat_map ( |variant| variant. data . fields ( ) . iter ( ) )
155
+ . any ( |field| field. hir_id == parent_hir_id)
156
+ {
157
+ return false ;
158
+ }
159
+ generics
160
+ }
161
+ _ => return false ,
162
+ } ;
163
+
164
+ let Ok ( rendered_ty) = tcx. sess . source_map ( ) . span_to_snippet ( self_ty. span ) else {
165
+ return false ;
166
+ } ;
167
+
168
+ let param = "TUV"
169
+ . chars ( )
170
+ . map ( |c| c. to_string ( ) )
171
+ . chain ( ( 0 ..) . map ( |i| format ! ( "P{i}" ) ) )
172
+ . find ( |s| !generics. params . iter ( ) . any ( |param| param. name . ident ( ) . as_str ( ) == s) )
173
+ . expect ( "we definitely can find at least one param name to generate" ) ;
174
+ let mut sugg = vec ! [ ( self_ty. span, param. to_string( ) ) ] ;
175
+ if let Some ( insertion_span) = generics. span_for_param_suggestion ( ) {
176
+ sugg. push ( ( insertion_span, format ! ( ", {param}: {}" , rendered_ty) ) ) ;
177
+ } else {
178
+ sugg. push ( ( generics. where_clause_span , format ! ( "<{param}: {}>" , rendered_ty) ) ) ;
149
179
}
150
- diag. multipart_suggestion_verbose ( msg, sugg, Applicability :: MachineApplicable ) ;
180
+ diag. multipart_suggestion_verbose (
181
+ "you might be missing a type parameter" ,
182
+ sugg,
183
+ Applicability :: MachineApplicable ,
184
+ ) ;
151
185
true
152
186
}
153
187
/// Make sure that we are in the condition to suggest the blanket implementation.
@@ -198,32 +232,59 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
198
232
}
199
233
}
200
234
235
+ /// Try our best to approximate when adding `dyn` would be helpful for a bare
236
+ /// trait object.
237
+ ///
238
+ /// Right now, this is if the type is either directly nested in another ty,
239
+ /// or if it's in the tail field within a struct. This approximates what the
240
+ /// user would've gotten on edition 2015, except for the case where we have
241
+ /// an *obvious* knock-on `Sized` error.
201
242
fn maybe_suggest_dyn_trait (
202
243
& self ,
203
244
self_ty : & hir:: Ty < ' _ > ,
204
- label : & str ,
205
245
sugg : Vec < ( Span , String ) > ,
206
246
diag : & mut Diag < ' _ > ,
207
247
) -> bool {
208
248
let tcx = self . tcx ( ) ;
209
- let parent_id = tcx. hir_get_parent_item ( self_ty. hir_id ) . def_id ;
210
- let parent_item = tcx. hir_node_by_def_id ( parent_id) . expect_item ( ) ;
211
249
212
- // If the parent item is an enum, don't suggest the dyn trait.
213
- if let hir:: ItemKind :: Enum ( ..) = parent_item. kind {
214
- return false ;
215
- }
250
+ // Look at the direct HIR parent, since we care about the relationship between
251
+ // the type and the thing that directly encloses it.
252
+ match tcx. parent_hir_node ( self_ty. hir_id ) {
253
+ // These are all generally ok. Namely, when a trait object is nested
254
+ // into another expression or ty, it's either very certain that they
255
+ // missed the ty (e.g. `&Trait`) or it's not really possible to tell
256
+ // what their intention is, so let's not give confusing suggestions and
257
+ // just mention `dyn`. The user can make up their mind what to do here.
258
+ hir:: Node :: Ty ( _)
259
+ | hir:: Node :: Expr ( _)
260
+ | hir:: Node :: PatExpr ( _)
261
+ | hir:: Node :: PathSegment ( _)
262
+ | hir:: Node :: AssocItemConstraint ( _)
263
+ | hir:: Node :: TraitRef ( _)
264
+ | hir:: Node :: Item ( _)
265
+ | hir:: Node :: WherePredicate ( _) => { }
216
266
217
- // If the parent item is a struct, check if self_ty is the last field.
218
- if let hir:: ItemKind :: Struct ( variant_data, _) = parent_item. kind {
219
- if variant_data. fields ( ) . last ( ) . unwrap ( ) . ty . span != self_ty. span {
220
- return false ;
267
+ hir:: Node :: Field ( field) => {
268
+ // Enums can't have unsized fields, fields can only have an unsized tail field.
269
+ if let hir:: Node :: Item ( hir:: Item {
270
+ kind : hir:: ItemKind :: Struct ( variant, _) , ..
271
+ } ) = tcx. parent_hir_node ( field. hir_id )
272
+ && variant
273
+ . fields ( )
274
+ . last ( )
275
+ . is_some_and ( |tail_field| tail_field. hir_id == field. hir_id )
276
+ {
277
+ // Ok
278
+ } else {
279
+ return false ;
280
+ }
221
281
}
282
+ _ => return false ,
222
283
}
223
284
224
285
// FIXME: Only emit this suggestion if the trait is dyn-compatible.
225
286
diag. multipart_suggestion_verbose (
226
- label . to_string ( ) ,
287
+ "you can add the `dyn` keyword if you want a trait object" ,
227
288
sugg,
228
289
Applicability :: MachineApplicable ,
229
290
) ;
0 commit comments