Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub(crate) struct UniversalRegionRelations<'tcx> {
/// Stores the outlives relations that are known to hold from the
/// implied bounds, in-scope where-clauses, and that sort of
/// thing.
outlives: TransitiveRelation<RegionVid>,
pub(crate) outlives: TransitiveRelation<RegionVid>,

/// This is the `<=` relation; that is, if `a: b`, then `b <= a`,
/// and we store that here. This is useful when figuring out how
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,22 @@ pub(crate) fn type_check<'tcx>(

let polonius_context = typeck.polonius_context;

let mut converter = constraint_conversion::ConstraintConversion::new(
typeck.infcx,
typeck.universal_regions,
typeck.region_bound_pairs,
typeck.known_type_outlives_obligations,
Locations::All(rustc_span::DUMMY_SP),
rustc_span::DUMMY_SP,
ConstraintCategory::Boring,
typeck.constraints,
);
typeck.infcx.destructure_solver_region_constraints_for_borrowck(
&mut converter,
typeck.known_type_outlives_obligations,
universal_region_relations.outlives.clone(),
);

// In case type check encountered an error region, we suppress unhelpful extra
// errors in by clearing out all outlives bounds that we may end up checking.
if let Some(guar) = universal_region_relations.universal_regions.encountered_re_error() {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_infer/src/infer/at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl<'tcx> InferCtxt<'tcx> {
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
tainted_by_errors: self.tainted_by_errors.clone(),
universe: self.universe.clone(),
universe_assumptions_for_next_solver: self.universe_assumptions_for_next_solver.clone(),
next_trait_solver: self.next_trait_solver,
obligation_inspector: self.obligation_inspector.clone(),
}
Expand All @@ -106,6 +107,7 @@ impl<'tcx> InferCtxt<'tcx> {
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
tainted_by_errors: self.tainted_by_errors.clone(),
universe: self.universe.clone(),
universe_assumptions_for_next_solver: self.universe_assumptions_for_next_solver.clone(),
next_trait_solver: self.next_trait_solver,
obligation_inspector: self.obligation_inspector.clone(),
};
Expand Down
37 changes: 37 additions & 0 deletions compiler/rustc_infer/src/infer/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
self.next_trait_solver
}

fn higher_ranked_assumptions_v2(&self) -> bool {
self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2
}

fn typing_mode(&self) -> ty::TypingMode<'tcx> {
self.typing_mode()
}
Expand All @@ -34,6 +38,27 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
self.create_next_universe()
}

fn insert_universe_assumptions(
&self,
u: ty::UniverseIndex,
assumptions: Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>>,
) {
self.universe_assumptions_for_next_solver.borrow_mut().insert(u, assumptions);
}

fn get_universe_assumptions(
&self,
u: ty::UniverseIndex,
) -> Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>> {
self.universe_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned()
}

fn get_solve_region_constraint(
Copy link
Copy Markdown
Member Author

@BoxyUwU BoxyUwU Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo (and in many other places i say solve not solver)

View changes since the review

&self,
) -> rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>> {
self.inner.borrow().solver_region_constraint_storage.get_constraint()
}

fn universe_of_ty(&self, vid: ty::TyVid) -> Option<ty::UniverseIndex> {
match self.try_resolve_ty_var(vid) {
Err(universe) => Some(universe),
Expand Down Expand Up @@ -272,6 +297,18 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
);
}

fn register_solver_region_constraint(
&self,
c: rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
) {
let mut inner = self.inner.borrow_mut();
use rustc_data_structures::undo_log::UndoLogs;

use crate::infer::UndoLog;
inner.undo_log.push(UndoLog::PushSolverRegionConstraint);
inner.solver_region_constraint_storage.push(c);
}

fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) {
self.register_type_outlives_constraint(ty, r, &ObligationCause::dummy_with_span(span));
}
Expand Down
58 changes: 58 additions & 0 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ pub struct InferCtxtInner<'tcx> {
/// region constraints would've been added.
region_constraint_storage: Option<RegionConstraintStorage<'tcx>>,

/// Used by the next solver when `-Zhigher-ranked-assumptions=v2` is set.
solver_region_constraint_storage: SolverRegionConstraintStorage<'tcx>,

/// A set of constraints that regionck must validate.
///
/// Each constraint has the form `T:'a`, meaning "some type `T` must
Expand Down Expand Up @@ -170,6 +173,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
float_unification_storage: Default::default(),
float_origin_origin_storage: Default::default(),
region_constraint_storage: Some(Default::default()),
solver_region_constraint_storage: SolverRegionConstraintStorage::new(),
region_obligations: Default::default(),
region_assumptions: Default::default(),
hir_typeck_potentially_region_dependent_goals: Default::default(),
Expand Down Expand Up @@ -314,6 +318,16 @@ pub struct InferCtxt<'tcx> {
/// bound.
universe: Cell<ty::UniverseIndex>,

