1use std::fmt::Write;
4use std::ops::ControlFlow;
5
6use rustc_data_structures::fx::FxHashMap;
7use rustc_errors::{
8 Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, listify, pluralize,
9};
10use rustc_hir::def::DefKind;
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, 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 rustc_errors::DiagArgValue::Str(std::borrow::Cow::Owned(ty))
27 })
28 }
29}
30
31into_diag_arg_using_display! {
32 ty::Region<'_>,
33}
34
35impl<'tcx> Ty<'tcx> {
36 pub fn is_primitive_ty(self) -> bool {
38 matches!(
39 self.kind(),
40 Bool | Char
41 | Str
42 | Int(_)
43 | Uint(_)
44 | Float(_)
45 | Infer(
46 InferTy::IntVar(_)
47 | InferTy::FloatVar(_)
48 | InferTy::FreshIntTy(_)
49 | InferTy::FreshFloatTy(_)
50 )
51 )
52 }
53
54 pub fn is_simple_ty(self) -> bool {
57 match self.kind() {
58 Bool
59 | Char
60 | Str
61 | Int(_)
62 | Uint(_)
63 | Float(_)
64 | Infer(
65 InferTy::IntVar(_)
66 | InferTy::FloatVar(_)
67 | InferTy::FreshIntTy(_)
68 | InferTy::FreshFloatTy(_),
69 ) => true,
70 Ref(_, x, _) | Array(x, _) | Slice(x) => x.peel_refs().is_simple_ty(),
71 Tuple(tys) if tys.is_empty() => true,
72 _ => false,
73 }
74 }
75
76 pub fn is_simple_text(self) -> bool {
81 match self.kind() {
82 Adt(_, args) => args.non_erasable_generics().next().is_none(),
83 Ref(_, ty, _) => ty.is_simple_text(),
84 _ => self.is_simple_ty(),
85 }
86 }
87}
88
89pub trait IsSuggestable<'tcx>: Sized {
90 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool;
100
101 fn make_suggestable(
102 self,
103 tcx: TyCtxt<'tcx>,
104 infer_suggestable: bool,
105 placeholder: Option<Ty<'tcx>>,
106 ) -> Option<Self>;
107}
108
109impl<'tcx, T> IsSuggestable<'tcx> for T
110where
111 T: TypeVisitable<TyCtxt<'tcx>> + TypeFoldable<TyCtxt<'tcx>>,
112{
113 #[tracing::instrument(level = "debug", skip(tcx))]
114 fn is_suggestable(self, tcx: TyCtxt<'tcx>, infer_suggestable: bool) -> bool {
115 self.visit_with(&mut IsSuggestableVisitor { tcx, infer_suggestable }).is_continue()
116 }
117
118 fn make_suggestable(
119 self,
120 tcx: TyCtxt<'tcx>,
121 infer_suggestable: bool,
122 placeholder: Option<Ty<'tcx>>,
123 ) -> Option<T> {
124 self.try_fold_with(&mut MakeSuggestableFolder { tcx, infer_suggestable, placeholder }).ok()
125 }
126}
127
128pub fn suggest_arbitrary_trait_bound<'tcx>(
129 tcx: TyCtxt<'tcx>,
130 generics: &hir::Generics<'_>,
131 err: &mut Diag<'_>,
132 trait_pred: PolyTraitPredicate<'tcx>,
133 associated_ty: Option<(&'static str, Ty<'tcx>)>,
134) -> bool {
135 if !trait_pred.is_suggestable(tcx, false) {
136 return false;
137 }
138
139 let param_name = trait_pred.skip_binder().self_ty().to_string();
140 let mut constraint = trait_pred.to_string();
141
142 if let Some((name, term)) = associated_ty {
143 if let Some(stripped) = constraint.strip_suffix('>') {
146 constraint = format!("{stripped}, {name} = {term}>");
147 } else {
148 constraint.push_str(&format!("<{name} = {term}>"));
149 }
150 }
151
152 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
153
154 if param.is_some() && param_name == "Self" {
156 return false;
157 }
158
159 err.span_suggestion_verbose(
161 generics.tail_span_for_predicate_suggestion(),
162 format!(
163 "consider {} `where` clause, but there might be an alternative better way to express \
164 this requirement",
165 if generics.where_clause_span.is_empty() { "introducing a" } else { "extending the" },
166 ),
167 format!("{} {constraint}", generics.add_where_or_trailing_comma()),
168 Applicability::MaybeIncorrect,
169 );
170 true
171}
172
173#[derive(Debug, Clone, Copy)]
174enum SuggestChangingConstraintsMessage<'a> {
175 RestrictBoundFurther,
176 RestrictType { ty: &'a str },
177 RestrictTypeFurther { ty: &'a str },
178 RemoveMaybeUnsized,
179 ReplaceMaybeUnsizedWithSized,
180}
181
182fn suggest_changing_unsized_bound(
183 generics: &hir::Generics<'_>,
184 suggestions: &mut Vec<(Span, String, String, SuggestChangingConstraintsMessage<'_>)>,
185 param: &hir::GenericParam<'_>,
186 def_id: Option<DefId>,
187) {
188 for (where_pos, predicate) in generics.predicates.iter().enumerate() {
192 let WherePredicateKind::BoundPredicate(predicate) = predicate.kind else {
193 continue;
194 };
195 if !predicate.is_param_bound(param.def_id.to_def_id()) {
196 continue;
197 };
198
199 let unsized_bounds = predicate
200 .bounds
201 .iter()
202 .enumerate()
203 .filter(|(_, bound)| {
204 if let hir::GenericBound::Trait(poly) = bound
205 && let hir::BoundPolarity::Maybe(_) = poly.modifiers.polarity
206 && poly.trait_ref.trait_def_id() == def_id
207 {
208 true
209 } else {
210 false
211 }
212 })
213 .collect::<Vec<_>>();
214
215 if unsized_bounds.is_empty() {
216 continue;
217 }
218
219 let mut push_suggestion =
220 |sp, msg| suggestions.push((sp, "Sized".to_string(), String::new(), msg));
221
222 if predicate.bounds.len() == unsized_bounds.len() {
223 if predicate.origin == PredicateOrigin::ImplTrait {
230 let first_bound = unsized_bounds[0].1;
231 let first_bound_span = first_bound.span();
232 if first_bound_span.can_be_used_for_suggestions() {
233 let question_span =
234 first_bound_span.with_hi(first_bound_span.lo() + BytePos(1));
235 push_suggestion(
236 question_span,
237 SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized,
238 );
239
240 for (pos, _) in unsized_bounds.iter().skip(1) {
241 let sp = generics.span_for_bound_removal(where_pos, *pos);
242 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
243 }
244 }
245 } else {
246 let sp = generics.span_for_predicate_removal(where_pos);
247 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
248 }
249 } else {
250 for (pos, _) in unsized_bounds {
253 let sp = generics.span_for_bound_removal(where_pos, pos);
254 push_suggestion(sp, SuggestChangingConstraintsMessage::RemoveMaybeUnsized);
255 }
256 }
257 }
258}
259
260pub fn suggest_constraining_type_param(
265 tcx: TyCtxt<'_>,
266 generics: &hir::Generics<'_>,
267 err: &mut Diag<'_>,
268 param_name: &str,
269 constraint: &str,
270 def_id: Option<DefId>,
271 span_to_replace: Option<Span>,
272) -> bool {
273 suggest_constraining_type_params(
274 tcx,
275 generics,
276 err,
277 [(param_name, constraint, def_id)].into_iter(),
278 span_to_replace,
279 )
280}
281
282pub fn suggest_constraining_type_params<'a>(
284 tcx: TyCtxt<'_>,
285 generics: &hir::Generics<'_>,
286 err: &mut Diag<'_>,
287 param_names_and_constraints: impl Iterator<Item = (&'a str, &'a str, Option<DefId>)>,
288 span_to_replace: Option<Span>,
289) -> bool {
290 let mut grouped = FxHashMap::default();
291 let mut unstable_suggestion = false;
292 param_names_and_constraints.for_each(|(param_name, constraint, def_id)| {
293 let stable = match def_id {
294 Some(def_id) => match tcx.lookup_stability(def_id) {
295 Some(s) => s.level.is_stable(),
296 None => true,
297 },
298 None => true,
299 };
300 if stable || tcx.sess.is_nightly_build() {
301 grouped.entry(param_name).or_insert(Vec::new()).push((
302 constraint,
303 def_id,
304 if stable { "" } else { "unstable " },
305 ));
306 if !stable {
307 unstable_suggestion = true;
308 }
309 }
310 });
311
312 let mut applicability = Applicability::MachineApplicable;
313 let mut suggestions = Vec::new();
314
315 for (param_name, mut constraints) in grouped {
316 let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name);
317 let Some(param) = param else { return false };
318
319 {
320 let mut sized_constraints = constraints.extract_if(.., |(_, def_id, _)| {
321 def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::Sized))
322 });
323 if let Some((_, def_id, _)) = sized_constraints.next() {
324 applicability = Applicability::MaybeIncorrect;
325
326 err.span_label(param.span, "this type parameter needs to be `Sized`");
327 suggest_changing_unsized_bound(generics, &mut suggestions, param, def_id);
328 }
329 }
330 let bound_message = if constraints.iter().any(|(_, def_id, _)| def_id.is_none()) {
331 SuggestChangingConstraintsMessage::RestrictBoundFurther
332 } else {
333 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name }
334 };
335
336 let bound_trait_defs: Vec<DefId> = generics
340 .bounds_for_param(param.def_id)
341 .flat_map(|bound| {
342 bound.bounds.iter().flat_map(|b| b.trait_ref().and_then(|t| t.trait_def_id()))
343 })
344 .collect();
345
346 constraints
347 .retain(|(_, def_id, _)| def_id.is_none_or(|def| !bound_trait_defs.contains(&def)));
348
349 if constraints.is_empty() {
350 continue;
351 }
352
353 let mut constraint = constraints.iter().map(|&(c, _, _)| c).collect::<Vec<_>>();
354 constraint.sort();
355 constraint.dedup();
356 let all_known = constraints.iter().all(|&(_, def_id, _)| def_id.is_some());
357 let all_stable = constraints.iter().all(|&(_, _, stable)| stable.is_empty());
358 let all_unstable = constraints.iter().all(|&(_, _, stable)| !stable.is_empty());
359 let post = if all_stable || all_unstable {
360 let mut trait_names = constraints
362 .iter()
363 .map(|&(c, def_id, _)| match def_id {
364 None => format!("`{c}`"),
365 Some(def_id) => format!("`{}`", tcx.item_name(def_id)),
366 })
367 .collect::<Vec<_>>();
368 trait_names.sort();
369 trait_names.dedup();
370 let n = trait_names.len();
371 let stable = if all_stable { "" } else { "unstable " };
372 let trait_ = if all_known { format!("trait{}", pluralize!(n)) } else { String::new() };
373 let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false };
374 format!("{stable}{trait_} {trait_names}")
375 } else {
376 let mut trait_names = constraints
378 .iter()
379 .map(|&(c, def_id, stable)| match def_id {
380 None => format!("`{c}`"),
381 Some(def_id) => format!("{stable}trait `{}`", tcx.item_name(def_id)),
382 })
383 .collect::<Vec<_>>();
384 trait_names.sort();
385 trait_names.dedup();
386 match listify(&trait_names, |t| t.to_string()) {
387 Some(names) => names,
388 None => return false,
389 }
390 };
391 let constraint = constraint.join(" + ");
392 let mut suggest_restrict = |span, bound_list_non_empty, open_paren_sp| {
393 let suggestion = if span_to_replace.is_some() {
394 constraint.clone()
395 } else if constraint.starts_with('<') {
396 constraint.clone()
397 } else if bound_list_non_empty {
398 format!(" + {constraint}")
399 } else {
400 format!(" {constraint}")
401 };
402
403 if let Some(open_paren_sp) = open_paren_sp {
404 suggestions.push((open_paren_sp, post.clone(), "(".to_string(), bound_message));
405 suggestions.push((span, post.clone(), format!("){suggestion}"), bound_message));
406 } else {
407 suggestions.push((span, post.clone(), suggestion, bound_message));
408 }
409 };
410
411 if let Some(span) = span_to_replace {
412 suggest_restrict(span, true, None);
413 continue;
414 }
415
416 if let Some((span, open_paren_sp)) = generics.bounds_span_for_suggestions(param.def_id) {
445 suggest_restrict(span, true, open_paren_sp);
446 continue;
447 }
448
449 if generics.has_where_clause_predicates {
450 suggestions.push((
464 generics.tail_span_for_predicate_suggestion(),
465 post,
466 constraints.iter().fold(String::new(), |mut string, &(constraint, _, _)| {
467 write!(string, ", {param_name}: {constraint}").unwrap();
468 string
469 }),
470 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
471 ));
472 continue;
473 }
474
475 if matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) {
485 let where_prefix = if generics.where_clause_span.is_empty() { " where" } else { "" };
490
491 suggestions.push((
494 generics.tail_span_for_predicate_suggestion(),
495 post,
496 format!("{where_prefix} {param_name}: {constraint}"),
497 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty: param_name },
498 ));
499 continue;
500 }
501
502 if let Some(colon_span) = param.colon_span {
507 suggestions.push((
508 colon_span.shrink_to_hi(),
509 post,
510 format!(" {constraint}"),
511 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
512 ));
513 continue;
514 }
515
516 suggestions.push((
521 param.span.shrink_to_hi(),
522 post,
523 format!(": {constraint}"),
524 SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
525 ));
526 }
527
528 suggestions = suggestions
530 .into_iter()
531 .filter(|(span, _, _, _)| !span.in_derive_expansion())
532 .collect::<Vec<_>>();
533 let suggested = !suggestions.is_empty();
534 if suggestions.len() == 1 {
535 let (span, post, suggestion, msg) = suggestions.pop().unwrap();
536 let msg = match msg {
537 SuggestChangingConstraintsMessage::RestrictBoundFurther => {
538 format!("consider further restricting this bound")
539 }
540 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty }
541 | SuggestChangingConstraintsMessage::RestrictType { ty }
542 if ty.starts_with("impl ") =>
543 {
544 format!("consider restricting opaque type `{ty}` with {post}")
545 }
546 SuggestChangingConstraintsMessage::RestrictType { ty } => {
547 format!("consider restricting type parameter `{ty}` with {post}")
548 }
549 SuggestChangingConstraintsMessage::RestrictTypeFurther { ty } => {
550 format!("consider further restricting type parameter `{ty}` with {post}")
551 }
552 SuggestChangingConstraintsMessage::RemoveMaybeUnsized => {
553 format!("consider removing the `?Sized` bound to make the type parameter `Sized`")
554 }
555 SuggestChangingConstraintsMessage::ReplaceMaybeUnsizedWithSized => {
556 format!("consider replacing `?Sized` with `Sized`")
557 }
558 };
559
560 err.span_suggestion_verbose(span, msg, suggestion, applicability);
561 } else if suggestions.len() > 1 {
562 let post = if unstable_suggestion { " (some of them are unstable traits)" } else { "" };
563 err.multipart_suggestion_verbose(
564 format!("consider restricting type parameters{post}"),
565 suggestions.into_iter().map(|(span, _, suggestion, _)| (span, suggestion)).collect(),
566 applicability,
567 );
568 }
569
570 suggested
571}
572
573pub struct TraitObjectVisitor<'tcx>(pub Vec<&'tcx hir::Ty<'tcx>>, pub crate::hir::map::Map<'tcx>);
575
576impl<'v> hir::intravisit::Visitor<'v> for TraitObjectVisitor<'v> {
577 fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) {
578 match ty.kind {
579 hir::TyKind::TraitObject(_, tagged_ptr)
580 if let hir::Lifetime {
581 res:
582 hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static,
583 ..
584 } = tagged_ptr.pointer() =>
585 {
586 self.0.push(ty.as_unambig_ty())
587 }
588 hir::TyKind::OpaqueDef(..) => self.0.push(ty.as_unambig_ty()),
589 _ => {}
590 }
591 hir::intravisit::walk_ty(self, ty);
592 }
593}
594
595pub struct StaticLifetimeVisitor<'tcx>(pub Vec<Span>, pub crate::hir::map::Map<'tcx>);
597
598impl<'v> hir::intravisit::Visitor<'v> for StaticLifetimeVisitor<'v> {
599 fn visit_lifetime(&mut self, lt: &'v hir::Lifetime) {
600 if let hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Static = lt.res
601 {
602 self.0.push(lt.ident.span);
603 }
604 }
605}
606
607pub struct IsSuggestableVisitor<'tcx> {
608 tcx: TyCtxt<'tcx>,
609 infer_suggestable: bool,
610}
611
612impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for IsSuggestableVisitor<'tcx> {
613 type Result = ControlFlow<()>;
614
615 fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
616 match *t.kind() {
617 Infer(InferTy::TyVar(_)) if self.infer_suggestable => {}
618
619 FnDef(..)
620 | Closure(..)
621 | Infer(..)
622 | Coroutine(..)
623 | CoroutineWitness(..)
624 | Bound(_, _)
625 | Placeholder(_)
626 | Error(_) => {
627 return ControlFlow::Break(());
628 }
629
630 Alias(Opaque, AliasTy { def_id, .. }) => {
631 let parent = self.tcx.parent(def_id);
632 let parent_ty = self.tcx.type_of(parent).instantiate_identity();
633 if let DefKind::TyAlias | DefKind::AssocTy = self.tcx.def_kind(parent)
634 && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) =
635 *parent_ty.kind()
636 && parent_opaque_def_id == def_id
637 {
638 } else {
640 return ControlFlow::Break(());
641 }
642 }
643
644 Alias(Projection, AliasTy { def_id, .. })
645 if self.tcx.def_kind(def_id) != DefKind::AssocTy =>
646 {
647 return ControlFlow::Break(());
648 }
649
650 Param(param) if param.name.as_str().starts_with("impl ") => {
656 return ControlFlow::Break(());
657 }
658
659 _ => {}
660 }
661
662 t.super_visit_with(self)
663 }
664
665 fn visit_const(&mut self, c: Const<'tcx>) -> Self::Result {
666 match c.kind() {
667 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => {}
668
669 ConstKind::Infer(..)
670 | ConstKind::Bound(..)
671 | ConstKind::Placeholder(..)
672 | ConstKind::Error(..) => {
673 return ControlFlow::Break(());
674 }
675 _ => {}
676 }
677
678 c.super_visit_with(self)
679 }
680}
681
682pub struct MakeSuggestableFolder<'tcx> {
683 tcx: TyCtxt<'tcx>,
684 infer_suggestable: bool,
685 placeholder: Option<Ty<'tcx>>,
686}
687
688impl<'tcx> FallibleTypeFolder<TyCtxt<'tcx>> for MakeSuggestableFolder<'tcx> {
689 type Error = ();
690
691 fn cx(&self) -> TyCtxt<'tcx> {
692 self.tcx
693 }
694
695 fn try_fold_ty(&mut self, t: Ty<'tcx>) -> Result<Ty<'tcx>, Self::Error> {
696 let t = match *t.kind() {
697 Infer(InferTy::TyVar(_)) if self.infer_suggestable => t,
698
699 FnDef(def_id, args) if self.placeholder.is_none() => {
700 Ty::new_fn_ptr(self.tcx, self.tcx.fn_sig(def_id).instantiate(self.tcx, args))
701 }
702
703 Closure(..)
704 | FnDef(..)
705 | Infer(..)
706 | Coroutine(..)
707 | CoroutineWitness(..)
708 | Bound(_, _)
709 | Placeholder(_)
710 | Error(_) => {
711 if let Some(placeholder) = self.placeholder {
712 placeholder
714 } else {
715 return Err(());
716 }
717 }
718
719 Alias(Opaque, AliasTy { def_id, .. }) => {
720 let parent = self.tcx.parent(def_id);
721 let parent_ty = self.tcx.type_of(parent).instantiate_identity();
722 if let hir::def::DefKind::TyAlias | hir::def::DefKind::AssocTy =
723 self.tcx.def_kind(parent)
724 && let Alias(Opaque, AliasTy { def_id: parent_opaque_def_id, .. }) =
725 *parent_ty.kind()
726 && parent_opaque_def_id == def_id
727 {
728 t
729 } else {
730 return Err(());
731 }
732 }
733
734 Param(param) if param.name.as_str().starts_with("impl ") => {
740 return Err(());
741 }
742
743 _ => t,
744 };
745
746 t.try_super_fold_with(self)
747 }
748
749 fn try_fold_const(&mut self, c: Const<'tcx>) -> Result<Const<'tcx>, ()> {
750 let c = match c.kind() {
751 ConstKind::Infer(InferConst::Var(_)) if self.infer_suggestable => c,
752
753 ConstKind::Infer(..)
754 | ConstKind::Bound(..)
755 | ConstKind::Placeholder(..)
756 | ConstKind::Error(..) => {
757 return Err(());
758 }
759
760 _ => c,
761 };
762
763 c.try_super_fold_with(self)
764 }
765}