Skip to main content

rustc_middle/ty/
diagnostics.rs

1//! Diagnostics related methods for `Ty`.
2
3use std::fmt::Write;
4use std::ops::ControlFlow;
5
6use rustc_data_structures::fx::FxIndexMap;
7use rustc_errors::{Applicability, Diag, DiagArgValue, IntoDiagArg, listify, pluralize};
8use rustc_hir::attrs::lang_items::LangItem;
9use rustc_hir::def::{DefKind, Namespace};
10use rustc_hir::def_id::DefId;
11use rustc_hir::{self as hir, AmbigArg, PredicateOrigin, WherePredicateKind};
12use rustc_span::{BytePos, Span};
13use rustc_type_ir::TyKind::*;
14
15use crate::ty::{
16    self, AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Instance, Opaque,
17    PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable,
18    TypeSuperVisitable, TypeVisitable, TypeVisitor,
19};
20
21impl IntoDiagArg for Ty<'_> {
22    fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue {
23        ty::tls::with(|tcx| {
24            let ty = tcx.short_string(tcx.lift(self), path);
25            DiagArgValue::Str(std::borrow::Cow::Owned(ty))
26        })
27    }
28}
29
30impl IntoDiagArg for Instance<'_> {
31    fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue {
32        ty::tls::with(|tcx| {
33            let instance = tcx.short_string_namespace(tcx.lift(self), path, Namespace::ValueNS);
34            DiagArgValue::Str(std::borrow::Cow::Owned(instance))
35        })
36    }
37}
38
39impl<'tcx> Ty<'tcx> {
40    /// Similar to `Ty::is_primitive`, but also considers inferred numeric values to be primitive.
41    pub fn is_primitive_ty(self) -> bool {
42        #[allow(non_exhaustive_omitted_patterns)] match self.kind() {
    Bool | Char | Str | Int(_) | Uint(_) | Float(_) |
        Infer(InferTy::IntVar(_) | InferTy::FloatVar(_) |
        InferTy::FreshIntTy(_) | InferTy::FreshFloatTy(_)) => true,
    _ => false,
}matches!(
43            self.kind(),
44            Bool | Char
45                | Str
46                | Int(_)
47                | Uint(_)
48                | Float(_)
49                | Infer(
50                    InferTy::IntVar(_)
51                        | InferTy::FloatVar(_)
52                        | InferTy::FreshIntTy(_)
53                        | InferTy::FreshFloatTy(_)
54                )
55        )
56    }
57
58    /// Whether the type is succinctly representable as a type instead of just referred to with a
59    /// description in error messages. This is used in the main error message.
60    pub fn is_simple_ty(self) -> bool {
61        match self.kind() {
62            Bool
63            | Char
64            | Str
65            | Int(_)
66            | Uint(_)
67            | Float(_)
68            | Infer(
69                InferTy::IntVar(_)
70                | InferTy::FloatVar(_)
71                | InferTy::FreshIntTy(_)
72                | InferTy::FreshFloatTy(_),
73            ) => true,
74            Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
75            Tuple(tys) if tys.is_empty() => true,
76            _ => false,
77        }
78    }
79
80    /// Whether the type is succinctly representable as a type instead of just referred to with a
81    /// description in error messages. This is used in the primary span label. Beyond what
82    /// `is_simple_ty` includes, it also accepts ADTs with no type arguments and references to
83    /// ADTs with no type arguments.
84    pub fn is_simple_text(self) -> bool {
85        match self.kind() {
86            Adt(_, args) => args.non_erasable_generics().next().is_none(),
87            Ref(_, ty, _) => ty.is_simple_text(),
88            _ => self.is_simple_ty(),
89        }
90    }
91}
92
93pub trait IsSuggestable<'tcx>: Sized {
94    /// Whether this makes sense to suggest in a diagnostic.
95    ///
96    /// We filter out certain types and constants since they don't provide
97    /// meaningful rendered suggestions when pretty-printed. We leave some
98    /// nonsense, such as region vars, since those render as `'_` and are
99    /// usually okay to reinterpret as elided lifetimes.
100    ///
101    /// Only if `infer_suggestable` is true, we consider type and const
102    /// inference variables to be suggestable.
103    fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool;
104
105    fn make_suggestable(
106        self,
107        tcx: TyCtxt<'tcx>,
108        infer_suggestable: bool,
109        placeholder: Option<Ty<'tcx>>,
110    ) -> Option<Self>;
111}
112
113impl<'tcx, T> IsSuggestable<'tcx> for T
114where
115    T: TypeVisitable<TyCtxt<'tcx>> + TypeFoldable<TyCtxt<'tcx>>,
116{
117    #[allow(clippy :: suspicious_else_formatting)]
{
    let __tracing_attr_span;
    let __tracing_attr_guard;
    if ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() ||
            { false } {
        __tracing_attr_span =
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("is_suggestable",
                                    "rustc_middle::ty::diagnostics", ::tracing::Level::DEBUG,
                                    ::tracing_core::__macro_support::Option::Some("compiler/rustc_middle/src/ty/diagnostics.rs"),
                                    ::tracing_core::__macro_support::Option::Some(117u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_middle::ty::diagnostics"),
                                    ::tracing_core::field::FieldSet::new(&[{
                                                        const NAME:
                                                            ::tracing::__macro_support::FieldName<{
                                                                ::tracing::__macro_support::FieldName::len("self")
                                                            }> =
                                                            ::tracing::__macro_support::FieldName::new("self");
                                                        NAME.as_str()
                                                    },
                                                    {
                                                        const NAME:
                                                            ::tracing::__macro_support::FieldName<{
                                                                ::tracing::__macro_support::FieldName::len("infer_suggestable")
                                                            }> =
                                                            ::tracing::__macro_support::FieldName::new("infer_suggestable");
                                                        NAME.as_str()
                                                    }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::SPAN)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let mut interest = ::tracing::subscriber::Interest::never();
                if ::tracing::Level::DEBUG <=
                                    ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                ::tracing::Level::DEBUG <=
                                    ::tracing::level_filters::LevelFilter::current() &&
                            { interest = __CALLSITE.interest(); !interest.is_never() }
                        &&
                        ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                            interest) {
                    let meta = __CALLSITE.metadata();
                    ::tracing::Span::new(meta,
                        &{
                                #[allow(unused_imports)]
                                use ::tracing::field::{debug, display, Value};
                                meta.fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&self)
                                                            as &dyn ::tracing::field::Value)),
                                                (::tracing::__macro_support::Option::Some(&infer_suggestable
                                                            as &dyn ::tracing::field::Value))])
                            })
                } else {
                    let span =
                        ::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
                    {};
                    span
                }
            };
        __tracing_attr_guard = __tracing_attr_span.enter();
    }

    #[warn(clippy :: suspicious_else_formatting)]
    {

        #[allow(unknown_lints, unreachable_code, clippy ::
        diverging_sub_expression, clippy :: empty_loop, clippy ::
        let_unit_value, clippy :: let_with_type_underscore, clippy ::
        needless_return, clippy :: unreachable)]
        if false {
            let __tracing_attr_fake_return: bool = loop {};
            return __tracing_attr_fake_return;
        }
        {
            self.visit_with(&mut IsSuggestableVisitor {
                            tcx,
                            infer_suggestable,
                        }).is_continue()
        }
    }
}#[tracing::instrument(level = "debug", skip(tcx))]
118    fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
119        self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
120    }
121
122    fn make_suggestable(
123        self,
124        tcx: TyCtxt<'tcx>,
125        infer_suggestable: bool,
126        placeholder: Option<Ty<'tcx>>,
127    ) -> Option<T> {
128        self.try_fold_with(&mut MakeSuggestableFolder { tcx, infer_suggestable, placeholder }).ok()
129    }
130}
131
132pub fn suggest_arbitrary_trait_bound<'tcx>(
133    tcx: TyCtxt<'tcx>,
134    generics: &hir::Generics<'_>,
135    err: &mut Diag<'_>,
136    trait_pred: PolyTraitPredicate<'tcx>,
137    associated_ty: Option<(&'static str, Ty<'tcx>)>,
138) -> bool {
139    if !trait_pred.is_suggestable(tcx, false) {
140        return false;
141    }
142
143    let param_name = trait_pred.skip_binder().self_ty().to_string();
144    let mut constraint = trait_pred.to_string();
145
146    if let Some((name, term)) = associated_ty {
147        // FIXME: this case overlaps with code in TyCtxt::note_and_explain_type_err.
148        // That should be extracted into a helper function.
149        if let Some(stripped) = constraint.strip_suffix('>') {
150            constraint = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}, {1} = {2}>", stripped, name,
                term))
    })format!("{stripped}, {name} = {term}>");