/// List of assumed wellformed types which we can derive implied
/// bounds on a `for<...>` from. Only used unstabley and by the
/// new solver.
universe_assumptions_for_next_solver: RefCell<
Copy link
Copy Markdown
Member Author

@BoxyUwU BoxyUwU Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe should be in InferCtxtInner instead but also having it next to universe feels right? thonk

View changes since the review

FxIndexMap<
ty::UniverseIndex,
Option<rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>>,
>,
>,

next_trait_solver: bool,

pub obligation_inspector: Cell<Option<ObligationInspector<'tcx>>>,
Expand Down Expand Up @@ -606,6 +620,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
reported_signature_mismatch: Default::default(),
tainted_by_errors: Cell::new(None),
universe: Cell::new(ty::UniverseIndex::ROOT),
universe_assumptions_for_next_solver: RefCell::new(Default::default()),
next_trait_solver,
obligation_inspector: Cell::new(None),
}
Expand Down Expand Up @@ -1721,3 +1736,46 @@ impl<'tcx> InferCtxt<'tcx> {
}
}
}

type SolverRegionConstraint<'tcx> =
rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>;

#[derive(Clone, Debug)]
struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>);

impl<'tcx> SolverRegionConstraintStorage<'tcx> {
fn new() -> Self {
SolverRegionConstraintStorage(SolverRegionConstraint::And(Box::new([])))
}

fn get_constraint(&self) -> SolverRegionConstraint<'tcx> {
self.0.clone()
}

fn pop(&mut self) -> Option<SolverRegionConstraint<'tcx>> {
match &mut self.0 {
SolverRegionConstraint::And(and) => {
let mut and = core::mem::take(and).into_iter().collect::<Vec<_>>();
let popped = and.pop()?;
self.0 = SolverRegionConstraint::And(and.into_boxed_slice());
Some(popped)
}
_ => unreachable!(),
}
}

#[instrument(level = "debug")]
fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) {
match &mut self.0 {
SolverRegionConstraint::And(and) => {
let and = core::mem::take(and)
.into_iter()
.chain([constraint])
.collect::<Vec<_>>()
.into_boxed_slice();
self.0 = SolverRegionConstraint::And(and);
}
_ => unreachable!(),
}
}
}
89 changes: 87 additions & 2 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,17 @@
//! might later infer `?U` to something like `&'b u32`, which would
//! imply that `'b: 'a`.

use rustc_data_structures::transitive_relation::TransitiveRelation;
use rustc_data_structures::undo_log::UndoLogs;
use rustc_middle::bug;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::outlives::{Component, push_outlives_components};
use rustc_middle::ty::{
self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, Ty, TyCtxt,
self, GenericArgKind, GenericArgsRef, PolyTypeOutlivesPredicate, Region, RegionVid, Ty, TyCtxt,
TypeFoldable as _, TypeVisitableExt,
};
use rustc_span::DUMMY_SP;
use smallvec::smallvec;
use tracing::{debug, instrument};

Expand Down Expand Up @@ -142,6 +144,8 @@ impl<'tcx> InferCtxt<'tcx> {
sub_region: Region<'tcx>,
cause: &ObligationCause<'tcx>,
) {
assert!(!self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2);

// `is_global` means the type has no params, infer, placeholder, or non-`'static`
// free regions. If the type has none of these things, then we can skip registering
// this outlives obligation since it has no components which affect lifetime
Expand Down Expand Up @@ -196,6 +200,85 @@ impl<'tcx> InferCtxt<'tcx> {
std::mem::take(&mut self.inner.borrow_mut().region_assumptions)
}

pub fn destructure_solver_region_constraints_for_regionck(
&self,
outlives_env: &OutlivesEnvironment<'tcx>,
) {
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
outlives_env.known_type_outlives().into_iter().cloned().collect(),
outlives_env.free_region_map().relation.clone(),
);
self.destructure_solve_region_constraints(assumptions, self);
}

