From 5a6c953873ff75798ab0927aad504ac56118a4d4 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:59:34 +0100 Subject: [PATCH 01/10] split `rewrite_type_outlives_constraints_in_universe` into multiple fns --- .../rustc_type_ir/src/region_constraint.rs | 327 ++++++++++-------- 1 file changed, 181 insertions(+), 146 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index d34a1ec52d153..ecf29a3a24902 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -754,157 +754,21 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< match constraint { Ambiguity | RegionOutlives(..) => constraint, PlaceholderTyOutlives(ty, region) => { - let ty_u = max_universe(infcx, ty); - let region_u = max_universe(infcx, region); - - if region_u != u && ty_u != u { - return constraint; - } - - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => return Ambiguity, - }; - - let mut candidates = vec![]; - - // There could be `!T: 'region` assumptions in the env even if `!T` is in a - // smaller universe - candidates.extend( - regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) - .map(move |assumption_r| RegionOutlives(assumption_r, region)), - ); - - // We can express `!T: 'region` as `!T: 'r` where `'r: 'region`. This is only necessary - // if the placeholder type is in a smaller universe as otherwise we know all regions which - // the placeholder outlives and can just destructure into an OR of RegionOutlives. - if region_u == u && ty_u < u { - candidates.extend( - regions_outliving::(region, assumptions, infcx.cx()) - .filter(|r| max_universe(infcx, *r) < u) - .map(|r| PlaceholderTyOutlives(ty, r)), - ); - } - - Or(candidates.into_boxed_slice()) + rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + ty, + region, + u, + assumptions, + ) } AliasTyOutlivesViaEnv(bound_outlives) => { - let mut candidates = Vec::new(); - - // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that - // means that it's actually *always* possible for an alias outlive to be satisfied in the root universe - // which means there should *always* be atleast two candidates when destructuring alias outlives. The - // two candidates being component outlives and then a higher ranked alias outlives. - // - // we dont care about this for region outlives as `for<'a> 'a: 'b` can't exist as we don't elaborate - // higher ranked type outlives assumptions into higher ranked region outlives assumptions. similarly, - // we don't care about `for<'a> Foo<'a>: 'b` as we always destructure adts into their components and if - // we dont equivalently elaborate the assumption into assumptions on the adt's components we just drop the - // assumptions - // - // so actually only `for<'a, 'b> Alias<'a>: 'b` and `for<'a> T: 'a` are assumptions we actually need to - // handle. - // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - if max_universe(infcx, bound_outlives) == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_outlives = bound_outlives.skip_binder().fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_outlives = Binder::bind_with_vars( - escaping_outlives, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - let candidate = RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave - // a placeholder type in `u`, so this type-outlives constraint cannot be - // handled by the region-outlives-only eager placeholder machinery. - candidates.push(Ambiguity); - } - } - - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => { - candidates.push(Ambiguity); - return Or(candidates.into_boxed_slice()); - } - }; - - // Actually look at the assumptions and matching our higher ranked alias outlives goal - // against potentially higher ranked type outlives assumptions. - candidates.push(alias_outlives_candidates_from_assumptions( + rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( infcx, bound_outlives, + u, assumptions, - )); - - // we can rewrite `Alias_u1: 'u2` into `Or(Alias_u1: 'u1)` - // given a list of regions which outlive `'u2` - // - // we don't care about this when rewriting in the root universe as we know the complete set of assumptions - let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); - if max_universe(infcx, escaping_r) == u { - let mut replacer = PlaceholderReplacer { - cx: infcx.cx(), - existing_var_count: bound_outlives.bound_vars().len(), - bound_vars: IndexMap::default(), - universe: u, - current_index: DebruijnIndex::ZERO, - }; - let escaping_alias = escaping_alias.fold_with(&mut replacer); - let bound_vars = bound_outlives.bound_vars().iter().chain( - core::mem::take(&mut replacer.bound_vars) - .into_iter() - .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), - ); - let bound_alias = Binder::bind_with_vars( - escaping_alias, - I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), - ); - - // while we did skip the binder, bound vars aren't in any universe so - // this can't be an escaping bound var - for r2 in regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - { - let candidate = - AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2))); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - candidates.push(Ambiguity); - } - } - } - - // I'm not convinced our handling here is *complete* so for now - // let's be conservative and not let alias outlives' cause NoSolution - // in coherence - match infcx.typing_mode_raw() { - TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), - TypingMode::Typeck { .. } - | TypingMode::ErasedNotCoherence { .. } - | TypingMode::PostTypeckUntilBorrowck { .. } - | TypingMode::PostBorrowck { .. } - | TypingMode::Reflection - | TypingMode::PostAnalysis - | TypingMode::Codegen => (), - }; - - RegionConstraint::Or(candidates.into_boxed_slice()) + ) } And(constraints) => And(constraints .into_iter() @@ -931,6 +795,177 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< } } +fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + ty: I::Ty, + region: Region, + u: UniverseIndex, + assumptions: &Option>, +) -> RegionConstraint { + use RegionConstraint::*; + + let ty_u = max_universe(infcx, ty); + let region_u = max_universe(infcx, region); + + if region_u != u && ty_u != u { + return PlaceholderTyOutlives(ty, region); + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => return Ambiguity, + }; + + let mut candidates = vec![]; + + // There could be `!T: 'region` assumptions in the env even if `!T` is in a + // smaller universe + candidates.extend( + regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) + .map(move |assumption_r| RegionOutlives(assumption_r, region)), + ); + + // We can express `!T: 'region` as `!T: 'r` where `'r: 'region`. This is only necessary + // if the placeholder type is in a smaller universe as otherwise we know all regions which + // the placeholder outlives and can just destructure into an OR of RegionOutlives. + if region_u == u && ty_u < u { + candidates.extend( + regions_outliving::(region, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < u) + .map(|r| PlaceholderTyOutlives(ty, r)), + ); + } + + Or(candidates.into_boxed_slice()) +} + +fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< + Infcx: InferCtxtLike, + I: Interner, +>( + infcx: &Infcx, + bound_outlives: Binder, Region)>, + u: UniverseIndex, + assumptions: &Option>, +) -> RegionConstraint { + use RegionConstraint::*; + + let mut candidates = Vec::new(); + + // given there can be higher ranked assumptions, e.g. `for<'a> >::Assoc: 'c`, that + // means that it's actually *always* possible for an alias outlive to be satisfied in the root universe + // which means there should *always* be atleast two candidates when destructuring alias outlives. The + // two candidates being component outlives and then a higher ranked alias outlives. + // + // we dont care about this for region outlives as `for<'a> 'a: 'b` can't exist as we don't elaborate + // higher ranked type outlives assumptions into higher ranked region outlives assumptions. similarly, + // we don't care about `for<'a> Foo<'a>: 'b` as we always destructure adts into their components and if + // we dont equivalently elaborate the assumption into assumptions on the adt's components we just drop the + // assumptions + // + // so actually only `for<'a, 'b> Alias<'a>: 'b` and `for<'a> T: 'a` are assumptions we actually need to + // handle. + // + // we don't care about this when rewriting in the root universe as we know the complete set of assumptions + if max_universe(infcx, bound_outlives) == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, + }; + let escaping_outlives = bound_outlives.skip_binder().fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_outlives = Binder::bind_with_vars( + escaping_outlives, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + let candidate = RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives); + if max_universe(infcx, candidate.clone()) < u { + candidates.push(candidate); + } else { + // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave + // a placeholder type in `u`, so this type-outlives constraint cannot be + // handled by the region-outlives-only eager placeholder machinery. + candidates.push(Ambiguity); + } + } + + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => { + candidates.push(Ambiguity); + return Or(candidates.into_boxed_slice()); + } + }; + + // Actually look at the assumptions and matching our higher ranked alias outlives goal + // against potentially higher ranked type outlives assumptions. + candidates.push(alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions)); + + // we can rewrite `Alias_u1: 'u2` into `Or(Alias_u1: 'u1)` + // given a list of regions which outlive `'u2` + // + // we don't care about this when rewriting in the root universe as we know the complete set of assumptions + let (escaping_alias, escaping_r) = bound_outlives.skip_binder(); + if max_universe(infcx, escaping_r) == u { + let mut replacer = PlaceholderReplacer { + cx: infcx.cx(), + existing_var_count: bound_outlives.bound_vars().len(), + bound_vars: IndexMap::default(), + universe: u, + current_index: DebruijnIndex::ZERO, + }; + let escaping_alias = escaping_alias.fold_with(&mut replacer); + let bound_vars = bound_outlives.bound_vars().iter().chain( + core::mem::take(&mut replacer.bound_vars) + .into_iter() + .map(|(_, bound_region)| BoundVariableKind::Region(bound_region.kind)), + ); + let bound_alias = Binder::bind_with_vars( + escaping_alias, + I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), + ); + + // while we did skip the binder, bound vars aren't in any universe so + // this can't be an escaping bound var + for r2 in regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + { + let candidate = AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2))); + if max_universe(infcx, candidate.clone()) < u { + candidates.push(candidate); + } else { + candidates.push(Ambiguity); + } + } + } + + // I'm not convinced our handling here is *complete* so for now + // let's be conservative and not let alias outlives' cause NoSolution + // in coherence + match infcx.typing_mode_raw() { + TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), + TypingMode::Typeck { .. } + | TypingMode::ErasedNotCoherence { .. } + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::PostBorrowck { .. } + | TypingMode::Reflection + | TypingMode::PostAnalysis + | TypingMode::Codegen => (), + }; + + RegionConstraint::Or(candidates.into_boxed_slice()) +} + /// Returns all regions `r2` for which `r: r2` is known to hold in /// the universe associated with `assumptions` pub fn regions_outlived_by( From fb9c6536f2b19f59dd536b15742c005b9ce47d48 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 13:37:59 +0100 Subject: [PATCH 02/10] restructure `rewrite_type_outlives_constraints_in_universe` --- .../rustc_type_ir/src/region_constraint.rs | 73 ++++++++----------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index ecf29a3a24902..34713a3fe3b9a 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -451,7 +451,7 @@ pub fn eagerly_handle_placeholders_in_universe constraint, - PlaceholderTyOutlives(ty, region) => { - rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - ty, - region, - u, - assumptions, - ) - } - AliasTyOutlivesViaEnv(bound_outlives) => { - rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - bound_outlives, - u, - assumptions, - ) + let rewrite_and = |and: RegionConstraint| { + let mut rewritten_constraints = Vec::new(); + for c in and.unwrap_and() { + match c { + Ambiguity | RegionOutlives(..) => rewritten_constraints.push(c), + PlaceholderTyOutlives(ty, region) => { + rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + ty, + region, + u, + assumptions, + )); + } + AliasTyOutlivesViaEnv(bound_outlives) => { + rewritten_constraints.push(rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( + infcx, + bound_outlives, + u, + assumptions, + )) + } + And(_) | Or(_) => unreachable!(), + } } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - constraint, - u, - assumptions, - ) - }) - .collect()), - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| { - rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - constraint, - u, - assumptions, - ) - }) - .collect()), - } + + And(rewritten_constraints.into_boxed_slice()) + }; + + let ands = constraint.unwrap_or(); + Or(ands.into_iter().map(|and| rewrite_and(and)).collect::>().into_boxed_slice()) } fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< From 92354ff90994b4d8f8fc67fef072de4bc103564a Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 14:05:01 +0100 Subject: [PATCH 03/10] restructure `compute_new_region_constraints` --- .../rustc_type_ir/src/region_constraint.rs | 117 +++++++++--------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 34713a3fe3b9a..76c03c9bd2872 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -441,8 +441,6 @@ pub fn eagerly_handle_placeholders_in_universe, u: UniverseIndex, ) -> RegionConstraint { - use RegionConstraint::*; - let assumptions = infcx.get_placeholder_assumptions(u); // 1. rewrite type outlives constraints involving things from `u` into either region constraints @@ -456,28 +454,17 @@ pub fn eagerly_handle_placeholders_in_universe>() - .into_boxed_slice()); + // 3. rewrite region outlives constraints (potentially to false/true) + let constraint = + pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, &assumptions) + .canonical_form(); - // 5. actually evaluate the constraint to eagerly error on false + // 4. actually evaluate the constraint to eagerly error on false evaluate_solver_constraint(&constraint) } @@ -492,51 +479,63 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraints: &[RegionConstraint], + constraint: RegionConstraint, u: UniverseIndex, -) -> Vec> { +) -> RegionConstraint { use RegionConstraint::*; - let mut new_constraints = vec![]; - - let mut region_flows_builder = TransitiveRelationBuilder::default(); - let mut regions = IndexSet::new(); - for c in constraints { - match c { - And(..) | Or(..) => unreachable!(), - Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { - new_constraints.push(c.clone()) - } - RegionOutlives(r1, r2) => { - regions.insert(r1); - regions.insert(r2); - region_flows_builder.add(r2, r1); + let extend_from_and = |builder: &mut TransitiveRelationBuilder<_>, + regions: &mut IndexSet<_>, + constraints: &mut Vec<_>, + and: RegionConstraint| { + for c in and.unwrap_and() { + match c { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + constraints.push(c.clone()) + } + RegionOutlives(r1, r2) => { + regions.insert(r1); + regions.insert(r2); + builder.add(r2, r1); + } + Or(_) | And(_) => unreachable!(), } } - } - - let region_flow = region_flows_builder.freeze(); - for r in regions.into_iter() { - for ub in region_flow.reachable_from(r) { - // we want to retain any region constraints between two "placeholder-likes" where for our - // purposes a placeholder-like is either a placeholder or variable in a lower universe - let is_placeholder_like = |r: Region| match r.kind() { - RegionKind::ReLateParam(..) - | RegionKind::ReEarlyParam(..) - | RegionKind::RePlaceholder(..) - | RegionKind::ReStatic => true, - RegionKind::ReVar(..) => max_universe(infcx, r) < u, - RegionKind::ReError(..) => false, - RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), - }; + }; - if is_placeholder_like(*r) && is_placeholder_like(*ub) { - new_constraints.push(RegionOutlives(*ub, *r)); + let mut new_ands = Vec::new(); + for and in constraint.unwrap_or() { + let mut region_flows_builder = TransitiveRelationBuilder::default(); + let mut regions = IndexSet::new(); + let mut constraints = Vec::new(); + + extend_from_and(&mut region_flows_builder, &mut regions, &mut constraints, and); + + let region_flow = region_flows_builder.freeze(); + for r in regions.into_iter() { + for ub in region_flow.reachable_from(r) { + // we want to retain any region constraints between two "placeholder-likes" where for our + // purposes a placeholder-like is either a placeholder or variable in a lower universe + let is_placeholder_like = |r: Region| match r.kind() { + RegionKind::ReLateParam(..) + | RegionKind::ReEarlyParam(..) + | RegionKind::RePlaceholder(..) + | RegionKind::ReStatic => true, + RegionKind::ReVar(..) => max_universe(infcx, r) < u, + RegionKind::ReError(..) => false, + RegionKind::ReErased | RegionKind::ReBound(..) => unreachable!(), + }; + + if is_placeholder_like(r) && is_placeholder_like(ub) { + constraints.push(RegionOutlives(ub, r)); + } } } + + new_ands.push(And(constraints.into_boxed_slice())) } - new_constraints + Or(new_ands.into_boxed_slice()) } /// Evaluate ANDs and ORs to true/false/ambiguous based on whether their arguments are true/false/ambiguous @@ -679,7 +678,13 @@ fn pull_region_outlives_constraints_out_of_universe< pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) }) .collect()), - Or(_) => unreachable!(), + // NOTE: this will be reverted back to `unreachable!()` in a future commit + Or(constraints) => Or(constraints + .into_iter() + .map(|constraint| { + pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) + }) + .collect()), } } From 62ca88daa0e639feeb21596afa1c58bbf38d5e96 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:24:10 +0100 Subject: [PATCH 04/10] restructure `pull_region_outlives_constraints_out_of_universe` --- .../rustc_type_ir/src/region_constraint.rs | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 76c03c9bd2872..017d8ebc68933 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -460,12 +460,15 @@ pub fn eagerly_handle_placeholders_in_universe { - assert!(max_universe(infcx, constraint.clone()) < u); - constraint - } - RegionOutlives(region_1, region_2) => { - let region_1_u = max_universe(infcx, region_1); - let region_2_u = max_universe(infcx, region_2); + let pull_and = |and: RegionConstraint| { + let mut pulled_constraints = Vec::new(); + for c in and.unwrap_and() { + match c { + Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { + assert!(max_universe(infcx, c.clone()) < u); + pulled_constraints.push(c.clone()); + } + RegionOutlives(region_1, region_2) => { + let region_1_u = max_universe(infcx, region_1); + let region_2_u = max_universe(infcx, region_2); - if region_1_u != u && region_2_u != u { - return constraint; - } + if region_1_u != u && region_2_u != u { + pulled_constraints.push(c); + continue; + } - let assumptions = match assumptions { - Some(assumptions) => assumptions, - None => return RegionConstraint::Ambiguity, - }; + let assumptions = match assumptions { + Some(assumptions) => assumptions, + None => { + pulled_constraints.push(Ambiguity); + continue; + } + }; + + let mut candidates = vec![]; + + for ub in regions_outlived_by(region_1, assumptions) { + // FIXME(-Zassumptions-on-binders): if `region_2` is in a smaller universe there'll be both + // `'region_2` and `'static` as lower bounds which seems... unfortunate and may cause us to + // add a bunch of duplicate `'ub: 'static` candidates the more binders we leave. + for lb in regions_outliving(region_2, assumptions, infcx.cx()) + .filter(|r| max_universe(infcx, *r) < u) + { + // As long as any region outlived by `region_1` outlives any region region which + // `region_2` outlives, we know that `region_1: region_2` holds. In other words, + // there exists some set of 4 regions for which `'r1: 'i1` `'i1: 'i2` `'i2: 'r2` + candidates.push(RegionOutlives(ub, lb)); + } + } - let mut candidates = vec![]; - for ub in - regions_outlived_by(region_1, assumptions).filter(|r| max_universe(infcx, *r) < u) - { - // FIXME(-Zassumptions-on-binders): if `region_2` is in a smaller universe there'll be both - // `'region_2` and `'static` as lower bounds which seems... unfortunate and may cause us to - // add a bunch of duplicate `'ub: 'static` candidates the more binders we leave. - for lb in regions_outliving(region_2, assumptions, infcx.cx()) - .filter(|r| max_universe(infcx, *r) < u) - { - // As long as any region outlived by `region_1` outlives any region region which - // `region_2` outlives, we know that `region_1: region_2` holds. In other words, - // there exists some set of 4 regions for which `'r1: 'i1` `'i1: 'i2` `'i2: 'r2` - candidates.push(RegionOutlives(ub, lb)); + pulled_constraints.push(Or(candidates.into_boxed_slice())); } - } - - RegionConstraint::Or(candidates.into_boxed_slice()) + Or(_) | And(_) => unreachable!(), + }; } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) - }) - .collect()), - // NOTE: this will be reverted back to `unreachable!()` in a future commit - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| { - pull_region_outlives_constraints_out_of_universe(infcx, constraint, u, assumptions) - }) - .collect()), - } + + And(pulled_constraints.into_boxed_slice()) + }; + + let ands = constraint.unwrap_or(); + Or(ands.into_iter().map(|and| pull_and(and)).collect::>().into_boxed_slice()) } /// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of From fd355c13d70f1be46e2251812fe5e2f83253fa39 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:30:42 +0100 Subject: [PATCH 05/10] restructure `destructure_type_outlives_constraints_in_root` --- .../rustc_type_ir/src/region_constraint.rs | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 017d8ebc68933..8f3b36e409bdb 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -706,30 +706,31 @@ pub fn destructure_type_outlives_constraints_in_root< ) -> RegionConstraint { use RegionConstraint::*; - match constraint { - Ambiguity | RegionOutlives(..) => constraint, - PlaceholderTyOutlives(ty, r) => { - Or(regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) - .map(move |assumption_r| RegionOutlives(assumption_r, r)) - .collect::>() - .into_boxed_slice()) - } - AliasTyOutlivesViaEnv(bound_outlives) => { - alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions) + let destructure_and = |and: RegionConstraint| { + debug!("rewriting and {:?}", and); + + let mut destructured_constraints = Vec::new(); + for c in and.unwrap_and() { + match c { + Ambiguity | RegionOutlives(..) => destructured_constraints.push(c), + PlaceholderTyOutlives(ty, r) => destructured_constraints.push(Or( + regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) + .map(move |assumption_r| RegionOutlives(assumption_r, r)) + .collect::>() + .into_boxed_slice(), + )), + AliasTyOutlivesViaEnv(bound_outlives) => destructured_constraints.push( + alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions), + ), + And(_) | Or(_) => unreachable!(), + } } - And(constraints) => And(constraints - .into_iter() - .map(|constraint| { - destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) - }) - .collect()), - Or(constraints) => Or(constraints - .into_iter() - .map(|constraint| { - destructure_type_outlives_constraints_in_root(infcx, constraint, assumptions) - }) - .collect()), - } + debug!(?destructured_constraints); + And(destructured_constraints.into_boxed_slice()) + }; + + let ands = constraint.unwrap_or(); + Or(ands.into_iter().map(|and| destructure_and(and)).collect::>().into_boxed_slice()) } /// Converts type outlives constraints into either region outlives constraints, or type outlives From 2e3f5219f4db1bacfba8fa9c8ae2b85a459809ec Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 13:25:39 +0100 Subject: [PATCH 06/10] remove big manual derives --- .../rustc_type_ir/src/region_constraint.rs | 229 ------------------ 1 file changed, 229 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 8f3b36e409bdb..b69eed748fc24 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -121,133 +121,6 @@ pub enum RegionConstraint { Or(Box<[RegionConstraint]>), } -// This is not a derived impl because a perfect derive leads to inductive -// cycle causing the trait to never actually be implemented. -#[cfg(feature = "nightly")] -impl StableHash for RegionConstraint -where - Region: StableHash, - I::Ty: StableHash, - I::GenericArgs: StableHash, - I::TraitAssocTyId: StableHash, - I::InherentAssocTyId: StableHash, - I::OpaqueTyId: StableHash, - I::FreeTyAliasId: StableHash, - I::BoundVarKinds: StableHash, -{ - #[inline] - fn stable_hash(&self, hcx: &mut CTX, hasher: &mut StableHasher) { - use RegionConstraint::*; - - std::mem::discriminant(self).stable_hash(hcx, hasher); - match self { - Ambiguity => (), - RegionOutlives(a, b) => { - a.stable_hash(hcx, hasher); - b.stable_hash(hcx, hasher); - } - AliasTyOutlivesViaEnv(outlives) => { - outlives.stable_hash(hcx, hasher); - } - PlaceholderTyOutlives(a, b) => { - a.stable_hash(hcx, hasher); - b.stable_hash(hcx, hasher); - } - And(and) => { - for a in and.iter() { - a.stable_hash(hcx, hasher); - } - } - Or(or) => { - for a in or.iter() { - a.stable_hash(hcx, hasher); - } - } - } - } -} - -impl TypeFoldable for RegionConstraint { - fn try_fold_with>(self, f: &mut F) -> Result { - use RegionConstraint::*; - Ok(match self { - Ambiguity => self, - RegionOutlives(a, b) => RegionOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?), - AliasTyOutlivesViaEnv(outlives) => AliasTyOutlivesViaEnv(outlives.try_fold_with(f)?), - PlaceholderTyOutlives(a, b) => { - PlaceholderTyOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?) - } - And(and) => { - let mut new_and = Vec::new(); - for a in and { - new_and.push(a.try_fold_with(f)?); - } - And(new_and.into_boxed_slice()) - } - Or(or) => { - let mut new_or = Vec::new(); - for a in or { - new_or.push(a.try_fold_with(f)?); - } - Or(new_or.into_boxed_slice()) - } - }) - } - - fn fold_with>(self, f: &mut F) -> Self { - use RegionConstraint::*; - match self { - Ambiguity => self, - RegionOutlives(a, b) => RegionOutlives(a.fold_with(f), b.fold_with(f)), - AliasTyOutlivesViaEnv(outlives) => AliasTyOutlivesViaEnv(outlives.fold_with(f)), - PlaceholderTyOutlives(a, b) => PlaceholderTyOutlives(a.fold_with(f), b.fold_with(f)), - And(and) => { - let mut new_and = Vec::new(); - for a in and { - new_and.push(a.fold_with(f)); - } - And(new_and.into_boxed_slice()) - } - Or(or) => { - let mut new_or = Vec::new(); - for a in or { - new_or.push(a.fold_with(f)); - } - Or(new_or.into_boxed_slice()) - } - } - } -} - -impl TypeVisitable for RegionConstraint { - fn visit_with>(&self, f: &mut F) -> F::Result { - use RegionConstraint::*; - - match self { - Ambiguity => (), - RegionOutlives(a, b) => { - try_visit!(a.visit_with(f)); - try_visit!(b.visit_with(f)); - } - AliasTyOutlivesViaEnv(outlives) => { - try_visit!(outlives.visit_with(f)); - } - PlaceholderTyOutlives(a, b) => { - try_visit!(a.visit_with(f)); - try_visit!(b.visit_with(f)); - } - And(and) => { - walk_visitable_list!(f, and); - } - Or(or) => { - walk_visitable_list!(f, or); - } - }; - - F::Result::output() - } -} - impl Default for RegionConstraint { fn default() -> Self { Self::new_true() @@ -318,108 +191,6 @@ impl RegionConstraint { (this, other) => And(Box::new([this, other])), } } - - /// Converts the region constraint into an ORs of ANDs of "leaf" constraints. Where - /// a leaf constraint is a non-or/and constraint. - #[instrument(level = "debug", ret)] - pub fn canonical_form(self) -> Self { - use RegionConstraint::*; - - fn permutations( - ors: &[Vec>], - ) -> Vec>> { - match ors { - [] => vec![vec![]], - [or1] => { - let mut choices = vec![]; - for choice in or1 { - choices.push(vec![choice.clone()]); - } - choices - } - [or1, rest_ors @ ..] => { - let mut choices = vec![]; - for choice in or1 { - choices.extend( - permutations(rest_ors) - .into_iter() - .map(|and| std::iter::once(choice.clone()).chain(and).collect()), - ); - } - choices - } - } - } - - let canonical = match self { - And(ands) => { - // AND of OR of AND of LEAFs - // - // We can turn `AND of OR of X` into `OR of AND of X` by enumerating every set of choices - // for the list of ORs. For example if we have `AND ( OR(A, B), OR(C, D) )` we can convert this into - // `OR ( AND (A, C), AND (A, D), AND (B, C), AND (B, D ))` - // - // if A/B/C/D are all in canonical forms then we wind up with an `OR of AND of AND of LEAFs` which - // is trivially canonicalizeable by flattening the multiple layers of AND into one. - let ors = ands - .into_iter() - .map(|c| c.canonical_form().unwrap_or().to_vec()) - .collect::>(); - debug!(?ors); - let or_permutations = permutations(&ors); - debug!(?or_permutations); - - Or(or_permutations - .into_iter() - .map(|c| { - And(c - .into_iter() - .flat_map(|c2| c2.unwrap_and().into_iter()) - .collect::>() - .into_boxed_slice()) - }) - .collect::>() - .into_boxed_slice()) - } - Or(ors) => { - // OR of OR of AND of LEAFs - // - // trivially canonicalizeable by concatenating all of the ORs into one big OR - Or(ors - .into_iter() - .flat_map(|c| c.canonical_form().unwrap_or().into_iter()) - .collect::>() - .into_boxed_slice()) - } - _ => Or(Box::new([And(Box::new([self]))])), - }; - - assert!( - canonical.is_canonical_form(), - "non canonical form region constraint: {:?}", - canonical - ); - canonical - } - - fn is_leaf_constraint(&self) -> bool { - use RegionConstraint::*; - match self { - Ambiguity - | RegionOutlives(..) - | AliasTyOutlivesViaEnv(..) - | PlaceholderTyOutlives(..) => true, - And(..) | Or(..) => false, - } - } - - fn is_canonical_and(&self) -> bool { - if let Self::And(ands) = self { ands.iter().all(|c| c.is_leaf_constraint()) } else { false } - } - - pub fn is_canonical_form(&self) -> bool { - if let Self::Or(ors) = self { ors.iter().all(|c| c.is_canonical_and()) } else { false } - } } /// Takes any constraints involving placeholders from the current universe and eagerly checks them. From 66a2c9a752f0da7bb2c9c3b0ae6fbf753c0dd4ff Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 17:13:33 +0100 Subject: [PATCH 07/10] stub out `evaluate_solver_constraint` --- .../rustc_type_ir/src/region_constraint.rs | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index b69eed748fc24..78a893d503e94 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -317,56 +317,7 @@ fn compute_new_region_constraints, I: Interne pub fn evaluate_solver_constraint( constraint: &RegionConstraint, ) -> RegionConstraint { - use RegionConstraint::*; - match constraint { - Ambiguity | RegionOutlives(..) | AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { - constraint.clone() - } - And(and) => { - let mut and_constraints = Vec::new(); - let mut is_ambiguous_constraint = false; - for c in and.iter() { - let evaluated_constraint = evaluate_solver_constraint(c); - if evaluated_constraint.is_true() { - // - do nothing - } else if evaluated_constraint.is_false() { - return RegionConstraint::new_false(); - } else if evaluated_constraint.is_ambig() { - is_ambiguous_constraint = true; - } else { - and_constraints.push(evaluated_constraint); - } - } - - if is_ambiguous_constraint { - RegionConstraint::Ambiguity - } else { - RegionConstraint::And(and_constraints.into_boxed_slice()) - } - } - Or(or) => { - let mut or_constraints = Vec::new(); - let mut is_ambiguous_constraint = false; - for c in or.iter() { - let evaluated_constraint = evaluate_solver_constraint(c); - if evaluated_constraint.is_false() { - // do nothing - } else if evaluated_constraint.is_true() { - return RegionConstraint::new_true(); - } else if evaluated_constraint.is_ambig() { - is_ambiguous_constraint = true; - } else { - or_constraints.push(evaluated_constraint); - } - } - - if is_ambiguous_constraint { - RegionConstraint::Ambiguity - } else { - RegionConstraint::Or(or_constraints.into_boxed_slice()) - } - } - } + todo!("overhauled in future commit") } /// Handles converting region outlives constraints involving placeholders from `u` into OR constraints From 70a8c63c2e9e94313346379167cd3e4ed8d564d9 Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 24 Jul 2026 17:23:49 +0100 Subject: [PATCH 08/10] always canonical form --- compiler/rustc_infer/src/infer/context.rs | 18 +- compiler/rustc_infer/src/infer/mod.rs | 41 +- .../src/infer/outlives/obligations.rs | 34 +- .../src/infer/snapshot/undo_log.rs | 9 - .../src/solve/eval_ctxt/mod.rs | 8 +- .../eval_ctxt/solver_region_constraints.rs | 54 +- .../rustc_next_trait_solver/src/solve/mod.rs | 15 +- compiler/rustc_type_ir/src/infer_ctxt.rs | 6 +- .../rustc_type_ir/src/region_constraint.rs | 486 +++++++++++------- compiler/rustc_type_ir/src/solve/mod.rs | 6 +- 10 files changed, 383 insertions(+), 294 deletions(-) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index afc083aececaa..b0ea9a7f2f27b 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -63,13 +63,13 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn get_solver_region_constraint( &self, - ) -> rustc_type_ir::region_constraint::RegionConstraint> { + ) -> rustc_type_ir::region_constraint::CanonicalFormRegionConstraint> { self.inner.borrow().solver_region_constraint_storage.get_constraint() } fn overwrite_solver_region_constraint( &self, - constraint: rustc_type_ir::region_constraint::RegionConstraint>, + constraint: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, ) { let mut inner = self.inner.borrow_mut(); use rustc_data_structures::undo_log::UndoLogs; @@ -330,15 +330,21 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn register_solver_region_constraint( &self, - c: rustc_type_ir::region_constraint::RegionConstraint>, + c: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, ) { let mut inner = self.inner.borrow_mut(); use rustc_data_structures::undo_log::UndoLogs; + let old_constraint = inner.solver_region_constraint_storage.get_constraint(); + let new_constraint = + rustc_type_ir::region_constraint::CanonicalFormRegionConstraint::new_and( + c, + old_constraint.clone(), + ); + use crate::infer::UndoLog; - let previous_was_and = inner.solver_region_constraint_storage.is_and(); - inner.undo_log.push(UndoLog::PushSolverRegionConstraint { previous_was_and }); - inner.solver_region_constraint_storage.push(c); + inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); + inner.solver_region_constraint_storage.overwrite_solver_region_constraint(new_constraint); } fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 583fb1d7db21a..b94bdb849644a 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1810,58 +1810,23 @@ impl<'tcx> InferCtxt<'tcx> { } type SolverRegionConstraint<'tcx> = - rustc_type_ir::region_constraint::RegionConstraint>; + rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>; #[derive(Clone, Debug)] struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); impl<'tcx> SolverRegionConstraintStorage<'tcx> { fn new() -> Self { - SolverRegionConstraintStorage(SolverRegionConstraint::And(Box::new([]))) + SolverRegionConstraintStorage(SolverRegionConstraint::new_true()) } fn get_constraint(&self) -> SolverRegionConstraint<'tcx> { self.0.clone() } - fn is_and(&self) -> bool { - self.0.is_and() - } - - fn pop(&mut self, previous_was_and: bool) -> Option> { - match &mut self.0 { - SolverRegionConstraint::And(and) => { - let mut and = core::mem::take(and).into_iter().collect::>(); - let popped = and.pop()?; - if previous_was_and { - self.0 = SolverRegionConstraint::And(and.into_boxed_slice()); - } else { - assert_eq!(and.len(), 1); - self.0 = and.pop().unwrap(); - } - Some(popped) - } - _ => unreachable!(), - } - } - - #[instrument(level = "debug")] - fn push(&mut self, constraint: SolverRegionConstraint<'tcx>) { - match core::mem::replace(&mut self.0, SolverRegionConstraint::new_true()) { - SolverRegionConstraint::And(and) => { - let and = - and.into_iter().chain([constraint]).collect::>().into_boxed_slice(); - self.0 = SolverRegionConstraint::And(and); - } - previous => { - self.0 = SolverRegionConstraint::And(Box::new([previous, constraint])); - } - } - } - #[instrument(level = "debug", skip(self))] fn overwrite_solver_region_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) { - self.0 = constraint; + self.0 = constraint } } diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 67a85dbdd741b..da14e80e9eadd 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -69,6 +69,7 @@ use rustc_middle::ty::{ TyCtxt, TypeVisitableExt, eager_resolve_vars, }; use rustc_span::Span; +use rustc_type_ir::region_constraint::{self, LeafRegionConstraint}; use smallvec::smallvec; use tracing::{debug, instrument}; @@ -232,7 +233,7 @@ impl<'tcx> InferCtxt<'tcx> { region_outlives: TransitiveRelation, span: Span, ) { - let assumptions = rustc_type_ir::region_constraint::Assumptions::new( + let assumptions = region_constraint::Assumptions::new( known_type_outlives.into_iter().cloned().collect(), region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(), ); @@ -254,19 +255,24 @@ impl<'tcx> InferCtxt<'tcx> { 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_root( - self, - constraint, - &assumptions, - ); + let constraint = region_constraint::destructure_type_outlives_constraints_in_root( + self, + constraint, + &assumptions, + ); debug!(?constraint); - let constraint = rustc_type_ir::region_constraint::evaluate_solver_constraint(&constraint); + let constraint = 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::*; + // FIXME(-Zassumptions-on-binders): actually implement OR as an OR + for c in constraint.and_constraint.0.into_iter().chain( + constraint + .or_constraint + .0 + .into_iter() + .flat_map(|and_constraint| and_constraint.0.into_iter()), + ) { + use LeafRegionConstraint::*; match c { Ambiguity => { @@ -281,9 +287,9 @@ impl<'tcx> InferCtxt<'tcx> { category, ); } - // FIXME(-Zassumptions-on-binders): actually implement OR as an OR - And(nested) | Or(nested) => constraints.extend(nested), - AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => unreachable!(), + AliasTyOutlivesViaEnv(..) | PlaceholderTyOutlives(..) => { + unreachable!() + } } } } diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index eb5b3fe7bfd41..f6694cfcc39ac 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -28,7 +28,6 @@ pub(crate) enum UndoLog<'tcx> { RegionUnificationTable(sv::UndoLog>>), ProjectionCache(traits::UndoLog<'tcx>), PushTypeOutlivesConstraint, - PushSolverRegionConstraint { previous_was_and: bool }, OverwriteSolverRegionConstraint { old_constraint: SolverRegionConstraint<'tcx> }, PushRegionAssumption, PushHirTypeckPotentiallyRegionDependentGoal, @@ -79,14 +78,6 @@ impl<'tcx> Rollback> for InferCtxtInner<'tcx> { self.region_constraint_storage.as_mut().unwrap().unification_table.reverse(undo) } UndoLog::ProjectionCache(undo) => self.projection_cache.reverse(undo), - UndoLog::PushSolverRegionConstraint { previous_was_and } => { - let popped = self.solver_region_constraint_storage.pop(previous_was_and); - assert_matches!( - popped, - Some(_), - "pushed solver region constraint but could not pop it" - ); - } UndoLog::OverwriteSolverRegionConstraint { old_constraint } => { self.solver_region_constraint_storage .overwrite_solver_region_constraint(old_constraint); diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 559ca0a98c58e..f2ddab97cef54 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -5,7 +5,7 @@ use std::ops::ControlFlow; use rustc_macros::StableHash; use rustc_type_ir::data_structures::HashSet; use rustc_type_ir::inherent::*; -use rustc_type_ir::region_constraint::{RegionConstraint, evaluate_solver_constraint}; +use rustc_type_ir::region_constraint::{self, CanonicalFormRegionConstraint}; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{CandidateHeadUsages, LowerAvailableDepth, PathKind}; @@ -1328,7 +1328,7 @@ where args } - pub(super) fn register_solver_region_constraint(&self, c: RegionConstraint) { + pub(super) fn register_solver_region_constraint(&self, c: CanonicalFormRegionConstraint) { self.delegate.register_solver_region_constraint(c); } @@ -1664,11 +1664,11 @@ where let constraint = self.delegate.get_solver_region_constraint(); debug_assert_eq!( constraint, - evaluate_solver_constraint(&constraint.clone().canonical_form()) + region_constraint::evaluate_solver_constraint(constraint.clone()) ); constraint } else { - RegionConstraint::new_true() + CanonicalFormRegionConstraint::new_true() }) } else { ExternalRegionConstraints::Old(if let Certainty::Yes = certainty { diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 583dd391dd4d0..8393ec00f8d81 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -8,7 +8,7 @@ use rustc_type_ir::outlives::{Component, push_outlives_components}; #[cfg(not(feature = "nightly"))] use rustc_type_ir::region_constraint::TransitiveRelationBuilder; use rustc_type_ir::region_constraint::{ - Assumptions, RegionConstraint, eagerly_handle_placeholders_in_universe, + And, Assumptions, LeafRegionConstraint, Or, eagerly_handle_placeholders_in_universe, evaluate_solver_constraint, }; use rustc_type_ir::{ @@ -137,8 +137,9 @@ where .fold(constraint, |constraint, u| { eagerly_handle_placeholders_in_universe(&**self.delegate, constraint, u) }); - let constraint = evaluate_solver_constraint(&constraint.canonical_form()); + let constraint = evaluate_solver_constraint(constraint); + debug!("final constraint={:?}", constraint); self.delegate.overwrite_solver_region_constraint(constraint.clone()); if constraint.is_false() { @@ -154,36 +155,28 @@ where /// type outlives constraints between the "components" of the type. E.g. `Foo: 'b` /// will be turned into `T: 'b, 'a: 'b` #[instrument(level = "debug", skip(self), ret)] - pub(in crate::solve) fn destructure_type_outlives( - &mut self, - ty: I::Ty, - r: Region, - ) -> RegionConstraint { + pub(in crate::solve) fn destructure_type_outlives(&mut self, ty: I::Ty, r: Region) -> Or { let mut components = Default::default(); push_outlives_components(self.cx(), ty, &mut components); self.destructure_components(&components, r) } - fn destructure_components( - &mut self, - components: &[Component], - r: Region, - ) -> RegionConstraint { - RegionConstraint::And( - components.into_iter().map(|c| self.destructure_component(c, r)).collect(), - ) + fn destructure_components(&mut self, components: &[Component], r: Region) -> Or { + components + .into_iter() + .fold(Or::new_true(), |acc, c| Or::new_and(acc, self.destructure_component(c, r))) } - fn destructure_component(&mut self, c: &Component, r: Region) -> RegionConstraint { + fn destructure_component(&mut self, c: &Component, r: Region) -> Or { use Component::*; + use LeafRegionConstraint::*; match c { - Region(c_r) => RegionConstraint::RegionOutlives(*c_r, r), + Region(c_r) => Or::new([And::new([RegionOutlives(*c_r, r)])]), Placeholder(p) => { - RegionConstraint::PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r) + Or::new([And::new([PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r)])]) } - // The alias is either rigid or ambiguous in which case we'll return with ambiguity. Alias(_, alias) => self.destructure_alias_outlives(*alias, r), - UnresolvedInferenceVariable(_) => RegionConstraint::Ambiguity, + UnresolvedInferenceVariable(_) => Or::new([And::new([Ambiguity])]), Param(_) => panic!("Params should have been canonicalized to placeholders"), EscapingAlias(components) => self.destructure_components(components, r), } @@ -197,18 +190,16 @@ where /// 3. env assumptions. we defer handling `Alias: 'b` via where clauses until /// when exiting the current binder. See [`RegionConstraint::AliasTyOutlivesViaEnv`]. #[instrument(level = "debug", skip(self), ret)] - fn destructure_alias_outlives( - &mut self, - alias: AliasTy, - r: Region, - ) -> RegionConstraint { + fn destructure_alias_outlives(&mut self, alias: AliasTy, r: Region) -> Or { + use LeafRegionConstraint::*; + let item_bounds = rustc_type_ir::outlives::declared_bounds_from_definition(self.cx(), alias) - .map(|bound| RegionConstraint::RegionOutlives(bound, r)); - let item_bound_outlives = RegionConstraint::Or(item_bounds.collect()); + .map(|bound| And::new([RegionOutlives(bound, r)])); + let item_bound_outlives = Or::new(item_bounds); let where_clause_outlives = - RegionConstraint::AliasTyOutlivesViaEnv(Binder::dummy((alias, r))); + Or::new([And::new([AliasTyOutlivesViaEnv(Binder::dummy((alias, r)))])]); let mut components = Default::default(); rustc_type_ir::outlives::compute_alias_components_recursive( @@ -218,10 +209,7 @@ where ); let components_outlives = self.destructure_components(&components, r); - RegionConstraint::Or(Box::new([ - item_bound_outlives, - where_clause_outlives, - components_outlives, - ])) + let assumption_outlives = Or::new_or(item_bound_outlives, where_clause_outlives); + Or::new_or(assumption_outlives, components_outlives) } } diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 3504882834268..c6cf2209a4188 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -93,8 +93,12 @@ where let ty = self.normalize(GoalSource::Misc, goal.param_env, ty::Unnormalized::new_wip(ty))?; if self.cx().assumptions_on_binders() { + use rustc_type_ir::region_constraint::CanonicalFormRegionConstraint; + let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(constraint); + self.register_solver_region_constraint(CanonicalFormRegionConstraint::new_from_or( + constraint, + )); } else { self.register_ty_outlives(ty, lt); } @@ -119,8 +123,13 @@ where let ty::OutlivesClause(a, b) = goal.predicate; if self.cx().assumptions_on_binders() { - let constraint = - rustc_type_ir::region_constraint::RegionConstraint::RegionOutlives(a, b); + use rustc_type_ir::region_constraint::{ + And, CanonicalFormRegionConstraint, LeafRegionConstraint, Or, + }; + + let constraint = CanonicalFormRegionConstraint::new_from_or(Or::new([And::new([ + LeafRegionConstraint::RegionOutlives(a, b), + ])])); self.register_solver_region_constraint(constraint); } else { self.register_region_outlives(a, b, VisibleForLeakCheck::Yes); diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 1cd070365f651..8e342fcf2aa5f 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -395,10 +395,10 @@ pub trait InferCtxtLike: Sized { ) -> Option>; fn get_solver_region_constraint( &self, - ) -> crate::region_constraint::RegionConstraint; + ) -> crate::region_constraint::CanonicalFormRegionConstraint; fn overwrite_solver_region_constraint( &self, - constraint: crate::region_constraint::RegionConstraint, + constraint: crate::region_constraint::CanonicalFormRegionConstraint, ); fn universe_of_ty(&self, ty: ty::TyVid) -> Option; @@ -519,7 +519,7 @@ pub trait InferCtxtLike: Sized { fn register_solver_region_constraint( &self, - c: crate::region_constraint::RegionConstraint, + c: crate::region_constraint::CanonicalFormRegionConstraint, ); fn register_ty_outlives( diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 78a893d503e94..707661530c57b 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -3,9 +3,10 @@ use derive_where::derive_where; use indexmap::IndexSet; #[cfg(feature = "nightly")] -use rustc_data_structures::stable_hash::{StableHash, StableHashCtxt, StableHasher}; -#[cfg(feature = "nightly")] use rustc_data_structures::transitive_relation::{TransitiveRelation, TransitiveRelationBuilder}; +#[cfg(feature = "nightly")] +use rustc_macros::StableHash_NoContext; +use rustc_type_ir_macros::{GenericTypeVisitable, TypeFoldable_Generic, TypeVisitable_Generic}; use tracing::{debug, instrument}; // Workaround for TransitiveRelation being in rustc_data_structures which isn't accessible on stable @@ -50,11 +51,9 @@ use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; use crate::{ - AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, FallibleTypeFolder, - GenericTypeVisitable, InferCtxtLike, Interner, IsRigid, OutlivesClause, Region, RegionKind, - TyKind, TypeFoldable, TypeFolder, TypeVisitable, TypeVisitor, TypingMode, UniverseIndex, - Variance, VisitorResult, max_universe, set_aliases_to_non_rigid, try_visit, - walk_visitable_list, + AliasTy, Binder, BoundRegion, BoundVar, BoundVariableKind, DebruijnIndex, InferCtxtLike, + Interner, IsRigid, OutlivesClause, Region, RegionKind, TyKind, TypeFoldable, TypeFolder, + TypingMode, UniverseIndex, Variance, max_universe, set_aliases_to_non_rigid, }; #[derive_where(Clone, Debug; I: Interner)] @@ -91,9 +90,10 @@ impl Assumptions { } } -#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] -#[derive(GenericTypeVisitable)] -pub enum RegionConstraint { +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub enum LeafRegionConstraint { Ambiguity, RegionOutlives(Region, Region), /// Requirement that a (potentially higher ranked) alias outlives some (potentially higher ranked) @@ -116,83 +116,198 @@ pub enum RegionConstraint { /// We cannot eagerly look at assumptions as we are usually working with an incomplete set of assumptions /// and there may wind up being assumptions we can use to prove this when we're in a smaller universe. PlaceholderTyOutlives(I::Ty, Region), - - And(Box<[RegionConstraint]>), - Or(Box<[RegionConstraint]>), -} - -impl Default for RegionConstraint { - fn default() -> Self { - Self::new_true() - } } -impl RegionConstraint { +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct Or(pub Box<[And]>); +impl Or { pub fn new_true() -> Self { - RegionConstraint::And(Box::new([])) + Self(Box::new([And::new([])])) } pub fn is_true(&self) -> bool { - match self { - Self::And(and) => and.is_empty(), - _ => false, + // OR([AND([])]) + if let [and] = &*self.0 + && and.0.len() == 0 + { + true + } else { + false } } pub fn new_false() -> Self { - RegionConstraint::Or(Box::new([])) + Self(Box::new([])) } pub fn is_false(&self) -> bool { - match self { - Self::Or(or) => or.is_empty(), - _ => false, + // OR([]) + self.0.len() == 0 + } + + pub fn new(i: impl IntoIterator>) -> Self { + let ands = i.into_iter().collect::>().into_boxed_slice(); + let mut new_ands: Vec> = Vec::new(); + + for and in ands { + if new_ands.iter().all(|c| !c.is_and_equivalent_to(&and)) { + new_ands.push(and) + } } + + Self(new_ands.into_boxed_slice()) + } + + pub fn new_and(a: Or, b: Or) -> Self { + // I think this returns false if either a or b is false? + let mut ands = Vec::new(); + for b_and in b.0 { + ands.extend( + a.0.clone() + .into_iter() + .map(|a_and| And::new(a_and.0.into_iter().chain(b_and.0.clone()))), + ); + } + + Or::new(ands) + } + + pub fn new_or(a: Or, b: Or) -> Self { + Or::new(a.0.into_iter().chain(b.0)) } +} + +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct And(pub Box<[LeafRegionConstraint]>); +impl And { + pub fn new(i: impl IntoIterator>) -> Self { + Self( + i.into_iter() + .collect::>() + .into_iter() + .collect::>() + .into_boxed_slice(), + ) + } + + fn is_and_equivalent_to(&self, other: &And) -> bool { + let this = self.clone().0; + let other = other.clone().0; - pub fn is_or(&self) -> bool { - matches!(self, Self::Or(_)) + this.iter().all(|c1| other.iter().any(|c2| c1 == c2)) + && other.iter().all(|c2| this.iter().any(|c1| c1 == c2)) } +} + +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +/// CanonicalFormRegionConstraints always have constraints shared between every OR element moved +/// into the and_constraint. Additionally they are always in "OR of AND of LEAF" form instead of +/// supporting arbitrary nesting of ORs/ANDs. +/// +/// We also guarantee that there are no duplicate constraints in any of the `And` or `Or`s, though, +/// this is handled when constructing And/Ors rather than when constructing `CanonicalFormRegionConstraint`. +/// +/// It should also already be "evaluated", as in if `or_constraint` is `false` then `and_constraint` should be +/// empty. Or if an element in the `or_constraint` is `true` then it should be the only constraint. +pub struct CanonicalFormRegionConstraint { + pub and_constraint: And, + pub or_constraint: Or, +} + +impl CanonicalFormRegionConstraint { + pub fn new_from_or(or: Or) -> Self { + let Some(fst) = or.0.get(0).clone() else { + return CanonicalFormRegionConstraint::new_false(); + }; + let mut and_constraint = fst.0.to_vec(); - pub fn unwrap_or(self) -> Box<[RegionConstraint]> { - match self { - Self::Or(ors) => ors, - _ => panic!("`unwrap_or` on non-Or: {self:?}"), + for and in or.0.clone() { + and_constraint.retain(|c| and.0.iter().any(|c2| c == c2)); } + let and_constraint = And::new(and_constraint); + + let or_constraint = Or::new(or.0.into_iter().map(|and| { + And::new(and.0.into_iter().filter(|c| and_constraint.0.iter().all(|s_c| c != s_c))) + })); + + Self { + and_constraint: if or_constraint.is_false() { And::new([]) } else { and_constraint }, + or_constraint, + } + } + + pub fn splatted_and_constraints(&self) -> Or { + Or::new(self.or_constraint.0.iter().map(|and| { + And::new(and.0.iter().cloned().chain(self.and_constraint.0.iter().cloned())) + })) } - pub fn unwrap_and(self) -> Box<[RegionConstraint]> { - match self { - Self::And(ands) => ands, - _ => panic!("`unwrap_and` on non-And: {self:?}"), + pub fn new_and( + a: CanonicalFormRegionConstraint, + b: CanonicalFormRegionConstraint, + ) -> Self { + let and_constraint = And::new(a.and_constraint.0.into_iter().chain(b.and_constraint.0)); + let or_constraint = Or::new_and(a.or_constraint, b.or_constraint); + + Self { + and_constraint: if or_constraint.is_false() { And::new([]) } else { and_constraint }, + or_constraint, } } - pub fn is_and(&self) -> bool { - matches!(self, Self::And(_)) + pub fn new_true() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_true() } } - pub fn is_ambig(&self) -> bool { - matches!(self, Self::Ambiguity) + pub fn is_true(&self) -> bool { + self.and_constraint.0.is_empty() && self.or_constraint.is_true() } - pub fn and(self, other: RegionConstraint) -> RegionConstraint { - use RegionConstraint::*; + pub fn new_false() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_false() } + } - match (self, other) { - (And(a_ands), And(b_ands)) => And(a_ands - .into_iter() - .chain(b_ands.into_iter()) - .collect::>() - .into_boxed_slice()), - (And(ands), other) | (other, And(ands)) => { - And(ands.into_iter().chain([other]).collect::>().into_boxed_slice()) - } - (this, other) => And(Box::new([this, other])), + pub fn is_false(&self) -> bool { + self.or_constraint.is_false() + } + + pub fn new_ambig() -> Self { + Self { + and_constraint: And::new([LeafRegionConstraint::Ambiguity]), + or_constraint: Or::new_true(), + } + } + + pub fn is_ambig(&self) -> bool { + if let [c] = &*self.and_constraint.0 + && c.is_ambig() + && self.or_constraint.is_true() + { + true + } else { + false } } } +impl Default for CanonicalFormRegionConstraint { + fn default() -> Self { + Self::new_true() + } +} + +impl LeafRegionConstraint { + pub fn is_ambig(&self) -> bool { + matches!(self, Self::Ambiguity) + } +} + /// Takes any constraints involving placeholders from the current universe and eagerly checks them. /// This can be done a few ways: /// - There's an assumption on the binder introducing the placeholder which means the constraint is satisfied (true) @@ -209,9 +324,9 @@ impl RegionConstraint { #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, -) -> RegionConstraint { +) -> CanonicalFormRegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); // 1. rewrite type outlives constraints involving things from `u` into either region constraints @@ -220,7 +335,7 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, -) -> RegionConstraint { - use RegionConstraint::*; +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; let extend_from_and = |builder: &mut TransitiveRelationBuilder<_>, regions: &mut IndexSet<_>, constraints: &mut Vec<_>, - and: RegionConstraint| { - for c in and.unwrap_and() { + and: &And| { + for c in &and.0 { match c { Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { constraints.push(c.clone()) } RegionOutlives(r1, r2) => { - regions.insert(r1); - regions.insert(r2); - builder.add(r2, r1); + regions.insert(*r1); + regions.insert(*r2); + builder.add(*r2, *r1); } - Or(_) | And(_) => unreachable!(), } } }; - let mut new_ands = Vec::new(); - for and in constraint.unwrap_or() { - let mut region_flows_builder = TransitiveRelationBuilder::default(); - let mut regions = IndexSet::new(); - let mut constraints = Vec::new(); + let mut base_region_flows_builder = TransitiveRelationBuilder::default(); + let mut base_regions = IndexSet::new(); + let mut base_constraints = Vec::new(); + extend_from_and( + &mut base_region_flows_builder, + &mut base_regions, + &mut base_constraints, + &constraint.and_constraint, + ); + let mut new_ands = Vec::new(); + for and in &constraint.or_constraint.0 { + let mut region_flows_builder = base_region_flows_builder.clone(); + let mut regions = base_regions.clone(); + let mut constraints = base_constraints.clone(); extend_from_and(&mut region_flows_builder, &mut regions, &mut constraints, and); let region_flow = region_flows_builder.freeze(); @@ -306,17 +425,19 @@ fn compute_new_region_constraints, I: Interne } } - new_ands.push(And(constraints.into_boxed_slice())) + new_ands.push(Or::new([And::new(constraints)])) } - Or(new_ands.into_boxed_slice()) + CanonicalFormRegionConstraint::new_from_or( + new_ands.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)), + ) } /// Evaluate ANDs and ORs to true/false/ambiguous based on whether their arguments are true/false/ambiguous #[instrument(level = "debug", ret)] pub fn evaluate_solver_constraint( - constraint: &RegionConstraint, -) -> RegionConstraint { + constraint: CanonicalFormRegionConstraint, +) -> CanonicalFormRegionConstraint { todo!("overhauled in future commit") } @@ -346,10 +467,10 @@ fn pull_region_outlives_constraints_out_of_universe< I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { +) -> CanonicalFormRegionConstraint { assert!(max_universe(infcx, constraint.clone()) <= u); // FIXME(-Zassumptions-on-binders): we don't lower universes of region variables when exiting `u` @@ -359,28 +480,29 @@ fn pull_region_outlives_constraints_out_of_universe< // I'm not even sure this would be necessary given we filter out region constraints involving regions# // from the current universe and only retain those between placeholders. - use RegionConstraint::*; - let pull_and = |and: RegionConstraint| { + use LeafRegionConstraint::*; + + let pull_and = |and: And| { let mut pulled_constraints = Vec::new(); - for c in and.unwrap_and() { + for c in and.0 { match c { Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { assert!(max_universe(infcx, c.clone()) < u); - pulled_constraints.push(c.clone()); + pulled_constraints.push(Or::new([And::new([c.clone()])])); } RegionOutlives(region_1, region_2) => { let region_1_u = max_universe(infcx, region_1); let region_2_u = max_universe(infcx, region_2); if region_1_u != u && region_2_u != u { - pulled_constraints.push(c); + pulled_constraints.push(Or::new([And::new([c])])); continue; } let assumptions = match assumptions { Some(assumptions) => assumptions, None => { - pulled_constraints.push(Ambiguity); + pulled_constraints.push(Or::new([And::new([Ambiguity])])); continue; } }; @@ -401,58 +523,74 @@ fn pull_region_outlives_constraints_out_of_universe< } } - pulled_constraints.push(Or(candidates.into_boxed_slice())); + pulled_constraints.push(Or::new(candidates.into_iter().map(|c| And::new([c])))); } - Or(_) | And(_) => unreachable!(), }; } - And(pulled_constraints.into_boxed_slice()) + pulled_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) }; - let ands = constraint.unwrap_or(); - Or(ands.into_iter().map(|and| pull_and(and)).collect::>().into_boxed_slice()) + let and_constraint = pull_and(constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, pull_and(c))); + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of /// assumptions are known. This should not be called until the end of type checking. /// /// The returned region constraint will not have *any* PlaceholderTyOutlives or AliasTyOutlivesViaEnv constraints. +#[instrument(level = "debug", skip(infcx), ret)] pub fn destructure_type_outlives_constraints_in_root< Infcx: InferCtxtLike, I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, assumptions: &Assumptions, -) -> RegionConstraint { - use RegionConstraint::*; - - let destructure_and = |and: RegionConstraint| { - debug!("rewriting and {:?}", and); +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; + let destructure_and = |and: &And| { + debug!("rewriting and: {:?}", and); let mut destructured_constraints = Vec::new(); - for c in and.unwrap_and() { + for c in &and.0 { match c { - Ambiguity | RegionOutlives(..) => destructured_constraints.push(c), - PlaceholderTyOutlives(ty, r) => destructured_constraints.push(Or( - regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) - .map(move |assumption_r| RegionOutlives(assumption_r, r)) - .collect::>() - .into_boxed_slice(), + Ambiguity | RegionOutlives(..) => { + destructured_constraints.push(Or::new([And::new([c.clone()])])) + } + PlaceholderTyOutlives(ty, r) => destructured_constraints.push(Or::new( + regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()) + .map(move |assumption_r| And::new([RegionOutlives(assumption_r, *r)])), )), - AliasTyOutlivesViaEnv(bound_outlives) => destructured_constraints.push( - alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions), - ), - And(_) | Or(_) => unreachable!(), + AliasTyOutlivesViaEnv(bound_outlives) => { + destructured_constraints.push(alias_outlives_candidates_from_assumptions( + infcx, + *bound_outlives, + assumptions, + )); + } } } debug!(?destructured_constraints); - And(destructured_constraints.into_boxed_slice()) + let merged_constraints = + destructured_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)); + debug!(?merged_constraints); + merged_constraints }; - let ands = constraint.unwrap_or(); - Or(ands.into_iter().map(|and| destructure_and(and)).collect::>().into_boxed_slice()) + let and_constraint = destructure_and(&constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, destructure_and(&c))); + + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into either region outlives constraints, or type outlives @@ -471,10 +609,12 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< I: Interner, >( infcx: &Infcx, - constraint: RegionConstraint, + constraint: CanonicalFormRegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { +) -> CanonicalFormRegionConstraint { + use LeafRegionConstraint::*; + assert!( max_universe(infcx, constraint.clone()) <= u, "constraint {:?} contains terms from a larger universe than {:?}", @@ -482,38 +622,32 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< u ); - use RegionConstraint::*; - let rewrite_and = |and: RegionConstraint| { + let rewrite_and = |and: And| { let mut rewritten_constraints = Vec::new(); - for c in and.unwrap_and() { + for c in and.0 { match c { - Ambiguity | RegionOutlives(..) => rewritten_constraints.push(c), + Ambiguity | RegionOutlives(..) => { + rewritten_constraints.push(Or::new([And::new([c])])) + } PlaceholderTyOutlives(ty, region) => { - rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - ty, - region, - u, - assumptions, - )); + rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, ty, region, u, assumptions)); } AliasTyOutlivesViaEnv(bound_outlives) => { - rewritten_constraints.push(rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling( - infcx, - bound_outlives, - u, - assumptions, - )) + rewritten_constraints.push(rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, bound_outlives, u, assumptions)); } - And(_) | Or(_) => unreachable!(), } } - - And(rewritten_constraints.into_boxed_slice()) + rewritten_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) }; - let ands = constraint.unwrap_or(); - Or(ands.into_iter().map(|and| rewrite_and(and)).collect::>().into_boxed_slice()) + let and_constraint = rewrite_and(constraint.and_constraint); + let or_constraint = constraint + .or_constraint + .0 + .into_iter() + .fold(Or::new_false(), |acc, c| Or::new_or(acc, rewrite_and(c))); + + CanonicalFormRegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< @@ -525,19 +659,19 @@ fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder region: Region, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { - use RegionConstraint::*; +) -> Or { + use LeafRegionConstraint::*; let ty_u = max_universe(infcx, ty); let region_u = max_universe(infcx, region); if region_u != u && ty_u != u { - return PlaceholderTyOutlives(ty, region); + return Or::new([And::new([PlaceholderTyOutlives(ty, region)])]); } let assumptions = match assumptions { Some(assumptions) => assumptions, - None => return Ambiguity, + None => return Or::new([And::new([Ambiguity])]), }; let mut candidates = vec![]; @@ -560,7 +694,7 @@ fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder ); } - Or(candidates.into_boxed_slice()) + Or::new(candidates.into_iter().map(|c| And::new([c]))) } fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< @@ -571,8 +705,8 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl bound_outlives: Binder, Region)>, u: UniverseIndex, assumptions: &Option>, -) -> RegionConstraint { - use RegionConstraint::*; +) -> Or { + use LeafRegionConstraint::*; let mut candidates = Vec::new(); @@ -609,22 +743,14 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - let candidate = RegionConstraint::AliasTyOutlivesViaEnv(bound_outlives); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - // `PlaceholderReplacer` only folds regions. A non-lifetime binder can leave - // a placeholder type in `u`, so this type-outlives constraint cannot be - // handled by the region-outlives-only eager placeholder machinery. - candidates.push(Ambiguity); - } + candidates.push(Or::new([And::new([AliasTyOutlivesViaEnv(bound_outlives)])])); } let assumptions = match assumptions { Some(assumptions) => assumptions, None => { - candidates.push(Ambiguity); - return Or(candidates.into_boxed_slice()); + candidates.push(Or::new([And::new([Ambiguity])])); + return candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)); } }; @@ -658,33 +784,30 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // while we did skip the binder, bound vars aren't in any universe so // this can't be an escaping bound var - for r2 in regions_outliving(escaping_r, assumptions, infcx.cx()) - .filter(|r2| max_universe(infcx, *r2) < u) - { - let candidate = AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2))); - if max_universe(infcx, candidate.clone()) < u { - candidates.push(candidate); - } else { - candidates.push(Ambiguity); - } - } + candidates.push(Or::new( + regions_outliving(escaping_r, assumptions, infcx.cx()) + .filter(|r2| max_universe(infcx, *r2) < u) + .map(|r2| { + And::new([AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)))]) + }), + )); } // I'm not convinced our handling here is *complete* so for now // let's be conservative and not let alias outlives' cause NoSolution // in coherence match infcx.typing_mode_raw() { - TypingMode::Coherence => candidates.push(RegionConstraint::Ambiguity), + TypingMode::Coherence => candidates.push(Or::new([And::new([Ambiguity])])), TypingMode::Typeck { .. } + | TypingMode::Reflection | TypingMode::ErasedNotCoherence { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::PostBorrowck { .. } - | TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => (), }; - RegionConstraint::Or(candidates.into_boxed_slice()) + candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)) } /// Returns all regions `r2` for which `r: r2` is known to hold in @@ -776,7 +899,7 @@ fn alias_outlives_candidates_from_assumptions infcx: &Infcx, bound_outlives: Binder, Region)>, assumptions: &Assumptions, -) -> RegionConstraint { +) -> Or { let mut candidates = Vec::new(); let prev_universe = infcx.universe(); @@ -788,7 +911,7 @@ fn alias_outlives_candidates_from_assumptions let mut relation = HigherRankedAliasMatcher { infcx, - region_constraints: vec![RegionConstraint::RegionOutlives(r2, r)], + region_constraints: vec![LeafRegionConstraint::RegionOutlives(r2, r)], }; // FIXME(#155345): Both sides should be rigid in the future. @@ -797,28 +920,29 @@ fn alias_outlives_candidates_from_assumptions alias.to_ty(infcx.cx(), IsRigid::No), set_aliases_to_non_rigid(infcx.cx(), alias2).skip_norm_wip(), ) { - candidates - .push(RegionConstraint::And(relation.region_constraints.into_boxed_slice())); + candidates.push(And::new(relation.region_constraints)); } } }); - let constraint = RegionConstraint::Or(candidates.into_boxed_slice()); + let constraint = CanonicalFormRegionConstraint::new_from_or(Or::new(candidates)); let largest_universe = infcx.universe(); debug!(?prev_universe, ?largest_universe); - ((prev_universe.index() + 1)..=largest_universe.index()) + let canonical_constraint = ((prev_universe.index() + 1)..=largest_universe.index()) .map(|u| UniverseIndex::from_usize(u)) .rev() .fold(constraint, |constraint, u| { eagerly_handle_placeholders_in_universe(infcx, constraint, u) - }) + }); + + canonical_constraint.splatted_and_constraints() } struct HigherRankedAliasMatcher<'a, Infcx: InferCtxtLike, I: Interner> { infcx: &'a Infcx, - region_constraints: Vec>, + region_constraints: Vec>, } impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation @@ -859,8 +983,8 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation fn regions(&mut self, a: Region, b: Region) -> RelateResult> { if a != b { - self.region_constraints.push(RegionConstraint::RegionOutlives(a, b)); - self.region_constraints.push(RegionConstraint::RegionOutlives(b, a)); + self.region_constraints.push(LeafRegionConstraint::RegionOutlives(a, b)); + self.region_constraints.push(LeafRegionConstraint::RegionOutlives(b, a)); } Ok(a) } diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 6de031ed1bd51..9c5ffa7971b13 100644 --- a/compiler/rustc_type_ir/src/solve/mod.rs +++ b/compiler/rustc_type_ir/src/solve/mod.rs @@ -15,7 +15,7 @@ use tracing::debug; use crate::inherent::*; use crate::lang_items::SolverTraitLangItem; -use crate::region_constraint::RegionConstraint; +use crate::region_constraint::CanonicalFormRegionConstraint; use crate::search_graph::PathKind; use crate::{ self as ty, Canonical, CanonicalVarValues, CantBeErased, ConstVid, FloatVid, GenericArgKind, @@ -612,7 +612,7 @@ pub enum ExternalRegionConstraints { Old(Vec<(ty::RegionConstraint, VisibleForLeakCheck)>), /// new form of region constraints used when `-Zassumptions-on-binders` is enabled. /// supports ORs. - NextGen(RegionConstraint), + NextGen(CanonicalFormRegionConstraint), } impl ExternalRegionConstraints { @@ -639,7 +639,7 @@ impl Eq for ExternalConstraintsData {} impl ExternalConstraintsData { pub fn new(cx: I) -> Self { let region_constraints = match cx.assumptions_on_binders() { - true => ExternalRegionConstraints::NextGen(RegionConstraint::new_true()), + true => ExternalRegionConstraints::NextGen(CanonicalFormRegionConstraint::new_true()), false => ExternalRegionConstraints::Old(vec![]), }; From fe0d9d827207db9c7c96c55420ca3db47fba3812 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 12:28:31 +0100 Subject: [PATCH 09/10] add `new_leaf` functions --- .../eval_ctxt/solver_region_constraints.rs | 9 ++--- .../rustc_next_trait_solver/src/solve/mod.rs | 7 ++-- .../rustc_type_ir/src/region_constraint.rs | 37 +++++++++++++------ 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 8393ec00f8d81..ed174216c4be5 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -171,12 +171,12 @@ where use Component::*; use LeafRegionConstraint::*; match c { - Region(c_r) => Or::new([And::new([RegionOutlives(*c_r, r)])]), + Region(c_r) => Or::new_leaf(RegionOutlives(*c_r, r)), Placeholder(p) => { - Or::new([And::new([PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r)])]) + Or::new_leaf(PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r)) } Alias(_, alias) => self.destructure_alias_outlives(*alias, r), - UnresolvedInferenceVariable(_) => Or::new([And::new([Ambiguity])]), + UnresolvedInferenceVariable(_) => Or::new_ambig(), Param(_) => panic!("Params should have been canonicalized to placeholders"), EscapingAlias(components) => self.destructure_components(components, r), } @@ -198,8 +198,7 @@ where .map(|bound| And::new([RegionOutlives(bound, r)])); let item_bound_outlives = Or::new(item_bounds); - let where_clause_outlives = - Or::new([And::new([AliasTyOutlivesViaEnv(Binder::dummy((alias, r)))])]); + let where_clause_outlives = Or::new_leaf(AliasTyOutlivesViaEnv(Binder::dummy((alias, r)))); let mut components = Default::default(); rustc_type_ir::outlives::compute_alias_components_recursive( diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index c6cf2209a4188..c8950d3eed192 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -124,12 +124,11 @@ where if self.cx().assumptions_on_binders() { use rustc_type_ir::region_constraint::{ - And, CanonicalFormRegionConstraint, LeafRegionConstraint, Or, + CanonicalFormRegionConstraint, LeafRegionConstraint, }; - let constraint = CanonicalFormRegionConstraint::new_from_or(Or::new([And::new([ - LeafRegionConstraint::RegionOutlives(a, b), - ])])); + let constraint = + CanonicalFormRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b)); self.register_solver_region_constraint(constraint); } else { self.register_region_outlives(a, b, VisibleForLeakCheck::Yes); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 707661530c57b..f15ae4c877635 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -160,6 +160,14 @@ impl Or { Self(new_ands.into_boxed_slice()) } + pub fn new_ambig() -> Self { + Or::new_leaf(LeafRegionConstraint::Ambiguity) + } + + pub fn new_leaf(l: LeafRegionConstraint) -> Self { + Or(Box::new([And(Box::new([l]))])) + } + pub fn new_and(a: Or, b: Or) -> Self { // I think this returns false if either a or b is false? let mut ands = Vec::new(); @@ -294,6 +302,13 @@ impl CanonicalFormRegionConstraint { false } } + + pub fn new_leaf(l: LeafRegionConstraint) -> Self { + CanonicalFormRegionConstraint { + and_constraint: And(Box::new([l])), + or_constraint: Or::new_true(), + } + } } impl Default for CanonicalFormRegionConstraint { @@ -488,21 +503,21 @@ fn pull_region_outlives_constraints_out_of_universe< match c { Ambiguity | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => { assert!(max_universe(infcx, c.clone()) < u); - pulled_constraints.push(Or::new([And::new([c.clone()])])); + pulled_constraints.push(Or::new_leaf(c.clone())); } RegionOutlives(region_1, region_2) => { let region_1_u = max_universe(infcx, region_1); let region_2_u = max_universe(infcx, region_2); if region_1_u != u && region_2_u != u { - pulled_constraints.push(Or::new([And::new([c])])); + pulled_constraints.push(Or::new_leaf(c)); continue; } let assumptions = match assumptions { Some(assumptions) => assumptions, None => { - pulled_constraints.push(Or::new([And::new([Ambiguity])])); + pulled_constraints.push(Or::new_ambig()); continue; } }; @@ -561,7 +576,7 @@ pub fn destructure_type_outlives_constraints_in_root< for c in &and.0 { match c { Ambiguity | RegionOutlives(..) => { - destructured_constraints.push(Or::new([And::new([c.clone()])])) + destructured_constraints.push(Or::new_leaf(c.clone())) } PlaceholderTyOutlives(ty, r) => destructured_constraints.push(Or::new( regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()) @@ -626,9 +641,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< let mut rewritten_constraints = Vec::new(); for c in and.0 { match c { - Ambiguity | RegionOutlives(..) => { - rewritten_constraints.push(Or::new([And::new([c])])) - } + Ambiguity | RegionOutlives(..) => rewritten_constraints.push(Or::new_leaf(c)), PlaceholderTyOutlives(ty, region) => { rewritten_constraints.push(rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling(infcx, ty, region, u, assumptions)); } @@ -666,12 +679,12 @@ fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder let region_u = max_universe(infcx, region); if region_u != u && ty_u != u { - return Or::new([And::new([PlaceholderTyOutlives(ty, region)])]); + return Or::new_leaf(PlaceholderTyOutlives(ty, region)); } let assumptions = match assumptions { Some(assumptions) => assumptions, - None => return Or::new([And::new([Ambiguity])]), + None => return Or::new_ambig(), }; let mut candidates = vec![]; @@ -743,13 +756,13 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - candidates.push(Or::new([And::new([AliasTyOutlivesViaEnv(bound_outlives)])])); + candidates.push(Or::new_leaf(AliasTyOutlivesViaEnv(bound_outlives))); } let assumptions = match assumptions { Some(assumptions) => assumptions, None => { - candidates.push(Or::new([And::new([Ambiguity])])); + candidates.push(Or::new_ambig()); return candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)); } }; @@ -797,7 +810,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl // let's be conservative and not let alias outlives' cause NoSolution // in coherence match infcx.typing_mode_raw() { - TypingMode::Coherence => candidates.push(Or::new([And::new([Ambiguity])])), + TypingMode::Coherence => candidates.push(Or::new_ambig()), TypingMode::Typeck { .. } | TypingMode::Reflection | TypingMode::ErasedNotCoherence { .. } From 1bead0060c64abd06b1449de46ef8e288f43f8bc Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 17:10:40 +0100 Subject: [PATCH 10/10] propagate ambiguity not evaluate --- .../src/infer/outlives/obligations.rs | 2 +- .../src/solve/eval_ctxt/mod.rs | 2 +- .../eval_ctxt/solver_region_constraints.rs | 4 +- .../rustc_type_ir/src/region_constraint.rs | 46 +++++++++++++++++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index da14e80e9eadd..dd0ac1a25a91a 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -261,7 +261,7 @@ impl<'tcx> InferCtxt<'tcx> { &assumptions, ); debug!(?constraint); - let constraint = region_constraint::evaluate_solver_constraint(constraint); + let constraint = region_constraint::propagate_ambiguity(constraint); debug!(?constraint); // FIXME(-Zassumptions-on-binders): actually implement OR as an OR diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index f2ddab97cef54..c6435436fd711 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -1664,7 +1664,7 @@ where let constraint = self.delegate.get_solver_region_constraint(); debug_assert_eq!( constraint, - region_constraint::evaluate_solver_constraint(constraint.clone()) + region_constraint::propagate_ambiguity(constraint.clone()) ); constraint } else { diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index ed174216c4be5..45460817c1ebc 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -9,7 +9,7 @@ use rustc_type_ir::outlives::{Component, push_outlives_components}; use rustc_type_ir::region_constraint::TransitiveRelationBuilder; use rustc_type_ir::region_constraint::{ And, Assumptions, LeafRegionConstraint, Or, eagerly_handle_placeholders_in_universe, - evaluate_solver_constraint, + propagate_ambiguity, }; use rustc_type_ir::{ AliasTy, Binder, ClauseKind, InferCtxtLike, Interner, OutlivesClause, Region, TypeVisitable, @@ -137,7 +137,7 @@ where .fold(constraint, |constraint, u| { eagerly_handle_placeholders_in_universe(&**self.delegate, constraint, u) }); - let constraint = evaluate_solver_constraint(constraint); + let constraint = propagate_ambiguity(constraint); debug!("final constraint={:?}", constraint); self.delegate.overwrite_solver_region_constraint(constraint.clone()); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index f15ae4c877635..19e9715afb32c 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -364,8 +364,8 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interne ) } -/// Evaluate ANDs and ORs to true/false/ambiguous based on whether their arguments are true/false/ambiguous +/// Force the whole constraint to be ambiguous if it contains ambiguities which could +/// have caused the constraint to be `false` if they had been `false` themselves. +/// +/// For example if we have `'a: 'b AND ambig` it's possible that if we had more inference +/// information we could have produced a better region constraint than `ambig`, and that +/// constraint may then have gone on to be false, at which point we would have `'a: 'b AND false` +/// causing the whole constraint to be `false`. +/// +/// If we're not careful we can wind up returning `'a: 'b AND ambig` from passing trait solver +/// goals and then upon rerunning wind up returning `NoSolution` which would be dubious :3 +/// +/// This is inherently conservative and this method should be called as little as possible as it +/// can cause us to get ambiguities instead of `NoSolution` (for example if `'a: 'b` is `false`), +/// which can affect coherence, candidate selection, etc. +/// +/// FIXME(-Zassumptions-on-binders): this method should probably be trait-solver internal as it only +/// matters at trait solver query boundaries. We currently call it in more than just that location #[instrument(level = "debug", ret)] -pub fn evaluate_solver_constraint( +pub fn propagate_ambiguity( constraint: CanonicalFormRegionConstraint, ) -> CanonicalFormRegionConstraint { - todo!("overhauled in future commit") + if constraint.and_constraint.0.iter().any(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_ambig(); + } + + for and in constraint.or_constraint.0.iter() { + // FIXME(-Zassumptions-on-binders): This is overly conservative. If we have: + // `'a: 'b OR ambig` we don't necessarily want to propagate ambiguity here + // as we might end up with `'a: 'b` being satisfied in which case we unncessarily + // errored here. + // + // It's fine if the `ambig` wound up being `false` as that wouldn't cause a goal to + // become `NoSolution`, it would instead result in us returning the `'a: 'b` constraint + // by itself. + // + // `rust-lang/project-assumptions-on-binders#21` + if and.0.iter().any(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_ambig(); + } + } + + constraint } /// Handles converting region outlives constraints involving placeholders from `u` into OR constraints