Skip to content

Commit ef21cbd

Browse files
davidtwcoMark-Simulacrum
authored andcommitted
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 da27cc9 commit ef21cbd

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();
@@ -941,7 +942,9 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
941942
bug!("overflow should be handled before the `report_selection_error` path");
942943
}
943944
};
945+
944946
self.note_obligation_cause(&mut err, obligation);
947+
945948
err.emit();
946949
}
947950

@@ -1593,15 +1596,165 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
15931596
})
15941597
}
15951598

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

16071760
fn note_obligation_cause_code<T>(&self,

src/librustc/ty/context.rs

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

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

403435
impl<'tcx> TypeckTables<'tcx> {
@@ -423,6 +455,7 @@ impl<'tcx> TypeckTables<'tcx> {
423455
free_region_map: Default::default(),
424456
concrete_opaque_types: Default::default(),
425457
upvar_list: Default::default(),
458+
generator_interior_types: Default::default(),
426459
}
427460
}
428461

@@ -732,6 +765,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckTables<'tcx> {
732765
ref free_region_map,
733766
ref concrete_opaque_types,
734767
ref upvar_list,
768+
ref generator_interior_types,
735769

736770
} = *self;
737771

@@ -776,6 +810,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for TypeckTables<'tcx> {
776810
free_region_map.hash_stable(hcx, hasher);
777811
concrete_opaque_types.hash_stable(hcx, hasher);
778812
upvar_list.hash_stable(hcx, hasher);
813+
generator_interior_types.hash_stable(hcx, hasher);
779814
})
780815
}
781816
}

src/librustc/ty/mod.rs

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

7878
pub use self::context::{TyCtxt, FreeRegionInfo, AllArenas, tls, keep_local};
79-
pub use self::context::{Lift, TypeckTables, CtxtInterners, GlobalCtxt};
79+
pub use self::context::{Lift, GeneratorInteriorTypeCause, TypeckTables, CtxtInterners, GlobalCtxt};
8080
pub use self::context::{
8181
UserTypeAnnotationIndex, UserType, CanonicalUserType,
8282
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)