Properly drain pending obligations for coroutines

This commit is contained in:
Michael Goulet 2025-03-22 23:49:14 +00:00
parent 67df5b9cfa
commit 169955f3be
19 changed files with 242 additions and 64 deletions

View file

@ -163,6 +163,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// Resume type defaults to `()` if the coroutine has no argument.
let resume_ty = liberated_sig.inputs().get(0).copied().unwrap_or(tcx.types.unit);
// TODO: In the new solver, we can just instantiate this eagerly
// with the witness. This will ensure that goals that don't need
// to stall on interior types will get processed eagerly.
let interior = self.next_ty_var(expr_span);
self.deferred_coroutine_interiors.borrow_mut().push((expr_def_id, interior));

View file

@ -659,10 +659,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
obligations.extend(ok.obligations);
}
// FIXME: Use a real visitor for unstalled obligations in the new solver.
if !coroutines.is_empty() {
obligations
.extend(self.fulfillment_cx.borrow_mut().drain_unstalled_obligations(&self.infcx));
obligations.extend(
self.fulfillment_cx
.borrow_mut()
.drain_stalled_obligations_for_coroutines(&self.infcx),
);
}
self.typeck_results

View file

@ -84,7 +84,7 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
let hir_owner = tcx.local_def_id_to_hir_id(def_id).owner;
let infcx =
tcx.infer_ctxt().ignoring_regions().build(TypingMode::analysis_in_body(tcx, def_id));
tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id));
let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
TypeckRootCtxt {

View file

@ -967,7 +967,7 @@ impl<'tcx> InferCtxt<'tcx> {
pub fn can_define_opaque_ty(&self, id: impl Into<DefId>) -> bool {
debug_assert!(!self.next_trait_solver());
match self.typing_mode() {
TypingMode::Analysis { defining_opaque_types }
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ }
| TypingMode::Borrowck { defining_opaque_types } => {
id.into().as_local().is_some_and(|def_id| defining_opaque_types.contains(&def_id))
}
@ -1262,7 +1262,7 @@ impl<'tcx> InferCtxt<'tcx> {
// to handle them without proper canonicalization. This means we may cause cycle
// errors and fail to reveal opaques while inside of bodies. We should rename this
// function and require explicit comments on all use-sites in the future.
ty::TypingMode::Analysis { defining_opaque_types: _ }
ty::TypingMode::Analysis { defining_opaque_types: _, stalled_generators: _ }
| ty::TypingMode::Borrowck { defining_opaque_types: _ } => {
TypingMode::non_body_analysis()
}

View file

@ -94,7 +94,7 @@ pub trait TraitEngine<'tcx, E: 'tcx>: 'tcx {
/// Among all pending obligations, collect those are stalled on a inference variable which has
/// changed since the last call to `select_where_possible`. Those obligations are marked as
/// successful and returned.
fn drain_unstalled_obligations(
fn drain_stalled_obligations_for_coroutines(
&mut self,
infcx: &InferCtxt<'tcx>,
) -> PredicateObligations<'tcx>;

View file

@ -387,6 +387,15 @@ rustc_queries! {
}
}
query stalled_generators_within(
key: LocalDefId
) -> &'tcx ty::List<LocalDefId> {
desc {
|tcx| "computing the opaque types defined by `{}`",
tcx.def_path_str(key.to_def_id())
}
}
/// Returns the explicitly user-written *bounds* on the associated or opaque type given by `DefId`
/// that must be proven true at definition site (and which can be assumed at usage sites).
///

View file

@ -366,11 +366,11 @@ macro_rules! define_callbacks {
pub type Storage<'tcx> = <$($K)* as keys::Key>::Cache<Erase<$V>>;
// Ensure that keys grow no larger than 80 bytes by accident.
// Ensure that keys grow no larger than 96 bytes by accident.
// Increase this limit if necessary, but do try to keep the size low if possible
#[cfg(target_pointer_width = "64")]
const _: () = {
if size_of::<Key<'static>>() > 88 {
if size_of::<Key<'static>>() > 96 {
panic!("{}", concat!(
"the query `",
stringify!($name),

View file

@ -106,7 +106,7 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
) -> Self::PredefinedOpaques {
self.mk_predefined_opaques_in_body(data)
}
type DefiningOpaqueTypes = &'tcx ty::List<LocalDefId>;
type LocalDefIds = &'tcx ty::List<LocalDefId>;
type CanonicalVars = CanonicalVarInfos<'tcx>;
fn mk_canonical_var_infos(self, infos: &[ty::CanonicalVarInfo<Self>]) -> Self::CanonicalVars {
self.mk_canonical_var_infos(infos)
@ -674,9 +674,13 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.anonymize_bound_vars(binder)
}
fn opaque_types_defined_by(self, defining_anchor: LocalDefId) -> Self::DefiningOpaqueTypes {
fn opaque_types_defined_by(self, defining_anchor: LocalDefId) -> Self::LocalDefIds {
self.opaque_types_defined_by(defining_anchor)
}
fn stalled_generators_within(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds {
self.stalled_generators_within(defining_anchor)
}
}
macro_rules! bidirectional_lang_item_map {

View file

@ -329,7 +329,10 @@ where
TypingMode::Coherence | TypingMode::PostAnalysis => false,
// During analysis, opaques are rigid unless they may be defined by
// the current body.
TypingMode::Analysis { defining_opaque_types: non_rigid_opaques }
TypingMode::Analysis {
defining_opaque_types: non_rigid_opaques,
stalled_generators: _,
}
| TypingMode::Borrowck { defining_opaque_types: non_rigid_opaques }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: non_rigid_opaques } => {
!def_id.as_local().is_some_and(|def_id| non_rigid_opaques.contains(&def_id))

View file

@ -33,7 +33,7 @@ where
);
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
}
TypingMode::Analysis { defining_opaque_types } => {
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ } => {
let Some(def_id) = opaque_ty
.def_id
.as_local()

View file

@ -208,6 +208,21 @@ where
}
}
if let ty::CoroutineWitness(def_id, _) = goal.predicate.self_ty().kind() {
match ecx.typing_mode() {
TypingMode::Analysis { stalled_generators, defining_opaque_types: _ } => {
if def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id))
{
return ecx.forced_ambiguity(MaybeCause::Ambiguity);
}
}
TypingMode::Coherence
| TypingMode::PostAnalysis
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => {}
}
}
ecx.probe_and_evaluate_goal_for_constituent_tys(
CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
goal,

View file

@ -1,18 +1,25 @@
use std::marker::PhantomData;
use std::mem;
use std::ops::ControlFlow;
use rustc_data_structures::thinvec::ExtractIf;
use rustc_hir::def_id::LocalDefId;
use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::{
FromSolverError, PredicateObligation, PredicateObligations, TraitEngine,
};
use rustc_middle::ty::{
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor, TypingMode,
};
use rustc_next_trait_solver::solve::{GenerateProofTree, HasChanged, SolverDelegateEvalExt as _};
use rustc_span::Span;
use tracing::instrument;
use self::derive_errors::*;
use super::Certainty;
use super::delegate::SolverDelegate;
use super::inspect::{self, ProofTreeInferCtxtExt};
use crate::traits::{FulfillmentError, ScrubbedTraitError};
mod derive_errors;
@ -39,7 +46,7 @@ pub struct FulfillmentCtxt<'tcx, E: 'tcx> {
_errors: PhantomData<E>,
}
#[derive(Default)]
#[derive(Default, Debug)]
struct ObligationStorage<'tcx> {
/// Obligations which resulted in an overflow in fulfillment itself.
///
@ -55,20 +62,23 @@ impl<'tcx> ObligationStorage<'tcx> {
self.pending.push(obligation);
}
fn has_pending_obligations(&self) -> bool {
!self.pending.is_empty() || !self.overflowed.is_empty()
}
fn clone_pending(&self) -> PredicateObligations<'tcx> {
let mut obligations = self.pending.clone();
obligations.extend(self.overflowed.iter().cloned());
obligations
}
fn take_pending(&mut self) -> PredicateObligations<'tcx> {
let mut obligations = mem::take(&mut self.pending);
obligations.append(&mut self.overflowed);
obligations
}
fn unstalled_for_select(&mut self) -> impl Iterator<Item = PredicateObligation<'tcx>> + 'tcx {
mem::take(&mut self.pending).into_iter()
fn drain_pending(
&mut self,
cond: impl Fn(&PredicateObligation<'tcx>) -> bool,
) -> PredicateObligations<'tcx> {
let (unstalled, pending) = mem::take(&mut self.pending).into_iter().partition(cond);
self.pending = pending;
unstalled
}
fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'tcx>) {
@ -160,7 +170,7 @@ where
}
let mut has_changed = false;
for obligation in self.obligations.unstalled_for_select() {
for obligation in self.obligations.drain_pending(|_| true) {
let goal = obligation.as_goal();
let result = <&SolverDelegate<'tcx>>::from(infcx)
.evaluate_root_goal(goal, GenerateProofTree::No, obligation.cause.span)
@ -196,15 +206,79 @@ where
}
fn has_pending_obligations(&self) -> bool {
!self.obligations.pending.is_empty() || !self.obligations.overflowed.is_empty()
self.obligations.has_pending_obligations()
}
fn pending_obligations(&self) -> PredicateObligations<'tcx> {
self.obligations.clone_pending()
}
fn drain_unstalled_obligations(&mut self, _: &InferCtxt<'tcx>) -> PredicateObligations<'tcx> {
self.obligations.take_pending()
fn drain_stalled_obligations_for_coroutines(
&mut self,
infcx: &InferCtxt<'tcx>,
) -> PredicateObligations<'tcx> {
self.obligations.drain_pending(|obl| {
let stalled_generators = match infcx.typing_mode() {
TypingMode::Analysis { defining_opaque_types: _, stalled_generators } => {
stalled_generators
}
TypingMode::Coherence
| TypingMode::Borrowck { defining_opaque_types: _ }
| TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
| TypingMode::PostAnalysis => return false,
};
if stalled_generators.is_empty() {
return false;
}
infcx.probe(|_| {
infcx
.visit_proof_tree(
obl.as_goal(),
&mut StalledOnCoroutines { stalled_generators, span: obl.cause.span },
)
.is_break()
})
})
}
}
struct StalledOnCoroutines<'tcx> {
stalled_generators: &'tcx ty::List<LocalDefId>,
span: Span,
// TODO: Cache
}
impl<'tcx> inspect::ProofTreeVisitor<'tcx> for StalledOnCoroutines<'tcx> {
type Result = ControlFlow<()>;
fn span(&self) -> rustc_span::Span {
self.span
}
fn visit_goal(&mut self, inspect_goal: &super::inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
inspect_goal.goal().predicate.visit_with(self)?;
if let Some(candidate) = inspect_goal.unique_applicable_candidate() {
candidate.visit_nested_no_probe(self)
} else {
ControlFlow::Continue(())
}
}
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for StalledOnCoroutines<'tcx> {
type Result = ControlFlow<()>;
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
if let ty::CoroutineWitness(def_id, _) = *ty.kind()
&& def_id.as_local().is_some_and(|def_id| self.stalled_generators.contains(&def_id))
{
return ControlFlow::Break(());
}
ty.super_visit_with(self)
}
}

