Skip to content

Commit

Permalink
Rollup merge of rust-lang#79790 - LeSeulArtichaut:issue-79683, r=lcnr
Browse files Browse the repository at this point in the history
Take into account negative impls in "trait item not found" suggestions

This removes the suggestion to implement a trait for a type when that type already has a negative implementation for the trait, and replaces it with a note to point out that the trait is explicitely unimplemented, as suggested by `@scottmcm.`

Helps with rust-lang#79683.

r? `@scottmcm` do you want to review this?
  • Loading branch information
Dylan-DPC authored Dec 16, 2020
2 parents cec4573 + cfc38d2 commit 553d632
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 28 deletions.
108 changes: 80 additions & 28 deletions compiler/rustc_typeck/src/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_hir::lang_items::LangItem;
use rustc_hir::{ExprKind, Node, QPath};
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::hir::map as hir_map;
use rustc_middle::ty::fast_reject::simplify_type;
use rustc_middle::ty::print::with_crate_prefix;
use rustc_middle::ty::{
self, ToPolyTraitRef, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
Expand Down Expand Up @@ -1074,19 +1075,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
"items from traits can only be used if the trait is implemented and in scope"
});
let candidates_len = candidates.len();
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" },
if candidates_len == 1 { "trait defines" } else { "traits define" },
action = action,
one_of_them = if candidates.len() == 1 { "it" } else { "one of them" },
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_typeck_results)
{
Expand Down Expand Up @@ -1147,7 +1148,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MaybeIncorrect,
);
}
suggested = true;
return;
}
Node::Item(hir::Item {
kind: hir::ItemKind::Trait(.., bounds, _),
Expand All @@ -1167,45 +1168,96 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}),
Applicability::MaybeIncorrect,
);
suggested = true;
return;
}
_ => {}
}
}
}