pub fn destructure_solver_region_constraints_for_borrowck(
&self,
// this is always ConstraintConversion but lol
conversion: impl TypeOutlivesDelegate<'tcx>,
known_type_outlives: &[PolyTypeOutlivesPredicate<'tcx>],
region_outlives: TransitiveRelation<RegionVid>,
) {
let assumptions = rustc_type_ir::region_constraint::Assumptions::new(
known_type_outlives.into_iter().cloned().collect(),
region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(),
);
self.destructure_solve_region_constraints(assumptions, conversion);
}

#[instrument(level = "debug", skip(self, conversion))]
pub fn destructure_solve_region_constraints(
&self,
assumptions: rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>,
mut conversion: impl TypeOutlivesDelegate<'tcx>,
) {
if !self.tcx.sess.opts.unstable_opts.higher_ranked_assumptions_v2 {
return;
}

assert!(self.next_trait_solver());

// FIXME(-Zhigher-ranked-assumptions-v2): Implement diagnostics
let origin = SubregionOrigin::Reborrow(DUMMY_SP);
let category = origin.to_constraint_category();

let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint();
debug!(?constraint);
let constraint =
rustc_type_ir::region_constraint::destructure_type_outlives_constraints_in_universe(
self,
constraint,
None,
&Some(assumptions),
);
debug!(?constraint);
let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint);
debug!(?constraint);

let mut constraints = vec![constraint];
while let Some(c) = constraints.pop() {
use rustc_type_ir::region_constraint::RegionConstraint::*;

match c {
Ambiguity => {
self.dcx().err("unable to satisfy constraints involving placeholders due to unknown implied bounds");
}
RegionOutlives(a, b) => {
conversion.push_sub_region_constraint(
origin.clone(),
// we flip these because regionck is silly :>
b,
a,
category,
);
}
// FIXME(-Zhigher-ranked-assumptions-v2): actually implement OR as an OR
And(nested) | Or(nested) => constraints.extend(nested),
AliasTyOutlivesFromEnv(..) => unreachable!(),
PlaceholderTyOutlives(..) => unreachable!(),
}
}
}

/// Process the region obligations that must be proven (during
/// `regionck`) for the given `body_id`, given information about
/// the region bounds in scope and so forth.
Expand All @@ -217,6 +300,8 @@ impl<'tcx> InferCtxt<'tcx> {
) -> Result<(), (PolyTypeOutlivesPredicate<'tcx>, SubregionOrigin<'tcx>)> {
assert!(!self.in_snapshot(), "cannot process registered region obligations in a snapshot");

self.destructure_solver_region_constraints_for_regionck(outlives_env);

// Must loop since the process of normalizing may itself register region obligations.
for iteration in 0.. {
let my_region_obligations = self.take_registered_region_obligations();
Expand Down Expand Up @@ -455,7 +540,7 @@ where
// These are guaranteed to apply, no matter the inference
// results.
let trait_bounds: Vec<_> =
self.verify_bound.declared_bounds_from_definition(alias_ty).collect();
rustc_type_ir::outlives::declared_bounds_from_definition(self.tcx, alias_ty).collect();

debug!(?trait_bounds);

Expand Down
50 changes: 4 additions & 46 deletions compiler/rustc_infer/src/infer/outlives/verify.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::assert_matches;

use rustc_middle::ty::outlives::{Component, compute_alias_components_recursive};
use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt, Unnormalized};
use rustc_middle::ty::{self, OutlivesPredicate, Ty, TyCtxt};
use smallvec::smallvec;
use tracing::{debug, instrument, trace};
use tracing::{debug, instrument};

use crate::infer::outlives::env::RegionBoundPairs;
use crate::infer::region_constraints::VerifyIfEq;
Expand Down Expand Up @@ -121,7 +121,8 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {

// Extend with bounds that we can find from the definition.
let definition_bounds =
self.declared_bounds_from_definition(alias_ty).map(|r| VerifyBound::OutlivedBy(r));
rustc_type_ir::outlives::declared_bounds_from_definition(self.tcx, alias_ty)
.map(|r| VerifyBound::OutlivedBy(r));

// see the extensive comment in projection_must_outlive
let recursive_bound = {
Expand Down Expand Up @@ -247,47 +248,4 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {

bounds
}

/// Given a projection like `<T as Foo<'x>>::Bar`, returns any bounds
/// declared in the trait definition. For example, if the trait were
///
/// ```rust
/// trait Foo<'a> {
/// type Bar: 'a;
/// }
/// ```
///
/// If we were given the `DefId` of `Foo::Bar`, we would return
/// `'a`. You could then apply the instantiations from the
/// projection to convert this into your namespace. This also
/// works if the user writes `where <Self as Foo<'a>>::Bar: 'a` on
/// the trait. In fact, it works by searching for just such a
/// where-clause.
///
/// It will not, however, work for higher-ranked bounds like:
///
/// ```ignore(this does compile today, previously was marked as `compile_fail,E0311`)
/// trait Foo<'a, 'b>
/// where for<'x> <Self as Foo<'x, 'b>>::Bar: 'x
/// {
/// type Bar;
/// }
/// ```
///
/// This is for simplicity, and because we are not really smart
/// enough to cope with such bounds anywhere.
pub(crate) fn declared_bounds_from_definition(
&self,
alias_ty: ty::AliasTy<'tcx>,
) -> impl Iterator<Item = ty::Region<'tcx>> {
let tcx = self.tcx;
let bounds = tcx.item_self_bounds(alias_ty.kind.def_id());
trace!("{:#?}", bounds.skip_binder());
bounds
.iter_instantiated(tcx, alias_ty.args)
.map(Unnormalized::skip_norm_wip)
.filter_map(|p| p.as_type_outlives_clause())
.filter_map(|p| p.no_bound_vars())
.map(|OutlivesPredicate(_, r)| r)
}
}
Loading
Loading