Greatly improve error reporting for futures and generators in note_obligation_cause_code

Most futures don't go through this code path, because they're caught by
`maybe_note_obligation_cause_for_async_await`. But all generators do,
and `maybe_note` is imperfect and doesn't catch all futures. Improve the error message for those it misses.

At some point, we may want to consider unifying this with the code for `maybe_note_async_await`,
so that `async_await` notes all parent constraints, and `note_obligation` can point to yield points.
But both functions are quite complicated, and it's not clear to me how to combine them;
this seems like a good incremental improvement.
This commit is contained in:
Joshua Nelson 2022-06-19 13:59:36 -05:00
parent cdcc53b7dc
commit 1deca0425d
21 changed files with 389 additions and 72 deletions

View file

@ -8,6 +8,7 @@ use crate::infer::InferCtxt;
use crate::traits::normalize_to;
use hir::HirId;
use rustc_ast::Movability;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{
@ -2397,24 +2398,104 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
{
let parent_trait_ref =
self.resolve_vars_if_possible(data.parent_trait_pred);
let ty = parent_trait_ref.skip_binder().self_ty();
matches!(ty.kind(), ty::Generator(..))
|| matches!(ty.kind(), ty::Closure(..))
let nested_ty = parent_trait_ref.skip_binder().self_ty();
matches!(nested_ty.kind(), ty::Generator(..))
|| matches!(nested_ty.kind(), ty::Closure(..))
} else {
false
}
};
let future_trait = self.tcx.lang_items().future_trait().unwrap();
let opaque_ty_is_future = |def_id| {
self.tcx.explicit_item_bounds(def_id).iter().any(|(predicate, _)| {
if let ty::PredicateKind::Trait(trait_predicate) =
predicate.kind().skip_binder()
{
trait_predicate.trait_ref.def_id == future_trait
} else {
false
}
})
};
let from_generator = tcx.lang_items().from_generator_fn().unwrap();
// Don't print the tuple of capture types
if !is_upvar_tys_infer_tuple {
let msg = format!("required because it appears within the type `{}`", ty);
match ty.kind() {
ty::Adt(def, _) => match self.tcx.opt_item_ident(def.did()) {
Some(ident) => err.span_note(ident.span, &msg),
None => err.note(&msg),
},
_ => err.note(&msg),
};
'print: {
if !is_upvar_tys_infer_tuple {
let msg = format!("required because it appears within the type `{}`", ty);
match ty.kind() {
ty::Adt(def, _) => {
// `gen_future` is used in all async functions; it doesn't add any additional info.
if self.tcx.is_diagnostic_item(sym::gen_future, def.did()) {
break 'print;
}
match self.tcx.opt_item_ident(def.did()) {
Some(ident) => err.span_note(ident.span, &msg),
None => err.note(&msg),
}
}
ty::Opaque(def_id, _) => {
// Avoid printing the future from `core::future::from_generator`, it's not helpful
if tcx.parent(*def_id) == from_generator {
break 'print;
}
// If the previous type is `from_generator`, this is the future generated by the body of an async function.
// Avoid printing it twice (it was already printed in the `ty::Generator` arm below).
let is_future = opaque_ty_is_future(def_id);
debug!(
?obligated_types,
?is_future,
"note_obligation_cause_code: check for async fn"
);
if opaque_ty_is_future(def_id)
&& obligated_types.last().map_or(false, |ty| match ty.kind() {
ty::Opaque(last_def_id, _) => {
tcx.parent(*last_def_id) == from_generator
}
_ => false,
})
{
break 'print;
}
err.span_note(self.tcx.def_span(def_id), &msg)
}
ty::GeneratorWitness(bound_tys) => {
use std::fmt::Write;
// FIXME: this is kind of an unusual format for rustc, can we make it more clear?
// Maybe we should just remove this note altogether?
// FIXME: only print types which don't meet the trait requirement
let mut msg =
"required because it captures the following types: ".to_owned();
for ty in bound_tys.skip_binder() {
write!(msg, "`{}`, ", ty).unwrap();
}
err.note(msg.trim_end_matches(", "))
}
ty::Generator(def_id, _, movability) => {
let sp = self.tcx.def_span(def_id);
// Special-case this to say "async block" instead of `[static generator]`.
let kind = if *movability == Movability::Static {
"async block"
} else {
"generator"
};
err.span_note(
sp,
&format!("required because it's used within this {}", kind),
)
}
ty::Closure(def_id, _) => err.span_note(
self.tcx.def_span(def_id),
&format!("required because it's used within this closure"),
),
_ => err.note(&msg),
};
}
}
obligated_types.push(ty);