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

Use structured suggestion for restricting bounds #65192

Merged
merged 16 commits into from
Oct 19, 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
6 changes: 6 additions & 0 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,12 @@ impl WhereClause {
Some(self.span)
}
}

/// The `WhereClause` under normal circumstances points at either the predicates or the empty
/// space where the `where` clause should be. Only of use for diagnostic suggestions.
pub fn span_for_predicates_or_empty_place(&self) -> Span {
self.span
}
}

/// A single predicate in a where-clause.
Expand Down
176 changes: 174 additions & 2 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
// these notes will often be of the form
// "the type `T` can't be frobnicated"
// which is somewhat confusing.
err.help(&format!("consider adding a `where {}` bound",
trait_ref.to_predicate()));
self.suggest_restricting_param_bound(
&mut err,
&trait_ref,
obligation.cause.body_id,
);
} else {
if !have_alt_message {
// Can't show anything else useful, try to find similar impls.
Expand Down Expand Up @@ -960,6 +963,175 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
err.emit();
}

fn suggest_restricting_param_bound(
&self,
err: &mut DiagnosticBuilder<'_>,
trait_ref: &ty::PolyTraitRef<'_>,
body_id: hir::HirId,
) {
let self_ty = trait_ref.self_ty();
let (param_ty, projection) = match &self_ty.kind {
ty::Param(_) => (true, None),
ty::Projection(projection) => (false, Some(projection)),
_ => return,
};

let mut suggest_restriction = |generics: &hir::Generics, msg| {
let span = generics.where_clause.span_for_predicates_or_empty_place();
if !span.from_expansion() && span.desugaring_kind().is_none() {
err.span_suggestion(
generics.where_clause.span_for_predicates_or_empty_place().shrink_to_hi(),
&format!("consider further restricting {}", msg),
format!(
"{} {} ",
if !generics.where_clause.predicates.is_empty() {
","
} else {
" where"
},
trait_ref.to_predicate(),
),
Applicability::MachineApplicable,
);
}
};

// FIXME: Add check for trait bound that is already present, particularly `?Sized` so we
// don't suggest `T: Sized + ?Sized`.
let mut hir_id = body_id;
while let Some(node) = self.tcx.hir().find(hir_id) {
match node {
hir::Node::TraitItem(hir::TraitItem {
generics,
kind: hir::TraitItemKind::Method(..), ..
}) if param_ty && self_ty == self.tcx.types.self_param => {
// Restricting `Self` for a single method.
suggest_restriction(&generics, "`Self`");
return;
}

hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_, _, generics, _), ..
}) |
hir::Node::TraitItem(hir::TraitItem {
generics,
kind: hir::TraitItemKind::Method(..), ..
}) |
hir::Node::ImplItem(hir::ImplItem {
generics,
kind: hir::ImplItemKind::Method(..), ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Trait(_, _, generics, _, _), ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(_, _, _, generics, ..), ..
}) if projection.is_some() => {
// Missing associated type bound.
suggest_restriction(&generics, "the associated type");
return;
}

hir::Node::Item(hir::Item { kind: hir::ItemKind::Struct(_, generics), span, .. }) |
hir::Node::Item(hir::Item { kind: hir::ItemKind::Enum(_, generics), span, .. }) |
hir::Node::Item(hir::Item { kind: hir::ItemKind::Union(_, generics), span, .. }) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Trait(_, _, generics, ..), span, ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(_, _, _, generics, ..), span, ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Fn(_, _, generics, _), span, ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::TyAlias(_, generics), span, ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::TraitAlias(generics, _), span, ..
}) |
hir::Node::Item(hir::Item {
kind: hir::ItemKind::OpaqueTy(hir::OpaqueTy { generics, .. }), span, ..
}) |
hir::Node::TraitItem(hir::TraitItem { generics, span, .. }) |
hir::Node::ImplItem(hir::ImplItem { generics, span, .. })
if param_ty => {
// Missing generic type parameter bound.
let restrict_msg = "consider further restricting this bound";
let param_name = self_ty.to_string();
for param in generics.params.iter().filter(|p| {
&param_name == std::convert::AsRef::<str>::as_ref(&p.name.ident().as_str())
}) {
if param_name.starts_with("impl ") {
// `impl Trait` in argument:
// `fn foo(x: impl Trait) {}` → `fn foo(t: impl Trait + Trait2) {}`
err.span_suggestion(
param.span,
restrict_msg,
// `impl CurrentTrait + MissingTrait`
format!("{} + {}", param.name.ident(), trait_ref),
Applicability::MachineApplicable,
);
} else if generics.where_clause.predicates.is_empty() &&
param.bounds.is_empty()
{
// If there are no bounds whatsoever, suggest adding a constraint
// to the type parameter:
// `fn foo<T>(t: T) {}` → `fn foo<T: Trait>(t: T) {}`
err.span_suggestion(
param.span,
"consider restricting this bound",
format!("{}", trait_ref.to_predicate()),
Applicability::MachineApplicable,
);
} else if !generics.where_clause.predicates.is_empty() {
// There is a `where` clause, so suggest expanding it:
// `fn foo<T>(t: T) where T: Debug {}` →
// `fn foo<T>(t: T) where T: Debug, T: Trait {}`
err.span_suggestion(
generics.where_clause.span().unwrap().shrink_to_hi(),
&format!(
"consider further restricting type parameter `{}`",
param_name,
),
format!(", {}", trait_ref.to_predicate()),
Applicability::MachineApplicable,
);
} else {
// If there is no `where` clause lean towards constraining to the
// type parameter:
// `fn foo<X: Bar, T>(t: T, x: X) {}` → `fn foo<T: Trait>(t: T) {}`
// `fn foo<T: Bar>(t: T) {}` → `fn foo<T: Bar + Trait>(t: T) {}`
let sp = param.span.with_hi(span.hi());
let span = self.tcx.sess.source_map()
.span_through_char(sp, ':');
if sp != param.span && sp != span {
// Only suggest if we have high certainty that the span
// covers the colon in `foo<T: Trait>`.
err.span_suggestion(span, restrict_msg, format!(
"{} + ",
trait_ref.to_predicate(),
), Applicability::MachineApplicable);
} else {
err.span_label(param.span, &format!(
"consider adding a `where {}` bound",
trait_ref.to_predicate(),
));
}
}
return;
}
}

