Skip to content

Commit f22bc2d

Browse files
committed
Suggest trait bound on type parameter when it is unconstrained
Given ``` mented on Jan 26, 2015 • trait Foo { fn method(&self) {} } fn call_method<T>(x: &T) { x.method() } ``` suggest constraining `T` with `Foo`.
1 parent 2eb0bc5 commit f22bc2d

File tree

5 files changed

+148
-36
lines changed

5 files changed

+148
-36
lines changed

src/librustc_typeck/check/method/suggest.rs

+98-30
Original file line numberDiff line numberDiff line change
@@ -643,13 +643,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
643643
}
644644
}
645645

646-
fn suggest_traits_to_import<'b>(&self,
647-
err: &mut DiagnosticBuilder<'_>,
648-
span: Span,
649-
rcvr_ty: Ty<'tcx>,
650-
item_name: ast::Ident,
651-
source: SelfSource<'b>,
652-
valid_out_of_scope_traits: Vec<DefId>) {
646+
fn suggest_traits_to_import<'b>(
647+
&self,
648+
err: &mut DiagnosticBuilder<'_>,
649+
span: Span,
650+
rcvr_ty: Ty<'tcx>,
651+
item_name: ast::Ident,
652+
source: SelfSource<'b>,
653+
valid_out_of_scope_traits: Vec<DefId>,
654+
) {
653655
if self.suggest_valid_traits(err, valid_out_of_scope_traits) {
654656
return;
655657
}
@@ -683,30 +685,96 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
683685
candidates.sort_by(|a, b| a.cmp(b).reverse());
684686
candidates.dedup();
685687

686-
// FIXME #21673: this help message could be tuned to the case
687-
// of a type parameter: suggest adding a trait bound rather
688-
// than implementing.
689-
err.help("items from traits can only be used if the trait is implemented and in scope");
690-
let mut msg = format!("the following {traits_define} an item `{name}`, \
691-
perhaps you need to implement {one_of_them}:",
692-
traits_define = if candidates.len() == 1 {
693-
"trait defines"
694-
} else {
695-
"traits define"
696-
},
697-
one_of_them = if candidates.len() == 1 {
698-
"it"
699-
} else {
700-
"one of them"
701-
},
702-
name = item_name);
703-
704-
for (i, trait_info) in candidates.iter().enumerate() {
705-
msg.push_str(&format!("\ncandidate #{}: `{}`",
706-
i + 1,
707-
self.tcx.def_path_str(trait_info.def_id)));
688+
let param_type = match rcvr_ty.sty {
689+
ty::Param(param) => Some(param),
690+
ty::Ref(_, ty, _) => match ty.sty {
691+
ty::Param(param) => Some(param),
692+
_ => None,
693+
}
694+
_ => None,
695+
};
696+
err.help(if param_type.is_some() {
697+
"items from traits can only be used if the type parameter is bounded by the trait"
698+
} else {
699+
"items from traits can only be used if the trait is implemented and in scope"
700+
});
701+
let mut msg = format!(
702+
"the following {traits_define} an item `{name}`, perhaps you need to {action} \
703+
{one_of_them}:",
704+
traits_define = if candidates.len() == 1 {
705+
"trait defines"
706+
} else {
707+
"traits define"
708+
},
709+
action = if let Some(param) = param_type {
710+
format!("restrict type parameter `{}` with", param)
711+
} else {
712+
"implement".to_string()
713+
},
714+
one_of_them = if candidates.len() == 1 {
715+
"it"
716+
} else {
717+
"one of them"
718+
},
719+
name = item_name,
720+
);
721+
// Obtain the span for `param` and use it for a structured suggestion.
722+
let mut suggested = false;
723+
if let (Some(ref param), Some(ref table)) = (param_type, self.in_progress_tables) {
724+
let table = table.borrow();
725+
if let Some(did) = table.local_id_root {
726+
let generics = self.tcx.generics_of(did);
727+
let type_param = generics.type_param(param, self.tcx);
728+
let hir = &self.tcx.hir();
729+
if let Some(id) = hir.as_local_hir_id(type_param.def_id) {
730+
// Get the `hir::Param` to verify whether it already has any bounds.
731+
// We do this to avoid suggesting code that ends up as `T: FooBar`,
732+
// instead we suggest `T: Foo + Bar` in that case.
733+
let mut has_bounds = false;
734+
if let Node::GenericParam(ref param) = hir.get(id) {
735+
has_bounds = !param.bounds.is_empty();
736+
}
737+
let sp = hir.span(id);
738+
// `sp` only covers `T`, change it so that it covers
739+
// `T:` when appropriate
740+
let sp = if has_bounds {
741+
sp.to(self.tcx
742+
.sess
743+
.source_map()
744+
.next_point(self.tcx.sess.source_map().next_point(sp)))
745+
} else {
746+
sp
747+
};
748+
749+
// FIXME: contrast `t.def_id` against `param.bounds` to not suggest traits
750+
// already there. That can happen when the cause is that we're in a const
751+
// scope or associated function used as a method.
752+
err.span_suggestions(
753+
sp,
754+
&msg[..],
755+
candidates.iter().map(|t| format!(
756+
"{}: {}{}",
757+
param,
758+
self.tcx.def_path_str(t.def_id),
759+
if has_bounds { " +"} else { "" },
760+
)),
761+
Applicability::MaybeIncorrect,
762+
);
763+
suggested = true;
764+
}
765+
};
766+
}
767+
768+
if !suggested {
769+
for (i, trait_info) in candidates.iter().enumerate() {
770+
msg.push_str(&format!(
771+
"\ncandidate #{}: `{}`",
772+
i + 1,
773+
self.tcx.def_path_str(trait_info.def_id),
774+
));
775+
}
776+
err.note(&msg[..]);
708777
}
709-
err.note(&msg[..]);
710778
}
711779
}
712780

