Skip to content

Commit 04fa9b1

Browse files
committed
async/await: improve obligation errors
This commit improves obligation errors for async/await: ``` note: future does not implement `std::marker::Send` because this value is used across an await --> $DIR/issue-64130-non-send-future-diags.rs:15:5 | LL | let g = x.lock().unwrap(); | - has type `std::sync::MutexGuard<'_, u32>` LL | baz().await; | ^^^^^^^^^^^ await occurs here, with `g` maybe used later LL | } | - `g` is later dropped here ``` Signed-off-by: David Wood <[email protected]>
1 parent d046ffd commit 04fa9b1

File tree

9 files changed

+281
-28
lines changed

9 files changed

+281
-28
lines changed

src/librustc/traits/error_reporting.rs

+165-12
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::hir::def_id::DefId;
2424
use crate::infer::{self, InferCtxt};
2525
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
2626
use crate::session::DiagnosticMessageId;
27-
use crate::ty::{self, AdtKind, ToPredicate, ToPolyTraitRef, Ty, TyCtxt, TypeFoldable};
27+
use crate::ty::{self, AdtKind, DefIdTree, ToPredicate, ToPolyTraitRef, Ty, TyCtxt, TypeFoldable};
2828
use crate::ty::GenericParamDefKind;
2929
use crate::ty::error::ExpectedFound;
3030
use crate::ty::fast_reject;
@@ -37,7 +37,7 @@ use errors::{Applicability, DiagnosticBuilder, pluralise};
3737
use std::fmt;
3838
use syntax::ast;
3939
use syntax::symbol::{sym, kw};
40-
use syntax_pos::{DUMMY_SP, Span, ExpnKind};
40+
use syntax_pos::{DUMMY_SP, Span, ExpnKind, MultiSpan};
4141

4242
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
4343
pub fn report_fulfillment_errors(
@@ -550,7 +550,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
550550
self.suggest_new_overflow_limit(&mut err);
551551
}
552552

553-
self.note_obligation_cause(&mut err, obligation);
553+
self.note_obligation_cause_code(&mut err, &obligation.predicate, &obligation.cause.code,
554+
&mut vec![]);
554555

555556
err.emit();
556557
self.tcx.sess.abort_if_errors();
@@ -940,7 +941,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
940941
bug!("overflow should be handled before the `report_selection_error` path");
941942
}
942943
};
944+
943945
self.note_obligation_cause(&mut err, obligation);
946+
944947
err.emit();
945948
}
946949

@@ -1604,15 +1607,165 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16041607
})
16051608
}
16061609

