Skip to content

Commit 9580059

Browse files
committed
When a projection is expected, suggest constraining or calling method
1 parent a3e7f64 commit 9580059

13 files changed

+228
-84
lines changed

src/librustc_infer/infer/error_reporting/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
13841384
terr: &TypeError<'tcx>,
13851385
) {
13861386
let span = cause.span(self.tcx);
1387+
debug!("note_type_err cause={:?} values={:?}, terr={:?}", cause, values, terr);
13871388

13881389
// For some types of errors, expected-found does not make
13891390
// sense, so just ignore the values we were given.
@@ -1595,11 +1596,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
15951596
self.tcx.hir().body_owner_def_id(hir::BodyId { hir_id: cause.body_id })
15961597
});
15971598
self.check_and_note_conflicting_crates(diag, terr);
1598-
self.tcx.note_and_explain_type_err(diag, terr, span, body_owner_def_id.to_def_id());
1599+
self.tcx.note_and_explain_type_err(diag, terr, cause, span, body_owner_def_id.to_def_id());
15991600

16001601
// It reads better to have the error origin as the final
16011602
// thing.
1602-
self.note_error_origin(diag, &cause, exp_found);
1603+
self.note_error_origin(diag, cause, exp_found);
16031604
}
16041605

16051606
/// When encountering a case where `.as_ref()` on a `Result` or `Option` would be appropriate,

src/librustc_middle/ty/error.rs

+157-41
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
use crate::traits::{ObligationCause, ObligationCauseCode};
12
use crate::ty::{self, BoundRegion, Region, Ty, TyCtxt};
23
use rustc_ast::ast;
3-
use rustc_errors::{pluralize, Applicability, DiagnosticBuilder};
4+
use rustc_errors::Applicability::{MachineApplicable, MaybeIncorrect};
5+
use rustc_errors::{pluralize, DiagnosticBuilder};
46
use rustc_hir as hir;
57
use rustc_hir::def_id::DefId;
6-
use rustc_span::{BytePos, Span};
8+
use rustc_span::{BytePos, MultiSpan, Span};
79
use rustc_target::spec::abi;
810

