From a31c5bfb6427afd162f554e48711829bf100f40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 30 Dec 2024 20:55:39 +0000 Subject: [PATCH] Account for type parameters in bound suggestion When encountering a missing bound that involves a type parameter, on associated functions we look at the generics to see if the type parameter is present. If so, we suggest the bound on the associated function instead of on the impl/trait. At the impl/trait doesn't have another type parameter of that name, then we don't suggest the bound at that item level. ``` error[E0277]: the trait bound `B: From` is not satisfied --> $DIR/suggest-restriction-involving-type-param.rs:20:33 | LL | B { x: v.iter().map(|e| B::from(e.clone()).x).collect::>().join(" ") } | ^ the trait `From` is not implemented for `B` | help: consider further restricting the type | LL | pub fn from_many + Clone>(v: Vec) -> Self where B: From { | ++++++++++++++++ ``` Fix #104089. --- .../src/error_reporting/traits/suggestions.rs | 67 ++++++++++++++++++- ...est-restriction-involving-type-param.fixed | 31 +++++++++ ...uggest-restriction-involving-type-param.rs | 31 +++++++++ ...st-restriction-involving-type-param.stderr | 14 ++++ 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 tests/ui/methods/suggest-restriction-involving-type-param.fixed create mode 100644 tests/ui/methods/suggest-restriction-involving-type-param.rs create mode 100644 tests/ui/methods/suggest-restriction-involving-type-param.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 6a8f7f4ee3513..2e81d595b9491 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -33,8 +33,9 @@ use rustc_middle::ty::print::{ }; use rustc_middle::ty::{ self, AdtKind, GenericArgs, InferTy, IsSuggestable, ToPolyTraitRef, Ty, TyCtxt, TypeFoldable, - TypeFolder, TypeSuperFoldable, TypeVisitableExt, TypeckResults, Upcast, - suggest_arbitrary_trait_bound, suggest_constraining_type_param, + TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitableExt, TypeVisitor, + TypeckResults, Upcast, UpcastFrom, suggest_arbitrary_trait_bound, + suggest_constraining_type_param, }; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::LocalDefId; @@ -260,6 +261,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { _ => (false, None), }; + let mut v = ParamFinder { params: vec![] }; + // Get all type parameters from the predicate. If the predicate references a type parameter + // at all, then we can only suggestion a bound on an item that has access to that parameter. + v.visit_predicate(UpcastFrom::upcast_from(trait_pred, self.tcx)); + // FIXME: Add check for trait bound that is already present, particularly `?Sized` so we // don't suggest `T: Sized + ?Sized`. loop { @@ -327,6 +333,40 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); return; } + + hir::Node::TraitItem(hir::TraitItem { + generics, + kind: hir::TraitItemKind::Fn(fn_sig, ..), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + generics, + kind: hir::ImplItemKind::Fn(fn_sig, ..), + .. + }) if projection.is_none() + && !param_ty + && generics.params.iter().any(|param| { + v.params.iter().any(|p| p.name == param.name.ident().name) + }) => + { + // This associated function has a generic type parameter that matches a + // parameter from the trait predicate, which means that we should suggest + // constraining the complex type here, and not at the trait/impl level (if + // it doesn't have that type parameter). This can be something like + // `Type: From`. + suggest_restriction( + self.tcx, + body_id, + generics, + "the type", + err, + Some(fn_sig), + projection, + trait_pred, + None, + ); + } + hir::Node::Item(hir::Item { kind: hir::ItemKind::Trait(_, _, generics, ..) @@ -425,7 +465,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { | hir::ItemKind::Const(_, generics, _) | hir::ItemKind::TraitAlias(generics, _), .. - }) if !param_ty => { + }) if !param_ty + && (generics.params.iter().any(|param| { + v.params.iter().any(|p| p.name == param.name.ident().name) + }) || v.params.is_empty()) => + { // Missing generic type parameter bound. if suggest_arbitrary_trait_bound( self.tcx, @@ -5439,6 +5483,23 @@ fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, Vec) { (ty, refs) } +/// Look for type parameters. +struct ParamFinder { + params: Vec, +} + +impl<'tcx> TypeVisitor> for ParamFinder { + fn visit_ty(&mut self, t: Ty<'tcx>) { + match t.kind() { + ty::Param(param) if param.name != kw::SelfUpper => { + self.params.push(*param); + } + _ => {} + } + t.super_visit_with(self) + } +} + /// Look for type `param` in an ADT being used only through a reference to confirm that suggesting /// `param: ?Sized` would be a valid constraint. struct FindTypeParam { diff --git a/tests/ui/methods/suggest-restriction-involving-type-param.fixed b/tests/ui/methods/suggest-restriction-involving-type-param.fixed new file mode 100644 index 0000000000000..066a491f07533 --- /dev/null +++ b/tests/ui/methods/suggest-restriction-involving-type-param.fixed @@ -0,0 +1,31 @@ +//@ run-rustfix + +#[derive(Clone)] +struct A { + x: String +} + +struct B { + x: String +} + +impl From for B { + fn from(a: A) -> Self { + B { x: a.x } + } +} + +impl B { + pub fn from_many + Clone>(v: Vec) -> Self where B: From { + B { x: v.iter().map(|e| B::from(e.clone()).x).collect::>().join(" ") } + //~^ ERROR the trait bound `B: From` is not satisfied + } +} + +fn main() { + let _b: B = B { x: "foobar".to_string() }; + let a: A = A { x: "frob".to_string() }; + let ab: B = a.into(); + println!("Hello, {}!", ab.x); + let _c: B = B::from_many(vec![A { x: "x".to_string() }]); +} diff --git a/tests/ui/methods/suggest-restriction-involving-type-param.rs b/tests/ui/methods/suggest-restriction-involving-type-param.rs new file mode 100644 index 0000000000000..748224044d9fd --- /dev/null +++ b/tests/ui/methods/suggest-restriction-involving-type-param.rs @@ -0,0 +1,31 @@ +//@ run-rustfix + +#[derive(Clone)] +struct A { + x: String +} + +struct B { + x: String +} + +impl From for B { + fn from(a: A) -> Self { + B { x: a.x } + } +} + +impl B { + pub fn from_many + Clone>(v: Vec) -> Self { + B { x: v.iter().map(|e| B::from(e.clone()).x).collect::>().join(" ") } + //~^ ERROR the trait bound `B: From` is not satisfied + } +} + +fn main() { + let _b: B = B { x: "foobar".to_string() }; + let a: A = A { x: "frob".to_string() }; + let ab: B = a.into(); + println!("Hello, {}!", ab.x); + let _c: B = B::from_many(vec![A { x: "x".to_string() }]); +} diff --git a/tests/ui/methods/suggest-restriction-involving-type-param.stderr b/tests/ui/methods/suggest-restriction-involving-type-param.stderr new file mode 100644 index 0000000000000..a59acbb822534 --- /dev/null +++ b/tests/ui/methods/suggest-restriction-involving-type-param.stderr @@ -0,0 +1,14 @@ +error[E0277]: the trait bound `B: From` is not satisfied + --> $DIR/suggest-restriction-involving-type-param.rs:20:33 + | +LL | B { x: v.iter().map(|e| B::from(e.clone()).x).collect::>().join(" ") } + | ^ the trait `From` is not implemented for `B` + | +help: consider further restricting the type + | +LL | pub fn from_many + Clone>(v: Vec) -> Self where B: From { + | ++++++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.