1607-
fn note_obligation_cause<T>(&self,
1608-
err: &mut DiagnosticBuilder<'_>,
1609-
obligation: &Obligation<'tcx, T>)
1610-
where T: fmt::Display
1611-
{
1612-
self.note_obligation_cause_code(err,
1613-
&obligation.predicate,
1614-
&obligation.cause.code,
1615-
&mut vec![]);
1610+
fn note_obligation_cause(
1611+
&self,
1612+
err: &mut DiagnosticBuilder<'_>,
1613+
obligation: &PredicateObligation<'tcx>,
1614+
) {
1615+
// First, attempt to add note to this error with an async-await-specific
1616+
// message, and fall back to regular note otherwise.
1617+
if !self.note_obligation_cause_for_async_await(err, obligation) {
1618+
self.note_obligation_cause_code(err, &obligation.predicate, &obligation.cause.code,
1619+
&mut vec![]);
1620+
}
1621+
}
1622+
1623+
/// Adds an async-await specific note to the diagnostic:
1624+
///
1625+
/// ```ignore (diagnostic)
1626+
/// note: future does not implement `std::marker::Send` because this value is used across an
1627+
/// await
1628+
/// --> $DIR/issue-64130-non-send-future-diags.rs:15:5
1629+
/// |
1630+
/// LL | let g = x.lock().unwrap();
1631+
/// | - has type `std::sync::MutexGuard<'_, u32>`
1632+
/// LL | baz().await;
1633+
/// | ^^^^^^^^^^^ await occurs here, with `g` maybe used later
1634+
/// LL | }
1635+
/// | - `g` is later dropped here
1636+
/// ```
1637+
///
1638+
/// Returns `true` if an async-await specific note was added to the diagnostic.
1639+
fn note_obligation_cause_for_async_await(
1640+
&self,
1641+
err: &mut DiagnosticBuilder<'_>,
1642+
obligation: &PredicateObligation<'tcx>,
1643+
) -> bool {
1644+
debug!("note_obligation_cause_for_async_await: obligation.predicate={:?} \
1645+
obligation.cause.span={:?}", obligation.predicate, obligation.cause.span);
1646+
let source_map = self.tcx.sess.source_map();
1647+
1648+
// Look into the obligation predicate to determine the type in the generator which meant
1649+
// that the predicate was not satisifed.
1650+
let (trait_ref, target_ty) = match obligation.predicate {
1651+
ty::Predicate::Trait(trait_predicate) =>
1652+
(trait_predicate.skip_binder().trait_ref, trait_predicate.skip_binder().self_ty()),
1653+
_ => return false,
1654+
};
1655+
debug!("note_obligation_cause_for_async_await: target_ty={:?}", target_ty);
1656+
1657+
// Attempt to detect an async-await error by looking at the obligation causes, looking
1658+
// for only generators, generator witnesses, opaque types or `std::future::GenFuture` to
1659+
// be present.
1660+
//
1661+
// When a future does not implement a trait because of a captured type in one of the
1662+
// generators somewhere in the call stack, then the result is a chain of obligations.
1663+
// Given a `async fn` A that calls a `async fn` B which captures a non-send type and that
1664+
// future is passed as an argument to a function C which requires a `Send` type, then the
1665+
// chain looks something like this:
1666+
//
1667+
// - `BuiltinDerivedObligation` with a generator witness (B)
1668+
// - `BuiltinDerivedObligation` with a generator (B)
1669+
// - `BuiltinDerivedObligation` with `std::future::GenFuture` (B)
1670+
// - `BuiltinDerivedObligation` with `impl std::future::Future` (B)
1671+
// - `BuiltinDerivedObligation` with `impl std::future::Future` (B)
1672+
// - `BuiltinDerivedObligation` with a generator witness (A)
1673+
// - `BuiltinDerivedObligation` with a generator (A)
1674+
// - `BuiltinDerivedObligation` with `std::future::GenFuture` (A)
1675+
// - `BuiltinDerivedObligation` with `impl std::future::Future` (A)
1676+
// - `BuiltinDerivedObligation` with `impl std::future::Future` (A)
1677+
// - `BindingObligation` with `impl_send (Send requirement)
1678+
//
1679+
// The first obligations in the chain can be used to get the details of the type that is
1680+
// captured but the entire chain must be inspected to detect this case.
1681+
let mut generator = None;
1682+
let mut next_code = Some(&obligation.cause.code);
1683+
while let Some(code) = next_code {
1684+
debug!("note_obligation_cause_for_async_await: code={:?}", code);
1685+
match code {
1686+
ObligationCauseCode::BuiltinDerivedObligation(derived_obligation) |
1687+
ObligationCauseCode::ImplDerivedObligation(derived_obligation) => {
1688+
debug!("note_obligation_cause_for_async_await: self_ty.kind={:?}",
1689+
derived_obligation.parent_trait_ref.self_ty().kind);
1690+
match derived_obligation.parent_trait_ref.self_ty().kind {
1691+
ty::Adt(ty::AdtDef { did, .. }, ..) if
1692+
self.tcx.is_diagnostic_item(sym::gen_future, *did) => {},
1693+
ty::Generator(did, ..) => generator = generator.or(Some(did)),
1694+
ty::GeneratorWitness(_) | ty::Opaque(..) => {},
1695+
_ => return false,
1696+
}
1697+
1698+
next_code = Some(derived_obligation.parent_code.as_ref());
1699+
},
1700+
ObligationCauseCode::ItemObligation(_) | ObligationCauseCode::BindingObligation(..)
1701+
if generator.is_some() => break,
1702+
_ => return false,
1703+
}
1704+
}
1705+
1706+
let generator_did = generator.expect("can only reach this if there was a generator");
1707+
1708+
// Only continue to add a note if the generator is from an `async` function.
1709+
let parent_node = self.tcx.parent(generator_did)
1710+
.and_then(|parent_did| self.tcx.hir().get_if_local(parent_did));
1711+
debug!("note_obligation_cause_for_async_await: parent_node={:?}", parent_node);
1712+
if let Some(hir::Node::Item(hir::Item {
1713+
kind: hir::ItemKind::Fn(_, header, _, _),
1714+
..
1715+
})) = parent_node {
1716+
debug!("note_obligation_cause_for_async_await: header={:?}", header);
1717+
if header.asyncness != hir::IsAsync::Async {
1718+
return false;
1719+
}
1720+
}
1721+
1722+
let span = self.tcx.def_span(generator_did);
1723+
let tables = self.tcx.typeck_tables_of(generator_did);
1724+
debug!("note_obligation_cause_for_async_await: generator_did={:?} span={:?} ",
1725+
generator_did, span);
1726+
1727+
// Look for a type inside the generator interior that matches the target type to get
1728+
// a span.
1729+
let target_span = tables.generator_interior_types.iter()
1730+
.find(|ty::GeneratorInteriorTypeCause { ty, .. }| ty::TyS::same_type(*ty, target_ty))
1731+
.map(|ty::GeneratorInteriorTypeCause { span, scope_span, .. }|
1732+
(span, source_map.span_to_snippet(*span), scope_span));
1733+
if let Some((target_span, Ok(snippet), scope_span)) = target_span {
1734+
// Look at the last interior type to get a span for the `.await`.
1735+
let await_span = tables.generator_interior_types.iter().map(|i| i.span).last().unwrap();
1736+
let mut span = MultiSpan::from_span(await_span);
1737+
span.push_span_label(
1738+
await_span, format!("await occurs here, with `{}` maybe used later", snippet));
1739+
1740+
span.push_span_label(*target_span, format!("has type `{}`", target_ty));
1741+
1742+
// If available, use the scope span to annotate the drop location.
1743+
if let Some(scope_span) = scope_span {
1744+
span.push_span_label(
1745+
source_map.end_point(*scope_span),
1746+
format!("`{}` is later dropped here", snippet),
1747+
);
1748+
}
1749+
1750+
err.span_note(span, &format!(
1751+
"future does not implement `{}` as this value is used across an await",
1752+
trait_ref,
1753+
));
1754+
1755+
// Add a note for the item obligation that remains - normally a note pointing to the
1756+
// bound that introduced the obligation (e.g. `T: Send`).
1757+
debug!("note_obligation_cause_for_async_await: next_code={:?}", next_code);
1758+
self.note_obligation_cause_code(
1759+
err,
1760+
&obligation.predicate,
1761+
next_code.unwrap(),
1762+
&mut Vec::new(),
1763+
);
1764+
1765+
true
1766+
} else {
1767+
false
1768+
}
16161769
}
16171770

16181771
fn note_obligation_cause_code<T>(&self,

src/librustc/ty/context.rs

+35
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,34 @@ pub struct ResolvedOpaqueTy<'tcx> {
288288
pub substs: SubstsRef<'tcx>,
289289
}
290290

291+
/// Whenever a value may be live across a generator yield, the type of that value winds up in the
292+
/// `GeneratorInteriorTypeCause` struct. This struct adds additional information about such
293+
/// captured types that can be useful for diagnostics. In particular, it stores the span that
294+
/// caused a given type to be recorded, along with the scope that enclosed the value (which can
295+
/// be used to find the await that the value is live across).
296+
///
297+
/// For example:
298+
///
299+
/// ```ignore (pseudo-Rust)
300+
/// async move {
301+
/// let x: T = ...;
302+
/// foo.await
303+
/// ...
304+
/// }
305+
/// ```
306+
///
307+
/// Here, we would store the type `T`, the span of the value `x`, and the "scope-span" for
308+
/// the scope that contains `x`.
309+
#[derive(RustcEncodable, RustcDecodable, Clone, Debug, Eq, Hash, HashStable, PartialEq)]
310+
pub struct GeneratorInteriorTypeCause<'tcx> {
311+
/// Type of the captured binding.
312+
pub ty: Ty<'tcx>,
313+
/// Span of the binding that was captured.
314+
pub span: Span,
315+
/// Span of the scope of the captured binding.
316+
pub scope_span: Option<Span>,
317+
}
318+
291319
#[derive(RustcEncodable, RustcDecodable, Debug)]
292320
pub struct TypeckTables<'tcx> {
293321
/// The HirId::owner all ItemLocalIds in this table are relative to.
@@ -397,6 +425,10 @@ pub struct TypeckTables<'tcx> {
397425
/// leading to the member of the struct or tuple that is used instead of the
398426
/// entire variable.
399427
pub upvar_list: ty::UpvarListMap,
428+
429+
/// Stores the type, span and optional scope span of all types
430+
/// that are live across the yield of this generator (if a generator).
431+
pub generator_interior_types: Vec<GeneratorInteriorTypeCause<'tcx>>,
400432
}
401433

402434
impl<'tcx> TypeckTables<'tcx> {
@@ -422,6 +454,7 @@ impl<'tcx> TypeckTables<'tcx> {
422454
free_region_map: Default::default(),
423455
concrete_opaque_types: Default::default(),
424456
upvar_list: Default::default(),
457+
generator_interior_types: Default::default(),
425458
}
426459
}
427460

@@ -729,6 +762,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckTables<'tcx> {
729762
ref free_region_map,
730763
ref concrete_opaque_types,
731764
ref upvar_list,
765+
ref generator_interior_types,
732766

733767
} = *self;
734768

