diff --git a/src/librustc_typeck/check/method/suggest.rs b/src/librustc_typeck/check/method/suggest.rs index 5febc694def..cb83630100a 100644 --- a/src/librustc_typeck/check/method/suggest.rs +++ b/src/librustc_typeck/check/method/suggest.rs @@ -643,13 +643,15 @@ fn suggest_valid_traits(&self, } } - 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) { + 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, + ) { if self.suggest_valid_traits(err, valid_out_of_scope_traits) { return; } @@ -683,30 +685,96 @@ fn suggest_traits_to_import<'b>(&self, 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); + 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 + }; - for (i, trait_info) in candidates.iter().enumerate() { - msg.push_str(&format!("\ncandidate #{}: `{}`", - i + 1, - self.tcx.def_path_str(trait_info.def_id))); + // 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[..]); } } diff --git a/src/test/ui/issues/issue-39559.stderr b/src/test/ui/issues/issue-39559.stderr index aded0c2de45..b945b5e6654 100644 --- a/src/test/ui/issues/issue-39559.stderr +++ b/src/test/ui/issues/issue-39559.stderr @@ -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 { + | ^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/span/issue-7575.stderr b/src/test/ui/span/issue-7575.stderr index 614638752f1..36db5bea862 100644 --- a/src/test/ui/span/issue-7575.stderr +++ b/src/test/ui/span/issue-7575.stderr @@ -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: T) -> bool { + | ^^^^^^^^^^^^^^^^^^ error: aborting due to 3 previous errors diff --git a/src/test/ui/suggestions/issue-21673.rs b/src/test/ui/suggestions/issue-21673.rs new file mode 100644 index 00000000000..9d66cae056a --- /dev/null +++ b/src/test/ui/suggestions/issue-21673.rs @@ -0,0 +1,13 @@ +trait Foo { + fn method(&self) {} +} + +fn call_method(x: &T) { + x.method() //~ ERROR E0599 +} + +fn call_method_2(x: T) { + x.method() //~ ERROR E0599 +} + +fn main() {} diff --git a/src/test/ui/suggestions/issue-21673.stderr b/src/test/ui/suggestions/issue-21673.stderr new file mode 100644 index 00000000000..6cf71c8b7c5 --- /dev/null +++ b/src/test/ui/suggestions/issue-21673.stderr @@ -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(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(x: T) { + | ^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0599`.