@@ -433,34 +433,19 @@ pub fn report_dyn_incompatibility<'tcx>(
433433 hir:: Node :: Item ( item) => Some ( item. ident . span ) ,
434434 _ => None ,
435435 } ) ;
436+
436437 let mut err = struct_span_code_err ! (
437438 tcx. dcx( ) ,
438439 span,
439440 E0038 ,
440- "the {} `{}` cannot be made into an object " ,
441+ "the {} `{}` is not dyn compatible " ,
441442 tcx. def_descr( trait_def_id) ,
442443 trait_str
443444 ) ;
444- err. span_label ( span, format ! ( "`{trait_str}` cannot be made into an object" ) ) ;
445-
446- if let Some ( hir_id) = hir_id
447- && let hir:: Node :: Ty ( ty) = tcx. hir_node ( hir_id)
448- && let hir:: TyKind :: TraitObject ( [ trait_ref, ..] , ..) = ty. kind
449- {
450- let mut hir_id = hir_id;
451- while let hir:: Node :: Ty ( ty) = tcx. parent_hir_node ( hir_id) {
452- hir_id = ty. hir_id ;
453- }
454- if tcx. parent_hir_node ( hir_id) . fn_sig ( ) . is_some ( ) {
455- // Do not suggest `impl Trait` when dealing with things like super-traits.
456- err. span_suggestion_verbose (
457- ty. span . until ( trait_ref. span ) ,
458- "consider using an opaque type instead" ,
459- "impl " ,
460- Applicability :: MaybeIncorrect ,
461- ) ;
462- }
463- }
445+ err. span_label ( span, format ! ( "`{trait_str}` is not dyn compatible" ) ) ;
446+
447+ attempt_dyn_to_impl_suggestion ( tcx, hir_id, & mut err) ;
448+
464449 let mut reported_violations = FxIndexSet :: default ( ) ;
465450 let mut multi_span = vec ! [ ] ;
466451 let mut messages = vec ! [ ] ;
@@ -475,7 +460,7 @@ pub fn report_dyn_incompatibility<'tcx>(
475460 if reported_violations. insert ( violation. clone ( ) ) {
476461 let spans = violation. spans ( ) ;
477462 let msg = if trait_span. is_none ( ) || spans. is_empty ( ) {
478- format ! ( "the trait cannot be made into an object because {}" , violation. error_msg( ) )
463+ format ! ( "the trait is not dyn compatible because {}" , violation. error_msg( ) )
479464 } else {
480465 format ! ( "...because {}" , violation. error_msg( ) )
481466 } ;
@@ -492,24 +477,20 @@ pub fn report_dyn_incompatibility<'tcx>(
492477 let has_multi_span = !multi_span. is_empty ( ) ;
493478 let mut note_span = MultiSpan :: from_spans ( multi_span. clone ( ) ) ;
494479 if let ( Some ( trait_span) , true ) = ( trait_span, has_multi_span) {
495- note_span. push_span_label ( trait_span, "this trait cannot be made into an object ..." ) ;
480+ note_span. push_span_label ( trait_span, "this trait is not dyn compatible ..." ) ;
496481 }
497482 for ( span, msg) in iter:: zip ( multi_span, messages) {
498483 note_span. push_span_label ( span, msg) ;
499484 }
500485 // FIXME(dyn_compat_renaming): Update the URL.
501486 err. span_note (
502487 note_span,
503- "for a trait to be \" dyn-compatible\" it needs to allow building a vtable to allow the call \
504- to be resolvable dynamically; for more information visit \
505- <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
488+ "for a trait to be dyn compatible it needs to allow building a vtable\n \
489+ for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
506490 ) ;
507491
508492 // Only provide the help if its a local trait, otherwise it's not actionable.
509493 if trait_span. is_some ( ) {
510- let mut reported_violations: Vec < _ > = reported_violations. into_iter ( ) . collect ( ) ;
511- reported_violations. sort ( ) ;
512-
513494 let mut potential_solutions: Vec < _ > =
514495 reported_violations. into_iter ( ) . map ( |violation| violation. solution ( ) ) . collect ( ) ;
515496 potential_solutions. sort ( ) ;
@@ -520,68 +501,116 @@ pub fn report_dyn_incompatibility<'tcx>(
520501 }
521502 }
522503
504+ attempt_dyn_to_enum_suggestion ( tcx, trait_def_id, & * trait_str, & mut err) ;
505+
506+ err
507+ }
508+
509+ /// Attempt to suggest converting the `dyn Trait` argument to an enumeration
510+ /// over the types that implement `Trait`.
511+ fn attempt_dyn_to_enum_suggestion (
512+ tcx : TyCtxt < ' _ > ,
513+ trait_def_id : DefId ,
514+ trait_str : & str ,
515+ err : & mut Diag < ' _ > ,
516+ ) {
523517 let impls_of = tcx. trait_impls_of ( trait_def_id) ;
524- let impls = if impls_of. blanket_impls ( ) . is_empty ( ) {
525- impls_of
526- . non_blanket_impls ( )
527- . values ( )
528- . flatten ( )
529- . filter ( |def_id| {
530- !matches ! ( tcx. type_of( * def_id) . instantiate_identity( ) . kind( ) , ty:: Dynamic ( ..) )
531- } )
532- . collect :: < Vec < _ > > ( )
533- } else {
534- vec ! [ ]
535- } ;
536- let externally_visible = if !impls. is_empty ( )
537- && let Some ( def_id) = trait_def_id. as_local ( )
518+
519+ if !impls_of. blanket_impls ( ) . is_empty ( ) {
520+ return ;
521+ }
522+
523+ let concrete_impls: Option < Vec < Ty < ' _ > > > = impls_of
524+ . non_blanket_impls ( )
525+ . values ( )
526+ . flatten ( )
527+ . map ( |impl_id| {
528+ // Don't suggest conversion to enum if the impl types have type parameters.
529+ // It's unlikely the user wants to define a generic enum.
530+ let Some ( impl_type) = tcx. type_of ( * impl_id) . no_bound_vars ( ) else { return None } ;
531+
532+ // Obviously unsized impl types won't be usable in an enum.
533+ // Note: this doesn't use `Ty::is_trivially_sized` because that function
534+ // defaults to assuming that things are *not* sized, whereas we want to
535+ // fall back to assuming that things may be sized.
536+ match impl_type. kind ( ) {
537+ ty:: Str | ty:: Slice ( _) | ty:: Dynamic ( _, _, ty:: DynKind :: Dyn ) => {
538+ return None ;
539+ }
540+ _ => { }
541+ }
542+ Some ( impl_type)
543+ } )
544+ . collect ( ) ;
545+ let Some ( concrete_impls) = concrete_impls else { return } ;
546+
547+ const MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM : usize = 9 ;
548+ if concrete_impls. is_empty ( ) || concrete_impls. len ( ) > MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM {
549+ return ;
550+ }
551+
552+ let externally_visible = if let Some ( def_id) = trait_def_id. as_local ( ) {
538553 // We may be executing this during typeck, which would result in cycle
539554 // if we used effective_visibilities query, which looks into opaque types
540555 // (and therefore calls typeck).
541- && tcx. resolutions ( ( ) ) . effective_visibilities . is_exported ( def_id)
542- {
543- true
556+ tcx. resolutions ( ( ) ) . effective_visibilities . is_exported ( def_id)
544557 } else {
545558 false
546559 } ;
547- match & impls[ ..] {
548- [ ] => { }
549- _ if impls. len ( ) > 9 => { }
550- [ only] if externally_visible => {
551- err. help ( with_no_trimmed_paths ! ( format!(
552- "only type `{}` is seen to implement the trait in this crate, consider using it \
553- directly instead",
554- tcx. type_of( * only) . instantiate_identity( ) ,
555- ) ) ) ;
556- }
557- [ only] => {
558- err. help ( with_no_trimmed_paths ! ( format!(
559- "only type `{}` implements the trait, consider using it directly instead" ,
560- tcx. type_of( * only) . instantiate_identity( ) ,
561- ) ) ) ;
562- }
563- impls => {
564- let types = impls
565- . iter ( )
566- . map ( |t| {
567- with_no_trimmed_paths ! ( format!( " {}" , tcx. type_of( * t) . instantiate_identity( ) , ) )
568- } )
569- . collect :: < Vec < _ > > ( ) ;
570- err. help ( format ! (
571- "the following types implement the trait, consider defining an enum where each \
572- variant holds one of these types, implementing `{}` for this new enum and using \
573- it instead:\n {}",
574- trait_str,
575- types. join( "\n " ) ,
576- ) ) ;
577- }
560+
561+ if let [ only_impl] = & concrete_impls[ ..] {
562+ let within = if externally_visible { " within this crate" } else { "" } ;
563+ err. help ( with_no_trimmed_paths ! ( format!(
564+ "only type `{only_impl}` implements `{trait_str}`{within}; \
565+ consider using it directly instead."
566+ ) ) ) ;
567+ } else {
568+ let types = concrete_impls
569+ . iter ( )
570+ . map ( |t| with_no_trimmed_paths ! ( format!( " {}" , t) ) )
571+ . collect :: < Vec < String > > ( )
572+ . join ( "\n " ) ;
573+
574+ err. help ( format ! (
575+ "the following types implement `{trait_str}`:\n \
576+ {types}\n \
577+ consider defining an enum where each variant holds one of these types,\n \
578+ implementing `{trait_str}` for this new enum and using it instead",
579+ ) ) ;
578580 }
581+
579582 if externally_visible {
580583 err. note ( format ! (
581- "`{trait_str}` can be implemented in other crates; if you want to support your users \
584+ "`{trait_str}` may be implemented in other crates; if you want to support your users \
582585 passing their own types here, you can't refer to a specific type",
583586 ) ) ;
584587 }
588+ }
585589
586- err
590+ /// Attempt to suggest that a `dyn Trait` argument or return type be converted
591+ /// to use `impl Trait`.
592+ fn attempt_dyn_to_impl_suggestion ( tcx : TyCtxt < ' _ > , hir_id : Option < hir:: HirId > , err : & mut Diag < ' _ > ) {
593+ let Some ( hir_id) = hir_id else { return } ;
594+ let hir:: Node :: Ty ( ty) = tcx. hir_node ( hir_id) else { return } ;
595+ let hir:: TyKind :: TraitObject ( [ trait_ref, ..] , ..) = ty. kind else { return } ;
596+
597+ // Only suggest converting `dyn` to `impl` if we're in a function signature.
598+ // This ensures that we don't suggest converting e.g.
599+ // `type Alias = Box<dyn DynIncompatibleTrait>;` to
600+ // `type Alias = Box<impl DynIncompatibleTrait>;`
601+ let Some ( ( _id, first_non_type_parent_node) ) =
602+ tcx. hir ( ) . parent_iter ( hir_id) . find ( |( _id, node) | !matches ! ( node, hir:: Node :: Ty ( _) ) )
603+ else {
604+ return ;
605+ } ;
606+ if first_non_type_parent_node. fn_sig ( ) . is_none ( ) {
607+ return ;
608+ }
609+
610+ err. span_suggestion_verbose (
611+ ty. span . until ( trait_ref. span ) ,
612+ "consider using an opaque type instead" ,
613+ "impl " ,
614+ Applicability :: MaybeIncorrect ,
615+ ) ;
587616}
0 commit comments