Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
handle region dependent goals due to infer vars
  • Loading branch information
lcnr committed Jul 30, 2025
commit b6cbe33aeb526d6437304f4810762c947bddcd4a
17 changes: 16 additions & 1 deletion compiler/rustc_hir_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use rustc_hir_analysis::check::{check_abi, check_custom_abi};
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
use rustc_infer::traits::{ObligationCauseCode, ObligationInspector, WellFormedLoc};
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::{bug, span_bug};
use rustc_session::config;
use rustc_span::Span;
Expand Down Expand Up @@ -259,6 +259,21 @@ fn typeck_with_inspect<'tcx>(

let typeck_results = fcx.resolve_type_vars_in_body(body);

// Handle potentially region dependent goals, see `InferCtxt::in_hir_typeck`.
if let None = fcx.infcx.tainted_by_errors() {
for obligation in fcx.take_hir_typeck_potentially_region_dependent_goals() {
let obligation = fcx.resolve_vars_if_possible(obligation);
if obligation.has_non_region_infer() {
bug!("unexpected inference variable after writeback: {obligation:?}");
}
fcx.register_predicate(obligation);
}
fcx.select_obligations_where_possible(|_| {});
Comment thread
BoxyUwU marked this conversation as resolved.
if let None = fcx.infcx.tainted_by_errors() {
fcx.report_ambiguity_errors();
}
}

fcx.detect_opaque_types_added_during_writeback();

// Consistency check our TypeckResults instance can hold all ItemLocalIds
Expand Down
57 changes: 41 additions & 16 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ use snapshot::undo_log::InferCtxtUndoLogs;
use tracing::{debug, instrument};
use type_variable::TypeVariableOrigin;

use crate::infer::region_constraints::UndoLog;
use crate::infer::snapshot::undo_log::UndoLog;
use crate::infer::unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey};
use crate::traits::{
self, ObligationCause, ObligationInspector, PredicateObligations, TraitEngine,
self, ObligationCause, ObligationInspector, PredicateObligation, PredicateObligations,
TraitEngine,
};

pub mod at;
Expand Down Expand Up @@ -156,6 +157,12 @@ pub struct InferCtxtInner<'tcx> {
/// which may cause types to no longer be considered well-formed.
region_assumptions: Vec<ty::ArgOutlivesPredicate<'tcx>>,

/// `-Znext-solver`: Successfully proven goals during HIR typeck which
/// reference inference variables and get reproven after writeback.
///
/// See the documentation of `InferCtxt::in_hir_typeck` for more details.
hir_typeck_potentially_region_dependent_goals: Vec<PredicateObligation<'tcx>>,

/// Caches for opaque type inference.
opaque_type_storage: OpaqueTypeStorage<'tcx>,
}
Expand All @@ -173,6 +180,7 @@ impl<'tcx> InferCtxtInner<'tcx> {
region_constraint_storage: Some(Default::default()),
region_obligations: Default::default(),
region_assumptions: Default::default(),
hir_typeck_potentially_region_dependent_goals: Default::default(),
opaque_type_storage: Default::default(),
}
}
Expand Down Expand Up @@ -247,24 +255,25 @@ pub struct InferCtxt<'tcx> {
/// the root universe. Most notably, this is used during HIR typeck as region
/// solving is left to borrowck instead.
pub considering_regions: bool,
/// Whether this inference context is used by HIR typeck. If so, we uniquify regions
/// with `-Znext-solver`. This is necessary as borrowck will start by replacing each
/// occurance of a free region with a unique inference variable so if HIR typeck
/// ends up depending on two regions being equal we'd get unexpected mismatches
/// between HIR typeck and MIR typeck, resulting in an ICE.
/// `-Znext-solver`: Whether this inference context is used by HIR typeck. If so, we
/// need to make sure we don't rely on region identity in the trait solver or when
/// relating types. This is necessary as borrowck starts by replacing each occurrence of a
/// free region with a unique inference variable. If HIR typeck ends up depending on two
/// regions being equal we'd get unexpected mismatches between HIR typeck and MIR typeck,
/// resulting in an ICE.
///
/// The trait solver sometimes depends on regions being identical. As a concrete example
/// the trait solver ignores other candidates if one candidate exists without any constraints.
/// The goal `&'a u32: Equals<&'a u32>` has no constraints right now, but if we replace
/// each occurance of `'a` with a unique region the goal now equates these regions.
///
/// See the tests in trait-system-refactor-initiative#27 for concrete examples.
/// The goal `&'a u32: Equals<&'a u32>` has no constraints right now. If we replace each
/// occurrence of `'a` with a unique region the goal now equates these regions. See
/// the tests in trait-system-refactor-initiative#27 for concrete examples.
///
/// FIXME(-Znext-solver): This is insufficient in theory as a goal `T: Trait<?x, ?x>`
/// may rely on the two occurances of `?x` being identical. If `?x` gets inferred to a
/// type containing regions, this will no longer be the case. We can handle this case
/// by storing goals which hold while still depending on inference vars and then
/// reproving them before writeback.
/// We handle this by *uniquifying* region when canonicalizing root goals during HIR typeck.
/// This is still insufficient as inference variables may *hide* region variables, so e.g.
/// `dyn TwoSuper<?x, ?x>: Super<?x>` may hold but MIR typeck could end up having to prove
/// `dyn TwoSuper<&'0 (), &'1 ()>: Super<&'2 ()>` which is now ambiguous. Because of this we
/// stash all successfully proven goals which reference inference variables and then reprove
/// them after writeback.
pub in_hir_typeck: bool,

