Skip to content

Account for type parameters in bound suggestion #134937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use .upcast().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what do you mean? We should never be suggesting a bound involving a parameter that we don't have "access to" --we shouldn't be checking predicates involving parameters that aren't in scope anyways?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently have a fallback where regardless of what the predicate that failed, we end up suggesting a bound (with weasel wording that there might be a better way). This is in place because there are some complex (if uncommon) bounds that are still useful to have that were given only by that. But in #104089, we had a report of that fallback doing the wrong thing (obvious in retrospect). The predicate might be something like Self: From<T>, where the T is a param only in the associated item and not at the trait/impl level. That's what this additional check guards against: If any part of the predicate references one of the generics of that item, we don't skip suggesting the bound at that level because it would be invalid in the parent item.


// FIXME: Add check for trait bound that is already present, particularly `?Sized` so we
// don't suggest `T: Sized + ?Sized`.
loop {
Expand Down Expand Up @@ -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<Param>`.
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, ..)
Expand Down Expand Up @@ -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()) =>
{
Comment on lines +468 to +472
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want this?

Suggested change
}) if !param_ty
&& (generics.params.iter().any(|param| {
v.params.iter().any(|p| p.name == param.name.ident().name)
}) || v.params.is_empty()) =>
{
}) if !param_ty
&& v.params.iter().all(|param| {
generics.params.iter().any(|p| p.name == param.name.ident().name)
}) =>
{

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the logic: what's a suggestion you're trying to avoid emitting by adding this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently suggest

help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
   |
15 | impl B where B: From<T> {
   |        ++++++++++++++++

Which is incorrect because the impl block doesn't have a type parameter T, but the method does:

help: consider further restricting the type
   |
LL |     pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self where B: From<T> {
   |                                                             ++++++++++++++++

Copy link
Member

@Nadrieril Nadrieril Jan 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the logic as you've written it, it seems like the incorrect suggestion would be emitted because generics.params.iter().any(|param| {...}) will return true (the parameter B validates this predicate). What am I missing?

// Missing generic type parameter bound.
if suggest_arbitrary_trait_bound(
self.tcx,
Expand Down Expand Up @@ -5439,6 +5483,23 @@ fn get_deref_type_and_refs(mut ty: Ty<'_>) -> (Ty<'_>, Vec<hir::Mutability>) {
(ty, refs)
}

/// Look for type parameters.
struct ParamFinder {
params: Vec<ty::ParamTy>,
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> 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 {
Expand Down
31 changes: 31 additions & 0 deletions tests/ui/methods/suggest-restriction-involving-type-param.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//@ run-rustfix

#[derive(Clone)]
struct A {
x: String
}

struct B {
x: String
}

impl From<A> for B {
fn from(a: A) -> Self {
B { x: a.x }
}
}

impl B {
pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self where B: From<T> {
B { x: v.iter().map(|e| B::from(e.clone()).x).collect::<Vec<String>>().join(" ") }
//~^ ERROR the trait bound `B: From<T>` 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() }]);
}
31 changes: 31 additions & 0 deletions tests/ui/methods/suggest-restriction-involving-type-param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//@ run-rustfix

#[derive(Clone)]
struct A {
x: String
}

struct B {
x: String
}

impl From<A> for B {
fn from(a: A) -> Self {
B { x: a.x }
}
}

impl B {
pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self {
B { x: v.iter().map(|e| B::from(e.clone()).x).collect::<Vec<String>>().join(" ") }
//~^ ERROR the trait bound `B: From<T>` 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() }]);
}
14 changes: 14 additions & 0 deletions tests/ui/methods/suggest-restriction-involving-type-param.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0277]: the trait bound `B: From<T>` 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::<Vec<String>>().join(" ") }
| ^ the trait `From<T>` is not implemented for `B`
|
help: consider further restricting the type
|
LL | pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self where B: From<T> {
| ++++++++++++++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
Loading