151        } else {
152            constraint.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("<{0} = {1}>", name, term))
    })format!("<{name} = {term}>"));
153        }
154    }
155
156    let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
157
158    // Skip, there is a param named Self
159    if param.is_some() && param_name == "Self" {
160        return false;
161    }
162
163    // Suggest a where clause bound for a non-type parameter.
164    err.span_suggestion_verbose(
165        generics.tail_span_for_predicate_suggestion(),
166        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider {0} `where` clause, but there might be an alternative better way to express this requirement",
                if generics.where_clause_span.is_empty() {
                    "introducing a"
                } else { "extending the" }))
    })format!(
167            "consider {} `where` clause, but there might be an alternative better way to express \
168             this requirement",
169            if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
170        ),
171        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} {1}",
                generics.add_where_or_trailing_comma(), constraint))
    })format!("{} {constraint}", generics.add_where_or_trailing_comma()),
172        Applicability::MaybeIncorrect,
173    );
174    true
175}
176
177#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for SuggestChangingConstraintsMessage<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            SuggestChangingConstraintsMessage::RestrictBoundFurther =>
                ::core::fmt::Formatter::write_str(f, "RestrictBoundFurther"),
            SuggestChangingConstraintsMessage::RestrictType { ty: __self_0 }
                =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "RestrictType", "ty", &__self_0),
            SuggestChangingConstraintsMessage::RestrictTypeFurther {
                ty: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "RestrictTypeFurther", "ty", &__self_0),
            SuggestChangingConstraintsMessage::RemoveMaybeUnsized =>
                ::core::fmt::Formatter::write_str(f, "RemoveMaybeUnsized"),
            SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized =>
                ::core::fmt::Formatter::write_str(f,
                    "ReplaceMaybeUnsizedWithSized"),
        }
    }
}Debug, #[automatically_derived]
impl<'a> ::core::clone::Clone for SuggestChangingConstraintsMessage<'a> {
    #[inline]
    fn clone(&self) -> SuggestChangingConstraintsMessage<'a> {
        let _: ::core::clone::AssertParamIsClone<&'a str>;
        let _: ::core::clone::AssertParamIsClone<&'a str>;
        *self
    }
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for SuggestChangingConstraintsMessage<'a> { }Copy)]
178enum SuggestChangingConstraintsMessage<'a> {
179    RestrictBoundFurther,
180    RestrictType { ty: &'a str },
181    RestrictTypeFurther { ty: &'a str },
182    RemoveMaybeUnsized,
183    ReplaceMaybeUnsizedWithSized,
184}
185
186fn suggest_changing_unsized_bound(
187    generics: &hir::Generics<'_>,
188    suggestions: &mut Vec<(Span, String, String, SuggestChangingConstraintsMessage<'_>)>,
189    param: &hir::GenericParam<'_>,
190    def_id: Option<DefId>,
191) {
192    // See if there's a `?Sized` bound that can be removed to suggest that.
193    // First look at the `where` clause because we can have `where T: ?Sized`,
194    // then look at params.
195    for (where_pos, predicate) in generics.predicates.iter().enumerate() {
196        let WherePredicateKind::BoundPredicate(predicate) = predicate.kind else {
197            continue;
198        };
199        if !predicate.is_param_bound(param.def_id.to_def_id()) {
200            continue;
201        };
202
203        let unsized_bounds = predicate
204            .bounds
205            .iter()
206            .enumerate()
207            .filter(|(_, bound)| {
208                if let hir::GenericBound::Trait(poly) = bound
209                    && let hir::BoundPolarity::Maybe(_) = poly.modifiers.polarity
210                    && poly.trait_ref.trait_def_id() == def_id
211                {
212                    true
213                } else {
214                    false
215                }
216            })
217            .collect::<Vec<_>>();
218
219        if unsized_bounds.is_empty() {
220            continue;
221        }
222
223        let mut push_suggestion =
224            |sp, msg| suggestions.push((sp, "Sized".to_string(), String::new(), msg));
225
226        if predicate.bounds.len() == unsized_bounds.len() {
227            // All the bounds are unsized bounds, e.g.
228            // `T: ?Sized + ?Sized` or `_: impl ?Sized + ?Sized`,
229            // so in this case:
230            // - if it's an impl trait predicate suggest changing the
231            //   the first bound to sized and removing the rest
232            // - Otherwise simply suggest removing the entire predicate
233            if predicate.origin == PredicateOrigin::ImplTrait {
234                let first_bound = unsized_bounds[0].1;
235                let first_bound_span = first_bound.span();
236                if first_bound_span.can_be_used_for_suggestions() {
237                    let question_span =
238                        first_bound_span.with_hi(first_bound_span.lo() + BytePos(1));
239                    push_suggestion(
240                        question_span,
241                        SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized,
242                    );
243
244                    for (pos, _) in unsized_bounds.iter().skip(1) {
245                        let sp = generics.span_for_bound_removal(where_pos, *pos);
246                        push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
247                    }
248                }
249            } else {
250                let sp = generics.span_for_predicate_removal(where_pos);
251                push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
252            }
253        } else {
254            // Some of the bounds are other than unsized.
255            // So push separate removal suggestion for each unsized bound
256            for (pos, _) in unsized_bounds {
257                let sp = generics.span_for_bound_removal(where_pos, pos);
258                push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
259            }
260        }
261    }
262}
263
264/// Suggest restricting a type param with a new bound.
265///
266/// If `span_to_replace` is provided, then that span will be replaced with the
267/// `constraint`. If one wasn't provided, then the full bound will be suggested.
268pub fn suggest_constraining_type_param(
269    tcx: TyCtxt<'_>,
270    generics: &hir::Generics<'_>,
271    err: &mut Diag<'_>,
272    param_name: &str,
273    constraint: &str,
274    def_id: Option<DefId>,
275    span_to_replace: Option<Span>,
276) -> bool {
277    suggest_constraining_type_params(
278        tcx,
279        generics,
280        err,
281        [(param_name, constraint, def_id)].into_iter(),
282        span_to_replace,
283    )
284}
285
286/// Suggest restricting a type param with a new bound.
287pub fn suggest_constraining_type_params<'a>(
288    tcx: TyCtxt<'_>,
289    generics: &hir::Generics<'_>,
290    err: &mut Diag<'_>,
291    param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
292    span_to_replace: Option<Span>,
293) -> bool {
294    let mut grouped = FxIndexMap::default();
295    let mut unstable_suggestion = false;
296    param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
297        let stable = match def_id {
298            Some(def_id) => match tcx.lookup_stability(def_id) {
299                Some(s) => s.level.is_stable(),
300                None => true,
301            },
302            None => true,
303        };
304        if stable || tcx.sess.is_nightly_build() {
305            grouped.entry(param_name).or_insert(Vec::new()).push((
306                constraint,
307                def_id,
308                if stable { "" } else { "unstable " },
309            ));
310            if !stable {
311                unstable_suggestion = true;
312            }
313        }
314    });
315
316    let mut applicability = Applicability::MachineApplicable;
317    let mut suggestions = Vec::new();
318
319    for (param_name, mut constraints) in grouped {
320        let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
321        let Some(param) = param else { return false };
322
323        {
324            let mut sized_constraints = constraints.extract_if(.., |(_, def_id, _)| {
325                def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::Sized))
326            });
327            if let Some((_, def_id, _)) = sized_constraints.next() {
328                applicability = Applicability::MaybeIncorrect;
329
330                err.span_label(param.span, "this type parameter needs to be `Sized`");
331                suggest_changing_unsized_bound(generics, &mut suggestions, param, def_id);
332            }
333        }
334        let bound_message = if constraints.iter().any(|(_, def_id, _)| def_id.is_none()) {
335            SuggestChangingConstraintsMessage::RestrictBoundFurther
336        } else {
337            SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }
338        };
339
340        // in the scenario like impl has stricter requirements than trait,
341        // we should not suggest restrict bound on the impl, here we double check
342        // the whether the param already has the constraint by checking `def_id`
343        let bound_trait_defs: Vec<DefId> = generics
344            .bounds_for_param(param.def_id)
345            .flat_map(|bound| {
346                bound.bounds.iter().flat_map(|b| b.trait_ref().and_then(|t| t.trait_def_id()))
347            })
348            .collect();
349
350        constraints
351            .retain(|(_, def_id, _)| def_id.is_none_or(|def| !bound_trait_defs.contains(&def)));
352
353        if constraints.is_empty() {
354            continue;
355        }
356
357        let mut constraint = constraints.iter().map(|&(c, _, _)| c).collect::<Vec<_>>();
358        constraint.sort();
359        constraint.dedup();
360        let all_known = constraints.iter().all(|&(_, def_id, _)| def_id.is_some());
361        let all_stable = constraints.iter().all(|&(_, _, stable)| stable.is_empty());
362        let all_unstable = constraints.iter().all(|&(_, _, stable)| !stable.is_empty());
363        let post = if all_stable || all_unstable {
364            // Don't redundantly say "trait `X`, trait `Y`", instead "traits `X` and `Y`"
365            let mut trait_names = constraints
366                .iter()
367                .map(|&(c, def_id, _)| match def_id {
368                    None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}`", c))
    })format!("`{c}`"),
369                    Some(def_id) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}`", tcx.item_name(def_id)))
    })format!("`{}`", tcx.item_name(def_id)),
