Skip to content

Commit

Permalink
Rollup merge of rust-lang#62772 - estebank:trait-bound, r=matthewjasper
Browse files Browse the repository at this point in the history
Suggest trait bound on type parameter when it is unconstrained

Given

```
trait Foo { fn method(&self) {} }

fn call_method<T>(x: &T) {
    x.method()
}
```

suggest constraining `T` with `Foo`.

Fix rust-lang#21673, fix rust-lang#41030.
  • Loading branch information
Centril committed Jul 21, 2019
2 parents 9182b70 + f22bc2d commit 785acaf
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 36 deletions.
128 changes: 98 additions & 30 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,13 +643,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

fn suggest_traits_to_import<'b>(&self,
err: &mut DiagnosticBuilder<'_>,
span: Span,
rcvr_ty: Ty<'tcx>,
item_name: ast::Ident,
source: SelfSource<'b>,
valid_out_of_scope_traits: Vec<DefId>) {
fn suggest_traits_to_import<'b>(
&self,
err: &mut DiagnosticBuilder<'_>,
span: Span,
rcvr_ty: Ty<'tcx>,
item_name: ast::Ident,
source: SelfSource<'b>,
valid_out_of_scope_traits: Vec<DefId>,
) {
if self.suggest_valid_traits(err, valid_out_of_scope_traits) {
return;
}
Expand Down Expand Up @@ -683,30 +685,96 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
candidates.sort_by(|a, b| a.cmp(b).reverse());
candidates.dedup();

// FIXME #21673: this help message could be tuned to the case
// of a type parameter: suggest adding a trait bound rather
// than implementing.
err.help("items from traits can only be used if the trait is implemented and in scope");
let mut msg = format!("the following {traits_define} an item `{name}`, \
perhaps you need to implement {one_of_them}:",
traits_define = if candidates.len() == 1 {
"trait defines"
} else {
"traits define"
},
one_of_them = if candidates.len() == 1 {
"it"
} else {
"one of them"
},
name = item_name);

for (i, trait_info) in candidates.iter().enumerate() {
msg.push_str(&format!("\ncandidate #{}: `{}`",
i + 1,
self.tcx.def_path_str(trait_info.def_id)));
let param_type = match rcvr_ty.sty {
ty::Param(param) => Some(param),
ty::Ref(_, ty, _) => match ty.sty {
ty::Param(param) => Some(param),
_ => None,
}
_ => None,
};
err.help(if param_type.is_some() {
"items from traits can only be used if the type parameter is bounded by the trait"
} else {
"items from traits can only be used if the trait is implemented and in scope"
});
let mut msg = format!(
"the following {traits_define} an item `{name}`, perhaps you need to {action} \
{one_of_them}:",
traits_define = if candidates.len() == 1 {
"trait defines"
} else {
"traits define"
},
action = if let Some(param) = param_type {
format!("restrict type parameter `{}` with", param)
} else {
"implement".to_string()
},
one_of_them = if candidates.len() == 1 {
"it"
} else {
"one of them"
},
name = item_name,
);
// Obtain the span for `param` and use it for a structured suggestion.
let mut suggested = false;
if let (Some(ref param), Some(ref table)) = (param_type, self.in_progress_tables) {
let table = table.borrow();
if let Some(did) = table.local_id_root {
let generics = self.tcx.generics_of(did);
let type_param = generics.type_param(param, self.tcx);
let hir = &self.tcx.hir();
if let Some(id) = hir.as_local_hir_id(type_param.def_id) {
// Get the `hir::Param` to verify whether it already has any bounds.
// We do this to avoid suggesting code that ends up as `T: FooBar`,
// instead we suggest `T: Foo + Bar` in that case.
let mut has_bounds = false;
if let Node::GenericParam(ref param) = hir.get(id) {
has_bounds = !param.bounds.is_empty();
}
let sp = hir.span(id);
// `sp` only covers `T`, change it so that it covers
// `T:` when appropriate
let sp = if has_bounds {
sp.to(self.tcx
.sess
.source_map()
.next_point(self.tcx.sess.source_map().next_point(sp)))
} else {
sp
};

// FIXME: contrast `t.def_id` against `param.bounds` to not suggest traits
// already there. That can happen when the cause is that we're in a const
// scope or associated function used as a method.
err.span_suggestions(
sp,
&msg[..],
candidates.iter().map(|t| format!(
"{}: {}{}",
param,
self.tcx.def_path_str(t.def_id),
if has_bounds { " +"} else { "" },
)),
Applicability::MaybeIncorrect,
);
suggested = true;
}
};
}

if !suggested {
for (i, trait_info) in candidates.iter().enumerate() {
msg.push_str(&format!(
"\ncandidate #{}: `{}`",
i + 1,
self.tcx.def_path_str(trait_info.def_id),
));
}
err.note(&msg[..]);
}
err.note(&msg[..]);
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/test/ui/issues/issue-39559.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ error[E0599]: no function or associated item named `dim` found for type `D` in t
LL | entries: [T; D::dim()],
| ^^^ function or associated item not found in `D`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `dim`, perhaps you need to implement it:
candidate #1: `Dim`
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `dim`, perhaps you need to restrict type parameter `D` with it:
|
LL | pub struct Vector<T, D: Dim + Dim> {
| ^^^^^^^^

error: aborting due to previous error

Expand Down
8 changes: 5 additions & 3 deletions src/test/ui/span/issue-7575.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ note: the candidate is defined in the trait `ManyImplTrait`
LL | fn is_str() -> bool {
| ^^^^^^^^^^^^^^^^^^^
= help: to disambiguate the method call, write `ManyImplTrait::is_str(t)` instead
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `is_str`, perhaps you need to implement it:
candidate #1: `ManyImplTrait`
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `is_str`, perhaps you need to restrict type parameter `T` with it:
|
LL | fn param_bound<T: ManyImplTrait + ManyImplTrait>(t: T) -> bool {
| ^^^^^^^^^^^^^^^^^^

error: aborting due to 3 previous errors

Expand Down
13 changes: 13 additions & 0 deletions src/test/ui/suggestions/issue-21673.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
trait Foo {
fn method(&self) {}
}

fn call_method<T: std::fmt::Debug>(x: &T) {
x.method() //~ ERROR E0599
}

fn call_method_2<T>(x: T) {
x.method() //~ ERROR E0599
}

fn main() {}
27 changes: 27 additions & 0 deletions src/test/ui/suggestions/issue-21673.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0599]: no method named `method` found for type `&T` in the current scope
--> $DIR/issue-21673.rs:6:7
|
LL | x.method()
| ^^^^^^
|
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
|
LL | fn call_method<T: Foo + std::fmt::Debug>(x: &T) {
| ^^^^^^^^

error[E0599]: no method named `method` found for type `T` in the current scope
--> $DIR/issue-21673.rs:10:7
|
LL | x.method()
| ^^^^^^
|
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
|
LL | fn call_method_2<T: Foo>(x: T) {
| ^^^^^^

error: aborting due to 2 previous errors

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

0 comments on commit 785acaf

Please sign in to comment.