@@ -78,13 +78,16 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
7878 }
7979
8080 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+ ) ;
8588 if self_ty. span . can_be_used_for_suggestions ( )
8689 && !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)
8891 {
8992 self . maybe_suggest_add_generic_impl_trait ( self_ty, & mut diag) ;
9093 }
@@ -123,31 +126,62 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
123126 }
124127 }
125128
129+ /// For a struct or enum with an invalid bare trait object field, suggest turning
130+ /// it into a generic type bound.
126131 fn maybe_suggest_add_generic_impl_trait (
127132 & self ,
128133 self_ty : & hir:: Ty < ' _ > ,
129134 diag : & mut Diag < ' _ > ,
130135 ) -> bool {
131136 let tcx = self . tcx ( ) ;
132- let msg = "you might be missing a type parameter" ;
133- let mut sugg = vec ! [ ] ;
134137
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
147149 }
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) ) ) ;
149179 }
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+ ) ;
151185 true
152186 }
153187 /// Make sure that we are in the condition to suggest the blanket implementation.
@@ -198,32 +232,59 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
198232 }
199233 }
200234
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.
201242 fn maybe_suggest_dyn_trait (
202243 & self ,
203244 self_ty : & hir:: Ty < ' _ > ,
204- label : & str ,
205245 sugg : Vec < ( Span , String ) > ,
206246 diag : & mut Diag < ' _ > ,
207247 ) -> bool {
208248 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 ( ) ;
211249
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 ( _) => { }
216266
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+ }
221281 }
282+ _ => return false ,
222283 }
223284
224285 // FIXME: Only emit this suggestion if the trait is dyn-compatible.
225286 diag. multipart_suggestion_verbose (
226- label . to_string ( ) ,
287+ "you can add the `dyn` keyword if you want a trait object" ,
227288 sugg,
228289 Applicability :: MachineApplicable ,
229290 ) ;
0 commit comments