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