370                })
371                .collect::<Vec<_>>();
372            trait_names.sort();
373            trait_names.dedup();
374            let n = trait_names.len();
375            let stable = if all_stable { "" } else { "unstable " };
376            let trait_ = if all_known { ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("trait{0}",
                if n == 1 { "" } else { "s" }))
    })format!("trait{}", pluralize!(n)) } else { String::new() };
377            let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false };
378            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1} {2}", stable, trait_,
                trait_names))
    })format!("{stable}{trait_} {trait_names}")
379        } else {
380            // We're more explicit when there's a mix of stable and unstable traits.
381            let mut trait_names = constraints
382                .iter()
383                .map(|&(c, def_id, stable)| match def_id {
384                    None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}`", c))
    })format!("`{c}`"),
385                    Some(def_id) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{1}trait `{0}`",
                tcx.item_name(def_id), stable))
    })format!("{stable}trait `{}`", tcx.item_name(def_id)),
386                })
387                .collect::<Vec<_>>();
388            trait_names.sort();
389            trait_names.dedup();
390            match listify(&trait_names, |t| t.to_string()) {
391                Some(names) => names,
392                None => return false,
393            }
394        };
395        let constraint = constraint.join(" + ");
396        let mut suggest_restrict = |span, bound_list_non_empty, open_paren_sp| {
397            let suggestion = if span_to_replace.is_some() {
398                constraint.clone()
399            } else if constraint.starts_with('<') {
400                constraint.clone()
401            } else if bound_list_non_empty {
402                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" + {0}", constraint))
    })format!(" + {constraint}")
403            } else {
404                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" {0}", constraint))
    })format!(" {constraint}")
405            };
406
407            if let Some(open_paren_sp) = open_paren_sp {
408                suggestions.push((open_paren_sp, post.clone(), "(".to_string(), bound_message));
409                suggestions.push((span, post.clone(), ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("){0}", suggestion))
    })format!("){suggestion}"), bound_message));
410            } else {
411                suggestions.push((span, post.clone(), suggestion, bound_message));
412            }
413        };
414
415        if let Some(span) = span_to_replace {
416            suggest_restrict(span, true, None);
417            continue;
418        }
419
420        // When the type parameter has been provided bounds
421        //
422        //    Message:
423        //      fn foo<T>(t: T) where T: Foo { ... }
424        //                            ^^^^^^
425        //                            |
426        //                            help: consider further restricting this bound with `+ Bar`
427        //
428        //    Suggestion:
429        //      fn foo<T>(t: T) where T: Foo { ... }
430        //                                  ^
431        //                                  |
432        //                                  replace with: ` + Bar`
433        //
434        // Or, if user has provided some bounds, suggest restricting them:
435        //
436        //   fn foo<T: Foo>(t: T) { ... }
437        //             ---
438        //             |
439        //             help: consider further restricting this bound with `+ Bar`
440        //
441        // Suggestion for tools in this case is:
442        //
443        //   fn foo<T: Foo>(t: T) { ... }
444        //          --
445        //          |
446        //          replace with: `T: Bar +`
447
448        if let Some((span, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) {
449            suggest_restrict(span, true, open_paren_sp);
450            continue;
451        }
452
453        if generics.has_where_clause_predicates {
454            // This part is a bit tricky, because using the `where` clause user can
455            // provide zero, one or many bounds for the same type parameter, so we
456            // have following cases to consider:
457            //
458            // When the type parameter has been provided zero bounds
459            //
460            //    Message:
461            //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
462            //             - help: consider restricting this type parameter with `where X: Bar`
463            //
464            //    Suggestion:
465            //      fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... }
466            //                                           - insert: `, X: Bar`
467            suggestions.push((
468                generics.tail_span_for_predicate_suggestion(),
469                post,
470                constraints.iter().fold(String::new(), |mut string, &(constraint, _, _)| {
471                    string.write_fmt(format_args!(", {0}: {1}", param_name, constraint))write!(string, ", {param_name}: {constraint}").unwrap();
472                    string
473                }),
474                SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
475            ));
476            continue;
477        }
478
479        // Additionally, there may be no `where` clause but the generic parameter has a default:
480        //
481        //    Message:
482        //      trait Foo<T=()> {... }
483        //                - help: consider further restricting this type parameter with `where T: Zar`
484        //
485        //    Suggestion:
486        //      trait Foo<T=()> {... }
487        //                     - insert: `where T: Zar`
488        if #[allow(non_exhaustive_omitted_patterns)] match param.kind {
    hir::GenericParamKind::Type { default: Some(_), .. } => true,
    _ => false,
}matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
489            // If we are here and the where clause span is of non-zero length
490            // it means we're dealing with an empty where clause like this:
491            //      fn foo<X>(x: X) where { ... }
492            // In that case we don't want to add another "where" (Fixes #120838)
493            let where_prefix = if generics.where_clause_span.is_empty() { " where" } else { "" };
494
495            // Suggest a bound, but there is no existing `where` clause *and* the type param has a
496            // default (`<T=Foo>`), so we suggest adding `where T: Bar`.
497            suggestions.push((
498                generics.tail_span_for_predicate_suggestion(),
499                post,
500                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} {1}: {2}", where_prefix,
                param_name, constraint))
    })format!("{where_prefix} {param_name}: {constraint}"),
501                SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
502            ));
503            continue;
504        }
505
506        // If user has provided a colon, don't suggest adding another:
507        //
508        //   fn foo<T:>(t: T) { ... }
509        //            - insert: consider restricting this type parameter with `T: Foo`
510        if let Some(colon_span) = param.colon_span {
511            suggestions.push((
512                colon_span.shrink_to_hi(),
513                post,
514                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" {0}", constraint))
    })format!(" {constraint}"),
515                SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
516            ));
517            continue;
518        }
519
520        // If user hasn't provided any bounds, suggest adding a new one:
521        //
522        //   fn foo<T>(t: T) { ... }
523        //          - help: consider restricting this type parameter with `T: Foo`
524        let span = param.span.shrink_to_hi();
525        if span.can_be_used_for_suggestions() {
526            suggestions.push((
527                span,
528                post,
529                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(": {0}", constraint))
    })format!(": {constraint}"),
530                SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
531            ));
532        }
533    }
534
535    // FIXME: remove the suggestions that are from derive, as the span is not correct
536    suggestions = suggestions
537        .into_iter()
538        .filter(|(span, _, _, _)| !span.in_derive_expansion())
539        .collect::<Vec<_>>();
540    let suggested = !suggestions.is_empty();
541    if suggestions.len() == 1 {
542        let (span, post, suggestion, msg) = suggestions.pop().unwrap();
543        let msg = match msg {
544            SuggestChangingConstraintsMessage::RestrictBoundFurther => {
545                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider further restricting this bound"))
    })format!("consider further restricting this bound")
546            }
547            SuggestChangingConstraintsMessage::RestrictTypeFurther { ty }
548            | SuggestChangingConstraintsMessage::RestrictType { ty }
549                if ty.starts_with("impl ") =>
550            {
551                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider restricting opaque type `{0}` with {1}",
                ty, post))
    })format!("consider restricting opaque type `{ty}` with {post}")
552            }
553            SuggestChangingConstraintsMessage::RestrictType { ty } => {
554                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider restricting type parameter `{0}` with {1}",
                ty, post))
    })format!("consider restricting type parameter `{ty}` with {post}")
555            }
556            SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
557                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider further restricting type parameter `{0}` with {1}",
                ty, post))
    })format!("consider further restricting type parameter `{ty}` with {post}")
558            }
559            SuggestChangingConstraintsMessage::RemoveMaybeUnsized => {
560                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider removing the `?Sized` bound to make the type parameter `Sized`"))
    })format!("consider removing the `?Sized` bound to make the type parameter `Sized`")
561            }
562            SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized => {
563                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider replacing `?Sized` with `Sized`"))
    })format!("consider replacing `?Sized` with `Sized`")
564            }
565        };
566
567        err.span_suggestion_verbose(span, msg, suggestion, applicability);
568    } else if suggestions.len() > 1 {
569        let post = if unstable_suggestion { " (some of them are unstable traits)" } else { "" };
570        err.multipart_suggestion(
571            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider restricting type parameters{0}",
                post))
    })format!("consider restricting type parameters{post}"),
572            suggestions.into_iter().map(|(span, _, suggestion, _)| (span, suggestion)).collect(),
573            applicability,
574        );
575    }
576
577    suggested
578}
579
580/// Collect al types that have an implicit `'static` obligation that we could suggest `'_` for.
581pub(crate) struct TraitObjectVisitor<'tcx>(pub(crate) Vec<&'tcx hir::Ty<'tcx>>);
582
583impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
584    fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) {
585        match ty.kind {
586            hir::TyKind::TraitObject(_, tagged_ptr)
587                if let hir::Lifetime {
588                    kind:
589                        hir::LifetimeKind::ImplicitObjectLifetimeDefault | hir::LifetimeKind::Static,
590                    ..
591                } = tagged_ptr.pointer() =>
592            {
593                self.0.push(ty.as_unambig_ty())
594            }
595            hir::TyKind::OpaqueDef(..) => self.0.push(ty.as_unambig_ty()),
596            _ => {}
597        }
598        hir::intravisit::walk_ty(self, ty);
599    }
600}
601
602pub struct IsSuggestableVisitor<'tcx> {
603    tcx: TyCtxt<'tcx>,
604    infer_suggestable: bool,
605}
606
607impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IsSuggestableVisitor<'tcx> {
608    type Result = ControlFlow<()>;
609
610    fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
611        match *t.kind() {
612            Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
613
614            FnDef(..)
615            | Closure(..)
616            | Infer(..)
617            | Coroutine(..)
618            | CoroutineWitness(..)
619            | Bound(_, _)
620            | Placeholder(_)
621            | Error(_) => {
622                return ControlFlow::Break(());
623            }
624
625            Alias(_, AliasTy { kind: Opaque { def_id }, .. }) => {
626                let parent = self.tcx.parent(def_id);
627                let parent_ty = self.tcx.type_of(parent).instantiate_identity().skip_norm_wip();
628                if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent)
629                    && let Alias(_, AliasTy { kind: Opaque { def_id: parent_opaque_def_id }, .. }) =
630                        *parent_ty.kind()
631                    && parent_opaque_def_id == def_id
632                {
633                    // Okay
634                } else {
635                    return ControlFlow::Break(());
636                }
637            }
638
639            Alias(_, AliasTy { kind: Projection { def_id }, .. })
640                if self.tcx.def_kind(def_id) != DefKind::AssocTy =>
641            {
642                return ControlFlow::Break(());
643            }
644
645            // FIXME: It would be nice to make this not use string manipulation,
646            // but it's pretty hard to do this, since `ty::ParamTy` is missing
647            // sufficient info to determine if it is synthetic, and we don't
648            // always have a convenient way of getting `ty::Generics` at the call
649            // sites we invoke `IsSuggestable::is_suggestable`.
650            Param(param) if param.name.as_str().starts_with("impl ") => {
651                return ControlFlow::Break(());
652            }
653
654            _ => {}
655        }
656
657        t.super_visit_with(self)
658    }
659
660    fn visit_const(&mut self, c: Const<'tcx>) -> Self::Result {
661        match c.kind() {
662            ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
663
664            ConstKind::Infer(..)
665            | ConstKind::Bound(..)
666            | ConstKind::Placeholder(..)
667            | ConstKind::Error(..) => {
668                return ControlFlow::Break(());
669            }
670            _ => {}
671        }
672
673        c.super_visit_with(self)
674    }
675}
676
677pub struct MakeSuggestableFolder<'tcx> {
678    tcx: TyCtxt<'tcx>,
679    infer_suggestable: bool,
680    placeholder: Option<Ty<'tcx>>,
681}
682
683impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for MakeSuggestableFolder<'tcx> {
684    type Error = ();
685
686    fn cx(&self) -> TyCtxt<'tcx> {
687        self.tcx
688    }
689
690    fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
691        let t = match *t.kind() {
692            Infer(InferTy::TyVar(_)) if self.infer_suggestable => t,
693
694            FnDef(def_id, args) if self.placeholder.is_none() => Ty::new_fn_ptr(
695                self.tcx,
696                self.tcx
697                    .fn_sig(def_id)
698                    .instantiate(self.tcx, args.no_bound_vars().unwrap())
699                    .skip_norm_wip(),
700            ),
701
702            Closure(..)
703            | CoroutineClosure(..)
704            | FnDef(..)
705            | Infer(..)
706            | Coroutine(..)
707            | CoroutineWitness(..)
708            | Bound(_, _)
709            | Placeholder(_)
710            | Error(_) => {
711                let Some(placeholder) = self.placeholder else { return Err(()) };
712                // We replace these with infer (which is passed in from an infcx).
713                placeholder
714            }
715
716            Alias(_, AliasTy { kind: Opaque { def_id }, .. }) => {
717                let parent = self.tcx.parent(def_id);
718                let parent_ty = self.tcx.type_of(parent).instantiate_identity().skip_norm_wip();
719                if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy =
720                    self.tcx.def_kind(parent)
721                    && let Alias(_, AliasTy { kind: Opaque { def_id: parent_opaque_def_id }, .. }) =
722                        *parent_ty.kind()
723                    && parent_opaque_def_id == def_id
724                {
725                    t
726                } else {
727                    return Err(());
728                }
729            }
730
731            // FIXME: It would be nice to make this not use string manipulation,
732            // but it's pretty hard to do this, since `ty::ParamTy` is missing
733            // sufficient info to determine if it is synthetic, and we don't
734            // always have a convenient way of getting `ty::Generics` at the call
735            // sites we invoke `IsSuggestable::is_suggestable`.
736            Param(param) if param.name.as_str().starts_with("impl ") => {
737                return Err(());
738            }
739
740            _ => t,
741        };
742
743        t.try_super_fold_with(self)
744    }
745
746    fn try_fold_const(&mut self, c: Const<'tcx>) -> Result<Const<'tcx>, ()> {
747        let c = match c.kind() {
748            ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => c,
749
750            ConstKind::Infer(..)
751            | ConstKind::Bound(..)
752            | ConstKind::Placeholder(..)
753            | ConstKind::Error(..) => {
754                return Err(());
755            }
756
757            _ => c,
758        };
759
760        c.try_super_fold_with(self)
761    }
762}