hir::Node::Crate => return,

_ => {}
}

hir_id = self.tcx.hir().get_parent_item(hir_id);
}
}

/// When encountering an assignment of an unsized trait, like `let x = ""[..];`, provide a
/// suggestion to borrow the initializer in order to use have a slice instead.
fn suggest_borrow_on_unsized_slice(
Expand Down
6 changes: 1 addition & 5 deletions src/librustc_typeck/check/demand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// If the span is from a macro, then it's hard to extract the text
// and make a good suggestion, so don't bother.
let is_desugaring = match sp.desugaring_kind() {
Some(k) => sp.is_desugaring(k),
None => false
};
let is_macro = sp.from_expansion() && !is_desugaring;
let is_macro = sp.from_expansion() && sp.desugaring_kind().is_none();

// `ExprKind::DropTemps` is semantically irrelevant for these suggestions.
let expr = expr.peel_drop_temps();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ error[E0277]: the trait bound `A: Foo` is not satisfied
LL | const Y: usize;
| --------------- required by `Foo::Y`
...
LL | pub fn test<A: Foo, B: Foo>() {
| -- help: consider further restricting this bound: `A: Foo +`
LL | let _array = [4; <A as Foo>::Y];
| ^^^^^^^^^^^^^ the trait `Foo` is not implemented for `A`
|
= help: consider adding a `where A: Foo` bound

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ error[E0277]: the trait bound `A: Foo` is not satisfied
LL | const Y: usize;
| --------------- required by `Foo::Y`
...
LL | pub fn test<A: Foo, B: Foo>() {
| -- help: consider further restricting this bound: `A: Foo +`
LL | let _array: [u32; <A as Foo>::Y];
| ^^^^^^^^^^^^^ the trait `Foo` is not implemented for `A`
|
= help: consider adding a `where A: Foo` bound

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ LL | impl Case1 for S1 {
error[E0277]: `<<T as Case1>::C as std::iter::Iterator>::Item` is not an iterator
--> $DIR/bad-bounds-on-assoc-in-trait.rs:37:1
|
LL | / fn assume_case1<T: Case1>() {
LL | fn assume_case1<T: Case1>() {
| ^ - help: consider further restricting the associated type: `where <<T as Case1>::C as std::iter::Iterator>::Item: std::iter::Iterator`
| _|
| |
LL | |
LL | |
LL | |
Expand All @@ -19,15 +22,17 @@ LL | | }
| |_^ `<<T as Case1>::C as std::iter::Iterator>::Item` is not an iterator
|
= help: the trait `std::iter::Iterator` is not implemented for `<<T as Case1>::C as std::iter::Iterator>::Item`
= help: consider adding a `where <<T as Case1>::C as std::iter::Iterator>::Item: std::iter::Iterator` bound

error[E0277]: `<<T as Case1>::C as std::iter::Iterator>::Item` cannot be sent between threads safely
--> $DIR/bad-bounds-on-assoc-in-trait.rs:37:1
|
LL | trait Case1 {
| ----------- required by `Case1`
...
LL | / fn assume_case1<T: Case1>() {
LL | fn assume_case1<T: Case1>() {
| ^ - help: consider further restricting the associated type: `where <<T as Case1>::C as std::iter::Iterator>::Item: std::marker::Send`
| _|
| |
LL | |
LL | |
LL | |
Expand All @@ -37,15 +42,17 @@ LL | | }
| |_^ `<<T as Case1>::C as std::iter::Iterator>::Item` cannot be sent between threads safely
|
= help: the trait `std::marker::Send` is not implemented for `<<T as Case1>::C as std::iter::Iterator>::Item`
= help: consider adding a `where <<T as Case1>::C as std::iter::Iterator>::Item: std::marker::Send` bound

error[E0277]: `<<T as Case1>::C as std::iter::Iterator>::Item` cannot be shared between threads safely
--> $DIR/bad-bounds-on-assoc-in-trait.rs:37:1
|
LL | trait Case1 {
| ----------- required by `Case1`
...
LL | / fn assume_case1<T: Case1>() {
LL | fn assume_case1<T: Case1>() {
| ^ - help: consider further restricting the associated type: `where <<T as Case1>::C as std::iter::Iterator>::Item: std::marker::Sync`
| _|
| |
LL | |
LL | |
LL | |
Expand All @@ -55,7 +62,6 @@ LL | | }
| |_^ `<<T as Case1>::C as std::iter::Iterator>::Item` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `<<T as Case1>::C as std::iter::Iterator>::Item`
= help: consider adding a `where <<T as Case1>::C as std::iter::Iterator>::Item: std::marker::Sync` bound

error[E0277]: `<_ as Lam<&'a u8>>::App` doesn't implement `std::fmt::Debug`
--> $DIR/bad-bounds-on-assoc-in-trait.rs:37:1
Expand Down
29 changes: 29 additions & 0 deletions src/test/ui/associated-types/associated-types-bound-failure.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// run-rustfix
// Test equality constraints on associated types in a where clause.
#![allow(dead_code)]

pub trait ToInt {
fn to_int(&self) -> isize;
}

pub trait GetToInt
{
type R;

fn get(&self) -> <Self as GetToInt>::R;
}

fn foo<G>(g: G) -> isize
where G : GetToInt, <G as GetToInt>::R: ToInt
{
ToInt::to_int(&g.get()) //~ ERROR E0277
}

fn bar<G : GetToInt>(g: G) -> isize
where G::R : ToInt
{
ToInt::to_int(&g.get()) // OK
}

pub fn main() {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// run-rustfix
// Test equality constraints on associated types in a where clause.
#![allow(dead_code)]

pub trait ToInt {
fn to_int(&self) -> isize;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
error[E0277]: the trait bound `<G as GetToInt>::R: ToInt` is not satisfied
--> $DIR/associated-types-bound-failure.rs:17:19
--> $DIR/associated-types-bound-failure.rs:19:19
|
LL | fn to_int(&self) -> isize;
| -------------------------- required by `ToInt::to_int`
...
LL | where G : GetToInt
| - help: consider further restricting the associated type: `, <G as GetToInt>::R: ToInt`
LL | {
LL | ToInt::to_int(&g.get())
| ^^^^^^^^ the trait `ToInt` is not implemented for `<G as GetToInt>::R`
|
= help: consider adding a `where <G as GetToInt>::R: ToInt` bound

error: aborting due to previous error

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// run-rustfix
#![allow(unused_variables)]

trait Get {
type Value;
fn get(&self) -> <Self as Get>::Value;
}

trait Other {
fn uhoh<U:Get>(&self, foo: U, bar: <Self as Get>::Value) where Self: Get {}
//~^ ERROR the trait bound `Self: Get` is not satisfied
}

fn main() {
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// run-rustfix
#![allow(unused_variables)]

trait Get {
type Value;
fn get(&self) -> <Self as Get>::Value;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
error[E0277]: the trait bound `Self: Get` is not satisfied
--> $DIR/associated-types-for-unimpl-trait.rs:7:5
--> $DIR/associated-types-for-unimpl-trait.rs:10:5
|
LL | fn uhoh<U:Get>(&self, foo: U, bar: <Self as Get>::Value) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Get` is not implemented for `Self`
|
= help: consider adding a `where Self: Get` bound
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^
| | |
| | help: consider further restricting `Self`: `where Self: Get`
| the trait `Get` is not implemented for `Self`

error: aborting due to previous error

Expand Down
Loading