if !suggested {
let action = if let Some(param) = param_type {
format!("restrict type parameter `{}` with", param)
} else {
// FIXME: it might only need to be imported into scope, not implemented.
"implement".to_string()
};
let mut use_note = true;
if let [trait_info] = &candidates[..] {
if let Some(span) = self.tcx.hir().span_if_local(trait_info.def_id) {
err.span_note(
self.tcx.sess.source_map().guess_head_span(span),
&format!(
"`{}` defines an item `{}`, perhaps you need to {} it",
self.tcx.def_path_str(trait_info.def_id),
item_name,
action
),
);
use_note = false
let (potential_candidates, explicitly_negative) = if param_type.is_some() {
// FIXME: Even though negative bounds are not implemented, we could maybe handle
// cases where a positive bound implies a negative impl.
(candidates, Vec::new())
} else if let Some(simp_rcvr_ty) = simplify_type(self.tcx, rcvr_ty, true) {
let mut potential_candidates = Vec::new();
let mut explicitly_negative = Vec::new();
for candidate in candidates {
// Check if there's a negative impl of `candidate` for `rcvr_ty`
if self
.tcx
.all_impls(candidate.def_id)
.filter(|imp_did| {
self.tcx.impl_polarity(*imp_did) == ty::ImplPolarity::Negative
})
.any(|imp_did| {
let imp = self.tcx.impl_trait_ref(imp_did).unwrap();
let imp_simp = simplify_type(self.tcx, imp.self_ty(), true);
imp_simp.map(|s| s == simp_rcvr_ty).unwrap_or(false)
})
{
explicitly_negative.push(candidate);
} else {
potential_candidates.push(candidate);
}
}
if use_note {
(potential_candidates, explicitly_negative)
} else {
// We don't know enough about `recv_ty` to make proper suggestions.
(candidates, Vec::new())
};

let action = if let Some(param) = param_type {
format!("restrict type parameter `{}` with", param)
} else {
// FIXME: it might only need to be imported into scope, not implemented.
"implement".to_string()
};
match &potential_candidates[..] {
[] => {}
[trait_info] if trait_info.def_id.is_local() => {
let span = self.tcx.hir().span_if_local(trait_info.def_id).unwrap();
err.span_note(
self.tcx.sess.source_map().guess_head_span(span),
&format!(
"`{}` defines an item `{}`, perhaps you need to {} it",
self.tcx.def_path_str(trait_info.def_id),
item_name,
action
),
);
}
trait_infos => {
let mut msg = message(action);
for (i, trait_info) in candidates.iter().enumerate() {
for (i, trait_info) in trait_infos.iter().enumerate() {
msg.push_str(&format!(
"\ncandidate #{}: `{}`",
i + 1,
self.tcx.def_path_str(trait_info.def_id),
));
}
err.note(&msg[..]);
err.note(&msg);
}
}
match &explicitly_negative[..] {
[] => {}
[trait_info] => {
let msg = format!(
"the trait `{}` defines an item `{}`, but is explicitely unimplemented",
self.tcx.def_path_str(trait_info.def_id),
item_name
);
err.note(&msg);
}
trait_infos => {
let mut msg = format!(
"the following traits define an item `{}`, but are explicitely unimplemented:",
item_name
);
for trait_info in trait_infos {
msg.push_str(&format!("\n{}", self.tcx.def_path_str(trait_info.def_id)));
}
err.note(&msg);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This tests issue #79683: note in the error message that the trait is
// explicitely unimplemented instead of suggesting to implement it.

#![feature(negative_impls)]

struct Qux;
//~^ NOTE method `clone` not found for this
//~^^ NOTE method `foo` not found for this

impl !Clone for Qux {}

trait Bar {
fn bar(&self);
}

impl !Bar for u32 {}

trait Foo {
fn foo(&self);
}
//~^^^ NOTE `Foo` defines an item `foo`, perhaps you need to implement it

trait FooBar {
fn foo(&self);
}

impl !Foo for Qux {}

impl !FooBar for Qux {}

impl !FooBar for u32 {}

fn main() {
Qux.clone();
//~^ ERROR no method named `clone` found for struct `Qux`
//~| NOTE method not found in `Qux`
//~| NOTE `Clone` defines an item `clone`, but is explicitely unimplemented

0_u32.bar();
//~^ ERROR no method named `bar` found for type `u32`
//~| NOTE method not found in `u32`
//~| NOTE `Bar` defines an item `bar`, but is explicitely unimplemented

Qux.foo();
//~^ ERROR no method named `foo` found for struct `Qux`
//~| NOTE method not found in `Qux`
//~| NOTE the following traits define an item `foo`, but are explicitely unimplemented

0_u32.foo();
//~^ ERROR no method named `foo` found for type `u32`
//~| NOTE method not found in `u32`
//~| NOTE `FooBar` defines an item `foo`, but is explicitely unimplemented
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
error[E0599]: no method named `clone` found for struct `Qux` in the current scope
--> $DIR/explicitly-unimplemented-error-message.rs:34:9
|
LL | struct Qux;
| ----------- method `clone` not found for this
...
LL | Qux.clone();
| ^^^^^ method not found in `Qux`
|
::: $SRC_DIR/core/src/clone.rs:LL:COL
|
LL | fn clone(&self) -> Self;
| -----
| |
| the method is available for `Arc<Qux>` here
| the method is available for `Rc<Qux>` here
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the trait `Clone` defines an item `clone`, but is explicitely unimplemented

error[E0599]: no method named `bar` found for type `u32` in the current scope
--> $DIR/explicitly-unimplemented-error-message.rs:39:11
|
LL | 0_u32.bar();
| ^^^ method not found in `u32`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the trait `Bar` defines an item `bar`, but is explicitely unimplemented

error[E0599]: no method named `foo` found for struct `Qux` in the current scope
--> $DIR/explicitly-unimplemented-error-message.rs:44:9
|
LL | struct Qux;
| ----------- method `foo` not found for this
...
LL | Qux.foo();
| ^^^ method not found in `Qux`
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following traits define an item `foo`, but are explicitely unimplemented:
Foo
FooBar

error[E0599]: no method named `foo` found for type `u32` in the current scope
--> $DIR/explicitly-unimplemented-error-message.rs:49:11
|
LL | 0_u32.foo();
| ^^^ method not found in `u32`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `Foo` defines an item `foo`, perhaps you need to implement it
--> $DIR/explicitly-unimplemented-error-message.rs:18:1
|
LL | trait Foo {
| ^^^^^^^^^
= note: the trait `FooBar` defines an item `foo`, but is explicitely unimplemented

error: aborting due to 4 previous errors

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

0 comments on commit 553d632

Please sign in to comment.