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

Take into account negative impls in "trait item not found" suggestions #79790

Merged
merged 1 commit into from
Dec 17, 2020
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
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`.