rustc_trait_selection/error_reporting/traits/
mod.rs

1pub mod ambiguity;
2pub mod call_kind;
3pub mod fulfillment_errors;
4pub mod on_unimplemented;
5pub mod on_unimplemented_condition;
6pub mod on_unimplemented_format;
7mod overflow;
8pub mod suggestions;
9
10use std::{fmt, iter};
11
12use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
13use rustc_data_structures::unord::UnordSet;
14use rustc_errors::{Applicability, Diag, E0038, E0276, MultiSpan, struct_span_code_err};
15use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
16use rustc_hir::intravisit::Visitor;
17use rustc_hir::{self as hir, AmbigArg};
18use rustc_infer::traits::solve::Goal;
19use rustc_infer::traits::{
20    DynCompatibilityViolation, Obligation, ObligationCause, ObligationCauseCode,
21    PredicateObligation, SelectionError,
22};
23use rustc_middle::ty::print::{PrintTraitRefExt as _, with_no_trimmed_paths};
24use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt as _};
25use rustc_session::cstore::{ExternCrate, ExternCrateSource};
26use rustc_span::{DesugaringKind, ErrorGuaranteed, ExpnKind, Span};
27use tracing::{info, instrument};
28
29pub use self::overflow::*;
30use crate::error_reporting::TypeErrCtxt;
31use crate::traits::{FulfillmentError, FulfillmentErrorCode};
32
33// When outputting impl candidates, prefer showing those that are more similar.
34//
35// We also compare candidates after skipping lifetimes, which has a lower
36// priority than exact matches.
37#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
38pub enum CandidateSimilarity {
39    Exact { ignoring_lifetimes: bool },
40    Fuzzy { ignoring_lifetimes: bool },
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct ImplCandidate<'tcx> {
45    pub trait_ref: ty::TraitRef<'tcx>,
46    pub similarity: CandidateSimilarity,
47    impl_def_id: DefId,
48}
49
50enum GetSafeTransmuteErrorAndReason {
51    Silent,
52    Default,
53    Error { err_msg: String, safe_transmute_explanation: Option<String> },
54}
55
56/// Crude way of getting back an `Expr` from a `Span`.
57pub struct FindExprBySpan<'hir> {
58    pub span: Span,
59    pub result: Option<&'hir hir::Expr<'hir>>,
60    pub ty_result: Option<&'hir hir::Ty<'hir>>,
61    pub include_closures: bool,
62    pub tcx: TyCtxt<'hir>,
63}
64
65impl<'hir> FindExprBySpan<'hir> {
66    pub fn new(span: Span, tcx: TyCtxt<'hir>) -> Self {
67        Self { span, result: None, ty_result: None, tcx, include_closures: false }
68    }
69}
70
71impl<'v> Visitor<'v> for FindExprBySpan<'v> {
72    type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;
73
74    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
75        self.tcx
76    }
77
78    fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
79        if self.span == ex.span {
80            self.result = Some(ex);
81        } else {
82            if let hir::ExprKind::Closure(..) = ex.kind
83                && self.include_closures
84                && let closure_header_sp = self.span.with_hi(ex.span.hi())
85                && closure_header_sp == ex.span
86            {
87                self.result = Some(ex);
88            }
89            hir::intravisit::walk_expr(self, ex);
90        }
91    }
92
93    fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) {
94        if self.span == ty.span {
95            self.ty_result = Some(ty.as_unambig_ty());
96        } else {
97            hir::intravisit::walk_ty(self, ty);
98        }
99    }
100}
101
102/// Summarizes information
103#[derive(Clone)]
104pub enum ArgKind {
105    /// An argument of non-tuple type. Parameters are (name, ty)
106    Arg(String, String),
107
108    /// An argument of tuple type. For a "found" argument, the span is
109    /// the location in the source of the pattern. For an "expected"
110    /// argument, it will be None. The vector is a list of (name, ty)
111    /// strings for the components of the tuple.
112    Tuple(Option<Span>, Vec<(String, String)>),
113}
114
115impl ArgKind {
116    fn empty() -> ArgKind {
117        ArgKind::Arg("_".to_owned(), "_".to_owned())
118    }
119
120    /// Creates an `ArgKind` from the expected type of an
121    /// argument. It has no name (`_`) and an optional source span.
122    pub fn from_expected_ty(t: Ty<'_>, span: Option<Span>) -> ArgKind {
123        match t.kind() {
124            ty::Tuple(tys) => ArgKind::Tuple(
125                span,
126                tys.iter().map(|ty| ("_".to_owned(), ty.to_string())).collect::<Vec<_>>(),
127            ),
128            _ => ArgKind::Arg("_".to_owned(), t.to_string()),
129        }
130    }
131}
132
133#[derive(Copy, Clone)]
134pub enum DefIdOrName {
135    DefId(DefId),
136    Name(&'static str),
137}
138
139impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
140    pub fn report_fulfillment_errors(
141        &self,
142        mut errors: Vec<FulfillmentError<'tcx>>,
143    ) -> ErrorGuaranteed {
144        #[derive(Debug)]
145        struct ErrorDescriptor<'tcx> {
146            goal: Goal<'tcx, ty::Predicate<'tcx>>,
147            index: Option<usize>, // None if this is an old error
148        }
149
150        let mut error_map: FxIndexMap<_, Vec<_>> = self
151            .reported_trait_errors
152            .borrow()
153            .iter()
154            .map(|(&span, goals)| {
155                (span, goals.0.iter().map(|&goal| ErrorDescriptor { goal, index: None }).collect())
156            })
157            .collect();
158
159        // Ensure `T: Sized`, `T: MetaSized`, `T: PointeeSized` and `T: WF` obligations come last,
160        // and `Subtype` obligations from `FormatLiteral` desugarings come first.
161        // This lets us display diagnostics with more relevant type information and hide redundant
162        // E0282 errors.
163        #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
164        enum ErrorSortKey {
165            SubtypeFormat(usize, usize),
166            OtherKind,
167            SizedTrait,
168            MetaSizedTrait,
169            PointeeSizedTrait,
170            Coerce,
171            WellFormed,
172        }
173        errors.sort_by_key(|e| {
174            let maybe_sizedness_did = match e.obligation.predicate.kind().skip_binder() {
175                ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => Some(pred.def_id()),
176                ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(pred)) => Some(pred.def_id()),
177                _ => None,
178            };
179
180            match e.obligation.predicate.kind().skip_binder() {
181                ty::PredicateKind::Subtype(_)
182                    if matches!(
183                        e.obligation.cause.span.desugaring_kind(),
184                        Some(DesugaringKind::FormatLiteral { .. })
185                    ) =>
186                {
187                    let (_, row, col, ..) =
188                        self.tcx.sess.source_map().span_to_location_info(e.obligation.cause.span);
189                    ErrorSortKey::SubtypeFormat(row, col)
190                }
191                _ if maybe_sizedness_did == self.tcx.lang_items().sized_trait() => {
192                    ErrorSortKey::SizedTrait
193                }
194                _ if maybe_sizedness_did == self.tcx.lang_items().meta_sized_trait() => {
195                    ErrorSortKey::MetaSizedTrait
196                }
197                _ if maybe_sizedness_did == self.tcx.lang_items().pointee_sized_trait() => {
198                    ErrorSortKey::PointeeSizedTrait
199                }
200                ty::PredicateKind::Coerce(_) => ErrorSortKey::Coerce,
201                ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(_)) => {
202                    ErrorSortKey::WellFormed
203                }
204                _ => ErrorSortKey::OtherKind,
205            }
206        });
207
208        for (index, error) in errors.iter().enumerate() {
209            // We want to ignore desugarings here: spans are equivalent even
210            // if one is the result of a desugaring and the other is not.
211            let mut span = error.obligation.cause.span;
212            let expn_data = span.ctxt().outer_expn_data();
213            if let ExpnKind::Desugaring(_) = expn_data.kind {
214                span = expn_data.call_site;
215            }
216
217            error_map
218                .entry(span)
219                .or_default()
220                .push(ErrorDescriptor { goal: error.obligation.as_goal(), index: Some(index) });
221        }
222
223        // We do this in 2 passes because we want to display errors in order, though
224        // maybe it *is* better to sort errors by span or something.
225        let mut is_suppressed = vec![false; errors.len()];
226        for (_, error_set) in error_map.iter() {
227            // We want to suppress "duplicate" errors with the same span.
228            for error in error_set {
229                if let Some(index) = error.index {
230                    // Suppress errors that are either:
231                    // 1) strictly implied by another error.
232                    // 2) implied by an error with a smaller index.
233                    for error2 in error_set {
234                        if error2.index.is_some_and(|index2| is_suppressed[index2]) {
235                            // Avoid errors being suppressed by already-suppressed
236                            // errors, to prevent all errors from being suppressed
237                            // at once.
238                            continue;
239                        }
240
241                        if self.error_implies(error2.goal, error.goal)
242                            && !(error2.index >= error.index
243                                && self.error_implies(error.goal, error2.goal))
244                        {
245                            info!("skipping {:?} (implied by {:?})", error, error2);
246                            is_suppressed[index] = true;
247                            break;
248                        }
249                    }
250                }
251            }
252        }
253
254        let mut reported = None;
255        for from_expansion in [false, true] {
256            for (error, suppressed) in iter::zip(&errors, &is_suppressed) {
257                if !suppressed && error.obligation.cause.span.from_expansion() == from_expansion {
258                    if !error.references_error() {
259                        let guar = self.report_fulfillment_error(error);
260                        self.infcx.set_tainted_by_errors(guar);
261                        reported = Some(guar);
262                        // We want to ignore desugarings here: spans are equivalent even
263                        // if one is the result of a desugaring and the other is not.
264                        let mut span = error.obligation.cause.span;
265                        let expn_data = span.ctxt().outer_expn_data();
266                        if let ExpnKind::Desugaring(_) = expn_data.kind {
267                            span = expn_data.call_site;
268                        }
269                        self.reported_trait_errors
270                            .borrow_mut()
271                            .entry(span)
272                            .or_insert_with(|| (vec![], guar))
273                            .0
274                            .push(error.obligation.as_goal());
275                    }
276                    if let Some(guar) = self.dcx().has_errors() {
277                        self.infcx.set_tainted_by_errors(guar);
278                    }
279                }
280            }
281        }
282
283        // It could be that we don't report an error because we have seen an `ErrorReported` from
284        // another source. We should probably be able to fix most of these, but some are delayed
285        // bugs that get a proper error after this function.
286        reported.unwrap_or_else(|| self.dcx().delayed_bug("failed to report fulfillment errors"))
287    }
288
289    #[instrument(skip(self), level = "debug")]
290    fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) -> ErrorGuaranteed {
291        let mut error = FulfillmentError {
292            obligation: error.obligation.clone(),
293            code: error.code.clone(),
294            root_obligation: error.root_obligation.clone(),
295        };
296        if matches!(
297            error.code,
298            FulfillmentErrorCode::Select(crate::traits::SelectionError::Unimplemented)
299                | FulfillmentErrorCode::Project(_)
300        ) && self.apply_do_not_recommend(&mut error.obligation)
301        {
302            error.code = FulfillmentErrorCode::Select(SelectionError::Unimplemented);
303        }
304
305        match error.code {
306            FulfillmentErrorCode::Select(ref selection_error) => self.report_selection_error(
307                error.obligation.clone(),
308                &error.root_obligation,
309                selection_error,
310            ),
311            FulfillmentErrorCode::Project(ref e) => {
312                self.report_projection_error(&error.obligation, e)
313            }
314            FulfillmentErrorCode::Ambiguity { overflow: None } => {
315                self.maybe_report_ambiguity(&error.obligation)
316            }
317            FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) } => {
318                self.report_overflow_no_abort(error.obligation.clone(), suggest_increasing_limit)
319            }
320            FulfillmentErrorCode::Subtype(ref expected_found, ref err) => self
321                .report_mismatched_types(
322                    &error.obligation.cause,
323                    error.obligation.param_env,
324                    expected_found.expected,
325                    expected_found.found,
326                    *err,
327                )
328                .emit(),
329            FulfillmentErrorCode::ConstEquate(ref expected_found, ref err) => {
330                let mut diag = self.report_mismatched_consts(
331                    &error.obligation.cause,
332                    error.obligation.param_env,
333                    expected_found.expected,
334                    expected_found.found,
335                    *err,
336                );
337                let code = error.obligation.cause.code().peel_derives().peel_match_impls();
338                if let ObligationCauseCode::WhereClause(..)
339                | ObligationCauseCode::WhereClauseInExpr(..) = code
340                {
341                    self.note_obligation_cause_code(
342                        error.obligation.cause.body_id,
343                        &mut diag,
344                        error.obligation.predicate,
345                        error.obligation.param_env,
346                        code,
347                        &mut vec![],
348                        &mut Default::default(),
349                    );
350                }
351                diag.emit()
352            }
353            FulfillmentErrorCode::Cycle(ref cycle) => self.report_overflow_obligation_cycle(cycle),
354        }
355    }
356
357    /// If the crates of `expected_def_id` and `trait_def_id` are imported as extern crate
358    /// under the same name (`extern crate foo as a` and `extern crate bar as a`) returns true,
359    /// otherwise returns false.
360    fn extern_crates_with_the_same_name(
361        &self,
362        expected_def_id: DefId,
363        trait_def_id: DefId,
364    ) -> bool {
365        if expected_def_id.is_local() || trait_def_id.is_local() {
366            return false;
367        }
368        // We only compare direct dependencies of the current crate, so it avoids unnecessary
369        // processing and excludes indirect dependencies, like `std` or `core`. In such a case
370        // both would be imported under the same name `std`.
371        match (
372            self.tcx.extern_crate(expected_def_id.krate),
373            self.tcx.extern_crate(trait_def_id.krate),
374        ) {
375            (
376                Some(ExternCrate {
377                    src: ExternCrateSource::Extern(expected_def_id),
378                    dependency_of: LOCAL_CRATE,
379                    ..
380                }),
381                Some(ExternCrate {
382                    src: ExternCrateSource::Extern(trait_def_id),
383                    dependency_of: LOCAL_CRATE,
384                    ..
385                }),
386            ) => self.tcx.item_name(expected_def_id) == self.tcx.item_name(trait_def_id),
387            _ => false,
388        }
389    }
390
391    pub fn check_same_definition_different_crate<F>(
392        &self,
393        err: &mut Diag<'_>,
394        expected_did: DefId,
395        found_dids: impl Iterator<Item = DefId>,
396        get_impls: F,
397        ty: &str,
398    ) -> bool
399    where
400        F: Fn(DefId) -> Vec<Span>,
401    {
402        let krate = self.tcx.crate_name(expected_did.krate);
403        let name = self.tcx.item_name(expected_did);
404        let definitions_with_same_path: UnordSet<_> = found_dids
405            .filter(|def_id| {
406                def_id.krate != expected_did.krate
407                    && (self.extern_crates_with_the_same_name(expected_did, *def_id)
408                        || self.tcx.crate_name(def_id.krate) == krate)
409                    && self.tcx.item_name(def_id) == name
410            })
411            .map(|def_id| (self.tcx.def_path_str(def_id), def_id))
412            .collect();
413
414        let definitions_with_same_path =
415            definitions_with_same_path.into_items().into_sorted_stable_ord_by_key(|(p, _)| p);
416        let mut suggested = false;
417        let mut trait_is_impl = false;
418
419        if !definitions_with_same_path.is_empty() {
420            let mut span: MultiSpan = self.tcx.def_span(expected_did).into();
421            span.push_span_label(
422                self.tcx.def_span(expected_did),
423                format!("this is the expected {ty}"),
424            );
425            suggested = true;
426            for (_, definition_with_same_path) in &definitions_with_same_path {
427                let definitions_impls = get_impls(*definition_with_same_path);
428                if definitions_impls.is_empty() {
429                    continue;
430                }
431
432                for candidate_span in definitions_impls {
433                    span.push_span_label(candidate_span, format!("this is the found {ty}"));
434                    trait_is_impl = true;
435                }
436            }
437            if !trait_is_impl {
438                for (_, def_id) in definitions_with_same_path {
439                    span.push_span_label(
440                        self.tcx.def_span(def_id),
441                        format!("this is the {ty} that was imported"),
442                    );
443                }
444            }
445            self.note_two_crate_versions(expected_did, span, err);
446            err.help("you can use `cargo tree` to explore your dependency tree");
447        }
448        suggested
449    }
450}
451
452/// Recovers the "impl X for Y" signature from `impl_def_id` and returns it as a
453/// string.
454pub(crate) fn to_pretty_impl_header(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Option<String> {
455    use std::fmt::Write;
456
457    let trait_ref = tcx.impl_opt_trait_ref(impl_def_id)?.instantiate_identity();
458    let mut w = "impl".to_owned();
459
460    #[derive(Debug, Default)]
461    struct SizednessFound {
462        sized: bool,
463        meta_sized: bool,
464    }
465
466    let mut types_with_sizedness_bounds = FxIndexMap::<_, SizednessFound>::default();
467
468    let args = ty::GenericArgs::identity_for_item(tcx, impl_def_id);
469
470    let arg_names = args.iter().map(|k| k.to_string()).filter(|k| k != "'_").collect::<Vec<_>>();
471    if !arg_names.is_empty() {
472        w.push('<');
473        w.push_str(&arg_names.join(", "));
474        w.push('>');
475
476        for ty in args.types() {
477            // `PointeeSized` params might have no predicates.
478            types_with_sizedness_bounds.insert(ty, SizednessFound::default());
479        }
480    }
481
482    write!(
483        w,
484        " {}{} for {}",
485        tcx.impl_polarity(impl_def_id).as_str(),
486        trait_ref.print_only_trait_path(),
487        tcx.type_of(impl_def_id).instantiate_identity()
488    )
489    .unwrap();
490
491    let predicates = tcx.predicates_of(impl_def_id).predicates;
492    let mut pretty_predicates = Vec::with_capacity(predicates.len());
493
494    let sized_trait = tcx.lang_items().sized_trait();
495    let meta_sized_trait = tcx.lang_items().meta_sized_trait();
496
497    for (p, _) in predicates {
498        // Accumulate the sizedness bounds for each self ty.
499        if let Some(trait_clause) = p.as_trait_clause() {
500            let self_ty = trait_clause.self_ty().skip_binder();
501            let sizedness_of = types_with_sizedness_bounds.entry(self_ty).or_default();
502            if Some(trait_clause.def_id()) == sized_trait {
503                sizedness_of.sized = true;
504                continue;
505            } else if Some(trait_clause.def_id()) == meta_sized_trait {
506                sizedness_of.meta_sized = true;
507                continue;
508            }
509        }
510
511        pretty_predicates.push(p.to_string());
512    }
513
514    for (ty, sizedness) in types_with_sizedness_bounds {
515        if !tcx.features().sized_hierarchy() {
516            if sizedness.sized {
517                // Maybe a default bound, don't write anything.
518            } else {
519                pretty_predicates.push(format!("{ty}: ?Sized"));
520            }
521        } else {
522            if sizedness.sized {
523                // Maybe a default bound, don't write anything.
524                pretty_predicates.push(format!("{ty}: Sized"));
525            } else if sizedness.meta_sized {
526                pretty_predicates.push(format!("{ty}: MetaSized"));
527            } else {
528                pretty_predicates.push(format!("{ty}: PointeeSized"));
529            }
530        }
531    }
532
533    if !pretty_predicates.is_empty() {
534        write!(w, "\n  where {}", pretty_predicates.join(", ")).unwrap();
535    }
536
537    w.push(';');
538    Some(w)
539}
540
541impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
542    pub fn report_extra_impl_obligation(
543        &self,
544        error_span: Span,
545        impl_item_def_id: LocalDefId,
546        trait_item_def_id: DefId,
547        requirement: &dyn fmt::Display,
548    ) -> Diag<'a> {
549        let mut err = struct_span_code_err!(
550            self.dcx(),
551            error_span,
552            E0276,
553            "impl has stricter requirements than trait"
554        );
555
556        if !self.tcx.is_impl_trait_in_trait(trait_item_def_id) {
557            if let Some(span) = self.tcx.hir_span_if_local(trait_item_def_id) {
558                let item_name = self.tcx.item_name(impl_item_def_id.to_def_id());
559                err.span_label(span, format!("definition of `{item_name}` from trait"));
560            }
561        }
562
563        err.span_label(error_span, format!("impl has extra requirement {requirement}"));
564
565        err
566    }
567}
568
569pub fn report_dyn_incompatibility<'tcx>(
570    tcx: TyCtxt<'tcx>,
571    span: Span,
572    hir_id: Option<hir::HirId>,
573    trait_def_id: DefId,
574    violations: &[DynCompatibilityViolation],
575) -> Diag<'tcx> {
576    let trait_str = tcx.def_path_str(trait_def_id);
577    let trait_span = tcx.hir_get_if_local(trait_def_id).and_then(|node| match node {
578        hir::Node::Item(item) => match item.kind {
579            hir::ItemKind::Trait(_, _, _, ident, ..)
580            | hir::ItemKind::TraitAlias(_, ident, _, _) => Some(ident.span),
581            _ => unreachable!(),
582        },
583        _ => None,
584    });
585
586    let mut err = struct_span_code_err!(
587        tcx.dcx(),
588        span,
589        E0038,
590        "the {} `{}` is not dyn compatible",
591        tcx.def_descr(trait_def_id),
592        trait_str
593    );
594    err.span_label(span, format!("`{trait_str}` is not dyn compatible"));
595
596    attempt_dyn_to_impl_suggestion(tcx, hir_id, &mut err);
597
598    let mut reported_violations = FxIndexSet::default();
599    let mut multi_span = vec![];
600    let mut messages = vec![];
601    for violation in violations {
602        if let DynCompatibilityViolation::SizedSelf(sp) = &violation
603            && !sp.is_empty()
604        {
605            // Do not report `SizedSelf` without spans pointing at `SizedSelf` obligations
606            // with a `Span`.
607            reported_violations.insert(DynCompatibilityViolation::SizedSelf(vec![].into()));
608        }
609        if reported_violations.insert(violation.clone()) {
610            let spans = violation.spans();
611            let msg = if trait_span.is_none() || spans.is_empty() {
612                format!("the trait is not dyn compatible because {}", violation.error_msg())
613            } else {
614                format!("...because {}", violation.error_msg())
615            };
616            if spans.is_empty() {
617                err.note(msg);
618            } else {
619                for span in spans {
620                    multi_span.push(span);
621                    messages.push(msg.clone());
622                }
623            }
624        }
625    }
626    let has_multi_span = !multi_span.is_empty();
627    let mut note_span = MultiSpan::from_spans(multi_span.clone());
628    if let (Some(trait_span), true) = (trait_span, has_multi_span) {
629        note_span.push_span_label(trait_span, "this trait is not dyn compatible...");
630    }
631    for (span, msg) in iter::zip(multi_span, messages) {
632        note_span.push_span_label(span, msg);
633    }
634    err.span_note(
635        note_span,
636        "for a trait to be dyn compatible it needs to allow building a vtable\n\
637        for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>",
638    );
639
640    // Only provide the help if its a local trait, otherwise it's not actionable.
641    if trait_span.is_some() {
642        let mut potential_solutions: Vec<_> =
643            reported_violations.into_iter().map(|violation| violation.solution()).collect();
644        potential_solutions.sort();
645        // Allows us to skip suggesting that the same item should be moved to another trait multiple times.
646        potential_solutions.dedup();
647        for solution in potential_solutions {
648            solution.add_to(&mut err);
649        }
650    }
651
652    attempt_dyn_to_enum_suggestion(tcx, trait_def_id, &*trait_str, &mut err);
653
654    err
655}
656
657/// Attempt to suggest converting the `dyn Trait` argument to an enumeration
658/// over the types that implement `Trait`.
659fn attempt_dyn_to_enum_suggestion(
660    tcx: TyCtxt<'_>,
661    trait_def_id: DefId,
662    trait_str: &str,
663    err: &mut Diag<'_>,
664) {
665    let impls_of = tcx.trait_impls_of(trait_def_id);
666
667    if !impls_of.blanket_impls().is_empty() {
668        return;
669    }
670
671    let concrete_impls: Option<Vec<Ty<'_>>> = impls_of
672        .non_blanket_impls()
673        .values()
674        .flatten()
675        .map(|impl_id| {
676            // Don't suggest conversion to enum if the impl types have type parameters.
677            // It's unlikely the user wants to define a generic enum.
678            let Some(impl_type) = tcx.type_of(*impl_id).no_bound_vars() else { return None };
679
680            // Obviously unsized impl types won't be usable in an enum.
681            // Note: this doesn't use `Ty::has_trivial_sizedness` because that function
682            // defaults to assuming that things are *not* sized, whereas we want to
683            // fall back to assuming that things may be sized.
684            match impl_type.kind() {
685                ty::Str | ty::Slice(_) | ty::Dynamic(_, _) => {
686                    return None;
687                }
688                _ => {}
689            }
690            Some(impl_type)
691        })
692        .collect();
693    let Some(concrete_impls) = concrete_impls else { return };
694
695    const MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM: usize = 9;
696    if concrete_impls.is_empty() || concrete_impls.len() > MAX_IMPLS_TO_SUGGEST_CONVERTING_TO_ENUM {
697        return;
698    }
699
700    let externally_visible = if let Some(def_id) = trait_def_id.as_local() {
701        // We may be executing this during typeck, which would result in cycle
702        // if we used effective_visibilities query, which looks into opaque types
703        // (and therefore calls typeck).
704        tcx.resolutions(()).effective_visibilities.is_exported(def_id)
705    } else {
706        false
707    };
708
709    if let [only_impl] = &concrete_impls[..] {
710        let within = if externally_visible { " within this crate" } else { "" };
711        err.help(with_no_trimmed_paths!(format!(
712            "only type `{only_impl}` implements `{trait_str}`{within}; \
713            consider using it directly instead."
714        )));
715    } else {
716        let types = concrete_impls
717            .iter()
718            .map(|t| with_no_trimmed_paths!(format!("  {}", t)))
719            .collect::<Vec<String>>()
720            .join("\n");
721
722        err.help(format!(
723            "the following types implement `{trait_str}`:\n\
724             {types}\n\
725             consider defining an enum where each variant holds one of these types,\n\
726             implementing `{trait_str}` for this new enum and using it instead",
727        ));
728    }
729
730    if externally_visible {
731        err.note(format!(
732            "`{trait_str}` may be implemented in other crates; if you want to support your users \
733             passing their own types here, you can't refer to a specific type",
734        ));
735    }
736}
737
738/// Attempt to suggest that a `dyn Trait` argument or return type be converted
739/// to use `impl Trait`.
740fn attempt_dyn_to_impl_suggestion(tcx: TyCtxt<'_>, hir_id: Option<hir::HirId>, err: &mut Diag<'_>) {
741    let Some(hir_id) = hir_id else { return };
742    let hir::Node::Ty(ty) = tcx.hir_node(hir_id) else { return };
743    let hir::TyKind::TraitObject([trait_ref, ..], ..) = ty.kind else { return };
744
745    // Only suggest converting `dyn` to `impl` if we're in a function signature.
746    // This ensures that we don't suggest converting e.g.
747    //   `type Alias = Box<dyn DynIncompatibleTrait>;` to
748    //   `type Alias = Box<impl DynIncompatibleTrait>;`
749    let Some((_id, first_non_type_parent_node)) =
750        tcx.hir_parent_iter(hir_id).find(|(_id, node)| !matches!(node, hir::Node::Ty(_)))
751    else {
752        return;
753    };
754    if first_non_type_parent_node.fn_sig().is_none() {
755        return;
756    }
757
758    err.span_suggestion_verbose(
759        ty.span.until(trait_ref.span),
760        "consider using an opaque type instead",
761        "impl ",
762        Applicability::MaybeIncorrect,
763    );
764}