@@ -773,6 +807,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckTables<'tcx> {
773807
free_region_map.hash_stable(hcx, hasher);
774808
concrete_opaque_types.hash_stable(hcx, hasher);
775809
upvar_list.hash_stable(hcx, hasher);
810+
generator_interior_types.hash_stable(hcx, hasher);
776811
})
777812
}
778813
}

src/librustc/ty/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub use self::binding::BindingMode;
7575
pub use self::binding::BindingMode::*;
7676

7777
pub use self::context::{TyCtxt, FreeRegionInfo, AllArenas, tls, keep_local};
78-
pub use self::context::{Lift, TypeckTables, CtxtInterners, GlobalCtxt};
78+
pub use self::context::{Lift, GeneratorInteriorTypeCause, TypeckTables, CtxtInterners, GlobalCtxt};
7979
pub use self::context::{
8080
UserTypeAnnotationIndex, UserType, CanonicalUserType,
8181
CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, ResolvedOpaqueTy,

src/librustc_typeck/check/generator_interior.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::util::nodemap::FxHashMap;
1414

1515
struct InteriorVisitor<'a, 'tcx> {
1616
fcx: &'a FnCtxt<'a, 'tcx>,
17-
types: FxHashMap<Ty<'tcx>, usize>,
17+
types: FxHashMap<ty::GeneratorInteriorTypeCause<'tcx>, usize>,
1818
region_scope_tree: &'tcx region::ScopeTree,
1919
expr_count: usize,
2020
kind: hir::GeneratorKind,
@@ -83,7 +83,12 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
8383
} else {
8484
// Map the type to the number of types added before it
8585
let entries = self.types.len();
86-
self.types.entry(&ty).or_insert(entries);
86+
let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree));
87+
self.types.entry(ty::GeneratorInteriorTypeCause {
88+
span: source_span,
89+
ty: &ty,
90+
scope_span
91+
}).or_insert(entries);
8792
}
8893
} else {
8994
debug!("no type in expr = {:?}, count = {:?}, span = {:?}",
@@ -118,8 +123,12 @@ pub fn resolve_interior<'a, 'tcx>(
118123
// Sort types by insertion order
119124
types.sort_by_key(|t| t.1);
120125

126+
// Store the generator types and spans into the tables for this generator.
127+
let interior_types = types.iter().cloned().map(|t| t.0).collect::<Vec<_>>();
128+
visitor.fcx.inh.tables.borrow_mut().generator_interior_types = interior_types;
129+
121130
// Extract type components
122-
let type_list = fcx.tcx.mk_type_list(types.into_iter().map(|t| t.0));
131+
let type_list = fcx.tcx.mk_type_list(types.into_iter().map(|t| (t.0).ty));
123132

124133
// The types in the generator interior contain lifetimes local to the generator itself,
125134
// which should not be exposed outside of the generator. Therefore, we replace these

src/librustc_typeck/check/writeback.rs

+7
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5858
wbcx.visit_free_region_map();
5959
wbcx.visit_user_provided_tys();
6060
wbcx.visit_user_provided_sigs();
61+
wbcx.visit_generator_interior_types();
6162

6263
let used_trait_imports = mem::replace(
6364
&mut self.tables.borrow_mut().used_trait_imports,
@@ -430,6 +431,12 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
430431
}
431432
}
432433

434+
fn visit_generator_interior_types(&mut self) {
435+
let fcx_tables = self.fcx.tables.borrow();
436+
debug_assert_eq!(fcx_tables.local_id_root, self.tables.local_id_root);
437+
self.tables.generator_interior_types = fcx_tables.generator_interior_types.clone();
438+
}
439+
433440
fn visit_opaque_types(&mut self, span: Span) {
434441
for (&def_id, opaque_defn) in self.fcx.opaque_types.borrow().iter() {
435442
let hir_id = self.tcx().hir().as_local_hir_id(def_id).unwrap();

src/libstd/future.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn from_generator<T: Generator<Yield = ()>>(x: T) -> impl Future<Output = T:
2626
#[doc(hidden)]
2727
#[unstable(feature = "gen_future", issue = "50547")]
2828
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
29+
#[cfg_attr(not(test), rustc_diagnostic_item = "gen_future")]
2930
struct GenFuture<T: Generator<Yield = ()>>(T);
3031

3132
// We rely on the fact that async/await futures are immovable in order to create

0 commit comments

Comments
 (0)