View file

@ -109,10 +109,16 @@ pub(super) fn fulfillment_error_for_stalled<'tcx>(
false,
),
Ok((_, Certainty::Yes)) => {
bug!("did not expect successful goal when collecting ambiguity errors")
bug!(
"did not expect successful goal when collecting ambiguity errors for `{:?}`",
infcx.resolve_vars_if_possible(root_obligation.predicate),
)
}
Err(_) => {
bug!("did not expect selection error when collecting ambiguity errors")
bug!(
"did not expect selection error when collecting ambiguity errors for `{:?}`",
infcx.resolve_vars_if_possible(root_obligation.predicate),
)
}
}
});

View file

@ -76,7 +76,7 @@ where
let value = value.try_fold_with(&mut folder)?;
let goals = folder
.fulfill_cx
.drain_unstalled_obligations(at.infcx)
.drain_stalled_obligations_for_coroutines(at.infcx)
.into_iter()
.map(|obl| obl.as_goal())
.collect();
@ -130,7 +130,7 @@ where
);
self.fulfill_cx.register_predicate_obligation(infcx, obligation);
let errors = self.fulfill_cx.select_all_or_error(infcx);
let errors = self.fulfill_cx.select_where_possible(infcx);
if !errors.is_empty() {
return Err(errors);
}
@ -171,7 +171,7 @@ where
let result = if infcx.predicate_may_hold(&obligation) {
self.fulfill_cx.register_predicate_obligation(infcx, obligation);
let errors = self.fulfill_cx.select_all_or_error(infcx);
let errors = self.fulfill_cx.select_where_possible(infcx);
if !errors.is_empty() {
return Err(errors);
}
@ -286,27 +286,31 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for DeeplyNormalizeForDiagnosticsFolder<'_,
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
let infcx = self.at.infcx;
infcx
.commit_if_ok(|_| {
deeply_normalize_with_skipped_universes(
self.at,
ty,
vec![None; ty.outer_exclusive_binder().as_usize()],
)
})
.unwrap_or_else(|_: Vec<ScrubbedTraitError<'tcx>>| ty.super_fold_with(self))
let result =
infcx.commit_if_ok(|_| {
deeply_normalize_with_skipped_universes_and_ambiguous_goals::<
_,
ScrubbedTraitError<'tcx>,
>(self.at, ty, vec![None; ty.outer_exclusive_binder().as_usize()])
});
match result {
Ok((ty, _)) => ty,
Err(_) => ty.super_fold_with(self),
}
}
fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> {
let infcx = self.at.infcx;
infcx
.commit_if_ok(|_| {
deeply_normalize_with_skipped_universes(
self.at,
ct,
vec![None; ct.outer_exclusive_binder().as_usize()],
)
})
.unwrap_or_else(|_: Vec<ScrubbedTraitError<'tcx>>| ct.super_fold_with(self))
let result =
infcx.commit_if_ok(|_| {
deeply_normalize_with_skipped_universes_and_ambiguous_goals::<
_,
ScrubbedTraitError<'tcx>,
>(self.at, ct, vec![None; ct.outer_exclusive_binder().as_usize()])
});
match result {
Ok((ct, _)) => ct,
Err(_) => ct.super_fold_with(self),
}
}
}

View file

@ -162,7 +162,7 @@ where
self.select(selcx)
}
fn drain_unstalled_obligations(
fn drain_stalled_obligations_for_coroutines(
&mut self,
infcx: &InferCtxt<'tcx>,
) -> PredicateObligations<'tcx> {

View file

@ -1498,7 +1498,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
// However, if we disqualify *all* goals from being cached, perf suffers.
// This is likely fixed by better caching in general in the new solver.
// See: <https://github.com/rust-lang/rust/issues/132064>.
TypingMode::Analysis { defining_opaque_types }
TypingMode::Analysis { defining_opaque_types, stalled_generators: _ }
| TypingMode::Borrowck { defining_opaque_types } => {
defining_opaque_types.is_empty() || !pred.has_opaque_types()
}

View file

@ -1,6 +1,7 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit;
use rustc_hir::intravisit::Visitor;
use rustc_middle::query::Providers;
@ -355,6 +356,51 @@ fn opaque_types_defined_by<'tcx>(
tcx.mk_local_def_ids(&collector.opaques)
}
pub(super) fn provide(providers: &mut Providers) {
*providers = Providers { opaque_types_defined_by, ..*providers };
// TODO: Move this out of `opaque_types`
fn stalled_generators_within<'tcx>(
tcx: TyCtxt<'tcx>,
item: LocalDefId,
) -> &'tcx ty::List<LocalDefId> {
if !tcx.next_trait_solver_globally() {
return ty::List::empty();
}
let body = tcx.hir_body_owned_by(item);
let mut collector =
StalledGeneratorVisitor { tcx, root_def_id: item.to_def_id(), stalled_coroutines: vec![] };
collector.visit_body(body);
tcx.mk_local_def_ids(&collector.stalled_coroutines)
}
struct StalledGeneratorVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
root_def_id: DefId,
stalled_coroutines: Vec<LocalDefId>,
}
impl<'tcx> Visitor<'tcx> for StalledGeneratorVisitor<'tcx> {
fn visit_nested_body(&mut self, id: hir::BodyId) {
if self.tcx.typeck_root_def_id(self.tcx.hir_body_owner_def_id(id).to_def_id())
== self.root_def_id
{
let body = self.tcx.hir_body(id);
self.visit_body(body);
}
}
fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) {
if let hir::ExprKind::Closure(&hir::Closure {
def_id,
kind: hir::ClosureKind::Coroutine(_),
..
}) = ex.kind
{
self.stalled_coroutines.push(def_id);
}
intravisit::walk_expr(self, ex);
}
}
pub(super) fn provide(providers: &mut Providers) {
*providers = Providers { opaque_types_defined_by, stalled_generators_within, ..*providers };
}