src/test/ui/issues/issue-39559.stderr

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ error[E0599]: no function or associated item named `dim` found for type `D` in t
44
LL | entries: [T; D::dim()],
55
| ^^^ function or associated item not found in `D`
66
|
7-
= help: items from traits can only be used if the trait is implemented and in scope
8-
= note: the following trait defines an item `dim`, perhaps you need to implement it:
9-
candidate #1: `Dim`
7+
= help: items from traits can only be used if the type parameter is bounded by the trait
8+
help: the following trait defines an item `dim`, perhaps you need to restrict type parameter `D` with it:
9+
|
10+
LL | pub struct Vector<T, D: Dim + Dim> {
11+
| ^^^^^^^^
1012

1113
error: aborting due to previous error
1214

src/test/ui/span/issue-7575.stderr

+5-3
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ note: the candidate is defined in the trait `ManyImplTrait`
6161
LL | fn is_str() -> bool {
6262
| ^^^^^^^^^^^^^^^^^^^
6363
= help: to disambiguate the method call, write `ManyImplTrait::is_str(t)` instead
64-
= help: items from traits can only be used if the trait is implemented and in scope
65-
= note: the following trait defines an item `is_str`, perhaps you need to implement it:
66-
candidate #1: `ManyImplTrait`
64+
= help: items from traits can only be used if the type parameter is bounded by the trait
65+
help: the following trait defines an item `is_str`, perhaps you need to restrict type parameter `T` with it:
66+
|
67+
LL | fn param_bound<T: ManyImplTrait + ManyImplTrait>(t: T) -> bool {
68+
| ^^^^^^^^^^^^^^^^^^
6769

6870
error: aborting due to 3 previous errors
6971

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
trait Foo {
2+
fn method(&self) {}
3+
}
4+
5+
fn call_method<T: std::fmt::Debug>(x: &T) {
6+
x.method() //~ ERROR E0599
7+
}
8+
9+
fn call_method_2<T>(x: T) {
10+
x.method() //~ ERROR E0599
11+
}
12+
13+
fn main() {}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error[E0599]: no method named `method` found for type `&T` in the current scope
2+
--> $DIR/issue-21673.rs:6:7
3+
|
4+
LL | x.method()
5+
| ^^^^^^
6+
|
7+
= help: items from traits can only be used if the type parameter is bounded by the trait
8+
help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
9+
|
10+
LL | fn call_method<T: Foo + std::fmt::Debug>(x: &T) {
11+
| ^^^^^^^^
12+
13+
error[E0599]: no method named `method` found for type `T` in the current scope
14+
--> $DIR/issue-21673.rs:10:7
15+
|
16+
LL | x.method()
17+
| ^^^^^^
18+
|
19+
= help: items from traits can only be used if the type parameter is bounded by the trait
20+
help: the following trait defines an item `method`, perhaps you need to restrict type parameter `T` with it:
21+
|
22+
LL | fn call_method_2<T: Foo>(x: T) {
23+
| ^^^^^^
24+
25+
error: aborting due to 2 previous errors
26+
27+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)