Skip to content
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

Fix suggestion to constrain trait for method to be found #65242

Merged
merged 1 commit into from
Oct 16, 2019
Merged
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
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`.