1use std::fmt::Write;
4use std::ops::ControlFlow;
5
6use rustc_data_structures::fx::FxIndexMap;
7use rustc_errors::{
8 Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, listify, pluralize,
9};
10use rustc_hir::def::{DefKind, Namespace};
11use rustc_hir::def_id::DefId;
12use rustc_hir::{self as hir, AmbigArg, LangItem, PredicateOrigin, WherePredicateKind};
13use rustc_span::{BytePos, Span};
14use rustc_type_ir::TyKind::*;
15
16use crate::ty::{
17 self, AliasTy, Const, ConstKind, FallibleTypeFolder, InferConst, InferTy, Instance, Opaque,
18 PolyTraitPredicate, Projection, Ty, TyCtxt, TypeFoldable, TypeSuperFoldable,
19 TypeSuperVisitable, TypeVisitable, TypeVisitor,
20};
21
22impl IntoDiagArg for Ty<'_> {
23 fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue {
24 ty::tls::with(|tcx| {
25 let ty = tcx.short_string(self, path);
26 DiagArgValue::Str(std::borrow::Cow::Owned(ty))
27 })
28 }
29}
30
31impl IntoDiagArg for Instance<'_> {
32 fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> rustc_errors::DiagArgValue {
33 ty::tls::with(|tcx| {
34 let instance = tcx.short_string_namespace(self, path, Namespace::ValueNS);
35 DiagArgValue::Str(std::borrow::Cow::Owned(instance))
36 })
37 }
38}
39
40into_diag_arg_using_display! {
41 ty::Region<'_>,
42}
43
44impl<'tcx> Ty<'tcx> {
45 pub fn is_primitive_ty(self) -> bool {
47 matches!(
48 self.kind(),
49 Bool | Char
50 | Str
51 | Int(_)
52 | Uint(_)
53 | Float(_)
54 | Infer(
55 InferTy::IntVar(_)
56 | InferTy::FloatVar(_)
57 | InferTy::FreshIntTy(_)
58 | InferTy::FreshFloatTy(_)
59 )
60 )
61 }
62
63 pub fn is_simple_ty(self) -> bool {
66 match self.kind() {
67 Bool
68 | Char
69 | Str
70 | Int(_)
71 | Uint(_)
72 | Float(_)
73 | Infer(
74 InferTy::IntVar(_)
75 | InferTy::FloatVar(_)
76 | InferTy::FreshIntTy(_)
77 | InferTy::FreshFloatTy(_),
78 ) => true,
79 Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
80 Tuple(tys) if tys.is_empty() => true,
81 _ => false,
82 }
83 }
84
85 pub fn is_simple_text(self) -> bool {
90 match self.kind() {
91 Adt(_, args) => args.non_erasable_generics().next().is_none(),
92 Ref(_, ty, _) => ty.is_simple_text(),
93 _ => self.is_simple_ty(),
94 }
95 }
96}
97
98pub trait IsSuggestable<'tcx>: Sized {
99 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool;
109
110 fn make_suggestable(
111 self,
112 tcx: TyCtxt<'tcx>,
113 infer_suggestable: bool,
114 placeholder: Option<Ty<'tcx>>,
115 ) -> Option<Self>;
116}
117
118impl<'tcx, T> IsSuggestable<'tcx> for T
119where
120 T: TypeVisitable<TyCtxt<'tcx>> + TypeFoldable<TyCtxt<'tcx>>,
121{
122 #[tracing::instrument(level = "debug", skip(tcx))]
123 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
124 self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
125 }
126
127 fn make_suggestable(
128 self,
129 tcx: TyCtxt<'tcx>,
130 infer_suggestable: bool,
131 placeholder: Option<Ty<'tcx>>,
132 ) -> Option<T> {
133 self.try_fold_with(&mut MakeSuggestableFolder { tcx, infer_suggestable, placeholder }).ok()
134 }
135}
136
137pub fn suggest_arbitrary_trait_bound<'tcx>(
138 tcx: TyCtxt<'tcx>,
139 generics: &hir::Generics<'_>,
140 err: &mut Diag<'_>,
141 trait_pred: PolyTraitPredicate<'tcx>,
142 associated_ty: Option<(&'static str, Ty<'tcx>)>,
143) -> bool {
144 if !trait_pred.is_suggestable(tcx, false) {
145 return false;
146 }
147
148 let param_name = trait_pred.skip_binder().self_ty().to_string();
149 let mut constraint = trait_pred.to_string();
150
151 if let Some((name, term)) = associated_ty {
152 if let Some(stripped) = constraint.strip_suffix('>') {
155 constraint = format!("{stripped}, {name} = {term}>");
156 } else {
157 constraint.push_str(&format!("<{name} = {term}>"));
158 }
159 }
160
161 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
162
163 if param.is_some() && param_name == "Self" {
165 return false;
166 }
167
168 err.span_suggestion_verbose(
170 generics.tail_span_for_predicate_suggestion(),
171 format!(
172 "consider {} `where` clause, but there might be an alternative better way to express \
173 this requirement",
174 if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
175 ),
176 format!("{} {constraint}", generics.add_where_or_trailing_comma()),
177 Applicability::MaybeIncorrect,
178 );
179 true
180}
181
182#[derive(Debug, Clone, Copy)]
183enum SuggestChangingConstraintsMessage<'a> {
184 RestrictBoundFurther,
185 RestrictType { ty: &'a str },
186 RestrictTypeFurther { ty: &'a str },
187 RemoveMaybeUnsized,
188 ReplaceMaybeUnsizedWithSized,
189}
190
191fn suggest_changing_unsized_bound(
192 generics: &hir::Generics<'_>,
193 suggestions: &mut Vec<(Span, String, String, SuggestChangingConstraintsMessage<'_>)>,
194 param: &hir::GenericParam<'_>,
195 def_id: Option<DefId>,
196) {
197 for (where_pos, predicate) in generics.predicates.iter().enumerate() {
201 let WherePredicateKind::BoundPredicate(predicate) = predicate.kind else {
202 continue;
203 };
204 if !predicate.is_param_bound(param.def_id.to_def_id()) {
205 continue;
206 };
207
208 let unsized_bounds = predicate
209 .bounds
210 .iter()
211 .enumerate()
212 .filter(|(_, bound)| {
213 if let hir::GenericBound::Trait(poly) = bound
214 && let hir::BoundPolarity::Maybe(_) = poly.modifiers.polarity
215 && poly.trait_ref.trait_def_id() == def_id
216 {
217 true
218 } else {
219 false
220 }
221 })
222 .collect::<Vec<_>>();
223
224 if unsized_bounds.is_empty() {
225 continue;
226 }
227
228 let mut push_suggestion =
229 |sp, msg| suggestions.push((sp, "Sized".to_string(), String::new(), msg));
230
231 if predicate.bounds.len() == unsized_bounds.len() {
232 if predicate.origin == PredicateOrigin::ImplTrait {
239 let first_bound = unsized_bounds[0].1;
240 let first_bound_span = first_bound.span();
241 if first_bound_span.can_be_used_for_suggestions() {
242 let question_span =
243 first_bound_span.with_hi(first_bound_span.lo() + BytePos(1));
244 push_suggestion(
245 question_span,
246 SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized,
247 );
248
249 for (pos, _) in unsized_bounds.iter().skip(1) {
250 let sp = generics.span_for_bound_removal(where_pos, *pos);
251 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
252 }
253 }
254 } else {
255 let sp = generics.span_for_predicate_removal(where_pos);
256 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
257 }
258 } else {
259 for (pos, _) in unsized_bounds {
262 let sp = generics.span_for_bound_removal(where_pos, pos);
263 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
264 }
265 }
266 }
267}
268
269pub fn suggest_constraining_type_param(
274 tcx: TyCtxt<'_>,
275 generics: &hir::Generics<'_>,
276 err: &mut Diag<'_>,
277 param_name: &str,
278 constraint: &str,
279 def_id: Option<DefId>,
280 span_to_replace: Option<Span>,
281) -> bool {
282 suggest_constraining_type_params(
283 tcx,
284 generics,
285 err,
286 [(param_name, constraint, def_id)].into_iter(),
287 span_to_replace,
288 )
289}
290
291pub fn suggest_constraining_type_params<'a>(
293 tcx: TyCtxt<'_>,
294 generics: &hir::Generics<'_>,
295 err: &mut Diag<'_>,
296 param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
297 span_to_replace: Option<Span>,
298) -> bool {
299 let mut grouped = FxIndexMap::default();
300 let mut unstable_suggestion = false;
301 param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
302 let stable = match def_id {
303 Some(def_id) => match tcx.lookup_stability(def_id) {
304 Some(s) => s.level.is_stable(),
305 None => true,
306 },
307 None => true,
308 };
309 if stable || tcx.sess.is_nightly_build() {
310 grouped.entry(param_name).or_insert(Vec::new()).push((
311 constraint,
312 def_id,
313 if stable { "" } else { "unstable " },
314 ));
315 if !stable {
316 unstable_suggestion = true;
317 }
318 }
319 });
320
321 let mut applicability = Applicability::MachineApplicable;
322 let mut suggestions = Vec::new();
323
324 for (param_name, mut constraints) in grouped {
325 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
326 let Some(param) = param else { return false };
327
328 {
329 let mut sized_constraints = constraints.extract_if(.., |(_, def_id, _)| {
330 def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::Sized))
331 });
332 if let Some((_, def_id, _)) = sized_constraints.next() {
333 applicability = Applicability::MaybeIncorrect;
334
335 err.span_label(param.span, "this type parameter needs to be `Sized`");
336 suggest_changing_unsized_bound(generics, &mut suggestions, param, def_id);
337 }
338 }
339 let bound_message = if constraints.iter().any(|(_, def_id, _)| def_id.is_none()) {
340 SuggestChangingConstraintsMessage::RestrictBoundFurther
341 } else {
342 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }
343 };
344
345 let bound_trait_defs: Vec<DefId> = generics
349 .bounds_for_param(param.def_id)
350 .flat_map(|bound| {
351 bound.bounds.iter().flat_map(|b| b.trait_ref().and_then(|t| t.trait_def_id()))
352 })
353 .collect();
354
355 constraints
356 .retain(|(_, def_id, _)| def_id.is_none_or(|def| !bound_trait_defs.contains(&def)));
357
358 if constraints.is_empty() {
359 continue;
360 }
361
362 let mut constraint = constraints.iter().map(|&(c, _, _)| c).collect::<Vec<_>>();
363 constraint.sort();
364 constraint.dedup();
365 let all_known = constraints.iter().all(|&(_, def_id, _)| def_id.is_some());
366 let all_stable = constraints.iter().all(|&(_, _, stable)| stable.is_empty());
367 let all_unstable = constraints.iter().all(|&(_, _, stable)| !stable.is_empty());
368 let post = if all_stable || all_unstable {
369 let mut trait_names = constraints
371 .iter()
372 .map(|&(c, def_id, _)| match def_id {
373 None => format!("`{c}`"),
374 Some(def_id) => format!("`{}`", tcx.item_name(def_id)),
375 })
376 .collect::<Vec<_>>();
377 trait_names.sort();
378 trait_names.dedup();
379 let n = trait_names.len();
380 let stable = if all_stable { "" } else { "unstable " };
381 let trait_ = if all_known { format!("trait{}", pluralize!(n)) } else { String::new() };
382 let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false };
383 format!("{stable}{trait_} {trait_names}")
384 } else {
385 let mut trait_names = constraints
387 .iter()
388 .map(|&(c, def_id, stable)| match def_id {
389 None => format!("`{c}`"),
390 Some(def_id) => format!("{stable}trait `{}`", tcx.item_name(def_id)),
391 })
392 .collect::<Vec<_>>();
393 trait_names.sort();
394 trait_names.dedup();
395 match listify(&trait_names, |t| t.to_string()) {
396 Some(names) => names,
397 None => return false,
398 }
399 };
400 let constraint = constraint.join(" + ");
401 let mut suggest_restrict = |span, bound_list_non_empty, open_paren_sp| {
402 let suggestion = if span_to_replace.is_some() {
403 constraint.clone()
404 } else if constraint.starts_with('<') {
405 constraint.clone()
406 } else if bound_list_non_empty {
407 format!(" + {constraint}")
408 } else {
409 format!(" {constraint}")
410 };
411
412 if let Some(open_paren_sp) = open_paren_sp {
413 suggestions.push((open_paren_sp, post.clone(), "(".to_string(), bound_message));
414 suggestions.push((span, post.clone(), format!("){suggestion}"), bound_message));
415 } else {
416 suggestions.push((span, post.clone(), suggestion, bound_message));
417 }
418 };
419
420 if let Some(span) = span_to_replace {
421 suggest_restrict(span, true, None);
422 continue;
423 }
424
425 if let Some((span, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) {
454 suggest_restrict(span, true, open_paren_sp);
455 continue;
456 }
457
458 if generics.has_where_clause_predicates {
459 suggestions.push((
473 generics.tail_span_for_predicate_suggestion(),
474 post,
475 constraints.iter().fold(String::new(), |mut string, &(constraint, _, _)| {
476 write!(string, ", {param_name}: {constraint}").unwrap();
477 string
478 }),
479 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
480 ));
481 continue;
482 }
483
484 if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
494 let where_prefix = if generics.where_clause_span.is_empty() { " where" } else { "" };
499
500 suggestions.push((
503 generics.tail_span_for_predicate_suggestion(),
504 post,
505 format!("{where_prefix} {param_name}: {constraint}"),
506 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
507 ));
508 continue;
509 }
510
511 if let Some(colon_span) = param.colon_span {
516 suggestions.push((
517 colon_span.shrink_to_hi(),
518 post,
519 format!(" {constraint}"),
520 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
521 ));
522 continue;
523 }
524
525 suggestions.push((
530 param.span.shrink_to_hi(),
531 post,
532 format!(": {constraint}"),
533 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
534 ));
535 }
536
537 suggestions = suggestions
539 .into_iter()
540 .filter(|(span, _, _, _)| !span.in_derive_expansion())
541 .collect::<Vec<_>>();
542 let suggested = !suggestions.is_empty();
543 if suggestions.len() == 1 {
544 let (span, post, suggestion, msg) = suggestions.pop().unwrap();
545 let msg = match msg {
546 SuggestChangingConstraintsMessage::RestrictBoundFurther => {
547 format!("consider further restricting this bound")
548 }
549 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty }
550 | SuggestChangingConstraintsMessage::RestrictType { ty }
551 if ty.starts_with("impl ") =>
552 {
553 format!("consider restricting opaque type `{ty}` with {post}")
554 }
555 SuggestChangingConstraintsMessage::RestrictType { ty } => {
556 format!("consider restricting type parameter `{ty}` with {post}")
557 }
558 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
559 format!("consider further restricting type parameter `{ty}` with {post}")
560 }
561 SuggestChangingConstraintsMessage::RemoveMaybeUnsized => {
562 format!("consider removing the `?Sized` bound to make the type parameter `Sized`")
563 }
564 SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized => {
565 format!("consider replacing `?Sized` with `Sized`")
566 }
567 };
568
569 err.span_suggestion_verbose(span, msg, suggestion, applicability);
570 } else if suggestions.len() > 1 {
571 let post = if unstable_suggestion { " (some of them are unstable traits)" } else { "" };
572 err.multipart_suggestion_verbose(
573 format!("consider restricting type parameters{post}"),
574 suggestions.into_iter().map(|(span, _, suggestion, _)| (span, suggestion)).collect(),
575 applicability,
576 );
577 }
578
579 suggested
580}
581
582pub(crate) struct TraitObjectVisitor<'tcx>(pub(crate) Vec<&'tcx hir::Ty<'tcx>>);
584
585impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
586 fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) {
587 match ty.kind {
588 hir::TyKind::TraitObject(_, tagged_ptr)
589 if let hir::Lifetime {
590 kind:
591 hir::LifetimeKind::ImplicitObjectLifetimeDefault | hir::LifetimeKind::Static,
592 ..
593 } = tagged_ptr.pointer() =>
594 {
595 self.0.push(ty.as_unambig_ty())
596 }
597 hir::TyKind::OpaqueDef(..) => self.0.push(ty.as_unambig_ty()),
598 _ => {}
599 }
600 hir::intravisit::walk_ty(self, ty);
601 }
602}
603
604pub struct IsSuggestableVisitor<'tcx> {
605 tcx: TyCtxt<'tcx>,
606 infer_suggestable: bool,
607}
608
609impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IsSuggestableVisitor<'tcx> {
610 type Result = ControlFlow<()>;
611
612 fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
613 match *t.kind() {
614 Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
615
616 FnDef(..)
617 | Closure(..)
618 | Infer(..)
619 | Coroutine(..)
620 | CoroutineWitness(..)
621 | Bound(_, _)
622 | Placeholder(_)
623 | Error(_) => {
624 return ControlFlow::Break(());
625 }
626
627 Alias(Opaque, AliasTy { def_id, .. }) => {
628 let parent = self.tcx.parent(def_id);
629 let parent_ty = self.tcx.type_of(parent).instantiate_identity();
630 if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent)
631 && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) =
632 *parent_ty.kind()
633 && parent_opaque_def_id == def_id
634 {
635 } else {
637 return ControlFlow::Break(());
638 }
639 }
640
641 Alias(Projection, AliasTy { def_id, .. })
642 if self.tcx.def_kind(def_id) != DefKind::AssocTy =>
643 {
644 return ControlFlow::Break(());
645 }
646
647 Param(param) if param.name.as_str().starts_with("impl ") => {
653 return ControlFlow::Break(());
654 }
655
656 _ => {}
657 }
658
659 t.super_visit_with(self)
660 }
661
662 fn visit_const(&mut self, c: Const<'tcx>) -> Self::Result {
663 match c.kind() {
664 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
665
666 ConstKind::Infer(..)
667 | ConstKind::Bound(..)
668 | ConstKind::Placeholder(..)
669 | ConstKind::Error(..) => {
670 return ControlFlow::Break(());
671 }
672 _ => {}
673 }
674
675 c.super_visit_with(self)
676 }
677}
678
679pub struct MakeSuggestableFolder<'tcx> {
680 tcx: TyCtxt<'tcx>,
681 infer_suggestable: bool,
682 placeholder: Option<Ty<'tcx>>,
683}
684
685impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for MakeSuggestableFolder<'tcx> {
686 type Error = ();
687
688 fn cx(&self) -> TyCtxt<'tcx> {
689 self.tcx
690 }
691
692 fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
693 let t = match *t.kind() {
694 Infer(InferTy::TyVar(_)) if self.infer_suggestable => t,
695
696 FnDef(def_id, args) if self.placeholder.is_none() => {
697 Ty::new_fn_ptr(self.tcx, self.tcx.fn_sig(def_id).instantiate(self.tcx, args))
698 }
699
700 Closure(..)
701 | FnDef(..)
702 | Infer(..)
703 | Coroutine(..)
704 | CoroutineWitness(..)
705 | Bound(_, _)
706 | Placeholder(_)
707 | Error(_) => {
708 if let Some(placeholder) = self.placeholder {
709 placeholder
711 } else {
712 return Err(());
713 }
714 }
715
716 Alias(Opaque, AliasTy { def_id, .. }) => {
717 let parent = self.tcx.parent(def_id);
718 let parent_ty = self.tcx.type_of(parent).instantiate_identity();
719 if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy =
720 self.tcx.def_kind(parent)
721 && let Alias(Opaque, AliasTy { 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 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}