Auto merge of #144405 - lcnr:hir-typeck-uniquify, r=BoxyUwU

uniquify root goals during HIR typeck

We need to rely on region identity to deal with hangs such as https://github.com/rust-lang/trait-system-refactor-initiative/issues/210 and to keep the current behavior of `fn try_merge_responses`.

This is a problem as borrowck starts by replacing each *occurrence* of a region with a unique inference variable. This frequently splits a single region during HIR typeck into multiple distinct regions. As we assume goals to always succeed during borrowck, relying on two occurances of a region being identical during HIR typeck causes ICE. See the now fixed examples in https://github.com/rust-lang/trait-system-refactor-initiative/issues/27 and rust-lang/rust#139409.

We've previously tried to avoid this issue by always *uniquifying* regions when canonicalizing goals. This prevents caching subtrees during canonicalization which resulted in hangs for very large types. People rely on such types in practice, which caused us to revert our attempt to reinstate `#[type_length_limit]` in https://github.com/rust-lang/rust/pull/127670. The complete list of changes here:
- rust-lang/rust#107981
- rust-lang/rust#110180
- rust-lang/rust#114117
- rust-lang/rust#130821

After more consideration, all occurrences of such large types need to happen outside of typeck/borrowck. We know this as we already walk over all types in the MIR body when replacing their regions with nll vars.

This PR therefore enables us to rely on region identity inside of the trait solver by exclusively **uniquifying root goals during HIR typeck**. These are the only goals we assume to hold during borrowck. This is insufficient as type inference variables may "hide" regions we later uniquify. Because of this, we now stash proven goals which depend on inference variables in HIR typeck and reprove them after writeback. This closes https://github.com/rust-lang/trait-system-refactor-initiative/issues/127.

This was originally part of rust-lang/rust#144258 but I've moved it into a separate PR. While I believe we need to rely on region identity to fix the performance issues in some way, I don't know whether rust-lang/rust#144258 is the best approach to actually do so. Regardless of how we deal with the hangs however, this change is necessary and desirable regardless.

r? `@compiler-errors` or `@BoxyUwU`
This commit is contained in:
bors 2025-07-31 00:32:55 +00:00
commit 32e7a4b92b
20 changed files with 353 additions and 57 deletions

View file

@ -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;
@ -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(|_| {});
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

View file

@ -85,8 +85,11 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
pub(crate) fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self {
let hir_owner = tcx.local_def_id_to_hir_id(def_id).owner;
let infcx =
tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id));
let infcx = tcx
.infer_ctxt()
.ignoring_regions()
.in_hir_typeck()
.build(TypingMode::typeck_for_body(tcx, def_id));
let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
let fulfillment_cx = RefCell::new(<dyn TraitEngine<'_, _>>::new(&infcx));

View file

@ -71,6 +71,7 @@ impl<'tcx> InferCtxt<'tcx> {
tcx: self.tcx,
typing_mode: self.typing_mode,
considering_regions: self.considering_regions,
in_hir_typeck: self.in_hir_typeck,
skip_leak_check: self.skip_leak_check,
inner: self.inner.clone(),
lexical_region_resolutions: self.lexical_region_resolutions.clone(),
@ -95,6 +96,7 @@ impl<'tcx> InferCtxt<'tcx> {
tcx: self.tcx,
typing_mode,
considering_regions: self.considering_regions,
in_hir_typeck: self.in_hir_typeck,
skip_leak_check: self.skip_leak_check,
inner: self.inner.clone(),
lexical_region_resolutions: self.lexical_region_resolutions.clone(),

View file

@ -22,6 +22,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
self.next_trait_solver
}
fn in_hir_typeck(&self) -> bool {
self.in_hir_typeck
}
fn typing_mode(&self) -> ty::TypingMode<'tcx> {
self.typing_mode()
}

View file

@ -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;
@ -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>,
}
@ -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(),
}
}
@ -244,9 +252,29 @@ pub struct InferCtxt<'tcx> {
typing_mode: TypingMode<'tcx>,
/// Whether this inference context should care about region obligations in
/// the root universe. Most notably, this is used during hir typeck as region
/// the root universe. Most notably, this is used during HIR typeck as region
/// solving is left to borrowck instead.
pub considering_regions: bool,
/// `-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. 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.
///
/// 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
/// higher-ranked subtyping operations. This flag is a temporary one used
@ -506,6 +534,7 @@ pub struct TypeOutlivesConstraint<'tcx> {
pub struct InferCtxtBuilder<'tcx> {
tcx: TyCtxt<'tcx>,
considering_regions: bool,
in_hir_typeck: bool,
skip_leak_check: bool,
/// Whether we should use the new trait solver in the local inference context,
/// which affects things like which solver is used in `predicate_may_hold`.
@ -518,6 +547,7 @@ impl<'tcx> TyCtxt<'tcx> {
InferCtxtBuilder {
tcx: self,
considering_regions: true,
in_hir_typeck: false,
skip_leak_check: false,
next_trait_solver: self.next_trait_solver_globally(),
}
@ -535,6 +565,11 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
self
}
pub fn in_hir_typeck(mut self) -> Self {
self.in_hir_typeck = true;
self
}
pub fn skip_leak_check(mut self, skip_leak_check: bool) -> Self {
self.skip_leak_check = skip_leak_check;
self
@ -568,12 +603,18 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
}
pub fn build(&mut self, typing_mode: TypingMode<'tcx>) -> InferCtxt<'tcx> {
let InferCtxtBuilder { tcx, considering_regions, skip_leak_check, next_trait_solver } =
*self;
let InferCtxtBuilder {
tcx,
considering_regions,
in_hir_typeck,
skip_leak_check,
next_trait_solver,
} = *self;
InferCtxt {
tcx,
typing_mode,
considering_regions,
in_hir_typeck,
skip_leak_check,
inner: RefCell::new(InferCtxtInner::new()),
lexical_region_resolutions: RefCell::new(None),
@ -978,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);
}
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()
}