911
use std::borrow::Cow;
@@ -323,6 +325,7 @@ impl<'tcx> TyCtxt<'tcx> {
323325
self,
324326
db: &mut DiagnosticBuilder<'_>,
325327
err: &TypeError<'tcx>,
328+
cause: &ObligationCause<'tcx>,
326329
sp: Span,
327330
body_owner_def_id: DefId,
328331
) {
@@ -361,7 +364,7 @@ impl<'tcx> TyCtxt<'tcx> {
361364
sp,
362365
"use a float literal",
363366
format!("{}.0", snippet),
364-
Applicability::MachineApplicable,
367+
MachineApplicable,
365368
);
366369
}
367370
}
@@ -442,41 +445,27 @@ impl<T> Trait<T> for X {
442445
db.span_label(p_span, "this type parameter");
443446
}
444447
}
445-
(ty::Projection(_), _) => {
446-
db.note(&format!(
447-
"consider constraining the associated type `{}` to `{}` or calling a \
448-
method that returns `{0}`",
449-
values.expected, values.found,
450-
));
451-
if self.sess.teach(&db.get_code().unwrap()) {
452-
db.help(
453-
"given an associated type `T` and a method `foo`:
454-
```
455-
trait Trait {
456-
type T;
457-
fn foo(&self) -> Self::T;
458-
}
459-
```
460-
the only way of implementing method `foo` is to constrain `T` with an explicit associated type:
461-
```
462-
impl Trait for X {
463-
type T = String;
464-
fn foo(&self) -> Self::T { String::new() }
465-
}
466-
```",
467-
);
468-
}
469-
db.note(
470-
"for more information, visit \
471-
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html",
448+
(ty::Projection(proj_ty), _) => {
449+
self.expected_projection(
450+
db,
451+
proj_ty,
452+
values,
453+
body_owner_def_id,
454+
&cause.code,
472455
);
473456
}
474457
(_, ty::Projection(proj_ty)) => {
475458
let msg = format!(
476459
"consider constraining the associated type `{}` to `{}`",
477460
values.found, values.expected,
478461
);
479-
if !self.suggest_constraint(db, &msg, body_owner_def_id, proj_ty, values) {
462+
if !self.suggest_constraint(
463+
db,
464+
&msg,
465+
body_owner_def_id,
466+
proj_ty,
467+
values.expected,
468+
) {
480469
db.help(&msg);
481470
db.note(
482471
"for more information, visit \
@@ -512,7 +501,7 @@ impl Trait for X {
512501
msg: &str,
513502
body_owner_def_id: DefId,
514503
proj_ty: &ty::ProjectionTy<'tcx>,
515-
values: &ExpectedFound<Ty<'tcx>>,
504+
ty: Ty<'tcx>,
516505
) -> bool {
517506
let assoc = self.associated_item(proj_ty.item_def_id);
518507
let trait_ref = proj_ty.trait_ref(*self);
@@ -549,7 +538,7 @@ impl Trait for X {
549538
&trait_ref,
550539
pred.bounds,
551540
&assoc,
552-
values,
541+
ty,
553542
msg,
554543
) {
555544
return true;
@@ -566,7 +555,7 @@ impl Trait for X {
566555
&trait_ref,
567556
param.bounds,
568557
&assoc,
569-
values,
558+
ty,
570559
msg,
571560
);
572561
}
@@ -576,15 +565,145 @@ impl Trait for X {
576565
false
577566
}
578567

568+
fn expected_projection(
569+
&self,
570+
db: &mut DiagnosticBuilder<'_>,
571+
proj_ty: &ty::ProjectionTy<'tcx>,
572+
values: &ExpectedFound<Ty<'tcx>>,
573+
body_owner_def_id: DefId,
574+
cause_code: &ObligationCauseCode<'_>,
575+
) {
576+
let msg = format!(
577+
"consider constraining the associated type `{}` to `{}`",
578+
values.expected, values.found
579+
);
580+
let mut suggested = false;
581+
let body_owner = self.hir().get_if_local(body_owner_def_id);
582+
let current_method_ident = body_owner.and_then(|n| n.ident()).map(|i| i.name);
583+
584+
let callable_scope = match body_owner {
585+
Some(
586+
hir::Node::Item(hir::Item {
587+
kind:
588+
hir::ItemKind::Trait(..)
589+
| hir::ItemKind::Impl { .. }
590+
| hir::ItemKind::Const(..)
591+
| hir::ItemKind::Enum(..)
592+
| hir::ItemKind::Struct(..)
593+
| hir::ItemKind::Union(..),
594+
..
595+
})
596+
| hir::Node::TraitItem(hir::TraitItem {
597+
kind: hir::TraitItemKind::Const(..) | hir::TraitItemKind::Type(..),
598+
..
599+
})
600+
| hir::Node::ImplItem(hir::ImplItem {
601+
kind: hir::ImplItemKind::Const(..) | hir::ImplItemKind::TyAlias(..),
602+
..
603+
}),
604+
) => false,
605+
_ => true,
606+
};
607+
let impl_comparison =
608+
matches!(cause_code, ObligationCauseCode::CompareImplMethodObligation { .. });
609+
if !callable_scope || impl_comparison {
610+
// We do not want to suggest calling functions when the reason of the
611+
// type error is a comparison of an `impl` with its `trait` or when the
612+
// scope is outside of a `Body`.
613+
} else {
614+
let assoc = self.associated_item(proj_ty.item_def_id);
615+
let items = self.associated_items(assoc.container.id());
616+
// Find all the methods in the trait that could be called to construct the
617+
// expected associated type.
618+
let methods: Vec<(Span, String)> = items
619+
.items
620+
.iter()
621+
.filter(|(name, item)| {
622+
ty::AssocKind::Method == item.kind && Some(**name) != current_method_ident
623+
})
624+
.filter_map(|(_, item)| {
625+
let method = self.fn_sig(item.def_id);
626+
match method.output().skip_binder().kind {
627+
ty::Projection(ty::ProjectionTy { item_def_id, .. })
628+
if item_def_id == proj_ty.item_def_id =>
629+
{
630+
Some((
631+
self.sess.source_map().guess_head_span(self.def_span(item.def_id)),
632+
format!("consider calling `{}`", self.def_path_str(item.def_id)),
633+
))
634+
}
635+
_ => None,
636+
}
637+
})
638+
.collect();
639+
if !methods.is_empty() {
640+
// Use a single `help:` to show all the methods in the trait that can
641+
// be used to construct the expected associated type.
642+
let mut span: MultiSpan =
643+
methods.iter().map(|(sp, _)| *sp).collect::<Vec<Span>>().into();
644+
let msg = format!(
645+
"{some} method{s} {are} available that return{r} `{ty}`",
646+
some = if methods.len() == 1 { "a" } else { "some" },
647+
s = pluralize!(methods.len()),
648+
are = if methods.len() == 1 { "is" } else { "are" },
649+
r = if methods.len() == 1 { "s" } else { "" },
650+
ty = values.expected
651+
);
652+
for (sp, label) in methods.into_iter() {
653+
span.push_span_label(sp, label);
654+
}
655+
db.span_help(span, &msg);
656+
suggested = true;
657+
}
658+
// Possibly suggest constraining the associated type to conform to the
659+
// found type.
660+
suggested |=
661+
self.suggest_constraint(db, &msg, body_owner_def_id, proj_ty, values.found);
662+
}
663+
if !suggested && !impl_comparison {
664+
// Generic suggestion when we can't be more specific.
665+
if callable_scope {
666+
db.help(
667+
&format!("{} or calling a method that returns `{}`", msg, values.expected,),
668+
);
669+
} else {
670+
db.help(&msg);
671+
}
672+
db.note(
673+
"for more information, visit \
674+
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html",
675+
);
676+
}
677+
if self.sess.teach(&db.get_code().unwrap()) {
678+
db.help(
679+
"given an associated type `T` and a method `foo`:
680+
```
681+
trait Trait {
682+
type T;
683+
fn foo(&self) -> Self::T;
684+
}
685+
```
686+
the only way of implementing method `foo` is to constrain `T` with an explicit associated type:
687+
```
688+
impl Trait for X {
689+
type T = String;
690+
fn foo(&self) -> Self::T { String::new() }
691+
}
692+
```",
693+
);
694+
}
695+
}
696+
579697
fn constrain_associated_type_structured_suggestion(
580698
&self,
581699
db: &mut DiagnosticBuilder<'_>,
582700
trait_ref: &ty::TraitRef<'tcx>,
583701
bounds: hir::GenericBounds<'_>,
584702
assoc: &ty::AssocItem,
585-
values: &ExpectedFound<Ty<'tcx>>,
703+
ty: Ty<'tcx>,
586704
msg: &str,
587705
) -> bool {
706+
// FIXME: we would want to call `resolve_vars_if_possible` on `ty` before suggesting.
588707
for bound in bounds {
589708
match bound {
590709
hir::GenericBound::Trait(ptr, hir::TraitBoundModifier::None) => {
@@ -599,14 +718,11 @@ impl Trait for X {
599718
let (span, sugg) = if has_params {
600719
let pos = ptr.span.hi() - BytePos(1);
601720
let span = Span::new(pos, pos, ptr.span.ctxt());
602-
(span, format!(", {} = {}", assoc.ident, values.expected))
721+
(span, format!(", {} = {}", assoc.ident, ty))
603722
} else {
604-
(
605-
ptr.span.shrink_to_hi(),
606-
format!("<{} = {}>", assoc.ident, values.expected),
607-
)
723+
(ptr.span.shrink_to_hi(), format!("<{} = {}>", assoc.ident, ty))
608724
};
609-
db.span_suggestion(span, msg, sugg, Applicability::MaybeIncorrect);
725+
db.span_suggestion_verbose(span, msg, sugg, MaybeIncorrect);
610726
return true;
611727
}
612728
}

src/test/ui/associated-const/associated-const-generic-obligations.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ LL | const FROM: &'static str = "foo";
99
|
1010
= note: expected associated type `<T as Foo>::Out`
1111
found reference `&'static str`
12-
= note: consider constraining the associated type `<T as Foo>::Out` to `&'static str` or calling a method that returns `<T as Foo>::Out`
12+
= help: consider constraining the associated type `<T as Foo>::Out` to `&'static str`
1313
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
1414

1515
error: aborting due to previous error

src/test/ui/associated-types/defaults-in-other-trait-items.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ trait Tr {
1010
//~^ ERROR mismatched types
1111
//~| NOTE expected associated type, found `()`
1212
//~| NOTE expected associated type `<Self as Tr>::A`
13-
//~| NOTE consider constraining the associated type
13+
//~| HELP consider constraining the associated type
1414
//~| NOTE for more information, visit
1515
}
1616
}
@@ -38,7 +38,7 @@ trait AssocConst {
3838
//~^ ERROR mismatched types
3939
//~| NOTE expected associated type, found `u8`
4040
//~| NOTE expected associated type `<Self as AssocConst>::Ty`
41-
//~| NOTE consider constraining the associated type
41+
//~| HELP consider constraining the associated type
4242
//~| NOTE for more information, visit
4343
}
4444

src/test/ui/associated-types/defaults-in-other-trait-items.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ LL | let () = p;
66
|
77
= note: expected associated type `<Self as Tr>::A`
88
found unit type `()`
9-
= note: consider constraining the associated type `<Self as Tr>::A` to `()` or calling a method that returns `<Self as Tr>::A`
9+
= help: consider constraining the associated type `<Self as Tr>::A` to `()` or calling a method that returns `<Self as Tr>::A`
1010
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
1111

1212
error[E0308]: mismatched types
@@ -17,7 +17,7 @@ LL | const C: Self::Ty = 0u8;
1717
|
1818
= note: expected associated type `<Self as AssocConst>::Ty`
1919
found type `u8`
20-
= note: consider constraining the associated type `<Self as AssocConst>::Ty` to `u8` or calling a method that returns `<Self as AssocConst>::Ty`
20+
= help: consider constraining the associated type `<Self as AssocConst>::Ty` to `u8`
2121
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
2222

2323
error: aborting due to 2 previous errors

0 commit comments

Comments
 (0)