Skip to content

Commit

Permalink
Fix suggestion to constrain trait for method to be found
Browse files Browse the repository at this point in the history
  • Loading branch information
estebank committed Oct 15, 2019
1 parent e413dc3 commit dee53d7
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 43 deletions.
113 changes: 70 additions & 43 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,19 +777,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
"items from traits can only be used if the trait is implemented and in scope"
});
let mut msg = format!(
let message = |action| 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()
},
action = action,
one_of_them = if candidates.len() == 1 {
"it"
} else {
Expand All @@ -809,50 +805,81 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 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 = None;
let mut impl_trait = false;
if let Node::GenericParam(ref param) = hir.get(id) {
let kind = &param.kind;
if let hir::GenericParamKind::Type { synthetic: Some(_), .. } = kind {
// We've found `fn foo(x: impl Trait)` instead of
// `fn foo<T>(x: T)`. We want to suggest the correct
// `fn foo(x: impl Trait + TraitBound)` instead of
// `fn foo<T: TraitBound>(x: T)`. (See #63706.)
impl_trait = true;
has_bounds = param.bounds.get(1);
} else {
has_bounds = param.bounds.get(0);
match hir.get(id) {
Node::GenericParam(ref param) => {
let mut impl_trait = false;
let has_bounds = if let hir::GenericParamKind::Type {
synthetic: Some(_), ..
} = &param.kind {
// We've found `fn foo(x: impl Trait)` instead of
// `fn foo<T>(x: T)`. We want to suggest the correct
// `fn foo(x: impl Trait + TraitBound)` instead of
// `fn foo<T: TraitBound>(x: T)`. (#63706)
impl_trait = true;
param.bounds.get(1)
} else {
param.bounds.get(0)
};
let sp = hir.span(id);
let sp = if let Some(first_bound) = has_bounds {
// `sp` only covers `T`, change it so that it covers
// `T:` when appropriate
sp.until(first_bound.span())
} 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,
&message(format!(
"restrict type parameter `{}` with",
param.name.ident().as_str(),
)),
candidates.iter().map(|t| format!(
"{}{} {}{}",
param.name.ident().as_str(),
if impl_trait { " +" } else { ":" },
self.tcx.def_path_str(t.def_id),
if has_bounds.is_some() { " + "} else { "" },
)),
Applicability::MaybeIncorrect,
);
suggested = true;
}
Node::Item(hir::Item {
kind: hir::ItemKind::Trait(.., bounds, _), ident, ..
}) => {
let (sp, sep, article) = if bounds.is_empty() {
(ident.span.shrink_to_hi(), ":", "a")
} else {
(bounds.last().unwrap().span().shrink_to_hi(), " +", "another")
};
err.span_suggestions(
sp,
&message(format!("add {} supertrait for", article)),
candidates.iter().map(|t| format!(
"{} {}",
sep,
self.tcx.def_path_str(t.def_id),
)),
Applicability::MaybeIncorrect,
);
suggested = true;
}
_ => {}
}
let sp = hir.span(id);
// `sp` only covers `T`, change it so that it covers `T:` when appropriate.
let sp = if let Some(first_bound) = has_bounds {
sp.until(first_bound.span())
} 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,
if impl_trait { " +" } else { ":" },
self.tcx.def_path_str(t.def_id),
if has_bounds.is_some() { " + " } else { "" },
)),
Applicability::MaybeIncorrect,
);
suggested = true;
}
};
}

if !suggested {
let mut msg = message(if let Some(param) = param_type {
format!("restrict type parameter `{}` with", param)
} else {
"implement".to_string()
});
for (i, trait_info) in candidates.iter().enumerate() {
msg.push_str(&format!(
"\ncandidate #{}: `{}`",
Expand Down
47 changes: 47 additions & 0 deletions src/test/ui/suggestions/constrain-trait.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// run-rustfix
// check-only

#[derive(Debug)]
struct Demo {
a: String
}

trait GetString {
fn get_a(&self) -> &String;
}

trait UseString: std::fmt::Debug + GetString {
fn use_string(&self) {
println!("{:?}", self.get_a()); //~ ERROR no method named `get_a` found for type `&Self`
}
}

trait UseString2: GetString {
fn use_string(&self) {
println!("{:?}", self.get_a()); //~ ERROR no method named `get_a` found for type `&Self`
}
}

impl GetString for Demo {
fn get_a(&self) -> &String {
&self.a
}
}

impl UseString for Demo {}
impl UseString2 for Demo {}


#[cfg(test)]
mod tests {
use crate::{Demo, UseString};

#[test]
fn it_works() {
let d = Demo { a: "test".to_string() };
d.use_string();
}
}


fn main() {}
47 changes: 47 additions & 0 deletions src/test/ui/suggestions/constrain-trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// run-rustfix
// check-only

#[derive(Debug)]
struct Demo {
a: String
}

trait GetString {
fn get_a(&self) -> &String;
}

trait UseString: std::fmt::Debug {
fn use_string(&self) {
println!("{:?}", self.get_a()); //~ ERROR no method named `get_a` found for type `&Self`
}
}

trait UseString2 {
fn use_string(&self) {
println!("{:?}", self.get_a()); //~ ERROR no method named `get_a` found for type `&Self`
}
}

impl GetString for Demo {
fn get_a(&self) -> &String {
&self.a
}
}

impl UseString for Demo {}
impl UseString2 for Demo {}


#[cfg(test)]
mod tests {
use crate::{Demo, UseString};

#[test]
fn it_works() {
let d = Demo { a: "test".to_string() };
d.use_string();
}
}


fn main() {}
27 changes: 27 additions & 0 deletions src/test/ui/suggestions/constrain-trait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0599]: no method named `get_a` found for type `&Self` in the current scope
--> $DIR/constrain-trait.rs:15:31
|
LL | println!("{:?}", self.get_a());
| ^^^^^ method not found in `&Self`
|
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `get_a`, perhaps you need to add another supertrait for it:
|
LL | trait UseString: std::fmt::Debug + GetString {
| ^^^^^^^^^^^

error[E0599]: no method named `get_a` found for type `&Self` in the current scope
--> $DIR/constrain-trait.rs:21:31
|
LL | println!("{:?}", self.get_a());
| ^^^^^ method not found in `&Self`
|
= help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `get_a`, perhaps you need to add a supertrait for it:
|
LL | trait UseString2: GetString {
| ^^^^^^^^^^^

error: aborting due to 2 previous errors

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

0 comments on commit dee53d7

Please sign in to comment.