@@ -432,33 +432,18 @@ pub fn report_dyn_incompatibility<'tcx>(
432432 hir:: Node :: Item ( item) => Some ( item. ident . span ) ,
433433 _ => None ,
434434 } ) ;
435+
435436 let mut err = struct_span_code_err ! (
436437 tcx. dcx( ) ,
437438 span,
438439 E0038 ,
439- "the trait `{}` cannot be made into an object " ,
440+ "the trait `{}` is not dyn compatible " ,
440441 trait_str
441442 ) ;
442- err. span_label ( span, format ! ( "`{trait_str}` cannot be made into an object" ) ) ;
443-
444- if let Some ( hir_id) = hir_id
445- && let hir:: Node :: Ty ( ty) = tcx. hir_node ( hir_id)
446- && let hir:: TyKind :: TraitObject ( [ trait_ref, ..] , ..) = ty. kind
447- {
448- let mut hir_id = hir_id;
449- while let hir:: Node :: Ty ( ty) = tcx. parent_hir_node ( hir_id) {
450- hir_id = ty. hir_id ;
451- }
452- if tcx. parent_hir_node ( hir_id) . fn_sig ( ) . is_some ( ) {
453- // Do not suggest `impl Trait` when dealing with things like super-traits.
454- err. span_suggestion_verbose (
455- ty. span . until ( trait_ref. span ) ,
456- "consider using an opaque type instead" ,
457- "impl " ,
458- Applicability :: MaybeIncorrect ,
459- ) ;
460- }
461- }
443+ err. span_label ( span, format ! ( "`{trait_str}` is not dyn compatible" ) ) ;
444+
445+ attempt_dyn_to_impl_suggestion ( tcx, hir_id, & mut err) ;
446+
462447 let mut reported_violations = FxIndexSet :: default ( ) ;
463448 let mut multi_span = vec ! [ ] ;
464449 let mut messages = vec ! [ ] ;
@@ -473,7 +458,7 @@ pub fn report_dyn_incompatibility<'tcx>(
473458 if reported_violations. insert ( violation. clone ( ) ) {
474459 let spans = violation. spans ( ) ;
475460 let msg = if trait_span. is_none ( ) || spans. is_empty ( ) {
476- format ! ( "the trait cannot be made into an object because {}" , violation. error_msg( ) )
461+ format ! ( "the trait is not dyn compatible because {}" , violation. error_msg( ) )
477462 } else {
478463 format ! ( "...because {}" , violation. error_msg( ) )
479464 } ;
@@ -490,24 +475,20 @@ pub fn report_dyn_incompatibility<'tcx>(
490475 let has_multi_span = !multi_span. is_empty ( ) ;
491476 let mut note_span = MultiSpan :: from_spans ( multi_span. clone ( ) ) ;
492477 if let ( Some ( trait_span) , true ) = ( trait_span, has_multi_span) {
493- note_span. push_span_label ( trait_span, "this trait cannot be made into an object ..." ) ;
478+ note_span. push_span_label ( trait_span, "this trait is not dyn compatible ..." ) ;
494479 }
495480 for ( span, msg) in iter:: zip ( multi_span, messages) {
496481 note_span. push_span_label ( span, msg) ;
497482 }
498483 // FIXME(dyn_compat_renaming): Update the URL.
499484 err. span_note (
500485 note_span,
501- "for a trait to be \" dyn-compatible\" it needs to allow building a vtable to allow the call \
502- to be resolvable dynamically; for more information visit \
503- <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
486+ "for a trait to be dyn compatible it needs to allow building a vtable\n \
487+ for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>",
504488 ) ;
505489
506490 // Only provide the help if its a local trait, otherwise it's not actionable.
507491 if trait_span. is_some ( ) {
508- let mut reported_violations: Vec < _ > = reported_violations. into_iter ( ) . collect ( ) ;
509- reported_violations. sort ( ) ;
510-
511492 let mut potential_solutions: Vec < _ > =
512493 reported_violations. into_iter ( ) . map ( |violation| violation. solution ( ) ) . collect ( ) ;
513494 potential_solutions. sort ( ) ;
@@ -518,68 +499,124 @@ pub fn report_dyn_incompatibility<'tcx>(
518499 }
519500 }
520501
502+ attempt_dyn_to_enum_suggestion ( tcx, trait_def_id, & * trait_str, & mut err) ;
503+
504+ err
505+ }
506+
507+ /// Attempt to suggest converting the `dyn Trait` argument to an enumeration
508+ /// over the types that implement `Trait`.
509+ fn attempt_dyn_to_enum_suggestion (
510+ tcx : TyCtxt < ' _ > ,
511+ trait_def_id : DefId ,
512+ trait_str : & str ,
513+ err : & mut Diag < ' _ > ,
514+ ) {
521515 let impls_of = tcx. trait_impls_of ( trait_def_id) ;
522- let impls = if impls_of. blanket_impls ( ) . is_empty ( ) {
523- impls_of
524- . non_blanket_impls ( )
525- . values ( )
526- . flatten ( )
527- . filter ( |def_id| {
528- !matches ! ( tcx. type_of( * def_id) . instantiate_identity( ) . kind( ) , ty:: Dynamic ( ..) )
529- } )
530- . collect :: < Vec < _ > > ( )
531- } else {
532- vec ! [ ]
516+
517+ // Don't suggest converting to an enum if there are any blanket impls.
518+ if !impls_of. blanket_impls ( ) . is_empty ( ) {
519+ return ;
520+ }
521+
522+ let concrete_impls = {
523+ let mut concrete_impls = Vec :: new ( ) ;
524+ for impl_id in impls_of. non_blanket_impls ( ) . values ( ) . flatten ( ) {
525+ // Don't suggest converting to enum if there are any non-lifetime generics.
526+ if has_non_lifetime_generics ( tcx, * impl_id) {
527+ return ;
528+ }
529+
530+ let impl_type = tcx. type_of ( * impl_id) . instantiate_identity ( ) ;
531+
532+ // Don't suggest converting to enum if there are any
533+ // `impl Trait for dyn OtherTrait`
534+ if let ty:: Dynamic ( ..) = impl_type. kind ( ) {
535+ return ;
536+ }
537+
538+ concrete_impls. push ( impl_type) ;
539+ }
540+ concrete_impls
533541 } ;
534- let externally_visible = if !impls. is_empty ( )
535- && let Some ( def_id) = trait_def_id. as_local ( )
542+ if concrete_impls. is_empty ( ) {
543+ return ;
544+ }
545+
546+ const MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM : usize = 9 ;
547+ if concrete_impls. len ( ) > MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM {
548+ return ;
549+ }
550+
551+ let externally_visible = if let Some ( def_id) = trait_def_id. as_local ( ) {
536552 // We may be executing this during typeck, which would result in cycle
537553 // if we used effective_visibilities query, which looks into opaque types
538554 // (and therefore calls typeck).
539- && tcx. resolutions ( ( ) ) . effective_visibilities . is_exported ( def_id)
540- {
541- true
555+ tcx. resolutions ( ( ) ) . effective_visibilities . is_exported ( def_id)
542556 } else {
543557 false
544558 } ;
545- match & impls[ ..] {
546- [ ] => { }
547- _ if impls. len ( ) > 9 => { }
548- [ only] if externally_visible => {
549- err. help ( with_no_trimmed_paths ! ( format!(
550- "only type `{}` is seen to implement the trait in this crate, consider using it \
551- directly instead",
552- tcx. type_of( * only) . instantiate_identity( ) ,
553- ) ) ) ;
554- }
555- [ only] => {
556- err. help ( with_no_trimmed_paths ! ( format!(
557- "only type `{}` implements the trait, consider using it directly instead" ,
558- tcx. type_of( * only) . instantiate_identity( ) ,
559- ) ) ) ;
560- }
561- impls => {
562- let types = impls
563- . iter ( )
564- . map ( |t| {
565- with_no_trimmed_paths ! ( format!( " {}" , tcx. type_of( * t) . instantiate_identity( ) , ) )
566- } )
567- . collect :: < Vec < _ > > ( ) ;
568- err. help ( format ! (
569- "the following types implement the trait, consider defining an enum where each \
570- variant holds one of these types, implementing `{}` for this new enum and using \
571- it instead:\n {}",
572- trait_str,
573- types. join( "\n " ) ,
574- ) ) ;
575- }
559+
560+ if let [ only_impl] = & concrete_impls[ ..] {
561+ let within = if externally_visible { " within this crate" } else { "" } ;
562+ err. help ( with_no_trimmed_paths ! ( format!(
563+ "only type `{only_impl}` implements `{trait_str}`{within}. \
564+ Consider using it directly instead."
565+ ) ) ) ;
566+ } else {
567+ let types = concrete_impls
568+ . iter ( )
569+ . map ( |t| with_no_trimmed_paths ! ( format!( " {}" , t) ) )
570+ . collect :: < Vec < String > > ( )
571+ . join ( "\n " ) ;
572+
573+ err. help ( format ! (
574+ "the following types implement `{trait_str}`:\n \
575+ {types}\n \
576+ Consider defining an enum where each variant holds one of these types,\n \
577+ implementing `{trait_str}` for this new enum and using it instead.",
578+ ) ) ;
576579 }
580+
577581 if externally_visible {
578582 err. note ( format ! (
579- "`{trait_str}` can be implemented in other crates; if you want to support your users \
583+ "`{trait_str}` may be implemented in other crates; if you want to support your users \
580584 passing their own types here, you can't refer to a specific type",
581585 ) ) ;
582586 }
587+ }
583588
584- err
589+ fn has_non_lifetime_generics ( tcx : TyCtxt < ' _ > , def_id : DefId ) -> bool {
590+ tcx. generics_of ( def_id) . own_params . iter ( ) . any ( |param| match param. kind {
591+ ty:: GenericParamDefKind :: Type { .. } | ty:: GenericParamDefKind :: Const { .. } => true ,
592+ ty:: GenericParamDefKind :: Lifetime => false ,
593+ } )
594+ }
595+
596+ /// Attempt to suggest that a `dyn Trait` argument or return type be converted
597+ /// to use `impl Trait`.
598+ fn attempt_dyn_to_impl_suggestion ( tcx : TyCtxt < ' _ > , hir_id : Option < hir:: HirId > , err : & mut Diag < ' _ > ) {
599+ let Some ( hir_id) = hir_id else { return } ;
600+ let hir:: Node :: Ty ( ty) = tcx. hir_node ( hir_id) else { return } ;
601+ let hir:: TyKind :: TraitObject ( [ trait_ref, ..] , ..) = ty. kind else { return } ;
602+
603+ // Only suggest converting `dyn` to `impl` if we're in a function signature.
604+ // This ensures that we don't suggest converting e.g.
605+ // `type Alias = Box<dyn DynIncompatibleTrait>;` to
606+ // `type Alias = Box<impl DynIncompatibleTrait>;`
607+ let Some ( ( _id, first_non_type_parent_node) ) =
608+ tcx. hir ( ) . parent_iter ( hir_id) . find ( |( _id, node) | !matches ! ( node, hir:: Node :: Ty ( _) ) )
609+ else {
610+ return ;
611+ } ;
612+ if first_non_type_parent_node. fn_sig ( ) . is_none ( ) {
613+ return ;
614+ }
615+
616+ err. span_suggestion_verbose (
617+ ty. span . until ( trait_ref. span ) ,
618+ "consider using an opaque type instead" ,
619+ "impl " ,
620+ Applicability :: MaybeIncorrect ,
621+ ) ;
585622}
0 commit comments