View file

@ -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)
}

View file

@ -29,6 +29,7 @@ pub(crate) enum UndoLog<'tcx> {
ProjectionCache(traits::UndoLog<'tcx>),
PushTypeOutlivesConstraint,
PushRegionAssumption,
PushHirTypeckPotentiallyRegionDependentGoal,
}
macro_rules! impl_from {
@ -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");
}
}
}

View file

@ -19,6 +19,20 @@ const NEEDS_CANONICAL: TypeFlags = TypeFlags::from_bits(
)
.unwrap();
#[derive(Debug, Clone, Copy)]
enum CanonicalizeInputKind {
/// When canonicalizing the `param_env`, we keep `'static` as merging
/// trait candidates relies on it when deciding whether a where-bound
/// is trivial.
ParamEnv,
/// When canonicalizing predicates, we don't keep `'static`. If we're
/// currently outside of the trait solver and canonicalize the root goal
/// during HIR typeck, we replace each occurance of a region with a
/// unique region variable. See the comment on `InferCtxt::in_hir_typeck`
/// for more details.
Predicate { is_hir_typeck_root_goal: bool },
}
/// Whether we're canonicalizing a query input or the query response.
///
/// When canonicalizing an input we're in the context of the caller
@ -26,10 +40,7 @@ const NEEDS_CANONICAL: TypeFlags = TypeFlags::from_bits(
/// query.
#[derive(Debug, Clone, Copy)]
enum CanonicalizeMode {
/// When canonicalizing the `param_env`, we keep `'static` as merging
/// trait candidates relies on it when deciding whether a where-bound
/// is trivial.
Input { keep_static: bool },
Input(CanonicalizeInputKind),
/// FIXME: We currently return region constraints referring to
/// placeholders and inference variables from a binder instantiated
/// inside of the query.
@ -122,7 +133,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
let mut variables = Vec::new();
let mut env_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input { keep_static: true },
canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
variables: &mut variables,
variable_lookup_table: Default::default(),
@ -154,7 +165,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
} else {
let mut env_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input { keep_static: true },
canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv),
variables,
variable_lookup_table: Default::default(),
@ -180,6 +191,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
pub fn canonicalize_input<P: TypeFoldable<I>>(
delegate: &'a D,
variables: &'a mut Vec<I::GenericArg>,
is_hir_typeck_root_goal: bool,
input: QueryInput<I, P>,
) -> ty::Canonical<I, QueryInput<I, P>> {
// First canonicalize the `param_env` while keeping `'static`
@ -189,7 +201,9 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
// while *mostly* reusing the canonicalizer from above.
let mut rest_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input { keep_static: false },
canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
is_hir_typeck_root_goal,
}),
variables,
variable_lookup_table,
@ -296,7 +310,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
}
}
fn cached_fold_ty(&mut self, t: I::Ty) -> I::Ty {
fn inner_fold_ty(&mut self, t: I::Ty) -> I::Ty {
let kind = match t.kind() {
ty::Infer(i) => match i {
ty::TyVar(vid) => {
@ -413,10 +427,10 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
// We don't canonicalize `ReStatic` in the `param_env` as we use it
// when checking whether a `ParamEnv` candidate is global.
ty::ReStatic => match self.canonicalize_mode {
CanonicalizeMode::Input { keep_static: false } => {
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
}
CanonicalizeMode::Input { keep_static: true }
CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
| CanonicalizeMode::Response { .. } => return r,
},
@ -428,12 +442,12 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
// `ReErased`. We may be able to short-circuit registering region
// obligations if we encounter a `ReErased` on one side, for example.
ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { .. } => return r,
},
ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode {
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { .. } => {
panic!("unexpected region in response: {r:?}")
}
@ -441,7 +455,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
ty::RePlaceholder(placeholder) => match self.canonicalize_mode {
// We canonicalize placeholder regions as existentials in query inputs.
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { max_input_universe } => {
// If we have a placeholder region inside of a query, it must be from
// a new universe.
@ -459,9 +473,7 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
"region vid should have been resolved fully before canonicalization"
);
match self.canonicalize_mode {
CanonicalizeMode::Input { keep_static: _ } => {
CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
}
CanonicalizeMode::Input(_) => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { .. } => {
CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap())
}
@ -469,16 +481,34 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
}
};
let var = self.get_or_insert_bound_var(r, kind);
let var = if let CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
is_hir_typeck_root_goal: true,
}) = self.canonicalize_mode
{
let var = ty::BoundVar::from(self.variables.len());
self.variables.push(r.into());
self.var_kinds.push(kind);
var
} else {
self.get_or_insert_bound_var(r, kind)
};
Region::new_anon_bound(self.cx(), self.binder_index, var)
}
fn fold_ty(&mut self, t: I::Ty) -> I::Ty {
if let Some(&ty) = self.cache.get(&(self.binder_index, t)) {
if let CanonicalizeMode::Input(CanonicalizeInputKind::Predicate {
is_hir_typeck_root_goal: true,
}) = self.canonicalize_mode
{
// If we're canonicalizing a root goal during HIR typeck, we
// must not use the `cache` as we want to map each occurrence
// of a region to a unique existential variable.
self.inner_fold_ty(t)
} else if let Some(&ty) = self.cache.get(&(self.binder_index, t)) {
ty
} else {
let res = self.cached_fold_ty(t);
let res = self.inner_fold_ty(t);
let old = self.cache.insert((self.binder_index, t), res);
assert_eq!(old, None);
res
@ -541,9 +571,9 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
fn fold_clauses(&mut self, c: I::Clauses) -> I::Clauses {
match self.canonicalize_mode {
CanonicalizeMode::Input { keep_static: true }
CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv)
| CanonicalizeMode::Response { max_input_universe: _ } => {}
CanonicalizeMode::Input { keep_static: false } => {
CanonicalizeMode::Input(CanonicalizeInputKind::Predicate { .. }) => {
panic!("erasing 'static in env")
}
}

View file

@ -53,20 +53,19 @@ where
{
/// Canonicalizes the goal remembering the original values
/// for each bound variable.
///
/// This expects `goal` and `opaque_types` to be eager resolved.
pub(super) fn canonicalize_goal(
&self,
is_hir_typeck_root_goal: bool,
goal: Goal<I, I::Predicate>,
opaque_types: Vec<(ty::OpaqueTypeKey<I>, I::Ty)>,
) -> (Vec<I::GenericArg>, CanonicalInput<I, I::Predicate>) {
// We only care about one entry per `OpaqueTypeKey` here,
// so we only canonicalize the lookup table and ignore
// duplicate entries.
let opaque_types = self.delegate.clone_opaque_types_lookup_table();
let (goal, opaque_types) = eager_resolve_vars(self.delegate, (goal, opaque_types));
let mut orig_values = Default::default();
let canonical = Canonicalizer::canonicalize_input(
self.delegate,
&mut orig_values,
is_hir_typeck_root_goal,
QueryInput {
goal,
predefined_opaques_in_body: self

View file

@ -20,6 +20,7 @@ use super::has_only_region_constraints;
use crate::coherence;
use crate::delegate::SolverDelegate;
use crate::placeholder::BoundVarReplacer;
use crate::resolve::eager_resolve_vars;
use crate::solve::inspect::{self, ProofTreeBuilder};
use crate::solve::search_graph::SearchGraph;
use crate::solve::ty::may_use_unstable_feature;
@ -440,6 +441,7 @@ where
return Ok((
NestedNormalizationGoals::empty(),
GoalEvaluation {
goal,
certainty: Certainty::Maybe(stalled_on.stalled_cause),
has_changed: HasChanged::No,
stalled_on: Some(stalled_on),
@ -447,7 +449,16 @@ where
));
}
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
// We only care about one entry per `OpaqueTypeKey` here,
// so we only canonicalize the lookup table and ignore
// duplicate entries.
let opaque_types = self.delegate.clone_opaque_types_lookup_table();
let (goal, opaque_types) = eager_resolve_vars(self.delegate, (goal, opaque_types));
let is_hir_typeck_root_goal = matches!(goal_evaluation_kind, GoalEvaluationKind::Root)
&& self.delegate.in_hir_typeck();
let (orig_values, canonical_goal) =
self.canonicalize_goal(is_hir_typeck_root_goal, goal, opaque_types);
let mut goal_evaluation =
self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind);
let canonical_result = self.search_graph.evaluate_goal(
@ -525,7 +536,10 @@ where
},
};
Ok((normalization_nested_goals, GoalEvaluation { certainty, has_changed, stalled_on }))
Ok((
normalization_nested_goals,
GoalEvaluation { goal, certainty, has_changed, stalled_on },
))
}
pub(super) fn compute_goal(&mut self, goal: Goal<I, I::Predicate>) -> QueryResult<I> {
@ -661,7 +675,7 @@ where
let (
NestedNormalizationGoals(nested_goals),
GoalEvaluation { certainty, stalled_on, has_changed: _ },
GoalEvaluation { goal, certainty, stalled_on, has_changed: _ },
) = self.evaluate_goal_raw(
GoalEvaluationKind::Nested,
source,
@ -699,7 +713,15 @@ where
// FIXME: Do we need to eagerly resolve here? Or should we check
// if the cache key has any changed vars?
let with_resolved_vars = self.resolve_vars_if_possible(goal);
if pred.alias != goal.predicate.as_normalizes_to().unwrap().skip_binder().alias {
if pred.alias
!= with_resolved_vars
.predicate
.as_normalizes_to()
.unwrap()
.no_bound_vars()
.unwrap()
.alias
{
unchanged_certainty = None;
}
@ -711,7 +733,7 @@ where
}
}
} else {
let GoalEvaluation { certainty, has_changed, stalled_on } =
let GoalEvaluation { goal, certainty, has_changed, stalled_on } =
self.evaluate_goal(GoalEvaluationKind::Nested, source, goal, stalled_on)?;
if has_changed == HasChanged::Yes {
unchanged_certainty = None;

View file

@ -252,8 +252,6 @@ where
return None;
}
// FIXME(-Znext-solver): Add support to merge region constraints in
// responses to deal with trait-system-refactor-initiative#27.
let one = responses[0];
if responses[1..].iter().all(|&resp| resp == one) {
return Some(one);
@ -388,6 +386,23 @@ fn response_no_constraints_raw<I: Interner>(
/// The result of evaluating a goal.
pub struct GoalEvaluation<I: Interner> {
/// The goal we've evaluated. This is the input goal, but potentially with its
/// inference variables resolved. This never applies any inference constraints
/// from evaluating the goal.
///
/// We rely on this to check whether root goals in HIR typeck had an unresolved
/// type inference variable in the input. We must not resolve this after evaluating
/// the goal as even if the inference variable has been resolved by evaluating the
/// goal itself, this goal may still end up failing due to region uniquification
/// later on.
///
/// This is used as a minor optimization to avoid re-resolving inference variables
/// when reevaluating ambiguous goals. E.g. if we've got a goal `?x: Trait` with `?x`
/// already being constrained to `Vec<?y>`, then the first evaluation resolves it to
/// `Vec<?y>: Trait`. If this goal is still ambiguous and we later resolve `?y` to `u32`,
/// then reevaluating this goal now only needs to resolve `?y` while it would otherwise
/// have to resolve both `?x` and `?y`,
pub goal: Goal<I, I::Predicate>,
pub certainty: Certainty,
pub has_changed: HasChanged,
/// If the [`Certainty`] was `Maybe`, then keep track of whether the goal has changed

View file

@ -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);
@ -207,7 +213,7 @@ where
let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on);
self.inspect_evaluated_obligation(infcx, &obligation, &result);
let GoalEvaluation { certainty, has_changed, stalled_on } = match result {
let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result {
Ok(result) => result,
Err(NoSolution) => {
errors.push(E::from_solver_error(
@ -218,6 +224,10 @@ where
}
};
// We've resolved the goal in `evaluate_root_goal`, avoid redoing this work
// in the next iteration. This does not resolve the inference variables
// constrained by evaluating the goal.
obligation.predicate = goal.predicate;
if has_changed == HasChanged::Yes {
// We increment the recursion depth here to track the number of times
// this goal has resulted in inference progress. This doesn't precisely
@ -230,7 +240,22 @@ where
}
match certainty {
Certainty::Yes => {}
Certainty::Yes => {
// Goals may depend on structural identity. Region uniquification at the
// start of MIR borrowck may cause things to no longer be so, potentially
// causing an ICE.
//
// While we uniquify root goals in HIR this does not handle cases where
// regions are hidden inside of a type or const inference variable.
//
// FIXME(-Znext-solver): This does not handle inference variables hidden
// inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the
// storage, we can also rely on structural identity of `?x` even if we
// later uniquify it in MIR borrowck.
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),
}
}

View file

@ -148,6 +148,10 @@ pub trait InferCtxtLike: Sized {
true
}
fn in_hir_typeck(&self) -> bool {
false
}
fn typing_mode(&self) -> TypingMode<Self::Interner>;
fn universe(&self) -> ty::UniverseIndex;

View file

@ -1,12 +0,0 @@
//@ known-bug: #139409
//@ compile-flags: -Znext-solver=globally
fn main() {
trait B<C> {}
impl<C> B<C> for () {}
trait D<C, E>: B<C> + B<E> {
fn f(&self) {}
}
impl<C, E> D<C, E> for () {}
(&() as &dyn D<&(), &()>).f()
}

View file

@ -0,0 +1,19 @@
error[E0283]: type annotations needed: cannot satisfy `dyn D<&(), &()>: B<&()>`
--> $DIR/ambiguity-due-to-uniquification-1.rs:15:31
|
LL | (&() as &dyn D<&(), &()>).f()
| ^
|
= note: cannot satisfy `dyn D<&(), &()>: B<&()>`
= help: the trait `B<C>` is implemented for `()`
note: required by a bound in `D::f`
--> $DIR/ambiguity-due-to-uniquification-1.rs:10:16
|
LL | trait D<C, E>: B<C> + B<E> {
| ^^^^ required by this bound in `D::f`
LL | fn f(&self) {}
| - required by a bound in this associated function
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0283`.

View file

@ -0,0 +1,17 @@
//@ revisions: current next
//@[next] compile-flags: -Znext-solver
//@ ignore-compare-mode-next-solver (explicit revisions)
//@[current] check-pass
// Regression test for #139409 and trait-system-refactor-initiative#27.
trait B<C> {}
impl<C> B<C> for () {}
trait D<C, E>: B<C> + B<E> {
fn f(&self) {}
}
impl<C, E> D<C, E> for () {}
fn main() {
(&() as &dyn D<&(), &()>).f()
//[next]~^ ERROR type annotations needed: cannot satisfy `dyn D<&(), &()>: B<&()>`
}

View file

@ -0,0 +1,17 @@
error[E0283]: type annotations needed: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
--> $DIR/ambiguity-due-to-uniquification-2.rs:16:23
|
LL | impls_trait::<'y, _>(foo::<'x, 'y>());
| ^
|
= note: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
= help: the trait `Trait<'t>` is implemented for `()`
note: required by a bound in `impls_trait`
--> $DIR/ambiguity-due-to-uniquification-2.rs:13:23
|
LL | fn impls_trait<'x, T: Trait<'x>>(_: T) {}
| ^^^^^^^^^ required by this bound in `impls_trait`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0283`.

View file

@ -0,0 +1,20 @@
//@ 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.
trait Trait<'t> {}
impl<'t> Trait<'t> for () {}
fn foo<'x, 'y>() -> impl Trait<'x> + Trait<'y> {}
fn impls_trait<'x, T: Trait<'x>>(_: T) {}
fn bar<'x, 'y>() {
impls_trait::<'y, _>(foo::<'x, 'y>());
//[next]~^ ERROR type annotations needed: cannot satisfy `impl Trait<'x> + Trait<'y>: Trait<'y>`
}
fn main() {}

View file

@ -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`.

View file

@ -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() {}