Skip to content

Commit f1e6f94

Browse files
committed
Suggest an appropriate type for a binding of method chain
Do the same we do with fully-qualified type suggestions to the suggestion to specify a binding type: ``` error[E0282]: type annotations needed --> $DIR/slice-pattern-refutable.rs:14:9 | LL | let [a, b, c] = Zeroes.into() else { | ^^^^^^^^^ | help: consider giving this pattern a type | LL | let [a, b, c]: _ = Zeroes.into() else { | +++ help: consider giving this pattern a type | LL | let [a, b, c]: [usize; 3] = Zeroes.into() else { | ++++++++++++ ```
1 parent d083646 commit f1e6f94

File tree

5 files changed

+220
-16
lines changed

5 files changed

+220
-16
lines changed

compiler/rustc_trait_selection/src/error_reporting/infer/need_type_info.rs

+135-13
Original file line numberDiff line numberDiff line change
@@ -452,17 +452,134 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
452452
let mut infer_subdiags = Vec::new();
453453
let mut multi_suggestions = Vec::new();
454454
match kind {
455-
InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id } => {
456-
infer_subdiags.push(SourceKindSubdiag::LetLike {
457-
span: insert_span,
458-
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
459-
x_kind: arg_data.where_x_is_kind(ty),
460-
prefix_kind: arg_data.kind.clone(),
461-
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
462-
arg_name: arg_data.name,
463-
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
464-
type_name: ty_to_string(self, ty, def_id),
465-
});
455+
InferSourceKind::LetBinding { insert_span, pattern_name, ty, def_id, hir_id } => {
456+
let mut paths = vec![];
457+
let param_env = ty::ParamEnv::reveal_all();
458+
if let Some(def_id) = def_id
459+
&& let name = self.infcx.tcx.item_name(def_id)
460+
&& let Some(hir_id) = hir_id
461+
&& let expr = self.infcx.tcx.hir().expect_expr(hir_id)
462+
&& let hir::ExprKind::MethodCall(_, rcvr, _, _) = expr.kind
463+
&& let Some(ty) = typeck_results.node_type_opt(rcvr.hir_id)
464+
{
465+
// Look for all the possible implementations to suggest, otherwise we'll show
466+
// just suggest the syntax for the fully qualified path with placeholders.
467+
self.infcx.tcx.for_each_relevant_impl(
468+
self.infcx.tcx.parent(def_id), // Trait `DefId`
469+
ty, // `Self` type
470+
|impl_def_id| {
471+
let impl_args = self.fresh_args_for_item(DUMMY_SP, impl_def_id);
472+
let impl_trait_ref = self
473+
.infcx
474+
.tcx
475+
.impl_trait_ref(impl_def_id)
476+
.unwrap()
477+
.instantiate(self.infcx.tcx, impl_args);
478+
let impl_self_ty = impl_trait_ref.self_ty();
479+
if self.infcx.can_eq(param_env, impl_self_ty, ty) {
480+
// The expr's self type could conform to this impl's self type.
481+
} else {
482+
// Nope, don't bother.
483+
return;
484+
}
485+
let assocs = self.infcx.tcx.associated_items(impl_def_id);
486+
487+
if self
488+
.infcx
489+
.tcx
490+
.is_diagnostic_item(sym::blanket_into_impl, impl_def_id)
491+
&& let Some(did) = self.infcx.tcx.get_diagnostic_item(sym::From)
492+
{
493+
let mut found = false;
494+
self.infcx.tcx.for_each_impl(did, |impl_def_id| {
495+
// We had an `<A as Into<B>::into` and we've hit the blanket
496+
// impl for `From<A>`. So we try and look for the right `From`
497+
// impls that *would* apply. We *could* do this in a generalized
498+
// version by evaluating the `where` clauses, but that would be
499+
// way too involved to implement. Instead we special case the
500+
// arguably most common case of `expr.into()`.
501+
let Some(header) =
502+
self.infcx.tcx.impl_trait_header(impl_def_id)
503+
else {
504+
return;
505+
};
506+
let target = header.trait_ref.skip_binder().args.type_at(0);
507+
let _ty = header.trait_ref.skip_binder().args.type_at(1);
508+
if _ty == ty {
509+
paths.push(format!("{target}"));
510+
found = true;
511+
}
512+
});
513+
if found {
514+
return;
515+
}
516+
}
517+
518+
// We're at the `impl` level, but we want to get the same method we
519+
// called *on this `impl`*, in order to get the right DefId and args.
520+
let Some(assoc) = assocs.filter_by_name_unhygienic(name).next() else {
521+
// The method isn't in this `impl`? Not useful to us then.
522+
return;
523+
};
524+
// Let's ignore the generic params and replace them with `_` in the
525+
// suggested path.
526+
let identity_method = ty::GenericArgs::for_item(
527+
self.infcx.tcx,
528+
assoc.def_id,
529+
|param, _| {
530+
// We don't want to name the arguments, we just want to give an
531+
// idea of what the syntax is.
532+
match param.kind {
533+
ty::GenericParamDefKind::Lifetime => {
534+
self.infcx.tcx.lifetimes.re_erased.into()
535+
}
536+
ty::GenericParamDefKind::Type { .. } => {
537+
self.next_ty_var(DUMMY_SP).into()
538+
}
539+
ty::GenericParamDefKind::Const { .. } => {
540+
self.next_const_var(DUMMY_SP).into()
541+
}
542+
}
543+
},
544+
);
545+
let fn_sig = self
546+
.infcx
547+
.tcx
548+
.fn_sig(assoc.def_id)
549+
.instantiate(self.infcx.tcx, identity_method);
550+
let ret = fn_sig.skip_binder().output();
551+
paths.push(format!("{ret}"));
552+
},
553+
);
554+
}
555+
556+
if paths.is_empty() {
557+
infer_subdiags.push(SourceKindSubdiag::LetLike {
558+
span: insert_span,
559+
name: pattern_name.map(|name| name.to_string()).unwrap_or_else(String::new),
560+
x_kind: arg_data.where_x_is_kind(ty),
561+
prefix_kind: arg_data.kind.clone(),
562+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
563+
arg_name: arg_data.name,
564+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
565+
type_name: ty_to_string(self, ty, def_id),
566+
});
567+
} else {
568+
for type_name in paths {
569+
infer_subdiags.push(SourceKindSubdiag::LetLike {
570+
span: insert_span,
571+
name: pattern_name
572+
.map(|name| name.to_string())
573+
.unwrap_or_else(String::new),
574+
x_kind: arg_data.where_x_is_kind(ty),
575+
prefix_kind: arg_data.kind.clone(),
576+
prefix: arg_data.kind.try_get_prefix().unwrap_or_default(),
577+
arg_name: arg_data.name.clone(),
578+
kind: if pattern_name.is_some() { "with_pattern" } else { "other" },
579+
type_name,
580+
});
581+
}
582+
}
466583
}
467584
InferSourceKind::ClosureArg { insert_span, ty } => {
468585
infer_subdiags.push(SourceKindSubdiag::LetLike {
@@ -731,6 +848,7 @@ enum InferSourceKind<'tcx> {
731848
pattern_name: Option<Ident>,
732849
ty: Ty<'tcx>,
733850
def_id: Option<DefId>,
851+
hir_id: Option<HirId>,
734852
},
735853
ClosureArg {
736854
insert_span: Span,
@@ -916,8 +1034,11 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
9161034
let cost = self.source_cost(&new_source) + self.attempt;
9171035
debug!(?cost);
9181036
self.attempt += 1;
919-
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. }) =
920-
self.infer_source
1037+
if let Some(InferSource { kind: InferSourceKind::GenericArg { def_id: did, .. }, .. })
1038+
| Some(InferSource {
1039+
kind: InferSourceKind::FullyQualifiedMethodCall { def_id: did, .. },
1040+
..
1041+
}) = self.infer_source
9211042
&& let InferSourceKind::LetBinding { ref ty, ref mut def_id, .. } = new_source.kind
9221043
&& ty.is_ty_or_numeric_infer()
9231044
{
@@ -1226,6 +1347,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
12261347
pattern_name: local.pat.simple_ident(),
12271348
ty,
12281349
def_id: None,
1350+
hir_id: local.init.map(|e| e.hir_id),
12291351
},
12301352
})
12311353
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
struct Foo {
2+
inner: u32,
3+
}
4+
5+
struct Bar {
6+
inner: u32,
7+
}
8+
9+
#[derive(Clone, Copy)]
10+
struct Baz {
11+
inner: u32,
12+
}
13+
14+
impl Into<Bar> for Baz {
15+
fn into (self) -> Bar {
16+
Bar {
17+
inner: self.inner,
18+
}
19+
}
20+
}
21+
22+
impl From<Baz> for Foo {
23+
fn from(other: Baz) -> Self {
24+
Self {
25+
inner: other.inner,
26+
}
27+
}
28+
}
29+
30+
fn main() {
31+
let x: Baz = Baz { inner: 42 };
32+
33+
// DOESN'T Compile: Multiple options!
34+
let y = x.into(); //~ ERROR E0283
35+
36+
let y_1: Foo = x.into();
37+
let y_2: Bar = x.into();
38+
39+
let z_1 = Foo::from(y_1);
40+
let z_2 = Bar::from(y_2);
41+
42+
// No type annotations needed, the compiler KNOWS the type must be `Foo`!
43+
let m = magic_foo(x);
44+
}
45+
46+
fn magic_foo(arg: Baz) -> Foo {
47+
arg.into()
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0283]: type annotations needed
2+
--> $DIR/multiple-impl-apply-2.rs:34:9
3+
|
4+
LL | let y = x.into();
5+
| ^ ---- type must be known at this point
6+
|
7+
note: multiple `impl`s satisfying `Baz: Into<_>` found
8+
--> $DIR/multiple-impl-apply-2.rs:14:1
9+
|
10+
LL | impl Into<Bar> for Baz {
11+
| ^^^^^^^^^^^^^^^^^^^^^^
12+
= note: and another `impl` found in the `core` crate:
13+
- impl<T, U> Into<U> for T
14+
where U: From<T>;
15+
help: consider giving `y` an explicit type
16+
|
17+
LL | let y: Foo = x.into();
18+
| +++++
19+
help: consider giving `y` an explicit type
20+
|
21+
LL | let y: Bar = x.into();
22+
| +++++
23+
24+
error: aborting due to 1 previous error
25+
26+
For more information about this error, try `rustc --explain E0283`.

tests/ui/inference/multiple-impl-apply.stderr

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ LL | impl From<Baz> for Foo {
1515
= note: required for `Baz` to implement `Into<_>`
1616
help: consider giving `y` an explicit type
1717
|
18-
LL | let y: /* Type */ = x.into();
19-
| ++++++++++++
18+
LL | let y: Bar = x.into();
19+
| +++++
20+
help: consider giving `y` an explicit type
21+
|
22+
LL | let y: Foo = x.into();
23+
| +++++
2024

2125
error: aborting due to 1 previous error
2226

tests/ui/pattern/slice-pattern-refutable.stderr

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ LL | let [a, b, c] = Zeroes.into() else {
66
|
77
help: consider giving this pattern a type
88
|
9-
LL | let [a, b, c]: /* Type */ = Zeroes.into() else {
9+
LL | let [a, b, c]: _ = Zeroes.into() else {
10+
| +++
11+
help: consider giving this pattern a type
12+
|
13+
LL | let [a, b, c]: [usize; 3] = Zeroes.into() else {
1014
| ++++++++++++
1115

1216
error[E0282]: type annotations needed

0 commit comments

Comments
 (0)