Skip to content

Commit 8ee24f6

Browse files
committed
Auto merge of #65077 - estebank:mut-trait-expected, r=nikomatsakis
Note when a mutable trait object is needed Fix #63619, fix #37914. CC #64068.
2 parents aa45e03 + faf8a2a commit 8ee24f6

13 files changed

+297
-57
lines changed

src/librustc/hir/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,13 @@ impl Mutability {
10531053
MutImmutable => MutImmutable,
10541054
}
10551055
}
1056+
1057+
pub fn invert(self) -> Self {
1058+
match self {
1059+
MutMutable => MutImmutable,
1060+
MutImmutable => MutMutable,
1061+
}
1062+
}
10561063
}
10571064

10581065
#[derive(Copy, Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, Hash, HashStable)]

src/librustc/traits/error_reporting.rs

+99-21
Original file line numberDiff line numberDiff line change
@@ -453,21 +453,17 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
453453
}
454454
}
455455

456-
fn find_similar_impl_candidates(&self,
457-
trait_ref: ty::PolyTraitRef<'tcx>)
458-
-> Vec<ty::TraitRef<'tcx>>
459-
{
460-
let simp = fast_reject::simplify_type(self.tcx,
461-
trait_ref.skip_binder().self_ty(),
462-
true);
456+
fn find_similar_impl_candidates(
457+
&self,
458+
trait_ref: ty::PolyTraitRef<'tcx>,
459+
) -> Vec<ty::TraitRef<'tcx>> {
460+
let simp = fast_reject::simplify_type(self.tcx, trait_ref.skip_binder().self_ty(), true);
463461
let all_impls = self.tcx.all_impls(trait_ref.def_id());
464462

465463
match simp {
466464
Some(simp) => all_impls.iter().filter_map(|&def_id| {
467465
let imp = self.tcx.impl_trait_ref(def_id).unwrap();
468-
let imp_simp = fast_reject::simplify_type(self.tcx,
469-
imp.self_ty(),
470-
true);
466+
let imp_simp = fast_reject::simplify_type(self.tcx, imp.self_ty(), true);
471467
if let Some(imp_simp) = imp_simp {
472468
if simp != imp_simp {
473469
return None
@@ -482,10 +478,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
482478
}
483479
}
484480

485-
fn report_similar_impl_candidates(&self,
486-
impl_candidates: Vec<ty::TraitRef<'tcx>>,
487-
err: &mut DiagnosticBuilder<'_>)
488-
{
481+
fn report_similar_impl_candidates(
482+
&self,
483+
impl_candidates: Vec<ty::TraitRef<'tcx>>,
484+
err: &mut DiagnosticBuilder<'_>,
485+
) {
489486
if impl_candidates.is_empty() {
490487
return;
491488
}
@@ -720,10 +717,18 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
720717
// which is somewhat confusing.
721718
err.help(&format!("consider adding a `where {}` bound",
722719
trait_ref.to_predicate()));
723-
} else if !have_alt_message {
724-
// Can't show anything else useful, try to find similar impls.
725-
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
726-
self.report_similar_impl_candidates(impl_candidates, &mut err);
720+
} else {
721+
if !have_alt_message {
722+
// Can't show anything else useful, try to find similar impls.
723+
let impl_candidates = self.find_similar_impl_candidates(trait_ref);
724+
self.report_similar_impl_candidates(impl_candidates, &mut err);
725+
}
726+
self.suggest_change_mut(
727+
&obligation,
728+
&mut err,
729+
&trait_ref,
730+
points_at_arg,
731+
);
727732
}
728733

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

10821087
let substs = self.tcx.mk_substs_trait(trait_type, &[]);
10831088
let new_trait_ref = ty::TraitRef::new(trait_ref.def_id, substs);
1084-
let new_obligation = Obligation::new(ObligationCause::dummy(),
1085-
obligation.param_env,
1086-
new_trait_ref.to_predicate());
1089+
let new_obligation = Obligation::new(
1090+
ObligationCause::dummy(),
1091+
obligation.param_env,
1092+
new_trait_ref.to_predicate(),
1093+
);
10871094

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

1115+
/// Check if the trait bound is implemented for a different mutability and note it in the
1116+
/// final error.
1117+
fn suggest_change_mut(
1118+
&self,
1119+
obligation: &PredicateObligation<'tcx>,
1120+
err: &mut DiagnosticBuilder<'tcx>,
1121+
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
1122+
points_at_arg: bool,
1123+
) {
1124+
let span = obligation.cause.span;
1125+
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) {
1126+
let refs_number = snippet.chars()
1127+
.filter(|c| !c.is_whitespace())
1128+
.take_while(|c| *c == '&')
1129+
.count();
1130+
if let Some('\'') = snippet.chars()
1131+
.filter(|c| !c.is_whitespace())
1132+
.skip(refs_number)
1133+
.next()
1134+
{ // Do not suggest removal of borrow from type arguments.
1135+
return;
1136+
}
1137+
let trait_ref = self.resolve_vars_if_possible(trait_ref);
1138+
if trait_ref.has_infer_types() {
1139+
// Do not ICE while trying to find if a reborrow would succeed on a trait with
1140+
// unresolved bindings.
1141+
return;
1142+
}
1143+
1144+
if let ty::Ref(region, t_type, mutability) = trait_ref.skip_binder().self_ty().kind {
1145+
let trait_type = match mutability {
1146+
hir::Mutability::MutMutable => self.tcx.mk_imm_ref(region, t_type),
1147+
hir::Mutability::MutImmutable => self.tcx.mk_mut_ref(region, t_type),
1148+
};
1149+
1150+
let substs = self.tcx.mk_substs_trait(&trait_type, &[]);
1151+
let new_trait_ref = ty::TraitRef::new(trait_ref.skip_binder().def_id, substs);
1152+
let new_obligation = Obligation::new(
1153+
ObligationCause::dummy(),
1154+
obligation.param_env,
1155+
new_trait_ref.to_predicate(),
1156+
);
1157+
1158+
if self.evaluate_obligation_no_overflow(
1159+
&new_obligation,
1160+
).must_apply_modulo_regions() {
1161+
let sp = self.tcx.sess.source_map()
1162+
.span_take_while(span, |c| c.is_whitespace() || *c == '&');
1163+
if points_at_arg &&
1164+
mutability == hir::Mutability::MutImmutable &&
1165+
refs_number > 0
1166+
{
1167+
err.span_suggestion(
1168+
sp,
1169+
"consider changing this borrow's mutability",
1170+
"&mut ".to_string(),
1171+
Applicability::MachineApplicable,
1172+
);
1173+
} else {
1174+
err.note(&format!(
1175+
"`{}` is implemented for `{:?}`, but not for `{:?}`",
1176+
trait_ref,
1177+
trait_type,
1178+
trait_ref.skip_binder().self_ty(),
1179+
));
1180+
}
1181+
}
1182+
}
1183+
}
1184+
}
1185+
11081186
fn suggest_semicolon_removal(
11091187
&self,
11101188
obligation: &PredicateObligation<'tcx>,

src/librustc/traits/query/evaluate_obligation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl<'cx, 'tcx> InferCtxt<'cx, 'tcx> {
5656
// Helper function that canonicalizes and runs the query. If an
5757
// overflow results, we re-run it in the local context so we can
5858
// report a nice error.
59-
fn evaluate_obligation_no_overflow(
59+
crate fn evaluate_obligation_no_overflow(
6060
&self,
6161
obligation: &PredicateObligation<'tcx>,
6262
) -> EvaluationResult {

src/librustc_typeck/check/method/mod.rs

+40-24
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub enum MethodError<'tcx> {
5858

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

6363
// Found a match, but the return type is wrong
6464
BadReturnType,
@@ -213,33 +213,49 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
213213
segment,
214214
);
215215

216+
let mut needs_mut = false;
217+
if let ty::Ref(region, t_type, mutability) = self_ty.kind {
218+
let trait_type = self.tcx.mk_ref(region, ty::TypeAndMut {
219+
ty: t_type,
220+
mutbl: mutability.invert(),
221+
});
222+
match self.lookup_probe(
223+
span,
224+
segment.ident,
225+
trait_type,
226+
call_expr,
227+
ProbeScope::TraitsInScope
228+
) {
229+
Ok(ref new_pick) if *new_pick != pick => {
230+
needs_mut = true;
231+
}
232+
_ => {}
233+
}
234+
}
235+
216236
if result.illegal_sized_bound {
217237
// We probe again, taking all traits into account (not only those in scope).
218-
let candidates =
219-
match self.lookup_probe(span,
220-
segment.ident,
221-
self_ty,
222-
call_expr,
223-
ProbeScope::AllTraits) {
224-
225-
// If we find a different result the caller probably forgot to import a trait.
226-
Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container.id()],
227-
Err(Ambiguity(ref sources)) => {
228-
sources.iter()
229-
.filter_map(|source| {
230-
match *source {
231-
// Note: this cannot come from an inherent impl,
232-
// because the first probing succeeded.
233-
ImplSource(def) => self.tcx.trait_id_of_impl(def),
234-
TraitSource(_) => None,
235-
}
236-
})
237-
.collect()
238+
let candidates = match self.lookup_probe(
239+
span,
240+
segment.ident,
241+
self_ty,
242+
call_expr,
243+
ProbeScope::AllTraits,
244+
) {
245+
// If we find a different result the caller probably forgot to import a trait.
246+
Ok(ref new_pick) if *new_pick != pick => vec![new_pick.item.container.id()],
247+
Err(Ambiguity(ref sources)) => sources.iter().filter_map(|source| {
248+
match *source {
249+
// Note: this cannot come from an inherent impl,
250+
// because the first probing succeeded.
251+
ImplSource(def) => self.tcx.trait_id_of_impl(def),
252+
TraitSource(_) => None,
238253
}
239-
_ => Vec::new(),
240-
};
254+
}).collect(),
255+
_ => Vec::new(),
256+
};
241257

242-
return Err(IllegalSizedBound(candidates));
258+
return Err(IllegalSizedBound(candidates, needs_mut));
243259
}
244260

245261
Ok(result.callee)

src/librustc_typeck/check/method/suggest.rs

+22-11
Original file line numberDiff line numberDiff line change
@@ -593,22 +593,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
593593
err.emit();
594594
}
595595

596-
MethodError::IllegalSizedBound(candidates) => {
596+
MethodError::IllegalSizedBound(candidates, needs_mut) => {
597597
let msg = format!("the `{}` method cannot be invoked on a trait object", item_name);
598598
let mut err = self.sess().struct_span_err(span, &msg);
599599
if !candidates.is_empty() {
600-
let help = format!("{an}other candidate{s} {were} found in the following \
601-
trait{s}, perhaps add a `use` for {one_of_them}:",
602-
an = if candidates.len() == 1 {"an" } else { "" },
603-
s = pluralise!(candidates.len()),
604-
were = if candidates.len() == 1 { "was" } else { "were" },
605-
one_of_them = if candidates.len() == 1 {
606-
"it"
607-
} else {
608-
"one_of_them"
609-
});
600+
let help = format!(
601+
"{an}other candidate{s} {were} found in the following trait{s}, perhaps \
602+
add a `use` for {one_of_them}:",
603+
an = if candidates.len() == 1 {"an" } else { "" },
604+
s = pluralise!(candidates.len()),
605+
were = if candidates.len() == 1 { "was" } else { "were" },
606+
one_of_them = if candidates.len() == 1 {
607+
"it"
608+
} else {
609+
"one_of_them"
610+
},
611+
);
610612
self.suggest_use_candidates(&mut err, help, candidates);
611613
}
614+
if let ty::Ref(region, t_type, mutability) = rcvr_ty.kind {
615+
if needs_mut {
616+
let trait_type = self.tcx.mk_ref(region, ty::TypeAndMut {
617+
ty: t_type,
618+
mutbl: mutability.invert(),
619+
});
620+
err.note(&format!("you need `{}` instead of `{}`", trait_type, rcvr_ty));
621+
}
622+
}
612623
err.emit();
613624
}
614625

src/test/ui/not-panic/not-panic-safe.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ LL | assert::<&mut i32>();
88
| ^^^^^^^^^^^^^^^^^^ `&mut i32` may not be safely transferred across an unwind boundary
99
|
1010
= help: the trait `std::panic::UnwindSafe` is not implemented for `&mut i32`
11+
= note: `std::panic::UnwindSafe` is implemented for `&i32`, but not for `&mut i32`
1112

1213
error: aborting due to previous error
1314

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait Trait {}
2+
3+
struct S;
4+
5+
impl<'a> Trait for &'a mut S {}
6+
7+
fn foo<X: Trait>(_: X) {}
8+
9+
10+
fn main() {
11+
let s = S;
12+
foo(&s); //~ ERROR the trait bound `&S: Trait` is not satisfied
13+
foo(s); //~ ERROR the trait bound `S: Trait` is not satisfied
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error[E0277]: the trait bound `&S: Trait` is not satisfied
2+
--> $DIR/imm-ref-trait-object-literal.rs:12:7
3+
|
4+
LL | fn foo<X: Trait>(_: X) {}
5+
| --- ----- required by this bound in `foo`
6+
...
7+
LL | foo(&s);
8+
| -^
9+
| |
10+
| the trait `Trait` is not implemented for `&S`
11+
| help: consider changing this borrow's mutability: `&mut`
12+
|
13+
= help: the following implementations were found:
14+
<&'a mut S as Trait>
15+
16+
error[E0277]: the trait bound `S: Trait` is not satisfied
17+
--> $DIR/imm-ref-trait-object-literal.rs:13:7
18+
|
19+
LL | fn foo<X: Trait>(_: X) {}
20+
| --- ----- required by this bound in `foo`
21+
...
22+
LL | foo(s);
23+
| ^ the trait `Trait` is not implemented for `S`
24+
|
25+
= help: the following implementations were found:
26+
<&'a mut S as Trait>
27+
28+
error: aborting due to 2 previous errors
29+
30+
For more information about this error, try `rustc --explain E0277`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn test(t: &dyn Iterator<Item=&u64>) -> u64 {
2+
t.min().unwrap() //~ ERROR the `min` method cannot be invoked on a trait object
3+
}
4+
5+
fn main() {
6+
let array = [0u64];
7+
test(&mut array.iter());
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: the `min` method cannot be invoked on a trait object
2+
--> $DIR/imm-ref-trait-object.rs:2:8
3+
|
4+
LL | t.min().unwrap()
5+
| ^^^
6+
|
7+
= note: you need `&mut dyn std::iter::Iterator<Item = &u64>` instead of `&dyn std::iter::Iterator<Item = &u64>`
8+
9+
error: aborting due to previous error
10+

src/test/ui/suggestions/into-str.stderr

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ LL | foo(String::new());
88
| ^^^ the trait `std::convert::From<std::string::String>` is not implemented for `&str`
99
|
1010
= note: to coerce a `std::string::String` into a `&str`, use `&*` as a prefix
11+
= note: `std::convert::From<std::string::String>` is implemented for `&mut str`, but not for `&str`
1112
= note: required because of the requirements on the impl of `std::convert::Into<&str>` for `std::string::String`
1213

1314
error: aborting due to previous error

0 commit comments

Comments
 (0)