Skip to content

Commit 31b3566

Browse files
committed
When a projection is expected, suggest constraining or calling method
1 parent 3a795fb commit 31b3566

13 files changed

+228
-84
lines changed

src/librustc_infer/infer/error_reporting/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
13881388
terr: &TypeError<'tcx>,
13891389
) {
13901390
let span = cause.span(self.tcx);
1391+
debug!("note_type_err cause={:?} values={:?}, terr={:?}", cause, values, terr);
13911392

13921393
// For some types of errors, expected-found does not make
13931394
// sense, so just ignore the values we were given.
@@ -1599,11 +1600,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
15991600
self.tcx.hir().body_owner_def_id(hir::BodyId { hir_id: cause.body_id })
16001601
});
16011602
self.check_and_note_conflicting_crates(diag, terr);
1602-
self.tcx.note_and_explain_type_err(diag, terr, span, body_owner_def_id.to_def_id());
1603+
self.tcx.note_and_explain_type_err(diag, terr, cause, span, body_owner_def_id.to_def_id());
16031604

16041605
// It reads better to have the error origin as the final
16051606
// thing.
1606-
self.note_error_origin(diag, &cause, exp_found);
1607+
self.note_error_origin(diag, cause, exp_found);
16071608
}
16081609

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

src/librustc_middle/ty/error.rs

Lines changed: 157 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
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;
68
use rustc_span::symbol::sym;
7-
use rustc_span::{BytePos, Span};
9+
use rustc_span::{BytePos, MultiSpan, Span};
810
use rustc_target::spec::abi;
911

1012
use std::borrow::Cow;
@@ -332,6 +334,7 @@ impl<'tcx> TyCtxt<'tcx> {
332334
self,
333335
db: &mut DiagnosticBuilder<'_>,
334336
err: &TypeError<'tcx>,
337+
cause: &ObligationCause<'tcx>,
335338
sp: Span,
336339
body_owner_def_id: DefId,
337340
) {
@@ -370,7 +373,7 @@ impl<'tcx> TyCtxt<'tcx> {
370373
sp,
371374
"use a float literal",
372375
format!("{}.0", snippet),
373-
Applicability::MachineApplicable,
376+
MachineApplicable,
374377
);
375378
}
376379
}
@@ -451,41 +454,27 @@ impl<T> Trait<T> for X {
451454
db.span_label(p_span, "this type parameter");
452455
}
453456
}
454-
(ty::Projection(_), _) => {
455-
db.note(&format!(
456-
"consider constraining the associated type `{}` to `{}` or calling a \
457-
method that returns `{0}`",
458-
values.expected, values.found,
459-
));
460-
if self.sess.teach(&db.get_code().unwrap()) {
461-
db.help(
462-
"given an associated type `T` and a method `foo`:
463-
```
464-
trait Trait {
465-
type T;
466-
fn foo(&self) -> Self::T;
467-
}
468-
```
469-
the only way of implementing method `foo` is to constrain `T` with an explicit associated type:
470-
```
471-
impl Trait for X {
472-
type T = String;
473-
fn foo(&self) -> Self::T { String::new() }
474-
}
475-
```",
476-
);
477-
}
478-
db.note(
479-
"for more information, visit \
480-
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html",
457+
(ty::Projection(proj_ty), _) => {
458+
self.expected_projection(
459+
db,
460+
proj_ty,
461+
values,
462+
body_owner_def_id,
463+
&cause.code,
481464
);
482465
}
483466
(_, ty::Projection(proj_ty)) => {
484467
let msg = format!(
485468
"consider constraining the associated type `{}` to `{}`",
486469
values.found, values.expected,
487470
);
488-
if !self.suggest_constraint(db, &msg, body_owner_def_id, proj_ty, values) {
471+
if !self.suggest_constraint(
472+
db,
473+
&msg,
474+
body_owner_def_id,
475+
proj_ty,
476+
values.expected,
477+
) {
489478
db.help(&msg);
490479
db.note(
491480
"for more information, visit \
@@ -533,7 +522,7 @@ impl Trait for X {
533522
msg: &str,
534523
body_owner_def_id: DefId,
535524
proj_ty: &ty::ProjectionTy<'tcx>,
536-
values: &ExpectedFound<Ty<'tcx>>,
525+
ty: Ty<'tcx>,
537526
) -> bool {
538527
let assoc = self.associated_item(proj_ty.item_def_id);
539528
let trait_ref = proj_ty.trait_ref(*self);
@@ -570,7 +559,7 @@ impl Trait for X {
570559
&trait_ref,
571560
pred.bounds,
572561
&assoc,
573-
values,
562+
ty,
574563
msg,
575564
) {
576565
return true;
@@ -587,7 +576,7 @@ impl Trait for X {
587576
&trait_ref,
588577
param.bounds,
589578
&assoc,
590-
values,
579+
ty,
591580
msg,
592581
);
593582
}
@@ -597,15 +586,145 @@ impl Trait for X {
597586
false
598587
}
599588

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

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
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)