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

Note when a mutable trait object is needed #65077

Merged
merged 6 commits into from
Oct 10, 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
7 changes: 7 additions & 0 deletions src/librustc/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,13 @@ impl Mutability {
MutImmutable => MutImmutable,
}
}

pub fn invert(self) -> Self {
match self {
MutMutable => MutImmutable,
MutImmutable => MutMutable,
}
}
}

#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, Hash, HashStable)]
Expand Down
120 changes: 99 additions & 21 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,21 +453,17 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

fn find_similar_impl_candidates(&self,
trait_ref: ty::PolyTraitRef<'tcx>)
-> Vec<ty::TraitRef<'tcx>>
{
let simp = fast_reject::simplify_type(self.tcx,
trait_ref.skip_binder().self_ty(),
true);
fn find_similar_impl_candidates(
&self,
trait_ref: ty::PolyTraitRef<'tcx>,
) -> Vec<ty::TraitRef<'tcx>> {
let simp = fast_reject::simplify_type(self.tcx, trait_ref.skip_binder().self_ty(), true);
let all_impls = self.tcx.all_impls(trait_ref.def_id());

match simp {
Some(simp) => all_impls.iter().filter_map(|&def_id| {
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
let imp_simp = fast_reject::simplify_type(self.tcx,
imp.self_ty(),
true);
let imp_simp = fast_reject::simplify_type(self.tcx, imp.self_ty(), true);
if let Some(imp_simp) = imp_simp {
if simp != imp_simp {
return None
Expand All @@ -482,10 +478,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

fn report_similar_impl_candidates(&self,
impl_candidates: Vec<ty::TraitRef<'tcx>>,
err: &mut DiagnosticBuilder<'_>)
{
fn report_similar_impl_candidates(
&self,
impl_candidates: Vec<ty::TraitRef<'tcx>>,
err: &mut DiagnosticBuilder<'_>,
) {
if impl_candidates.is_empty() {
return;
}
Expand Down Expand Up @@ -720,10 +717,18 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
// which is somewhat confusing.
err.help(&format!("consider adding a `where {}` bound",
trait_ref.to_predicate()));
} else if !have_alt_message {
// Can't show anything else useful, try to find similar impls.
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
self.report_similar_impl_candidates(impl_candidates, &mut err);
} else {
if !have_alt_message {
// Can't show anything else useful, try to find similar impls.
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
self.report_similar_impl_candidates(impl_candidates, &mut err);
}
self.suggest_change_mut(
&obligation,
&mut err,
&trait_ref,
points_at_arg,
);
}

// If this error is due to `!: Trait` not implemented but `(): Trait` is
Expand Down Expand Up @@ -1081,9 +1086,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {

let substs = self.tcx.mk_substs_trait(trait_type, &[]);
let new_trait_ref = ty::TraitRef::new(trait_ref.def_id, substs);
let new_obligation = Obligation::new(ObligationCause::dummy(),
obligation.param_env,
new_trait_ref.to_predicate());
let new_obligation = Obligation::new(
ObligationCause::dummy(),
obligation.param_env,
new_trait_ref.to_predicate(),
);

if self.predicate_may_hold(&new_obligation) {
let sp = self.tcx.sess.source_map()
Expand All @@ -1105,6 +1112,77 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}
}

/// Check if the trait bound is implemented for a different mutability and note it in the
/// final error.
fn suggest_change_mut(
&self,
obligation: &PredicateObligation<'tcx>,
err: &mut DiagnosticBuilder<'tcx>,
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
points_at_arg: bool,
) {
let span = obligation.cause.span;
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
let refs_number = snippet.chars()
.filter(|c| !c.is_whitespace())
.take_while(|c| *c == '&')
.count();
if let Some('\'') = snippet.chars()
.filter(|c| !c.is_whitespace())
.skip(refs_number)
.next()
{ // Do not suggest removal of borrow from type arguments.
return;
}
let trait_ref = self.resolve_vars_if_possible(trait_ref);
if trait_ref.has_infer_types() {
// Do not ICE while trying to find if a reborrow would succeed on a trait with
// unresolved bindings.
return;
}

if let ty::Ref(region, t_type, mutability) = trait_ref.skip_binder().self_ty().kind {
let trait_type = match mutability {
hir::Mutability::MutMutable => self.tcx.mk_imm_ref(region, t_type),
hir::Mutability::MutImmutable => self.tcx.mk_mut_ref(region, t_type),
};

let substs = self.tcx.mk_substs_trait(&trait_type, &[]);
let new_trait_ref = ty::TraitRef::new(trait_ref.skip_binder().def_id, substs);
let new_obligation = Obligation::new(
ObligationCause::dummy(),
obligation.param_env,
new_trait_ref.to_predicate(),
);

if self.evaluate_obligation_no_overflow(
&new_obligation,
).must_apply_modulo_regions() {
let sp = self.tcx.sess.source_map()
.span_take_while(span, |c| c.is_whitespace() || *c == '&');
if points_at_arg &&
mutability == hir::Mutability::MutImmutable &&
refs_number > 0
{
err.span_suggestion(
sp,
"consider changing this borrow's mutability",
"&mut ".to_string(),
Applicability::MachineApplicable,
);
} else {
err.note(&format!(
"`{}` is implemented for `{:?}`, but not for `{:?}`",
trait_ref,
trait_type,
trait_ref.skip_binder().self_ty(),
));
}
}
}
}
}

fn suggest_semicolon_removal(
&self,
obligation: &PredicateObligation<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/traits/query/evaluate_obligation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
// Helper function that canonicalizes and runs the query. If an
// overflow results, we re-run it in the local context so we can
// report a nice error.
fn evaluate_obligation_no_overflow(
crate fn evaluate_obligation_no_overflow(
&self,
obligation: &PredicateObligation<'tcx>,
) -> EvaluationResult {
Expand Down
64 changes: 40 additions & 24 deletions src/librustc_typeck/check/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub enum MethodError<'tcx> {

// Found a `Self: Sized` bound where `Self` is a trait object, also the caller may have
// forgotten to import a trait.
IllegalSizedBound(Vec<DefId>),
IllegalSizedBound(Vec<DefId>, bool),

// Found a match, but the return type is wrong
BadReturnType,
Expand Down Expand Up @@ -213,33 +213,49 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
segment,
);

let mut needs_mut = false;
if let ty::Ref(region, t_type, mutability) = self_ty.kind {
let trait_type = self.tcx.mk_ref(region, ty::TypeAndMut {
ty: t_type,
mutbl: mutability.invert(),
});
match self.lookup_probe(
span,
segment.ident,
trait_type,
call_expr,
ProbeScope::TraitsInScope
) {
Ok(ref new_pick) if *new_pick != pick => {
needs_mut = true;
}
_ => {}
}
}

if result.illegal_sized_bound {
// We probe again, taking all traits into account (not only those in scope).
let candidates =
match self.lookup_probe(span,
segment.ident,
self_ty,
call_expr,
ProbeScope::AllTraits) {

// If we find a different result the caller probably forgot to import a trait.
Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container.id()],
Err(Ambiguity(ref sources)) => {
sources.iter()
.filter_map(|source| {
match *source {
// Note: this cannot come from an inherent impl,
// because the first probing succeeded.
ImplSource(def) => self.tcx.trait_id_of_impl(def),
TraitSource(_) => None,
}
})
.collect()
let candidates = match self.lookup_probe(
span,
segment.ident,
self_ty,
call_expr,
ProbeScope::AllTraits,
) {
// If we find a different result the caller probably forgot to import a trait.
Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container.id()],
Err(Ambiguity(ref sources)) => sources.iter().filter_map(|source| {
match *source {
// Note: this cannot come from an inherent impl,
// because the first probing succeeded.
ImplSource(def) => self.tcx.trait_id_of_impl(def),
TraitSource(_) => None,
}
_ => Vec::new(),
};
}).collect(),
_ => Vec::new(),
};

return Err(IllegalSizedBound(candidates));
return Err(IllegalSizedBound(candidates, needs_mut));
}

Ok(result.callee)
Expand Down
33 changes: 22 additions & 11 deletions src/librustc_typeck/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,22 +560,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err.emit();
}

MethodError::IllegalSizedBound(candidates) => {
MethodError::IllegalSizedBound(candidates, needs_mut) => {
let msg = format!("the `{}` method cannot be invoked on a trait object", item_name);
let mut err = self.sess().struct_span_err(span, &msg);
if !candidates.is_empty() {
let help = format!("{an}other candidate{s} {were} found in the following \
trait{s}, perhaps add a `use` for {one_of_them}:",
an = if candidates.len() == 1 {"an" } else { "" },
s = pluralise!(candidates.len()),
were = if candidates.len() == 1 { "was" } else { "were" },
one_of_them = if candidates.len() == 1 {
"it"
} else {
"one_of_them"
});
let help = format!(
"{an}other candidate{s} {were} found in the following trait{s}, perhaps \
add a `use` for {one_of_them}:",
an = if candidates.len() == 1 {"an" } else { "" },
s = pluralise!(candidates.len()),
were = if candidates.len() == 1 { "was" } else { "were" },
one_of_them = if candidates.len() == 1 {
"it"
} else {
"one_of_them"
},
);
self.suggest_use_candidates(&mut err, help, candidates);
}
if let ty::Ref(region, t_type, mutability) = rcvr_ty.kind {
if needs_mut {
let trait_type = self.tcx.mk_ref(region, ty::TypeAndMut {
ty: t_type,
mutbl: mutability.invert(),
});
err.note(&format!("you need `{}` instead of `{}`", trait_type, rcvr_ty));
}
}
err.emit();
}

Expand Down
1 change: 1 addition & 0 deletions src/test/ui/not-panic/not-panic-safe.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ LL | assert::<&mut i32>();
| ^^^^^^^^^^^^^^^^^^ `&mut i32` may not be safely transferred across an unwind boundary
|
= help: the trait `std::panic::UnwindSafe` is not implemented for `&mut i32`
= note: `std::panic::UnwindSafe` is implemented for `&i32`, but not for `&mut i32`

error: aborting due to previous error

Expand Down
14 changes: 14 additions & 0 deletions src/test/ui/suggestions/imm-ref-trait-object-literal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
trait Trait {}

struct S;

impl<'a> Trait for &'a mut S {}

fn foo<X: Trait>(_: X) {}


fn main() {
let s = S;
foo(&s); //~ ERROR the trait bound `&S: Trait` is not satisfied
foo(s); //~ ERROR the trait bound `S: Trait` is not satisfied
}
30 changes: 30 additions & 0 deletions src/test/ui/suggestions/imm-ref-trait-object-literal.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
error[E0277]: the trait bound `&S: Trait` is not satisfied
--> $DIR/imm-ref-trait-object-literal.rs:12:7
|
LL | fn foo<X: Trait>(_: X) {}
| --- ----- required by this bound in `foo`
...
LL | foo(&s);
| -^
| |
| the trait `Trait` is not implemented for `&S`
| help: consider changing this borrow's mutability: `&mut`
|
= help: the following implementations were found:
<&'a mut S as Trait>

error[E0277]: the trait bound `S: Trait` is not satisfied
--> $DIR/imm-ref-trait-object-literal.rs:13:7
|
LL | fn foo<X: Trait>(_: X) {}
| --- ----- required by this bound in `foo`
...
LL | foo(s);
| ^ the trait `Trait` is not implemented for `S`
|
= help: the following implementations were found:
<&'a mut S as Trait>

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0277`.
8 changes: 8 additions & 0 deletions src/test/ui/suggestions/imm-ref-trait-object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn test(t: &dyn Iterator<Item=&u64>) -> u64 {
t.min().unwrap() //~ ERROR the `min` method cannot be invoked on a trait object
}

fn main() {
let array = [0u64];
test(&mut array.iter());
}
10 changes: 10 additions & 0 deletions src/test/ui/suggestions/imm-ref-trait-object.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error: the `min` method cannot be invoked on a trait object
--> $DIR/imm-ref-trait-object.rs:2:8
|
LL | t.min().unwrap()
| ^^^
|
= note: you need `&mut dyn std::iter::Iterator<Item = &u64>` instead of `&dyn std::iter::Iterator<Item = &u64>`

error: aborting due to previous error

1 change: 1 addition & 0 deletions src/test/ui/suggestions/into-str.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ LL | foo(String::new());
| ^^^ the trait `std::convert::From<std::string::String>` is not implemented for `&str`
|
= note: to coerce a `std::string::String` into a `&str`, use `&*` as a prefix
= note: `std::convert::From<std::string::String>` is implemented for `&mut str`, but not for `&str`
= note: required because of the requirements on the impl of `std::convert::Into<&str>` for `std::string::String`

error: aborting due to previous error
Expand Down
Loading