/// If set, this flag causes us to skip the 'leak check' during
Expand Down Expand Up @@ -1010,6 +1019,22 @@ impl<'tcx> InferCtxt<'tcx> {
}
}

pub fn push_hir_typeck_potentially_region_dependent_goal(
&self,
goal: PredicateObligation<'tcx>,
) {
let mut inner = self.inner.borrow_mut();
inner.undo_log.push(UndoLog::PushHirTypeckPotentiallyRegionDependentGoal);
inner.hir_typeck_potentially_region_dependent_goals.push(goal);
Comment on lines +1026 to +1028
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

questions:
We only let people evaluate goals inside of a probe if the fulfillmentctxt/whatever was also created in that probe?
This codepath just exists to not (incorrectly) leak details from that previous scenario, to outside of the probe?
We don't actually have support for "rolling back" trait solving?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we do when using a separate fulfillment context inside of a probe

}

pub fn take_hir_typeck_potentially_region_dependent_goals(
&self,
) -> Vec<PredicateObligation<'tcx>> {
assert!(!self.in_snapshot(), "cannot take goals in a snapshot");
std::mem::take(&mut self.inner.borrow_mut().hir_typeck_potentially_region_dependent_goals)
}

pub fn ty_to_string(&self, t: Ty<'tcx>) -> String {
self.resolve_vars_if_possible(t).to_string()
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_infer/src/infer/outlives/obligations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ impl<'tcx> InferCtxt<'tcx> {
}

pub fn take_registered_region_assumptions(&self) -> Vec<ty::ArgOutlivesPredicate<'tcx>> {
assert!(!self.in_snapshot(), "cannot take registered region assumptions in a snapshot");
std::mem::take(&mut self.inner.borrow_mut().region_assumptions)
}

Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_infer/src/infer/snapshot/undo_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub(crate) enum UndoLog<'tcx> {
ProjectionCache(traits::UndoLog<'tcx>),
PushTypeOutlivesConstraint,
PushRegionAssumption,
PushHirTypeckPotentiallyRegionDependentGoal,
}

macro_rules! impl_from {
Expand Down Expand Up @@ -79,7 +80,12 @@ impl<'tcx> Rollback<UndoLog<'tcx>> for InferCtxtInner<'tcx> {
assert_matches!(popped, Some(_), "pushed region constraint but could not pop it");
}
UndoLog::PushRegionAssumption => {
self.region_assumptions.pop();
let popped = self.region_assumptions.pop();
assert_matches!(popped, Some(_), "pushed region assumption but could not pop it");
}
UndoLog::PushHirTypeckPotentiallyRegionDependentGoal => {
let popped = self.hir_typeck_potentially_region_dependent_goals.pop();
assert_matches!(popped, Some(_), "pushed goal but could not pop it");
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ where
delegate.compute_goal_fast_path(goal, obligation.cause.span)
{
match certainty {
// This fast path doesn't depend on region identity so it doesn't
// matter if the goal contains inference variables or not, so we
// don't need to call `push_hir_typeck_potentially_region_dependent_goal`
// here.
//
// Only goals proven via the trait solver should be region dependent.
Certainty::Yes => {}
Certainty::Maybe(_) => {
self.obligations.register(obligation, None);
Expand Down Expand Up @@ -234,7 +240,11 @@ where
}

match certainty {
Certainty::Yes => {}
Certainty::Yes => {
if infcx.in_hir_typeck && obligation.has_non_region_infer() {
infcx.push_hir_typeck_potentially_region_dependent_goal(obligation);
}
}
Certainty::Maybe(_) => self.obligations.register(obligation, stalled_on),
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0283]: type annotations needed: cannot satisfy `(dyn Object<&(), &()> + 'static): Trait<&()>`
--> $DIR/ambiguity-due-to-uniquification-3.rs:28:17
|
LL | impls_trait(obj, t);
| ----------- ^^^
| |
| required by a bound introduced by this call
|
= note: cannot satisfy `(dyn Object<&(), &()> + 'static): Trait<&()>`
= help: the trait `Trait<T>` is implemented for `()`
note: required by a bound in `impls_trait`
--> $DIR/ambiguity-due-to-uniquification-3.rs:24:19
|
LL | fn impls_trait<T: Trait<U>, U>(_: Inv<T>, _: Inv<U>) {}
| ^^^^^^^^ required by this bound in `impls_trait`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0283`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[current] check-pass

// Regression test from trait-system-refactor-initiative#27.
//
// Unlike in the previous two tests, `dyn Object<?x, ?y>: Trait<?x>` relies
// on structural identity of type inference variables. This inference variable
// gets constrained to a type containing a region later on. To prevent this
// from causing an ICE during MIR borrowck, we stash goals which depend on
// inference variables and then reprove them at the end of HIR typeck.

#![feature(rustc_attrs)]
#![rustc_no_implicit_bounds]
trait Trait<T> {}
impl<T> Trait<T> for () {}

trait Object<T, U>: Trait<T> + Trait<U> {}

#[derive(Clone, Copy)]
struct Inv<T>(*mut T);
fn foo<T: Sized, U: Sized>() -> (Inv<dyn Object<T, U>>, Inv<T>) { todo!() }
fn impls_trait<T: Trait<U>, U>(_: Inv<T>, _: Inv<U>) {}

fn bar() {
let (obj, t) = foo();
impls_trait(obj, t);
//[next]~^ ERROR type annotations needed
let _: Inv<dyn Object<&(), &()>> = obj;
}

fn main() {}