View file

@ -65,7 +65,7 @@ pub enum TypingMode<I: Interner> {
/// let x: <() as Assoc>::Output = true;
/// }
/// ```
Analysis { defining_opaque_types: I::DefiningOpaqueTypes },
Analysis { defining_opaque_types: I::LocalDefIds, stalled_generators: I::LocalDefIds },
/// The behavior during MIR borrowck is identical to `TypingMode::Analysis`
/// except that the initial value for opaque types is the type computed during
/// HIR typeck with unique unconstrained region inference variables.
@ -73,13 +73,13 @@ pub enum TypingMode<I: Interner> {
/// This is currently only used with by the new solver as it results in new
/// non-universal defining uses of opaque types, which is a breaking change.
/// See tests/ui/impl-trait/non-defining-use/as-projection-term.rs.
Borrowck { defining_opaque_types: I::DefiningOpaqueTypes },
Borrowck { defining_opaque_types: I::LocalDefIds },
/// Any analysis after borrowck for a given body should be able to use all the
/// hidden types defined by borrowck, without being able to define any new ones.
///
/// This is currently only used by the new solver, but should be implemented in
/// the old solver as well.
PostBorrowckAnalysis { defined_opaque_types: I::DefiningOpaqueTypes },
PostBorrowckAnalysis { defined_opaque_types: I::LocalDefIds },
/// After analysis, mostly during codegen and MIR optimizations, we're able to
/// reveal all opaque types. As the concrete type should *never* be observable
/// directly by the user, this should not be used by checks which may expose
@ -94,13 +94,26 @@ pub enum TypingMode<I: Interner> {
impl<I: Interner> TypingMode<I> {
/// Analysis outside of a body does not define any opaque types.
pub fn non_body_analysis() -> TypingMode<I> {
TypingMode::Analysis { defining_opaque_types: Default::default() }
TypingMode::Analysis {
defining_opaque_types: Default::default(),
stalled_generators: Default::default(),
}
}
pub fn typeck_for_body(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
TypingMode::Analysis {
defining_opaque_types: cx.opaque_types_defined_by(body_def_id),
stalled_generators: cx.stalled_generators_within(body_def_id),
}
}
/// While typechecking a body, we need to be able to define the opaque
/// types defined by that body.
pub fn analysis_in_body(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {
TypingMode::Analysis { defining_opaque_types: cx.opaque_types_defined_by(body_def_id) }
TypingMode::Analysis {
defining_opaque_types: cx.opaque_types_defined_by(body_def_id),
stalled_generators: Default::default(),
}
}
pub fn borrowck(cx: I, body_def_id: I::LocalDefId) -> TypingMode<I> {

View file

@ -56,7 +56,7 @@ pub trait Interner:
data: PredefinedOpaquesData<Self>,
) -> Self::PredefinedOpaques;
type DefiningOpaqueTypes: Copy
type LocalDefIds: Copy
+ Debug
+ Hash
+ Default
@ -330,10 +330,9 @@ pub trait Interner:
binder: ty::Binder<Self, T>,
) -> ty::Binder<Self, T>;
fn opaque_types_defined_by(
self,
defining_anchor: Self::LocalDefId,
) -> Self::DefiningOpaqueTypes;
fn opaque_types_defined_by(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds;
fn stalled_generators_within(self, defining_anchor: Self::LocalDefId) -> Self::LocalDefIds;
}
/// Imagine you have a function `F: FnOnce(&[T]) -> R`, plus an iterator `iter`