Skip to content

Commit 0496fde

Browse files
committed
Note inference failures using ? conversion
1 parent c97f11a commit 0496fde

10 files changed

+239
-44
lines changed

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use rustc_middle::ty::{
6969
subst::{Subst, SubstsRef},
7070
Region, Ty, TyCtxt, TypeFoldable,
7171
};
72-
use rustc_span::{BytePos, DesugaringKind, Pos, Span};
72+
use rustc_span::{sym, BytePos, DesugaringKind, Pos, Span};
7373
use rustc_target::spec::abi;
7474
use std::ops::ControlFlow;
7575
use std::{cmp, fmt};
@@ -2282,6 +2282,14 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
22822282
self.note_region_origin(&mut err, &sub_origin);
22832283
err.emit();
22842284
}
2285+
2286+
/// Determine whether an error associated with the given span and definition
2287+
/// should be treated as being caused by the implicit `From` conversion
2288+
/// within `?` desugaring.
2289+
pub fn is_try_conversion(&self, span: Span, trait_def_id: DefId) -> bool {
2290+
span.is_desugaring(DesugaringKind::QuestionMark)
2291+
&& self.tcx.is_diagnostic_item(sym::from_trait, trait_def_id)
2292+
}
22852293
}
22862294

22872295
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {

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

+159-29
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::infer::InferCtxt;
33
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
44
use rustc_hir as hir;
55
use rustc_hir::def::{DefKind, Namespace};
6+
use rustc_hir::def_id::DefId;
67
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
78
use rustc_hir::{Body, Expr, ExprKind, FnRetTy, HirId, Local, Pat};
89
use rustc_middle::hir::map::Map;
@@ -25,6 +26,7 @@ struct FindHirNodeVisitor<'a, 'tcx> {
2526
found_closure: Option<&'tcx Expr<'tcx>>,
2627
found_method_call: Option<&'tcx Expr<'tcx>>,
2728
found_exact_method_call: Option<&'tcx Expr<'tcx>>,
29+
found_use_diagnostic: Option<UseDiagnostic<'tcx>>,
2830
}
2931

3032
impl<'a, 'tcx> FindHirNodeVisitor<'a, 'tcx> {
@@ -39,34 +41,43 @@ impl<'a, 'tcx> FindHirNodeVisitor<'a, 'tcx> {
3941
found_closure: None,
4042
found_method_call: None,
4143
found_exact_method_call: None,
44+
found_use_diagnostic: None,
4245
}
4346
}
4447

45-
fn node_ty_contains_target(&mut self, hir_id: HirId) -> Option<Ty<'tcx>> {
46-
self.infcx
47-
.in_progress_typeck_results
48-
.and_then(|typeck_results| typeck_results.borrow().node_type_opt(hir_id))
49-
.map(|ty| self.infcx.resolve_vars_if_possible(ty))
50-
.filter(|ty| {
51-
ty.walk().any(|inner| {
52-
inner == self.target
53-
|| match (inner.unpack(), self.target.unpack()) {
54-
(GenericArgKind::Type(inner_ty), GenericArgKind::Type(target_ty)) => {
55-
use ty::{Infer, TyVar};
56-
match (inner_ty.kind(), target_ty.kind()) {
57-
(&Infer(TyVar(a_vid)), &Infer(TyVar(b_vid))) => self
58-
.infcx
59-
.inner
60-
.borrow_mut()
61-
.type_variables()
62-
.sub_unified(a_vid, b_vid),
63-
_ => false,
64-
}
48+
fn node_type_opt(&self, hir_id: HirId) -> Option<Ty<'tcx>> {
49+
self.infcx.in_progress_typeck_results?.borrow().node_type_opt(hir_id)
50+
}
51+
52+
fn node_ty_contains_target(&self, hir_id: HirId) -> Option<Ty<'tcx>> {
53+
self.node_type_opt(hir_id).map(|ty| self.infcx.resolve_vars_if_possible(ty)).filter(|ty| {
54+
ty.walk().any(|inner| {
55+
inner == self.target
56+
|| match (inner.unpack(), self.target.unpack()) {
57+
(GenericArgKind::Type(inner_ty), GenericArgKind::Type(target_ty)) => {
58+
use ty::{Infer, TyVar};
59+
match (inner_ty.kind(), target_ty.kind()) {
60+
(&Infer(TyVar(a_vid)), &Infer(TyVar(b_vid))) => self
61+
.infcx
62+
.inner
63+
.borrow_mut()
64+
.type_variables()
65+
.sub_unified(a_vid, b_vid),
66+
_ => false,
6567
}
66-
_ => false,
6768
}
68-
})
69+
_ => false,
70+
}
6971
})
72+
})
73+
}
74+
75+
/// Determine whether the expression, assumed to be the callee within a `Call`,
76+
/// corresponds to the `From::from` emitted in desugaring of the `?` operator.
77+
fn is_try_conversion(&self, callee: &Expr<'tcx>) -> bool {
78+
self.infcx
79+
.trait_def_from_hir_fn(callee.hir_id)
80+
.map_or(false, |def_id| self.infcx.is_try_conversion(callee.span, def_id))
7081
}
7182
}
7283

@@ -119,17 +130,91 @@ impl<'a, 'tcx> Visitor<'tcx> for FindHirNodeVisitor<'a, 'tcx> {
119130
// are handled specially, but instead they should be handled in `annotate_method_call`,
120131
// which currently doesn't work because this evaluates to `false` for const arguments.
121132
// See https://github.com/rust-lang/rust/pull/77758 for more details.
122-
if self.node_ty_contains_target(expr.hir_id).is_some() {
133+
if let Some(ty) = self.node_ty_contains_target(expr.hir_id) {
123134
match expr.kind {
124135
ExprKind::Closure(..) => self.found_closure = Some(&expr),
125136
ExprKind::MethodCall(..) => self.found_method_call = Some(&expr),
137+
138+
// If the given expression falls within the target span and is a
139+
// `From::from(e)` call emitted during desugaring of the `?` operator,
140+
// extract the types inferred before and after the call
141+
ExprKind::Call(callee, [arg])
142+
if self.target_span.contains(expr.span)
143+
&& self.found_use_diagnostic.is_none()
144+
&& self.is_try_conversion(callee) =>
145+
{
146+
self.found_use_diagnostic = self.node_type_opt(arg.hir_id).map(|pre_ty| {
147+
UseDiagnostic::TryConversion { pre_ty, post_ty: ty, span: callee.span }
148+
});
149+
}
126150
_ => {}
127151
}
128152
}
129153
intravisit::walk_expr(self, expr);
130154
}
131155
}
132156

157+
/// An observation about the use site of a type to be emitted as an additional
158+
/// note in an inference failure error.
159+
enum UseDiagnostic<'tcx> {
160+
/// Records the types inferred before and after `From::from` is called on the
161+
/// error value within the desugaring of the `?` operator.
162+
TryConversion { pre_ty: Ty<'tcx>, post_ty: Ty<'tcx>, span: Span },
163+
}
164+
165+
impl UseDiagnostic<'_> {
166+
/// Return a descriptor of the value at the use site
167+
fn descr(&self) -> &'static str {
168+
match self {
169+
Self::TryConversion { .. } => "`?` error",
170+
}
171+
}
172+
173+
/// Return a descriptor of the type at the use site
174+
fn type_descr(&self) -> &'static str {
175+
match self {
176+
Self::TryConversion { .. } => "`?` error type",
177+
}
178+
}
179+
180+
fn applies_to(&self, span: Span) -> bool {
181+
match *self {
182+
// In some cases the span for an inference failure due to try
183+
// conversion contains the antecedent expression as well as the `?`
184+
Self::TryConversion { span: s, .. } => span.contains(s) && span.hi() == s.hi(),
185+
}
186+
}
187+
188+
fn attach_note(&self, err: &mut DiagnosticBuilder<'_>) {
189+
match *self {
190+
Self::TryConversion { pre_ty, post_ty, .. } => {
191+
let pre_ty = pre_ty.to_string();
192+
let post_ty = post_ty.to_string();
193+
194+
let intro = "the `?` operation implicitly converts the error value";
195+
196+
let msg = match (pre_ty.as_str(), post_ty.as_str()) {
197+
("_", "_") => format!("{} using the `From` trait", intro),
198+
(_, "_") => {
199+
format!("{} into a type implementing `From<{}>`", intro, pre_ty)
200+
}
201+
("_", _) => {
202+
format!("{} into `{}` using the `From` trait", intro, post_ty)
203+
}
204+
(_, _) => {
205+
format!(
206+
"{} into `{}` using its implementation of `From<{}>`",
207+
intro, post_ty, pre_ty
208+
)
209+
}
210+
};
211+
212+
err.note(&msg);
213+
}
214+
}
215+
}
216+
}
217+
133218
/// Suggest giving an appropriate return type to a closure expression.
134219
fn closure_return_type_suggestion(
135220
span: Span,
@@ -139,6 +224,7 @@ fn closure_return_type_suggestion(
139224
descr: &str,
140225
name: &str,
141226
ret: &str,
227+
use_diag: Option<&UseDiagnostic<'_>>,
142228
parent_name: Option<String>,
143229
parent_descr: Option<&str>,
144230
) {
@@ -160,7 +246,15 @@ fn closure_return_type_suggestion(
160246
);
161247
err.span_label(
162248
span,
163-
InferCtxt::cannot_infer_msg("type", &name, &descr, parent_name, parent_descr),
249+
InferCtxt::cannot_infer_msg(
250+
span,
251+
"type",
252+
&name,
253+
&descr,
254+
use_diag,
255+
parent_name,
256+
parent_descr,
257+
),
164258
);
165259
}
166260

@@ -420,7 +514,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
420514

421515
// When `arg_data.name` corresponds to a type argument, show the path of the full type we're
422516
// trying to infer. In the following example, `ty_msg` contains
423-
// " in `std::result::Result<i32, E>`":
517+
// " for `std::result::Result<i32, E>`":
424518
// ```
425519
// error[E0282]: type annotations needed for `std::result::Result<i32, E>`
426520
// --> file.rs:L:CC
@@ -438,6 +532,13 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
438532
error_code,
439533
);
440534

