diff --git a/crates/hir-ty/src/next_solver/fulfill.rs b/crates/hir-ty/src/next_solver/fulfill.rs index e422f75a0207..24ae84e860d9 100644 --- a/crates/hir-ty/src/next_solver/fulfill.rs +++ b/crates/hir-ty/src/next_solver/fulfill.rs @@ -2,23 +2,26 @@ use std::ops::ControlFlow; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use rustc_next_trait_solver::{ delegate::SolverDelegate, solve::{GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt}, }; use rustc_type_ir::{ - Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, - inherent::IntoKind, - solve::{Certainty, NoSolution}, + GenericArgKind, InferConst, InferCtxtLike, InferTy, Interner, TyVid, TypeSuperVisitable, + TypeVisitable, TypeVisitableExt, TypeVisitor, + inherent::{Const as _, IntoKind, OpaqueTypeStorageEntries as _, Ty as _}, + solve::{Certainty, Goal, NoSolution}, }; +use smallvec::SmallVec; use crate::{ Span, next_solver::{ - DbInterner, SolverContext, SolverDefId, Ty, TyKind, TypingMode, + Const, ConstKind, DbInterner, GenericArg, Predicate, SolverContext, SolverDefId, Ty, + TyKind, TypingMode, infer::{ - InferCtxt, + InferCtxt, StalledVarKey, traits::{PredicateObligation, PredicateObligations}, }, inspect::ProofTreeVisitor, @@ -48,9 +51,30 @@ pub struct FulfillmentCtxt<'db> { /// gets rolled back. Because of this we explicitly check that we only /// use the context in exactly this snapshot. usable_in_snapshot: usize, + /// Read position into the infer context's changed-inference-variables log + /// (see [`InferCtxt::changed_vars_since`]). Everything before this position has already + /// been translated into wake-ups of the obligations parked in `obligations.watch`. The log + /// is shared by every `FulfillmentCtxt` on the same `InferCtxt` but reading it is + /// non-destructive, so short-lived contexts (nested probes, coercions, method resolution) + /// don't steal wake signals from the long-lived, body-scoped one. + changed_vars_cursor: usize, try_evaluate_obligations_scratch: PendingObligations<'db>, + changed_vars_scratch: Vec, } +/// An ambiguous obligation parked on the variables of its `GoalStalledOn`, together with the +/// watch keys it is filed under in [`ObligationStorage::watch`]. +#[derive(Debug, Clone)] +struct Slot<'db> { + obligation: PredicateObligation<'db>, + stalled_on: GoalStalledOn>, + keys: SmallVec<[StalledVarKey; 4]>, +} + +/// Obligations that are still ambiguous (`Certainty::Maybe`), indexed so that an inference +/// variable changing wakes exactly the obligations stalled on it, instead of every call to +/// `try_evaluate_obligations` rescanning everything still pending (which made large bodies +/// quadratic: O(calls) × O(pending)). #[derive(Default, Debug, Clone)] struct ObligationStorage<'db> { /// Obligations which resulted in an overflow in fulfillment itself. @@ -59,52 +83,232 @@ struct ObligationStorage<'db> { /// to avoid recomputing them each time `try_evaluate_obligations` is called. /// This also allows us to return the correct `FulfillmentError` for them. overflowed: Vec>, - pending: PendingObligations<'db>, + /// Obligations registered from the outside and not yet evaluated in this context. + /// Fully processed on the next `try_evaluate_obligations` call. + fresh: Vec>, + /// Obligations that evaluated to `Certainty::Maybe` but whose stall condition can't be + /// attached to any watchable variable (no `stalled_on` at all, a `Fresh*` inference + /// variable, or a stall set that had already been invalidated again by the time we got to + /// park it). Re-evaluated on the first round of every call and after each round that made + /// real progress — the same cadence at which the old code re-evaluated everything — but + /// crucially they do *not* requeue rounds by themselves, which is what guarantees the + /// fixpoint loop terminates once nothing is actually changing. + unwatchable: PendingObligations<'db>, + /// Ambiguous obligations parked on a known set of variables. `None` is a tombstone left + /// behind by `wake`; slots are compacted once tombstones outnumber live entries, and + /// `watch` is rebuilt from the keys stored in each slot at the same time. + slab: Vec>>, + /// Number of `Some` entries in `slab`. + live: usize, + /// Maps a variable (or the opaque-type storage as a whole, see [`StalledVarKey::Opaques`]) + /// to the slab slots of obligations parked on it. An obligation is listed under each of its + /// keys; waking it through any one of them empties its slot, turning the remaining entries + /// into harmless tombstone hits. + watch: FxHashMap>, +} + +/// Classifies every variable a [`GoalStalledOn`] is blocked on into a [`StalledVarKey`] for +/// indexing into [`ObligationStorage::watch`]. +/// +/// The caller must have just verified [`is_still_stalled`]: that check passing means every +/// tracked variable is an unresolved unification *root* (a resolved or re-rooted variable +/// counts as "changed"), so the variables can be used as watch keys directly. Mutations are +/// only observable on root slots, and a later union of the root itself fires a wake for it, +/// at which point the obligation re-registers under the merged root. Returns `None` if some +/// tracked variable isn't watchable (`Fresh*` inference variables, which no unification table +/// observes, and which `is_changed_arg` should already have rejected) — the caller must then +/// fall back to periodic rechecking rather than risk the obligation never being woken. +fn stalled_var_keys<'db>( + stalled_on: &GoalStalledOn>, +) -> Option> { + let mut keys = + SmallVec::with_capacity(stalled_on.stalled_vars.len() + stalled_on.sub_roots.len() + 1); + for arg in &stalled_on.stalled_vars { + match arg.kind() { + // Lifetimes can never stall a goal; the solver never puts one in `stalled_vars`. + GenericArgKind::Lifetime(_) => {} + GenericArgKind::Type(ty) => match ty.kind() { + TyKind::Infer(InferTy::TyVar(vid)) => keys.push(StalledVarKey::Ty(vid)), + TyKind::Infer(InferTy::IntVar(vid)) => keys.push(StalledVarKey::Int(vid)), + TyKind::Infer(InferTy::FloatVar(vid)) => keys.push(StalledVarKey::Float(vid)), + _ => return None, + }, + GenericArgKind::Const(ct) => match ct.kind() { + ConstKind::Infer(InferConst::Var(vid)) => keys.push(StalledVarKey::Const(vid)), + _ => return None, + }, + } + } + keys.extend(stalled_on.sub_roots.iter().map(|&vid| StalledVarKey::TySubRoot(vid))); + // `num_opaques` is a condition of every `GoalStalledOn`, not tied to a specific variable. + keys.push(StalledVarKey::Opaques); + Some(keys) } impl<'db> ObligationStorage<'db> { - fn register( + fn register_fresh(&mut self, obligation: PredicateObligation<'db>) { + self.fresh.push(obligation); + } + + /// Files an obligation that evaluated to `Certainty::Maybe`. If its stall condition is + /// intact and watchable it is parked in `slab`/`watch` and only ever revisited when one of + /// its variables is reported changed; otherwise it goes to `unwatchable` for periodic + /// rechecking. + fn park( &mut self, + infcx: &InferCtxt<'db>, obligation: PredicateObligation<'db>, stalled_on: Option>>, ) { - self.pending.push((obligation, stalled_on)); + match stalled_on { + // Only park an obligation whose stall condition currently holds: `is_still_stalled` + // is an absolute check ("no tracked variable is resolved or re-rooted"), so a stall + // set that is already invalidated here would never validate again, while the wake + // events for its variables may already have been consumed — parking it could leave + // it asleep forever. Such obligations must stay on the periodic recheck path. + Some(stalled_on) if is_still_stalled(infcx, &stalled_on) => { + self.park_verified(obligation, stalled_on) + } + stalled_on => self.unwatchable.push((obligation, stalled_on)), + } + } + + /// Like [`Self::park`], for callers that have just checked `is_still_stalled` themselves. + fn park_verified( + &mut self, + obligation: PredicateObligation<'db>, + stalled_on: GoalStalledOn>, + ) { + let Some(keys) = stalled_var_keys(&stalled_on) else { + self.unwatchable.push((obligation, Some(stalled_on))); + return; + }; + self.maybe_compact(); + let slot = self.slab.len(); + for &key in &keys { + self.watch.entry(key).or_default().push(slot); + } + self.slab.push(Some(Slot { obligation, stalled_on, keys })); + self.live += 1; + } + + /// Moves every obligation whose slab slot is watched under `key` into `out`, leaving + /// tombstones behind. + fn wake(&mut self, key: StalledVarKey, out: &mut PendingObligations<'db>) { + let Some(slots) = self.watch.remove(&key) else { return }; + for idx in slots { + if let Some(slot) = self.slab[idx].take() { + self.live -= 1; + out.push((slot.obligation, Some(slot.stalled_on))); + } + } + } + + /// Rebuilds `slab`/`watch` without tombstones once they outnumber live entries, so storage + /// stays proportional to what is actually parked instead of growing for the lifetime of + /// the context. + fn maybe_compact(&mut self) { + if self.slab.len() < 64 || 2 * self.live > self.slab.len() { + return; + } + self.slab.retain(Option::is_some); + self.watch.clear(); + for (idx, slot) in self.slab.iter().flatten().enumerate() { + for &key in &slot.keys { + self.watch.entry(key).or_default().push(idx); + } + } + } + + /// Removes and returns every pending obligation matching `pred`, across all three buckets. + fn extract_pending_if( + &mut self, + mut pred: impl FnMut(&PredicateObligation<'db>) -> bool, + ) -> PredicateObligations<'db> { + let mut result: PredicateObligations<'db> = + self.fresh.extract_if(.., |o| pred(o)).collect(); + result.extend(self.unwatchable.extract_if(.., |(o, _)| pred(o)).map(|(o, _)| o)); + for slot in self.slab.iter_mut() { + if let Some(taken) = slot.take_if(|slot| pred(&slot.obligation)) { + self.live -= 1; + result.push(taken.obligation); + } + } + result + } + + fn iter_pending(&self) -> impl Iterator> { + self.fresh + .iter() + .chain(self.unwatchable.iter().map(|(o, _)| o)) + .chain(self.slab.iter().flatten().map(|slot| &slot.obligation)) } fn clone_pending(&self) -> PredicateObligations<'db> { - let mut obligations: PredicateObligations<'db> = - self.pending.iter().map(|(o, _)| o.clone()).collect(); + let mut obligations: PredicateObligations<'db> = self.iter_pending().cloned().collect(); obligations.extend(self.overflowed.iter().cloned()); obligations } - fn drain_pending<'this, 'cond>( - &'this mut self, - cond: impl 'cond + Fn(&PredicateObligation<'db>) -> bool, - ) -> impl Iterator, Option>>)> - { - self.pending.extract_if(.., move |(o, _)| cond(o)) + fn drain_pending(&mut self) -> impl Iterator> { + self.watch.clear(); + self.live = 0; + self.fresh + .drain(..) + .chain(self.unwatchable.drain(..).map(|(o, _)| o)) + .chain(self.slab.drain(..).flatten().map(|slot| slot.obligation)) + } + + /// Debug-builds-only safety net for the event-driven wake machinery. Called after a + /// round's wake-ups have been applied: every obligation still parked must have an intact + /// stall condition, because anything that could invalidate one records a changed-variable + /// event, and consuming that event wakes the obligation. An entry failing this check has + /// missed a wake-up and could keep a stale ambiguity (i.e. wrong inference results) + /// forever. Also verifies the `live` counter, which the compaction heuristic relies on. + fn assert_no_missed_wakes(&self, infcx: &InferCtxt<'db>) { + if cfg!(debug_assertions) { + stdx::never!( + self.live != self.slab.iter().flatten().count(), + "`live` out of sync with the slab" + ); + for slot in self.slab.iter().flatten() { + stdx::never!( + !is_still_stalled(infcx, &slot.stalled_on), + "parked obligation missed a wake-up; its stall condition no longer holds" + ); + } + } } fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'db>) { + // Overflow is rare enough that unconditionally re-evaluating the parked obligations + // (like the old flat storage did) and dropping their stall state is fine; whatever is + // retained ends up in `unwatchable` (its `stalled_on` is `take`n below) and gets + // rechecked from scratch on the next call. + self.unwatchable.extend( + self.slab.drain(..).flatten().map(|slot| (slot.obligation, Some(slot.stalled_on))), + ); + self.watch.clear(); + self.live = 0; infcx.probe(|_| { // IMPORTANT: we must not use solve any inference variables in the obligations // as this is all happening inside of a probe. We use a probe to make sure // we get all obligations involved in the overflow. We pretty much check: if // we were to do another step of `try_evaluate_obligations`, which goals would // change. - // FIXME: is merged, this can be removed. + let delegate = <&SolverContext<'db>>::from(infcx); + let has_changed = + |o: &PredicateObligation<'db>, + stalled_on: Option>>| { + let result = + delegate.evaluate_root_goal(o.as_goal(), o.cause.span(), stalled_on); + matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. })) + }; + + self.overflowed.extend(self.fresh.extract_if(.., |o| has_changed(o, None))); self.overflowed.extend( - self.pending - .extract_if(.., |(o, stalled_on)| { - let goal = o.as_goal(); - let result = <&SolverContext<'db>>::from(infcx).evaluate_root_goal( - goal, - o.cause.span(), - stalled_on.take(), - ); - matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. })) - }) + self.unwatchable + .extract_if(.., |(o, stalled_on)| has_changed(o, stalled_on.take())) .map(|(o, _)| o), ); }) @@ -116,7 +320,10 @@ impl<'db> FulfillmentCtxt<'db> { FulfillmentCtxt { obligations: Default::default(), usable_in_snapshot: infcx.num_open_snapshots(), + // Nothing is parked yet, so variable changes before this point are irrelevant. + changed_vars_cursor: infcx.changed_vars_len(), try_evaluate_obligations_scratch: Default::default(), + changed_vars_scratch: Default::default(), } } } @@ -129,7 +336,7 @@ impl<'db> FulfillmentCtxt<'db> { obligation: PredicateObligation<'db>, ) { assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); - self.obligations.register(obligation, None); + self.obligations.register_fresh(obligation); } pub(crate) fn register_predicate_obligations( @@ -138,19 +345,17 @@ impl<'db> FulfillmentCtxt<'db> { obligations: impl IntoIterator>, ) { assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); - obligations.into_iter().for_each(|obligation| self.obligations.register(obligation, None)); + obligations.into_iter().for_each(|obligation| self.obligations.register_fresh(obligation)); } pub(crate) fn collect_remaining_errors( &mut self, _infcx: &InferCtxt<'db>, ) -> Vec> { - self.obligations - .pending - .drain(..) - .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) - .chain(self.obligations.overflowed.drain(..).map(NextSolverError::Overflow)) - .collect() + let mut errors: Vec<_> = + self.obligations.drain_pending().map(NextSolverError::Ambiguity).collect(); + errors.extend(self.obligations.overflowed.drain(..).map(NextSolverError::Overflow)); + errors } pub(crate) fn try_evaluate_obligations( @@ -160,10 +365,58 @@ impl<'db> FulfillmentCtxt<'db> { assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); self.try_evaluate_obligations_scratch.clear(); let mut errors = Vec::new(); + // Whether to re-evaluate the `unwatchable` obligations this round: once per call, and + // again after every round that made real inference progress (the cadence at which the + // old code re-evaluated everything still pending). Never merely because the previous + // round's batch was non-empty — an obligation that stays ambiguous without an + // attachable stall set would then requeue itself forever. + let mut recheck_unwatchable = true; loop { - let mut any_changed = false; - self.try_evaluate_obligations_scratch.extend(self.obligations.drain_pending(|_| true)); + // Gather this round's work: obligations that were newly registered, obligations we + // can't watch (periodically), and obligations parked on an inference variable that + // changed since we last looked — whether the change came from our own previous + // round, from solver entry points between calls, or from a nested fulfillment + // context sharing this infer context. + let batch = &mut self.try_evaluate_obligations_scratch; + batch.extend(self.obligations.fresh.drain(..).map(|o| (o, None))); + if recheck_unwatchable { + batch.append(&mut self.obligations.unwatchable); + } + if self.obligations.watch.is_empty() { + // Nothing is parked on any variable (every slab entry has at least the + // `Opaques` watch key), so skip scanning the changed-variable events and just + // advance past them. + self.changed_vars_cursor = infcx.changed_vars_len(); + } else { + self.changed_vars_scratch.clear(); + infcx.changed_vars_since( + &mut self.changed_vars_cursor, + &mut self.changed_vars_scratch, + ); + for &key in &self.changed_vars_scratch { + self.obligations.wake(key, batch); + } + } + self.obligations.assert_no_missed_wakes(infcx); + if batch.is_empty() { + break; + } + recheck_unwatchable = false; + for (mut obligation, stalled_on) in self.try_evaluate_obligations_scratch.drain(..) { + // If this goal stalled on a specific set of inference variables before and none + // of them has really changed (the wake-up was spurious — e.g. caused by a + // mutation that was rolled back since), evaluating it again is guaranteed to + // produce the same `Maybe` result (goal evaluation is pure), so park it again + // without touching the solver. + let stalled_on = match stalled_on { + Some(stalled_on) if is_still_stalled(infcx, &stalled_on) => { + self.obligations.park_verified(obligation, stalled_on); + continue; + } + stalled_on => stalled_on, + }; + if obligation.recursion_depth >= infcx.interner.recursion_limit() { self.obligations.on_fulfillment_overflow(infcx); // Only return true errors that we have accumulated while processing. @@ -178,7 +431,11 @@ impl<'db> FulfillmentCtxt<'db> { match certainty { Certainty::Yes => {} Certainty::Maybe { .. } => { - self.obligations.register(obligation, None); + self.obligations.park( + infcx, + obligation, + fast_path_stalled_on(infcx, goal), + ); } } continue; @@ -206,18 +463,14 @@ impl<'db> FulfillmentCtxt<'db> { // approximation and should only result in fulfillment overflow in // pathological cases. obligation.recursion_depth += 1; - any_changed = true; + recheck_unwatchable = true; } match certainty { Certainty::Yes => {} - Certainty::Maybe { .. } => self.obligations.register(obligation, stalled_on), + Certainty::Maybe { .. } => self.obligations.park(infcx, obligation, stalled_on), } } - - if !any_changed { - break; - } } errors @@ -258,24 +511,116 @@ impl<'db> FulfillmentCtxt<'db> { return Default::default(); } - self.obligations - .drain_pending(|obl| { - infcx.probe(|_| { - infcx - .visit_proof_tree( - obl.as_goal(), - &mut StalledOnCoroutines { - stalled_coroutines, - span: obl.cause.span(), - cache: Default::default(), - }, - ) - .is_break() - }) + let is_stalled_on_coroutine = |obl: &PredicateObligation<'db>| { + infcx.probe(|_| { + infcx + .visit_proof_tree( + obl.as_goal(), + &mut StalledOnCoroutines { + stalled_coroutines, + span: obl.cause.span(), + cache: Default::default(), + }, + ) + .is_break() }) - .map(|(o, _)| o) - .collect() + }; + + self.obligations.extract_pending_if(is_stalled_on_coroutine) + } +} + +/// Mirrors the stalled-goal fast path that `evaluate_goal_raw` runs internally on every +/// evaluation: if none of the tracked variables changed (and the opaque type storage didn't +/// grow) since `stalled_on` was recorded, the goal is guaranteed to still evaluate to the same +/// `Maybe` result. Running this cheap check *before* touching the solver at all is what makes +/// `try_evaluate_obligations` proportional to the obligations actually woken since the last +/// call rather than to the total pending set. +fn is_still_stalled<'db>( + infcx: &InferCtxt<'db>, + stalled_on: &GoalStalledOn>, +) -> bool { + !infcx.disable_trait_solver_fast_paths() + && !stalled_on.stalled_vars.iter().any(|&value| infcx.is_changed_arg(value)) + && !stalled_on.sub_roots.iter().any(|&vid| infcx.sub_unification_table_root_var(vid) != vid) + && !infcx.opaque_types_storage_num_entries().needs_reevaluation(stalled_on.num_opaques) +} + +/// Best-effort reconstruction of a [`GoalStalledOn`] for goals resolved by +/// [`crate::next_solver::solver::SolverContext::compute_goal_fast_path`]'s `Certainty::Maybe` +/// result. +/// +/// That fast path (unlike the real solver) does not build a `GoalStalledOn`, so without this +/// we would lose all stall-tracking for the obligations it intercepts, forcing them through the +/// fast path again on every single call to `try_evaluate_obligations` for the rest of the body. +/// We over-approximate by collecting every inference variable mentioned in the goal's +/// predicate: extra entries only cause spurious wake-ups and rechecks on unrelated changes, +/// which is wasteful but never unsound, whereas missing one could permanently mask real +/// progress. +fn fast_path_stalled_on<'db>( + infcx: &InferCtxt<'db>, + goal: Goal, Predicate<'db>>, +) -> Option>> { + struct CollectInferVars<'a, 'db> { + infcx: &'a InferCtxt<'db>, + stalled_vars: Vec>, + sub_roots: Vec, + } + + impl<'db> TypeVisitor> for CollectInferVars<'_, 'db> { + type Result = (); + + fn visit_ty(&mut self, ty: Ty<'db>) { + // Record variables under their current unification root: the interned predicate + // can mention variables that have since been unioned into another root, and + // `is_still_stalled` treats a non-root variable as always-changed, so recording + // such a variable raw would invalidate the stall set on arrival (the obligation + // would never park). + match ty.kind() { + TyKind::Infer(InferTy::TyVar(vid)) => { + let root = self.infcx.root_var(vid); + self.stalled_vars.push(Ty::new_var(self.infcx.interner, root).into()); + self.sub_roots.push(self.infcx.sub_unification_table_root_var(vid)); + } + TyKind::Infer(InferTy::IntVar(vid)) => { + let root = self.infcx.root_int_var(vid); + self.stalled_vars.push(Ty::new_int_var(self.infcx.interner, root).into()); + } + TyKind::Infer(InferTy::FloatVar(vid)) => { + let root = self.infcx.root_float_var(vid); + self.stalled_vars.push(Ty::new_float_var(self.infcx.interner, root).into()); + } + TyKind::Infer(_) => self.stalled_vars.push(ty.into()), + _ if ty.has_infer() => ty.super_visit_with(self), + _ => {} + } + } + + fn visit_const(&mut self, ct: Const<'db>) { + match ct.kind() { + ConstKind::Infer(InferConst::Var(vid)) => { + let root = self.infcx.root_const_var(vid); + self.stalled_vars.push(Const::new_var(self.infcx.interner, root).into()); + } + _ if ct.has_infer() => ct.super_visit_with(self), + _ => {} + } + } + } + + let mut collector = CollectInferVars { infcx, stalled_vars: Vec::new(), sub_roots: Vec::new() }; + goal.predicate.visit_with(&mut collector); + if collector.stalled_vars.is_empty() { + // We couldn't pin down what this goal is blocked on (e.g. the ambiguity came from the + // param-env rather than the predicate); fall back to always rechecking it. + return None; } + Some(GoalStalledOn { + num_opaques: infcx.opaque_types_storage_num_entries().opaque_type_count(), + stalled_vars: collector.stalled_vars, + sub_roots: collector.sub_roots, + stalled_certainty: Certainty::AMBIGUOUS, + }) } /// Detect if a goal is stalled on a coroutine that is owned by the current typeck root. diff --git a/crates/hir-ty/src/next_solver/infer/mod.rs b/crates/hir-ty/src/next_solver/infer/mod.rs index 7c419147d214..075aae644055 100644 --- a/crates/hir-ty/src/next_solver/infer/mod.rs +++ b/crates/hir-ty/src/next_solver/infer/mod.rs @@ -59,6 +59,7 @@ pub mod relate; pub mod resolve; pub mod select; pub(crate) mod snapshot; +pub(crate) use snapshot::StalledVarKey; pub mod traits; mod type_variable; mod unify_key; @@ -878,7 +879,7 @@ impl<'db> InferCtxt<'db> { pub fn take_opaque_types( &self, ) -> impl IntoIterator, OpaqueHiddenType<'db>)> + use<'db> { - self.inner.borrow_mut().opaque_type_storage.take_opaque_types() + self.inner.borrow_mut().opaque_types().take_opaque_types() } #[instrument(level = "debug", skip(self), ret)] @@ -1004,6 +1005,14 @@ impl<'db> InferCtxt<'db> { self.inner.borrow_mut().const_unification_table().find(var).vid } + pub(crate) fn root_int_var(&self, var: IntVid) -> IntVid { + self.inner.borrow_mut().int_unification_table().find(var) + } + + pub(crate) fn root_float_var(&self, var: FloatVid) -> FloatVid { + self.inner.borrow_mut().float_unification_table().find(var) + } + /// Resolves an int var to a rigid int type, if it was constrained to one, /// or else the root int var in the unification table. pub fn opportunistic_resolve_int_var(&self, vid: IntVid) -> Ty<'db> { diff --git a/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs b/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs index 894fe5eb7b87..7133f2928e4e 100644 --- a/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs +++ b/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs @@ -27,6 +27,15 @@ pub struct OpaqueTypeStorageEntries { duplicate_entries: usize, } +impl OpaqueTypeStorageEntries { + /// The raw entry count, for constructing a [`rustc_next_trait_solver::solve::GoalStalledOn`] + /// outside of the solver's own canonicalization (which is where `num_opaques` is normally + /// computed from). + pub(crate) fn opaque_type_count(self) -> usize { + self.opaque_types + } +} + impl rustc_type_ir::inherent::OpaqueTypeStorageEntries for OpaqueTypeStorageEntries { fn needs_reevaluation(self, canonicalized: usize) -> bool { self.opaque_types != canonicalized @@ -159,4 +168,15 @@ impl<'a, 'db> OpaqueTypeTable<'a, 'db> { self.storage.duplicate_entries.push((key, hidden_type)); self.undo_log.push(UndoLog::DuplicateOpaqueType); } + + pub(crate) fn take_opaque_types( + &mut self, + ) -> impl IntoIterator, OpaqueHiddenType<'db>)> + use<'db> { + if !self.storage.is_empty() { + // Draining the storage shrinks the entry count without going through the undo + // machinery; record it so obligations stalled on the opaque count get rechecked. + self.undo_log.mark_opaques_changed(); + } + self.storage.take_opaque_types() + } } diff --git a/crates/hir-ty/src/next_solver/infer/region_constraints/mod.rs b/crates/hir-ty/src/next_solver/infer/region_constraints/mod.rs index 544d79daf0a3..d577f7362562 100644 --- a/crates/hir-ty/src/next_solver/infer/region_constraints/mod.rs +++ b/crates/hir-ty/src/next_solver/infer/region_constraints/mod.rs @@ -323,7 +323,12 @@ impl<'db> RegionConstraintCollector<'db, '_> { /// /// Not legal during a snapshot. pub fn take_and_reset_data(&mut self) -> RegionConstraintData<'db> { - assert!(!UndoLogs::>::in_snapshot(&self.undo_log)); + // The *inherent* method, not the always-`true` trait impl; see `InferCtxtUndoLogs`. + // The explicit deref is needed because `ena` blanket-implements `UndoLogs` for + // `&mut U`, so on the reference itself only the trait method exists. Removing the + // deref is a compile error (E0283, ambiguous `T` in `UndoLogs`), not a silent + // switch to the trait impl. + assert!(!(*self.undo_log).in_snapshot()); // If you add a new field to `RegionConstraintCollector`, you // should think carefully about whether it needs to be cleared diff --git a/crates/hir-ty/src/next_solver/infer/snapshot/mod.rs b/crates/hir-ty/src/next_solver/infer/snapshot/mod.rs index 39c8a37adbdb..8ee6fc7a9719 100644 --- a/crates/hir-ty/src/next_solver/infer/snapshot/mod.rs +++ b/crates/hir-ty/src/next_solver/infer/snapshot/mod.rs @@ -10,6 +10,7 @@ use super::region_constraints::RegionSnapshot; mod fudge; pub(crate) mod undo_log; +pub(crate) use undo_log::StalledVarKey; use undo_log::{Snapshot, UndoLog}; #[must_use = "once you start a snapshot, you should always consume it"] @@ -40,13 +41,31 @@ impl<'db> InferCtxt<'db> { } pub fn in_snapshot(&self) -> bool { - UndoLogs::>::in_snapshot(&self.inner.borrow_mut().undo_log) + // The *inherent* method, not the always-`true` trait impl; see `InferCtxtUndoLogs`. + self.inner.borrow().undo_log.in_snapshot() } pub fn num_open_snapshots(&self) -> usize { UndoLogs::>::num_open_snapshots(&self.inner.borrow_mut().undo_log) } + /// The current length of the changed-inference-variables log, i.e. a cursor position from + /// which [`Self::changed_vars_since`] later reports what changed. See + /// [`undo_log::InferCtxtUndoLogs`]. + pub(crate) fn changed_vars_len(&self) -> usize { + self.inner.borrow().undo_log.changed_vars_len() + } + + /// Appends to `out` the variables that (potentially) changed since `cursor` (a position + /// previously obtained from [`Self::changed_vars_len`]), then advances `cursor` past them. + /// Reading is non-destructive, so any number of consumers can watch the same infer context + /// with their own cursors. + pub(crate) fn changed_vars_since(&self, cursor: &mut usize, out: &mut Vec) { + let inner = self.inner.borrow(); + out.extend_from_slice(inner.undo_log.changed_vars_since(*cursor)); + *cursor = inner.undo_log.changed_vars_len(); + } + pub fn start_snapshot(&self) -> CombinedSnapshot { debug!("start_snapshot()"); @@ -123,3 +142,102 @@ impl<'db> InferCtxt<'db> { self.inner.borrow().undo_log.opaque_types_in_snapshot(&snapshot.undo_snapshot) } } + +#[cfg(test)] +mod tests { + use rustc_type_ir::{TypingMode, inherent::Ty as _}; + use test_fixture::WithFixture; + + use crate::{ + next_solver::{ + DbInterner, Ty, + infer::{DbInternerInferExt, InferCtxt, StalledVarKey}, + }, + test_db::TestDB, + }; + + fn with_infcx(f: impl FnOnce(&InferCtxt<'_>)) { + let (db, file_id) = TestDB::with_single_file("fn f() {}"); + crate::attach_db(&db, || { + let krate = db.module_for_file(file_id.file_id(&db)).krate(&db); + let interner = DbInterner::new_with(&db, krate); + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + f(&infcx); + }); + } + + fn new_ty_var(infcx: &InferCtxt<'_>) -> rustc_type_ir::TyVid { + infcx.next_ty_vid(crate::Span::Dummy) + } + + fn changed_since(infcx: &InferCtxt<'_>, cursor: &mut usize) -> Vec { + let mut out = Vec::new(); + infcx.changed_vars_since(cursor, &mut out); + out + } + + /// A mutation performed while *no* snapshot is open must still be reported: `ena` gates + /// its undo-log `push` calls on `in_snapshot()`, which `InferCtxtUndoLogs` deliberately + /// short-circuits to `true` to keep this log complete. A lost event here would leave a + /// `FulfillmentCtxt` obligation parked on the variable with a stale `Certainty::Maybe` + /// forever, i.e. wrong inference results rather than a crash. + #[test] + fn zero_snapshot_mutation_is_reported() { + with_infcx(|infcx| { + assert_eq!(infcx.num_open_snapshots(), 0); + let vid = new_ty_var(infcx); + let mut cursor = infcx.changed_vars_len(); + infcx + .inner + .borrow_mut() + .type_variables() + .instantiate(vid, Ty::new_bool(infcx.interner)); + assert!(changed_since(infcx, &mut cursor).contains(&StalledVarKey::Ty(vid))); + }); + } + + /// Unioning two variables must be observable through *both* variables' identities, and + /// instantiating the surviving root afterwards must be observable through that root: a + /// watcher keyed on either original variable is woken by the union, re-registers under + /// the merged root, and is then woken again by the instantiation. + #[test] + fn union_then_instantiate_root_is_reported() { + with_infcx(|infcx| { + let v1 = new_ty_var(infcx); + let v2 = new_ty_var(infcx); + let mut cursor = infcx.changed_vars_len(); + infcx.inner.borrow_mut().type_variables().equate(v1, v2); + let after_union = changed_since(infcx, &mut cursor); + assert!(after_union.contains(&StalledVarKey::Ty(v1))); + assert!(after_union.contains(&StalledVarKey::Ty(v2))); + + let root = infcx.root_var(v1); + assert_eq!(root, infcx.root_var(v2)); + infcx + .inner + .borrow_mut() + .type_variables() + .instantiate(root, Ty::new_bool(infcx.interner)); + assert!(changed_since(infcx, &mut cursor).contains(&StalledVarKey::Ty(root))); + }); + } + + /// Changes made inside a probe are rolled back, but the wake events they produced must + /// stick around: waking an obligation spuriously is harmless, missing a wake is not, and + /// a consumer may only get to read the log after the rollback already happened. + #[test] + fn rolled_back_mutation_still_reported() { + with_infcx(|infcx| { + let vid = new_ty_var(infcx); + let mut cursor = infcx.changed_vars_len(); + infcx.probe(|_| { + infcx + .inner + .borrow_mut() + .type_variables() + .instantiate(vid, Ty::new_bool(infcx.interner)); + }); + assert!(changed_since(infcx, &mut cursor).contains(&StalledVarKey::Ty(vid))); + }); + } +} diff --git a/crates/hir-ty/src/next_solver/infer/snapshot/undo_log.rs b/crates/hir-ty/src/next_solver/infer/snapshot/undo_log.rs index f246af1f2e53..1392e4519a53 100644 --- a/crates/hir-ty/src/next_solver/infer/snapshot/undo_log.rs +++ b/crates/hir-ty/src/next_solver/infer/snapshot/undo_log.rs @@ -3,8 +3,10 @@ use ena::snapshot_vec as sv; use ena::undo_log::{Rollback, UndoLogs}; use ena::unify as ut; +use rustc_type_ir::ConstVid; use rustc_type_ir::FloatVid; use rustc_type_ir::IntVid; +use rustc_type_ir::TyVid; use tracing::debug; use crate::next_solver::OpaqueTypeKey; @@ -13,6 +15,60 @@ use crate::next_solver::infer::unify_key::ConstVidKey; use crate::next_solver::infer::unify_key::RegionVidKey; use crate::next_solver::infer::{InferCtxtInner, region_constraints, type_variable}; +/// Identifies something a `FulfillmentCtxt` obligation can be stalled on: a specific inference +/// variable, or the opaque type storage as a whole (mirroring `GoalStalledOn::num_opaques`, +/// which isn't tied to any single variable). +/// +/// Used both to index a parked obligation under the variables it is blocked on and, via +/// [`InferCtxtUndoLogs::changed_vars`], to report which of those have potentially changed, so +/// the obligation can be woken without re-scanning everything that's still genuinely stalled. +/// +/// Variable keys always refer to the *unification root* at the time the key was created: `ena` +/// applies value changes to root slots, and a union touches both involved roots, so an event +/// stream keyed by root indices is complete as long as watchers re-normalize their keys +/// whenever they are woken (a union wakes the old root's watchers, which then re-register +/// under the new root). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum StalledVarKey { + /// The `eq_relations` slot of this type variable changed (unified or instantiated). + Ty(TyVid), + /// The `sub_unification_table` slot of this type variable changed. + TySubRoot(TyVid), + Int(IntVid), + Float(FloatVid), + Const(ConstVid), + /// The opaque type storage changed (an opaque type was registered, replaced, or drained). + Opaques, +} + +/// Best-effort extraction of the variable a given undo entry pertains to. Only entries that +/// represent a *value* changing (`SetElem`) carry a meaningful variable identity: `NewElem` +/// (a fresh, still-unconstrained variable was created) cannot wake anything, because no +/// obligation can have been registered as stalled on a variable that didn't exist yet. +fn changed_var_key(undo: &UndoLog<'_>) -> Option { + match undo { + UndoLog::TypeVariables(type_variable::UndoLog::EqRelation(sv::UndoLog::SetElem( + idx, + _, + ))) => Some(StalledVarKey::Ty(TyVid::from_u32(*idx as u32))), + UndoLog::TypeVariables(type_variable::UndoLog::SubRelation(sv::UndoLog::SetElem( + idx, + _, + ))) => Some(StalledVarKey::TySubRoot(TyVid::from_u32(*idx as u32))), + UndoLog::IntUnificationTable(sv::UndoLog::SetElem(idx, _)) => { + Some(StalledVarKey::Int(IntVid::from_u32(*idx as u32))) + } + UndoLog::FloatUnificationTable(sv::UndoLog::SetElem(idx, _)) => { + Some(StalledVarKey::Float(FloatVid::from_u32(*idx as u32))) + } + UndoLog::ConstUnificationTable(sv::UndoLog::SetElem(idx, _)) => { + Some(StalledVarKey::Const(ConstVid::from_u32(*idx as u32))) + } + UndoLog::OpaqueTypes(..) | UndoLog::DuplicateOpaqueType => Some(StalledVarKey::Opaques), + _ => None, + } +} + pub struct Snapshot { pub(crate) undo_len: usize, } @@ -93,6 +149,16 @@ impl<'db> Rollback> for InferCtxtInner<'db> { pub(crate) struct InferCtxtUndoLogs<'db> { logs: Vec>, num_open_snapshots: usize, + /// Variables observed to have (potentially) changed, for waking `FulfillmentCtxt` + /// obligations that were parked on them. + /// + /// Unlike `logs`, this is appended to unconditionally, not just while a snapshot is open, + /// and it is never truncated by rollback: over-reporting a variable as changed (e.g. one + /// whose change was later rolled back, or a mere path-compression write) only causes an + /// obligation to be needlessly re-checked, never a missed wake-up. Consumers each keep a + /// cursor into this log (see `FulfillmentCtxt`) rather than draining it, so several + /// consumers can share one infer context without stealing each other's wake signals. + changed_vars: Vec, } /// The UndoLogs trait defines how we undo a particular kind of action (of type T). We can undo any @@ -106,27 +172,37 @@ where self.num_open_snapshots } + /// Deliberately claims a snapshot is always open. `ena`'s mutation methods + /// (`SnapshotVec::{set, update}` and everything in `unify` built on them) consult this + /// *before* calling [`Self::push`] and skip the call entirely when it returns false, which + /// would make `changed_vars` silently miss every mutation performed while no snapshot is + /// open. Answering `true` routes all mutations through `push`/`extend`, which record the + /// changed variable unconditionally and store the actual undo entry only when a snapshot + /// is really open (`num_open_snapshots > 0`), preserving the original rollback behavior. + #[inline] + fn in_snapshot(&self) -> bool { + true + } + #[inline] fn push(&mut self, undo: T) { - if self.in_snapshot() { - self.logs.push(undo.into()) + let undo = undo.into(); + if let Some(key) = changed_var_key(&undo) { + self.changed_vars.push(key); + } + if self.num_open_snapshots > 0 { + self.logs.push(undo) } } fn clear(&mut self) { + // Note that `changed_vars` is intentionally left alone: it is append-only for the + // lifetime of the infer context so that consumer cursors stay valid. self.logs.clear(); self.num_open_snapshots = 0; } - fn extend(&mut self, undos: J) - where - Self: Sized, - J: IntoIterator, - { - if self.in_snapshot() { - self.logs.extend(undos.into_iter().map(UndoLog::from)) - } - } + // `extend` is left at its provided default, which forwards to `push` element-wise. } impl<'db> InferCtxtInner<'db> { @@ -166,6 +242,13 @@ impl<'db> InferCtxtInner<'db> { } impl<'db> InferCtxtUndoLogs<'db> { + /// Whether a snapshot is really open. This inherent method shadows the + /// [`UndoLogs::in_snapshot`] trait method, which deliberately always answers `true` (see + /// the trait impl above for why); anything wanting the real state must call this one. + pub(crate) fn in_snapshot(&self) -> bool { + self.num_open_snapshots > 0 + } + pub(crate) fn start_snapshot(&mut self) -> Snapshot { self.num_open_snapshots += 1; Snapshot { undo_len: self.logs.len() } @@ -190,6 +273,27 @@ impl<'db> InferCtxtUndoLogs<'db> { assert!(self.logs.len() >= snapshot.undo_len); assert!(self.num_open_snapshots > 0); } + + /// The current length of the changed-variables log. Positions before this are in the past; + /// a consumer that has processed everything up to `len` can later ask for + /// [`Self::changed_vars_since`] that position to see only what changed in between. + pub(crate) fn changed_vars_len(&self) -> usize { + self.changed_vars.len() + } + + /// The variables that (potentially) changed since `cursor`, a position previously obtained + /// from [`Self::changed_vars_len`]. The log is append-only, so this is stable across + /// snapshots, rollbacks, and other consumers reading their own cursors. + pub(crate) fn changed_vars_since(&self, cursor: usize) -> &[StalledVarKey] { + &self.changed_vars[cursor.min(self.changed_vars.len())..] + } + + /// Records an opaque-type-storage change that doesn't go through the undo machinery + /// (draining the storage via `take_opaque_types` shrinks the entry count without pushing + /// any undo entry). + pub(crate) fn mark_opaques_changed(&mut self) { + self.changed_vars.push(StalledVarKey::Opaques); + } } impl<'db> std::ops::Index for InferCtxtUndoLogs<'db> { diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs index 0d08c75aad73..1ba74246cf6a 100644 --- a/crates/hir-ty/src/tests/regression.rs +++ b/crates/hir-ty/src/tests/regression.rs @@ -131,8 +131,8 @@ fn recursive_vars() { expect![[r#" 10..47 '{ ...&y]; }': () 20..21 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 24..31 'unknown': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 37..44 '[y, &y]': [&'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}; 2] + 24..31 'unknown': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 37..44 '[y, &y]': [&'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}; 2] 38..39 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 41..43 '&y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 42..43 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} @@ -152,19 +152,19 @@ fn recursive_vars_2() { "#, expect![[r#" 10..79 '{ ...x)]; }': () - 20..21 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 20..21 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 24..31 'unknown': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 41..42 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 45..52 'unknown': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 58..76 '[(x, y..., &x)]': [(&'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}, &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}); 2] 59..65 '(x, y)': (&'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}, &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}) - 60..61 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 60..61 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 63..64 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 67..75 '(&y, &x)': (&'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}, &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown}) 68..70 '&y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 69..70 'y': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 72..74 '&x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 73..74 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 72..74 '&x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 73..74 'x': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} "#]], ); } @@ -309,7 +309,7 @@ fn infer_std_crash_5() { 32..320 'for co... }': () 32..320 'for co... }': () 32..320 'for co... }': () - 36..43 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 36..43 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 47..60 'doesnt_matter': {unknown} 47..60 'doesnt_matter': fn into_iter<{unknown}>({unknown}) -> <{unknown} as IntoIterator>::IntoIter 47..60 'doesnt_matter': <{unknown} as IntoIterator>::IntoIter @@ -318,20 +318,20 @@ fn infer_std_crash_5() { 82..166 'if doe... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 85..98 'doesnt_matter': bool 99..128 '{ ... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 113..118 'first': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 113..118 'first': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 134..166 '{ ... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 148..156 '&content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 149..156 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 181..188 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 191..313 'if ICE... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 148..156 '&content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 149..156 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 181..188 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 191..313 'if ICE... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 194..231 'ICE_RE..._VALUE': {unknown} 194..247 'ICE_RE...&name)': bool 241..246 '&name': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 242..246 'name': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 248..276 '{ ... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} 262..266 'name': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 282..313 '{ ... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} - 296..303 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 282..313 '{ ... }': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} + 296..303 'content': &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? &'? {unknown} "#]], ); }