535+
let use_diag = local_visitor.found_use_diagnostic.as_ref();
536+
if let Some(use_diag) = use_diag {
537+
if use_diag.applies_to(err_span) {
538+
use_diag.attach_note(&mut err);
539+
}
540+
}
541+
441542
let suffix = match local_visitor.found_node_ty {
442543
Some(ty) if ty.is_closure() => {
443544
let substs =
@@ -460,6 +561,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
460561
&arg_data.description,
461562
&arg_data.name,
462563
&ret,
564+
use_diag,
463565
arg_data.parent_name,
464566
arg_data.parent_description,
465567
);
@@ -634,9 +736,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
634736
err.span_label(
635737
span,
636738
InferCtxt::cannot_infer_msg(
739+
span,
637740
kind_str,
638741
&arg_data.name,
639742
&arg_data.description,
743+
use_diag,
640744
arg_data.parent_name,
641745
arg_data.parent_description,
642746
),
@@ -646,6 +750,20 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
646750
err
647751
}
648752

753+
fn trait_def_from_hir_fn(&self, hir_id: hir::HirId) -> Option<DefId> {
754+
// The DefId will be the method's trait item ID unless this is an inherent impl
755+
if let Some((DefKind::AssocFn, def_id)) =
756+
self.in_progress_typeck_results?.borrow().type_dependent_def(hir_id)
757+
{
758+
return self
759+
.tcx
760+
.parent(def_id)
761+
.filter(|&parent_def_id| self.tcx.is_trait(parent_def_id));
762+
}
763+
764+
None
765+
}
766+
649767
/// If the `FnSig` for the method call can be found and type arguments are identified as
650768
/// needed, suggest annotating the call, otherwise point out the resulting type of the call.
651769
fn annotate_method_call(
@@ -711,9 +829,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
711829
err.span_label(
712830
span,
713831
InferCtxt::cannot_infer_msg(
832+
span,
714833
"type",
715834
&data.name,
716835
&data.description,
836+
None,
717837
data.parent_name,
718838
data.parent_description,
719839
),
@@ -722,25 +842,35 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
722842
}
723843

724844
fn cannot_infer_msg(
845+
span: Span,
725846
kind_str: &str,
726847
type_name: &str,
727848
descr: &str,
849+
use_diag: Option<&UseDiagnostic<'_>>,
728850
parent_name: Option<String>,
729851
parent_descr: Option<&str>,
730852
) -> String {
853+
let use_diag = use_diag.filter(|d| d.applies_to(span));
854+
731855
if type_name == "_" {
732-
format!("cannot infer {}", kind_str)
856+
if let Some(use_diag) = use_diag {
857+
format!("cannot infer {} of {}", kind_str, use_diag.descr())
858+
} else {
859+
format!("cannot infer {}", kind_str)
860+
}
733861
} else {
734-
let parent_desc = if let Some(parent_name) = parent_name {
862+
let extra_descr = if let Some(parent_name) = parent_name {
735863
let parent_type_descr = if let Some(parent_descr) = parent_descr {
736864
format!(" the {}", parent_descr)
737865
} else {
738866
"".into()
739867
};
740868

741869
format!(" declared on{} `{}`", parent_type_descr, parent_name)
870+
} else if let Some(use_diag) = use_diag {
871+
format!(" in {}", use_diag.type_descr())
742872
} else {
743-
"".to_string()
873+
"".into()
744874
};
745875

746876
// FIXME: We really shouldn't be dealing with strings here
@@ -749,7 +879,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
749879
// For example: "cannot infer type for type parameter `T`"
750880
format!(
751881
"cannot infer {} {} {} `{}`{}",
752-
kind_str, preposition, descr, type_name, parent_desc
882+
kind_str, preposition, descr, type_name, extra_descr
753883
)
754884
}
755885
}

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
280280
let OnUnimplementedNote { message, label, note, enclosing_scope } =
281281
self.on_unimplemented_note(trait_ref, obligation);
282282
let have_alt_message = message.is_some() || label.is_some();
283-
let is_try = self
284-
.tcx
285-
.sess
286-
.source_map()
287-
.span_to_snippet(span)
288-
.map(|s| &s == "?")
289-
.unwrap_or(false);
290-
let is_from = self.tcx.get_diagnostic_item(sym::from_trait)
291-
== Some(trait_ref.def_id());
283+
let is_try_conversion = self.is_try_conversion(span, trait_ref.def_id());
292284
let is_unsize =
293285
{ Some(trait_ref.def_id()) == self.tcx.lang_items().unsize_trait() };
294-
let (message, note) = if is_try && is_from {
286+
let (message, note) = if is_try_conversion {
295287
(
296288
Some(format!(
297289
"`?` couldn't convert the error to `{}`",
@@ -319,7 +311,7 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
319311
))
320312
);
321313

322-
if is_try && is_from {
314+
if is_try_conversion {
323315
let none_error = self
324316
.tcx
325317
.get_diagnostic_item(sym::none_error)

src/test/ui/inference/cannot-infer-async-enabled-impl-trait-bindings.stderr

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ error[E0282]: type annotations needed for `impl Future`
1313
LL | let fut = async {
1414
| --- consider giving `fut` the explicit type `impl Future`, with the type parameters specified
1515
LL | make_unit()?;
16-
| ^ cannot infer type
16+
| ^ cannot infer type of `?` error
17+
|
18+
= note: the `?` operation implicitly converts the error value into a type implementing `From<std::io::Error>`
1719

1820
error: aborting due to previous error; 1 warning emitted
1921

src/test/ui/inference/cannot-infer-async.stderr

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ error[E0282]: type annotations needed
44
LL | let fut = async {
55
| --- consider giving `fut` a type
66
LL | make_unit()?;
7-
| ^ cannot infer type
7+
| ^ cannot infer type of `?` error
8+
|
9+
= note: the `?` operation implicitly converts the error value into a type implementing `From<std::io::Error>`
810

911
error: aborting due to previous error
1012

0 commit comments

Comments
 (0)