From 282bc182f36f059cfd5dd051171f7dc75a51da07 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:59:34 +0100 Subject: [PATCH 01/24] 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 2923709432ee5..02c629f0ef078 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -795,157 +795,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() @@ -972,6 +836,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 580a5fccafa85d10b466126a1e2505285a35e631 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 13:37:59 +0100 Subject: [PATCH 02/24] 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 02c629f0ef078..0af55d42e3396 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -491,7 +491,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 221137add588a1f73e6beb8b9fa9ee75a51c3661 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 14:05:01 +0100 Subject: [PATCH 03/24] 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 0af55d42e3396..4a02c7f90a910 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -481,8 +481,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 @@ -496,28 +494,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) } @@ -532,51 +519,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 @@ -718,7 +717,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 15b08b4e5f951ce91d3b00423d817af7321dab3d Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:24:10 +0100 Subject: [PATCH 04/24] restructure `pull_region_outlives_constraints_out_of_universe` --- .../rustc_type_ir/src/region_constraint.rs | 106 +++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 4a02c7f90a910..ae44dfe8813e5 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -500,12 +500,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) + .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, ())); + } + } - 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 392edefa918b0df2fb9c3df8e2613d6739bf593c Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 15:30:42 +0100 Subject: [PATCH 05/24] restructure `destructure_type_outlives_constraints_in_root` --- .../rustc_type_ir/src/region_constraint.rs | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index ae44dfe8813e5..bd5b70c411241 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -748,31 +748,32 @@ pub fn destructure_type_outlives_constraints_in_root< ) -> RegionConstraint { use RegionConstraint::*; - match constraint { - Ambiguity(_) | RegionOutlives(..) => constraint, - PlaceholderTyOutlives(ty, r, span) => { - Or(regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) - .map(move |assumption_r| RegionOutlives(assumption_r, r, span.clone())) - .collect::>() - .into_boxed_slice()) - } - AliasTyOutlivesViaEnv(bound_outlives, span) => { - alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions) - .with_span(span) + 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, span) => destructured_constraints.push(Or( + regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) + .map(move |assumption_r| RegionOutlives(assumption_r, r, span.clone())) + .collect::>() + .into_boxed_slice(), + )), + AliasTyOutlivesViaEnv(bound_outlives, span) => destructured_constraints.push( + alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions) + .with_span(span), + ), + 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 4d35ce9f0e136f7362934c2a8306c8c511cd1205 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 13:25:39 +0100 Subject: [PATCH 06/24] remove big manual derives --- .../rustc_type_ir/src/region_constraint.rs | 271 +----------------- 1 file changed, 1 insertion(+), 270 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index bd5b70c411241..4c73aaef21fcc 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -121,174 +121,7 @@ pub enum RegionConstraint { Or(#[generic_type_visitable(bounds())] Box<[RegionConstraint]>), } -/// A solver region constraint together with the span that caused each leaf constraint. -/// -/// Solver query responses use [`RegionConstraint`] so source locations do not participate in -/// candidate equality or caching. Spans are attached when responses are applied to an inference -/// context. -pub type SpannedRegionConstraint = RegionConstraint::Span>; - -// 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, span) => { - RegionOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?, span) - } - AliasTyOutlivesViaEnv(outlives, span) => { - AliasTyOutlivesViaEnv(outlives.try_fold_with(f)?, span) - } - PlaceholderTyOutlives(a, b, span) => { - PlaceholderTyOutlives(a.try_fold_with(f)?, b.try_fold_with(f)?, span) - } - 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, span) => RegionOutlives(a.fold_with(f), b.fold_with(f), span), - AliasTyOutlivesViaEnv(outlives, span) => { - AliasTyOutlivesViaEnv(outlives.fold_with(f), span) - } - PlaceholderTyOutlives(a, b, span) => { - PlaceholderTyOutlives(a.fold_with(f), b.fold_with(f), span) - } - 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 RegionConstraint { - fn map_spans(self, f: &mut impl FnMut(S) -> T) -> RegionConstraint { - use RegionConstraint::*; - - match self { - Ambiguity(span) => Ambiguity(f(span)), - RegionOutlives(a, b, span) => RegionOutlives(a, b, f(span)), - AliasTyOutlivesViaEnv(outlives, span) => AliasTyOutlivesViaEnv(outlives, f(span)), - PlaceholderTyOutlives(ty, region, span) => PlaceholderTyOutlives(ty, region, f(span)), - And(constraints) => And(constraints.into_iter().map(|c| c.map_spans(f)).collect()), - Or(constraints) => Or(constraints.into_iter().map(|c| c.map_spans(f)).collect()), - } - } - - pub fn without_spans(self) -> RegionConstraint { - self.map_spans(&mut |_| ()) - } -} - -impl RegionConstraint { - pub fn with_span(self, span: S) -> RegionConstraint { - self.map_spans(&mut |_| span.clone()) - } -} - -impl Default for RegionConstraint { +impl Default for RegionConstraint { fn default() -> Self { Self::new_true() } @@ -358,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 58e60ab65e8cd6043f1555a44ca6f56b31306afb Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 17:13:33 +0100 Subject: [PATCH 07/24] stub out `evaluate_solver_constraint` --- .../rustc_type_ir/src/region_constraint.rs | 56 ++----------------- 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 4c73aaef21fcc..0a2a3de96bdc5 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -314,58 +314,10 @@ fn compute_new_region_constraints, I: Interne /// 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 { - use RegionConstraint::*; - match constraint { - Ambiguity(_) - | RegionOutlives(..) - | AliasTyOutlivesViaEnv(..) - | PlaceholderTyOutlives(..) => constraint.clone(), - And(and) => { - let mut and_constraints = Vec::new(); - let mut ambiguity = None; - 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 let Ambiguity(span) = evaluated_constraint { - ambiguity.get_or_insert(span); - } else { - and_constraints.push(evaluated_constraint); - } - } - - ambiguity.map_or_else( - || RegionConstraint::And(and_constraints.into_boxed_slice()), - RegionConstraint::Ambiguity, - ) - } - Or(or) => { - let mut or_constraints = Vec::new(); - let mut ambiguity = None; - 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 let Ambiguity(span) = evaluated_constraint { - ambiguity.get_or_insert(span); - } else { - or_constraints.push(evaluated_constraint); - } - } - - ambiguity.map_or_else( - || RegionConstraint::Or(or_constraints.into_boxed_slice()), - RegionConstraint::Ambiguity, - ) - } - } +pub fn evaluate_solver_constraint( + constraint: &RegionConstraint, +) -> RegionConstraint { + todo!("overhauled in future commit") } /// Handles converting region outlives constraints involving placeholders from `u` into OR constraints From 6cf17d2c625a6017b697f0835a12edeb2a181866 Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 24 Jul 2026 17:23:49 +0100 Subject: [PATCH 08/24] always canonical form --- .../rustc_hir_analysis/src/check/wfcheck.rs | 171 ++--- compiler/rustc_hir_analysis/src/collect.rs | 25 +- compiler/rustc_infer/src/infer/context.rs | 10 +- .../src/infer/outlives/obligations.rs | 49 +- .../src/infer/snapshot/undo_log.rs | 9 - .../src/infer/solver_region_constraints.rs | 42 +- .../src/solve/eval_ctxt/mod.rs | 8 +- .../eval_ctxt/solver_region_constraints.rs | 60 +- .../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 | 585 ++++++++++++------ compiler/rustc_type_ir/src/solve/mod.rs | 6 +- 12 files changed, 611 insertions(+), 375 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index f2dec4c7db89f..99c6f0ff2724c 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -21,6 +21,7 @@ use rustc_lint_defs::builtin::{REDUNDANT_LIFETIMES, SHADOWING_SUPERTRAIT_ITEMS}; use rustc_macros::{Diagnostic, TypeFoldable, TypeVisitable}; use rustc_middle::mir::interpret::ErrorHandled; use rustc_middle::traits::solve::NoSolution; +use rustc_middle::ty::region_constraint::{And, LeafRegionConstraint, Or}; use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{ self, GenericArgKind, GenericArgs, GenericParamDefKind, RegionExt, Ty, TyCtxt, TypeFlags, @@ -2335,7 +2336,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { pub(super) fn check_test_binder_body(&self, body: TestBinderBody<'tcx>) { let constraints = match validate(self.tcx(), &body.constraints) { Ok(()) => body.constraints, - Err(_guar) => ty::region_constraint::RegionConstraint::And(Box::new([])), + Err(_guar) => ty::region_constraint::CanonicalFormRegionConstraint::new_true(), }; self.infcx.register_solver_region_constraint(constraints); @@ -2351,40 +2352,43 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { tcx: TyCtxt<'tcx>, constraint: &SolverRegionConstraint<'tcx>, ) -> Result<(), ErrorGuaranteed> { - match constraint { - ty::region_constraint::RegionConstraint::Ambiguity(_) => Ok(()), - ty::region_constraint::RegionConstraint::RegionOutlives(..) => Ok(()), - ty::region_constraint::RegionConstraint::AliasTyOutlivesViaEnv(..) => Ok(()), - ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(ty, _, span) => { - // we can't check this during lowering, because the ty is a ty::Bound that gets - // instantiated with a placeholder when entering the containing forall. - if let ty::Placeholder(_) | ty::Param(_) = ty.kind() { - Ok(()) - } else { - let mut err = tcx.dcx().struct_span_err( - *span, - "the lhs of a ty outlives must be a placeholder", - ); - err.note(format!("it is a {ty}")); - err.note(format!("and here it is `Debug`ged :3 {ty:?}")); - Err(err.emit()) - } - } - ty::region_constraint::RegionConstraint::And(constraints) => { - let mut res = Ok(()); - for constraint in constraints { - res = res.and(validate(tcx, constraint)); - } - res - } - ty::region_constraint::RegionConstraint::Or(constraints) => { - let mut res = Ok(()); - for constraint in constraints { - res = res.and(validate(tcx, constraint)); + let mut r = Ok(()); + + let mut validate_and = |and: &And<_, _>| { + for c in and.0.iter() { + match c { + LeafRegionConstraint::Ambiguity(_) + | LeafRegionConstraint::RegionOutlives(..) + | LeafRegionConstraint::AliasTyOutlivesViaEnv(..) => (), // OK + LeafRegionConstraint::PlaceholderTyOutlives(ty, _, span) => { + // I couldn't tell you why without this line `ty.kind()` is an error + // because of not being able to infer the type of `ty`... Lmao + // - BoxyUwU + let ty: Ty<'_> = *ty; + // we can't check this during lowering, because the ty is a ty::Bound that gets + // instantiated with a placeholder when entering the containing forall. + if let ty::Placeholder(_) | ty::Param(_) = ty.kind() { + // all OK + } else { + let mut err = tcx.dcx().struct_span_err( + *span, + "the lhs of a ty outlives must be a placeholder", + ); + err.note(format!("it is a {ty}")); + err.note(format!("and here it is `Debug`ged :3 {ty:?}")); + r = Err(err.emit()); + } + } } - res } + }; + + validate_and(&constraint.and_constraint); + for and in constraint.or_constraint.0.iter() { + validate_and(and); } + + r } } @@ -2406,12 +2410,12 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { solver_region_constraint.without_spans(), u, ) - .with_span(forall.span); + .with_spans(forall.span); if let Some(assert_on_exit) = forall.assert_on_exit { self.check_test_binder_region_constraints( forall.span, - &assert_on_exit.clone().canonical_form(), - &constraint.clone().canonical_form(), + &assert_on_exit.clone(), + &constraint.clone(), ); } self.infcx.overwrite_solver_region_constraint(constraint); @@ -2425,58 +2429,75 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { expected: &SolverRegionConstraint<'tcx>, actual: &SolverRegionConstraint<'tcx>, ) { - fn span_of<'tcx>(constraint: &SolverRegionConstraint<'tcx>) -> Option { - match constraint { - SolverRegionConstraint::Ambiguity(sp) - | SolverRegionConstraint::RegionOutlives(_, _, sp) - | SolverRegionConstraint::AliasTyOutlivesViaEnv(_, sp) - | ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(_, _, sp) => { - Some(*sp) - } - SolverRegionConstraint::And(constraints) - | SolverRegionConstraint::Or(constraints) => constraints - .iter() - .map(span_of) - .flatten() - .fold(None, |l, r| Some(l.map_or(r, |l| l.to(r)))), - } - } fn err<'tcx>( tcx: TyCtxt<'tcx>, - fallback_span: Span, - expected: &SolverRegionConstraint<'tcx>, - actual: &SolverRegionConstraint<'tcx>, + expected_span: Span, + expected: impl std::fmt::Debug, + actual_span: Option, + actual: impl std::fmt::Debug, ) { - let mut err = tcx.dcx().struct_span_err( - span_of(expected).unwrap_or(fallback_span), - "forall expect clause failed", - ); - if let Some(actual_span) = span_of(actual) { + let mut err = tcx.dcx().struct_span_err(expected_span, "forall expect clause failed"); + if let Some(actual_span) = actual_span { err.span_note(actual_span, "constraint from here"); } err.note(format!("expected: {expected:?}")); err.note(format!("actual: {actual:?}")); err.emit(); } - match (expected, actual) { - ( - SolverRegionConstraint::And(expected_arr), - SolverRegionConstraint::And(actual_arr), - ) - | (SolverRegionConstraint::Or(expected_arr), SolverRegionConstraint::Or(actual_arr)) => { - if expected_arr.len() != actual_arr.len() { - err(self.tcx(), fallback_span, expected, actual); - } else { - for (expected, actual) in expected_arr.iter().zip(actual_arr) { - self.check_test_binder_region_constraints(fallback_span, expected, actual); - } + + let span_of_and = |c: &And<_, _>| { + let mut spans = c.0.iter().map(|leaf| leaf.span()); + let fst = spans.next()?; + Some(spans.fold(fst, |snd, acc: rustc_span::Span| acc.to(snd))) + }; + + let span_of_or = |c: &Or<_, _>| { + let mut spans = c.0.iter().flat_map(|and| span_of_and(and)); + let fst = spans.next()?; + Some(spans.fold(fst, |snd, acc| acc.to(snd))) + }; + + let check_leaf_constraint = + |expected: LeafRegionConstraint<_, _>, actual: LeafRegionConstraint<_, _>| { + if expected.clone().without_span() != actual.clone().without_span() { + err(self.tcx(), expected.span(), expected, Some(actual.span()), actual); + } + }; + + let check_and_constraint = |expected: And<_, _>, actual: And<_, _>| { + if expected.0.len() != actual.0.len() { + err( + self.tcx(), + span_of_and(&expected).unwrap_or(fallback_span), + expected, + span_of_and(&actual), + actual, + ) + } else { + for (expected, actual) in expected.0.into_iter().zip(actual.0.into_iter()) { + check_leaf_constraint(expected, actual); } } - _ if expected.clone().without_spans() != actual.clone().without_spans() => { - err(self.tcx(), fallback_span, expected, actual); + }; + + let check_or_constraint = |expected: Or<_, _>, actual: Or<_, _>| { + if expected.0.len() != actual.0.len() { + err( + self.tcx(), + span_of_or(&expected).unwrap_or(fallback_span), + expected, + span_of_or(&actual), + actual, + ) + } else { + for (expected, actual) in expected.0.into_iter().zip(actual.0.into_iter()) { + check_and_constraint(expected, actual); + } } - _ => (), - } + }; + + check_or_constraint(expected.or_constraint.clone(), actual.or_constraint.clone()); + check_and_constraint(expected.and_constraint.clone(), actual.and_constraint.clone()); } #[instrument(level = "debug", skip(self))] diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 65fd562a4ebf6..269628f577e4f 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -45,6 +45,7 @@ use rustc_trait_selection::traits::{ FulfillmentError, ObligationCtxt, hir_ty_lowering_dyn_compatibility_violations, }; use tracing::{debug, instrument}; +use ty::region_constraint::{And, LeafRegionConstraint, Or}; use crate::check::wfcheck::{TestBinderBody, TestBinderExists, TestBinderForall}; use crate::diagnostics::{self, ElidedLifetimesAreNotAllowedInDelegations}; @@ -432,19 +433,22 @@ impl<'tcx> ItemCtxt<'tcx> { constraint: &hir::TestBinderConstraint<'tcx>, ) -> SolverRegionConstraint<'tcx> { match constraint { - hir::TestBinderConstraint::And { items } => { - ty::region_constraint::RegionConstraint::And( - items.iter().map(|i| self.lower_test_binder_constraint(i)).collect(), - ) - } - hir::TestBinderConstraint::Or { items } => ty::region_constraint::RegionConstraint::Or( - items.iter().map(|i| self.lower_test_binder_constraint(i)).collect(), - ), + hir::TestBinderConstraint::And { items } => items + .into_iter() + .map(|item| self.lower_test_binder_constraint(item)) + .reduce(SolverRegionConstraint::new_and) + .unwrap_or(SolverRegionConstraint::new_true()), + hir::TestBinderConstraint::Or { items } => items + .into_iter() + .map(|item| self.lower_test_binder_constraint(item)) + .reduce(SolverRegionConstraint::new_or) + .unwrap_or(SolverRegionConstraint::new_true()), hir::TestBinderConstraint::Lifetime { lhs, rhs } => { let span = lhs.ident.span.to(rhs.ident.span); let lhs = self.lowerer().lower_lifetime(lhs, RegionInferReason::RegionPredicate); let rhs = self.lowerer().lower_lifetime(rhs, RegionInferReason::RegionPredicate); - ty::region_constraint::RegionConstraint::RegionOutlives(lhs, rhs, span) + let leaf = LeafRegionConstraint::RegionOutlives(lhs, rhs, span); + SolverRegionConstraint::new_from_or(Or::new([And::new([leaf])])) } hir::TestBinderConstraint::Type { lhs, rhs } => { let span = lhs.span.to(rhs.ident.span); @@ -453,7 +457,8 @@ impl<'tcx> ItemCtxt<'tcx> { // note that we cannot check that lhs is a placeholder at this moment, as at this // point it is a bound variable that is not yet instantiated with a placeholder. // instead, we check it when we emit the region constraint. - ty::region_constraint::RegionConstraint::PlaceholderTyOutlives(lhs, rhs, span) + let leaf = LeafRegionConstraint::PlaceholderTyOutlives(lhs, rhs, span); + SolverRegionConstraint::new_from_or(Or::new([And::new([leaf])])) } } } diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index e6ddbd879f9b9..31dfa2964b1cc 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -63,16 +63,16 @@ 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.get_solver_region_constraint().without_spans() } fn overwrite_solver_region_constraint( &self, - constraint: rustc_type_ir::region_constraint::RegionConstraint>, + constraint: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, span: Span, ) { - self.overwrite_solver_region_constraint(constraint.with_span(span)); + self.overwrite_solver_region_constraint(constraint.with_spans(span)); } fn universe_of_ty(&self, vid: ty::TyVid) -> Option { @@ -325,10 +325,10 @@ 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>, span: Span, ) { - self.register_solver_region_constraint(c.with_span(span)); + self.register_solver_region_constraint(c.with_spans(span)); } fn register_ty_outlives(&self, ty: Ty<'tcx>, r: ty::Region<'tcx>, span: Span) { diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index a861b89cceb8e..14de716de7b5c 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}; @@ -142,9 +143,18 @@ impl<'tcx> InferCtxt<'tcx> { pub fn register_solver_region_constraint(&self, c: SolverRegionConstraint<'tcx>) { let mut inner = self.inner.borrow_mut(); - 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); + 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; + inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); + inner.solver_region_constraint_storage.overwrite(new_constraint); } pub fn register_type_outlives_constraint( @@ -238,7 +248,7 @@ impl<'tcx> InferCtxt<'tcx> { known_type_outlives: &[PolyTypeOutlivesClause<'tcx>], region_outlives: TransitiveRelation, ) { - 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(), ); @@ -256,19 +266,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(span) => { @@ -287,9 +302,9 @@ impl<'tcx> InferCtxt<'tcx> { b, a, 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 fe697eb4c01ac..2b1ac29173483 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(old_constraint); } diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints.rs b/compiler/rustc_infer/src/infer/solver_region_constraints.rs index 268c677e0ff19..6e15c1b56f8e9 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints.rs @@ -1,56 +1,22 @@ use rustc_middle::ty::TyCtxt; -use rustc_type_ir::region_constraint::SpannedRegionConstraint; +use rustc_span::Span; use tracing::instrument; -pub type SolverRegionConstraint<'tcx> = SpannedRegionConstraint>; +pub type SolverRegionConstraint<'tcx> = + rustc_type_ir::region_constraint::CanonicalFormRegionConstraint, Span>; #[derive(Clone, Debug)] pub(crate) struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); impl<'tcx> SolverRegionConstraintStorage<'tcx> { pub(crate) fn new() -> Self { - Self(SolverRegionConstraint::And(Box::new([]))) + Self(SolverRegionConstraint::new_true()) } pub(crate) fn get_constraint(&self) -> SolverRegionConstraint<'tcx> { self.0.clone() } - pub(crate) fn is_and(&self) -> bool { - self.0.is_and() - } - - pub(crate) fn pop(&mut self, previous_was_and: bool) -> Option> { - match &mut self.0 { - SolverRegionConstraint::And(and) => { - let mut and = core::mem::take(and).into_vec(); - 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")] - pub(crate) 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))] pub(crate) fn overwrite(&mut self, constraint: SolverRegionConstraint<'tcx>) { self.0 = 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 a0abc918107df..b06c82f6d9d8b 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::{ @@ -1325,7 +1325,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, self.origin_span); } @@ -1663,11 +1663,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 2790b563f1ea7..e1d4555717c87 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(), self.origin_span); if constraint.is_false() { @@ -154,36 +155,30 @@ 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, ()), - Placeholder(p) => { - RegionConstraint::PlaceholderTyOutlives(Ty::new_placeholder(self.cx(), *p), r, ()) - } - // The alias is either rigid or ambiguous in which case we'll return with ambiguity. + Region(c_r) => Or::new([And::new([RegionOutlives(*c_r, r, ())])]), + Placeholder(p) => Or::new([And::new([PlaceholderTyOutlives( + Ty::new_placeholder(self.cx(), *p), + r, + (), + )])]), 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 +192,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 +211,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 d62a1627953ef..feafdbbe2adf5 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 7a6a7d7380bdd..6705d30f62bd7 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -411,10 +411,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, span: ::Span, ); @@ -536,7 +536,7 @@ pub trait InferCtxtLike: Sized { fn register_solver_region_constraint( &self, - c: crate::region_constraint::RegionConstraint, + c: crate::region_constraint::CanonicalFormRegionConstraint, span: ::Span, ); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 0a2a3de96bdc5..d6dd740cb71ee 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, S)] -#[derive(GenericTypeVisitable)] -pub enum RegionConstraint { +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub enum LeafRegionConstraint { Ambiguity(S), RegionOutlives(Region, Region, S), /// Requirement that a (potentially higher ranked) alias outlives some (potentially higher ranked) @@ -116,81 +116,289 @@ 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, S), +} + +impl LeafRegionConstraint { + pub fn with_span( + self, + span: S, + ) -> LeafRegionConstraint { + use LeafRegionConstraint::*; - And(#[generic_type_visitable(bounds())] Box<[RegionConstraint]>), - Or(#[generic_type_visitable(bounds())] Box<[RegionConstraint]>), + match self { + Ambiguity(()) => Ambiguity(span), + RegionOutlives(r1, r2, ()) => RegionOutlives(r1, r2, span), + AliasTyOutlivesViaEnv(bound_outlives, ()) => { + AliasTyOutlivesViaEnv(bound_outlives, span) + } + PlaceholderTyOutlives(ty, r, ()) => PlaceholderTyOutlives(ty, r, span), + } + } } -impl Default for RegionConstraint { - fn default() -> Self { - Self::new_true() +impl LeafRegionConstraint { + pub fn without_span(self) -> LeafRegionConstraint { + use LeafRegionConstraint::*; + + match self { + Ambiguity(_) => Ambiguity(()), + RegionOutlives(r1, r2, _) => RegionOutlives(r1, r2, ()), + AliasTyOutlivesViaEnv(bound_outlives, _) => AliasTyOutlivesViaEnv(bound_outlives, ()), + PlaceholderTyOutlives(ty, r, _) => PlaceholderTyOutlives(ty, r, ()), + } + } + + pub fn span(&self) -> S { + use LeafRegionConstraint::*; + + let (Ambiguity(s) + | RegionOutlives(_, _, s) + | AliasTyOutlivesViaEnv(_, s) + | PlaceholderTyOutlives(_, _, s)) = self; + s.clone() } } -impl RegionConstraint { +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct Or(pub Box<[And]>); +impl Or { + pub fn with_spans( + self, + span: S, + ) -> Or { + Or(self.0.into_iter().map(|and| and.with_spans(span.clone())).collect()) + } +} +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 is_or(&self) -> bool { - matches!(self, Self::Or(_)) + pub fn new_or(a: Or, b: Or) -> Self { + Or::new(a.0.into_iter().chain(b.0)) } - pub fn unwrap_or(self) -> Box<[RegionConstraint]> { - match self { - Self::Or(ors) => ors, - _ => panic!("`unwrap_or` on non-Or: {self:?}"), + pub fn without_spans(self) -> Or { + Or(self.0.into_iter().map(|and| and.without_spans()).collect()) + } +} + +#[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct And(pub Box<[LeafRegionConstraint]>); +impl And { + pub fn with_spans( + self, + span: S, + ) -> And { + And(self.0.into_iter().map(|leaf| leaf.with_span(span.clone())).collect()) + } +} +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; + + // FIXME(-Zassumptions-on-binders): using `==` here means we consider spans + this.iter().all(|c1| other.iter().any(|c2| c1 == c2)) + && other.iter().all(|c2| this.iter().any(|c1| c1 == c2)) + } + + pub fn without_spans(self) -> And { + And(self.0.into_iter().map(|leaf| leaf.without_span()).collect()) + } +} + +#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, S)] +#[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 with_spans( + self, + span: S, + ) -> CanonicalFormRegionConstraint { + CanonicalFormRegionConstraint { + and_constraint: self.and_constraint.with_spans(span.clone()), + or_constraint: self.or_constraint.with_spans(span.clone()), } } +} +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_and(self) -> Box<[RegionConstraint]> { - match self { - Self::And(ands) => ands, - _ => panic!("`unwrap_and` on non-And: {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 is_and(&self) -> bool { - matches!(self, Self::And(_)) + 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 is_ambig(&self) -> bool { - matches!(self, Self::Ambiguity(_)) + 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 and(self, other: RegionConstraint) -> RegionConstraint { - use RegionConstraint::*; + pub fn new_or( + a: CanonicalFormRegionConstraint, + b: CanonicalFormRegionConstraint, + ) -> Self { + Self::new_from_or(Or::new_or(a.splatted_and_constraints(), b.splatted_and_constraints())) + } - 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 new_true() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_true() } + } + + pub fn is_true(&self) -> bool { + self.and_constraint.0.is_empty() && self.or_constraint.is_true() + } + + pub fn new_false() -> Self { + Self { and_constraint: And::new([]), or_constraint: Or::new_false() } + } + + pub fn is_false(&self) -> bool { + self.or_constraint.is_false() + } + + pub fn new_ambig(span: S) -> Self { + Self { + and_constraint: And::new([LeafRegionConstraint::Ambiguity(span)]), + 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 + } + } + + pub fn without_spans(self) -> CanonicalFormRegionConstraint { + CanonicalFormRegionConstraint { + and_constraint: self.and_constraint.without_spans(), + or_constraint: self.or_constraint.without_spans(), + } + } +} + +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. @@ -209,9 +417,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 +428,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 +518,22 @@ 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 { +pub fn evaluate_solver_constraint< + I: Interner, + S: Clone + std::fmt::Debug + Eq + std::hash::Hash, +>( + constraint: CanonicalFormRegionConstraint, +) -> CanonicalFormRegionConstraint { todo!("overhauled in future commit") } @@ -346,10 +563,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 +576,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; } }; @@ -403,60 +621,81 @@ 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, - S: Clone + std::fmt::Debug, + S: Clone + std::fmt::Debug + Eq + std::hash::Hash, >( 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, span) => destructured_constraints.push(Or( - regions_outlived_by_placeholder(ty, assumptions, infcx.cx()) - .map(move |assumption_r| RegionOutlives(assumption_r, r, span.clone())) - .collect::>() - .into_boxed_slice(), + Ambiguity(_) | RegionOutlives(..) => { + destructured_constraints.push(Or::new([And::new([c.clone()])])) + } + PlaceholderTyOutlives(ty, r, span) => destructured_constraints.push(Or::new( + regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()).map( + move |assumption_r| { + And::new([RegionOutlives(assumption_r, *r, span.clone())]) + }, + ), )), - AliasTyOutlivesViaEnv(bound_outlives, span) => destructured_constraints.push( - alias_outlives_candidates_from_assumptions(infcx, bound_outlives, assumptions) - .with_span(span), - ), - And(_) | Or(_) => unreachable!(), + AliasTyOutlivesViaEnv(bound_outlives, span) => { + destructured_constraints.push( + alias_outlives_candidates_from_assumptions( + infcx, + *bound_outlives, + assumptions, + ) + .with_spans(span.clone()), + ); + } } } 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 @@ -475,10 +714,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 {:?}", @@ -486,38 +727,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< @@ -529,19 +764,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![]; @@ -564,7 +799,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< @@ -575,8 +810,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(); @@ -613,22 +848,22 @@ 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, ()); + let candidate = Or::new([And::new([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([Ambiguity(())])])); } } 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)); } }; @@ -662,33 +897,36 @@ 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| { + let candidate = + AliasTyOutlivesViaEnv(bound_alias.map_bound(|alias| (alias, r2)), ()); + if max_universe(infcx, candidate.clone()) < u { + And::new([candidate]) + } else { + And::new([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::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 @@ -780,7 +1018,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(); @@ -792,7 +1030,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. @@ -801,28 +1039,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 @@ -863,8 +1102,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 ec3887720575b2437be13bd9338cb878e118531b Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 12:28:31 +0100 Subject: [PATCH 09/24] add `new_leaf` functions --- compiler/rustc_hir_analysis/src/collect.rs | 12 +++--- .../eval_ctxt/solver_region_constraints.rs | 14 +++---- .../rustc_next_trait_solver/src/solve/mod.rs | 6 +-- .../rustc_type_ir/src/region_constraint.rs | 39 ++++++++++++------- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 269628f577e4f..aaa056de19673 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -45,7 +45,7 @@ use rustc_trait_selection::traits::{ FulfillmentError, ObligationCtxt, hir_ty_lowering_dyn_compatibility_violations, }; use tracing::{debug, instrument}; -use ty::region_constraint::{And, LeafRegionConstraint, Or}; +use ty::region_constraint::LeafRegionConstraint; use crate::check::wfcheck::{TestBinderBody, TestBinderExists, TestBinderForall}; use crate::diagnostics::{self, ElidedLifetimesAreNotAllowedInDelegations}; @@ -447,8 +447,9 @@ impl<'tcx> ItemCtxt<'tcx> { let span = lhs.ident.span.to(rhs.ident.span); let lhs = self.lowerer().lower_lifetime(lhs, RegionInferReason::RegionPredicate); let rhs = self.lowerer().lower_lifetime(rhs, RegionInferReason::RegionPredicate); - let leaf = LeafRegionConstraint::RegionOutlives(lhs, rhs, span); - SolverRegionConstraint::new_from_or(Or::new([And::new([leaf])])) + SolverRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives( + lhs, rhs, span, + )) } hir::TestBinderConstraint::Type { lhs, rhs } => { let span = lhs.span.to(rhs.ident.span); @@ -457,8 +458,9 @@ impl<'tcx> ItemCtxt<'tcx> { // note that we cannot check that lhs is a placeholder at this moment, as at this // point it is a bound variable that is not yet instantiated with a placeholder. // instead, we check it when we emit the region constraint. - let leaf = LeafRegionConstraint::PlaceholderTyOutlives(lhs, rhs, span); - SolverRegionConstraint::new_from_or(Or::new([And::new([leaf])])) + SolverRegionConstraint::new_leaf(LeafRegionConstraint::PlaceholderTyOutlives( + lhs, rhs, span, + )) } } } 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 e1d4555717c87..1f6141b4acc6b 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,14 +171,12 @@ where use Component::*; use LeafRegionConstraint::*; match c { - Region(c_r) => Or::new([And::new([RegionOutlives(*c_r, r, ())])]), - Placeholder(p) => Or::new([And::new([PlaceholderTyOutlives( - Ty::new_placeholder(self.cx(), *p), - r, - (), - )])]), + Region(c_r) => Or::new_leaf(RegionOutlives(*c_r, r, ())), + Placeholder(p) => { + 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), } @@ -201,7 +199,7 @@ where let item_bound_outlives = Or::new(item_bounds); let where_clause_outlives = - Or::new([And::new([AliasTyOutlivesViaEnv(Binder::dummy((alias, r)), ())])]); + 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 feafdbbe2adf5..36d45b50d91eb 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -124,12 +124,12 @@ 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([ + 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 d6dd740cb71ee..06e4ceb0f3293 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -209,6 +209,14 @@ impl Or { Self(new_ands.into_boxed_slice()) } + pub fn new_ambig(s: S) -> Self { + Or::new_leaf(LeafRegionConstraint::Ambiguity(s)) + } + + 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(); @@ -387,6 +395,13 @@ impl or_constraint: self.or_constraint.without_spans(), } } + + pub fn new_leaf(l: LeafRegionConstraint) -> Self { + CanonicalFormRegionConstraint { + and_constraint: And(Box::new([l])), + or_constraint: Or::new_true(), + } + } } impl Default for CanonicalFormRegionConstraint { @@ -584,21 +599,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; } }; @@ -660,7 +675,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, span) => destructured_constraints.push(Or::new( regions_outlived_by_placeholder(*ty, assumptions, infcx.cx()).map( @@ -731,9 +746,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)); } @@ -771,12 +784,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![]; @@ -848,21 +861,21 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl escaping_outlives, I::BoundVarKinds::from_vars(infcx.cx(), bound_vars), ); - let candidate = Or::new([And::new([AliasTyOutlivesViaEnv(bound_outlives, ())])]); + let candidate = Or::new_leaf(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(Or::new([And::new([Ambiguity(())])])); + candidates.push(Or::new_ambig(())); } } 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)); } }; @@ -916,7 +929,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 cb42b8dc4a226d7859e26012b14127b2c4c56eae Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 21 Aug 2026 17:10:40 +0100 Subject: [PATCH 10/24] 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 | 49 ++++++++++++++++--- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 14de716de7b5c..135841c8660e7 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -272,7 +272,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 b06c82f6d9d8b..f45c99f48d305 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 @@ -1663,7 +1663,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 1f6141b4acc6b..86cd608455d28 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(), self.origin_span); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 06e4ceb0f3293..09b393bbc3d8f 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -457,8 +457,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< - I: Interner, - S: Clone + std::fmt::Debug + Eq + std::hash::Hash, ->( +pub fn propagate_ambiguity( constraint: CanonicalFormRegionConstraint, ) -> CanonicalFormRegionConstraint { - todo!("overhauled in future commit") + if let Some(ambig) = constraint.and_constraint.0.iter().find(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_leaf(ambig.clone()); + } + + 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 let Some(ambig) = and.0.iter().find(|c| c.is_ambig()) { + return CanonicalFormRegionConstraint::new_leaf(ambig.clone()); + } + } + + constraint } /// Handles converting region outlives constraints involving placeholders from `u` into OR constraints From 9897fe90445299797956945ea27b7523b4a8871e Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 27 Aug 2026 20:24:50 +0100 Subject: [PATCH 11/24] unncessarily typo --- compiler/rustc_type_ir/src/region_constraint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 09b393bbc3d8f..ac0e7b69cab93 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -569,7 +569,7 @@ pub fn propagate_ambiguity Date: Thu, 27 Aug 2026 20:28:09 +0100 Subject: [PATCH 12/24] `Or::new_and` nit --- compiler/rustc_type_ir/src/region_constraint.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index ac0e7b69cab93..08c1cbad65800 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -221,11 +221,9 @@ impl Or { // 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()))), - ); + for a_and in a.0.clone().into_iter() { + ands.push(And::new(a_and.0.into_iter().chain(b_and.0.clone()))) + } } Or::new(ands) From 47be0ac1f9a7263a47343e44f93cd93fc27d4143 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 27 Aug 2026 20:29:15 +0100 Subject: [PATCH 13/24] `And::new` nit --- compiler/rustc_type_ir/src/region_constraint.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 08c1cbad65800..d64c8763f10c8 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -256,8 +256,7 @@ impl And { i.into_iter() .collect::>() .into_iter() - .collect::>() - .into_boxed_slice(), + .collect() ) } From 7748294d1117970193a296cdc064d4281fdcb9bf Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Thu, 27 Aug 2026 20:31:37 +0100 Subject: [PATCH 14/24] s/CanonicalFormRegionConstraint/RegionConstraint --- .../rustc_hir_analysis/src/check/wfcheck.rs | 2 +- compiler/rustc_infer/src/infer/context.rs | 6 +- .../src/infer/outlives/obligations.rs | 5 +- .../src/infer/solver_region_constraints.rs | 2 +- .../src/solve/eval_ctxt/mod.rs | 6 +- .../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 | 84 ++++++++----------- compiler/rustc_type_ir/src/solve/mod.rs | 6 +- 9 files changed, 54 insertions(+), 78 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 99c6f0ff2724c..5e1d311de13b2 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2336,7 +2336,7 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { pub(super) fn check_test_binder_body(&self, body: TestBinderBody<'tcx>) { let constraints = match validate(self.tcx(), &body.constraints) { Ok(()) => body.constraints, - Err(_guar) => ty::region_constraint::CanonicalFormRegionConstraint::new_true(), + Err(_guar) => ty::region_constraint::RegionConstraint::new_true(), }; self.infcx.register_solver_region_constraint(constraints); diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 31dfa2964b1cc..f9b08efad88cf 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::CanonicalFormRegionConstraint> { + ) -> rustc_type_ir::region_constraint::RegionConstraint> { self.get_solver_region_constraint().without_spans() } fn overwrite_solver_region_constraint( &self, - constraint: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, + constraint: rustc_type_ir::region_constraint::RegionConstraint>, span: Span, ) { self.overwrite_solver_region_constraint(constraint.with_spans(span)); @@ -325,7 +325,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn register_solver_region_constraint( &self, - c: rustc_type_ir::region_constraint::CanonicalFormRegionConstraint>, + c: rustc_type_ir::region_constraint::RegionConstraint>, span: Span, ) { self.register_solver_region_constraint(c.with_spans(span)); diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 135841c8660e7..8770cb9864195 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -147,10 +147,7 @@ impl<'tcx> InferCtxt<'tcx> { 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(), - ); + rustc_type_ir::region_constraint::RegionConstraint::new_and(c, old_constraint.clone()); use crate::infer::UndoLog; inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints.rs b/compiler/rustc_infer/src/infer/solver_region_constraints.rs index 6e15c1b56f8e9..09baae2ca8a42 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints.rs @@ -3,7 +3,7 @@ use rustc_span::Span; use tracing::instrument; pub type SolverRegionConstraint<'tcx> = - rustc_type_ir::region_constraint::CanonicalFormRegionConstraint, Span>; + rustc_type_ir::region_constraint::RegionConstraint, Span>; #[derive(Clone, Debug)] pub(crate) struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>); 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 f45c99f48d305..2462bd8d93759 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::{self, CanonicalFormRegionConstraint}; +use rustc_type_ir::region_constraint::{self, RegionConstraint}; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{ @@ -1325,7 +1325,7 @@ where args } - pub(super) fn register_solver_region_constraint(&self, c: CanonicalFormRegionConstraint) { + pub(super) fn register_solver_region_constraint(&self, c: RegionConstraint) { self.delegate.register_solver_region_constraint(c, self.origin_span); } @@ -1667,7 +1667,7 @@ where ); constraint } else { - CanonicalFormRegionConstraint::new_true() + RegionConstraint::new_true() }) } else { ExternalRegionConstraints::Old(if let Certainty::Yes = certainty { diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 36d45b50d91eb..8d20bcf4c7a6d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -93,12 +93,10 @@ 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; + use rustc_type_ir::region_constraint::RegionConstraint; let constraint = self.destructure_type_outlives(ty, lt); - self.register_solver_region_constraint(CanonicalFormRegionConstraint::new_from_or( - constraint, - )); + self.register_solver_region_constraint(RegionConstraint::new_from_or(constraint)); } else { self.register_ty_outlives(ty, lt); } @@ -123,13 +121,10 @@ where let ty::OutlivesClause(a, b) = goal.predicate; if self.cx().assumptions_on_binders() { - use rustc_type_ir::region_constraint::{ - CanonicalFormRegionConstraint, LeafRegionConstraint, - }; + use rustc_type_ir::region_constraint::{LeafRegionConstraint, RegionConstraint}; - let constraint = CanonicalFormRegionConstraint::new_leaf( - LeafRegionConstraint::RegionOutlives(a, b, ()), - ); + let constraint = + RegionConstraint::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/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 6705d30f62bd7..7a6a7d7380bdd 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -411,10 +411,10 @@ pub trait InferCtxtLike: Sized { ) -> Option>; fn get_solver_region_constraint( &self, - ) -> crate::region_constraint::CanonicalFormRegionConstraint; + ) -> crate::region_constraint::RegionConstraint; fn overwrite_solver_region_constraint( &self, - constraint: crate::region_constraint::CanonicalFormRegionConstraint, + constraint: crate::region_constraint::RegionConstraint, span: ::Span, ); @@ -536,7 +536,7 @@ pub trait InferCtxtLike: Sized { fn register_solver_region_constraint( &self, - c: crate::region_constraint::CanonicalFormRegionConstraint, + c: crate::region_constraint::RegionConstraint, span: ::Span, ); diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index d64c8763f10c8..ace238f79d668 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -252,12 +252,7 @@ impl And { } impl And { pub fn new(i: impl IntoIterator>) -> Self { - Self( - i.into_iter() - .collect::>() - .into_iter() - .collect() - ) + Self(i.into_iter().collect::>().into_iter().collect()) } fn is_and_equivalent_to(&self, other: &And) -> bool { @@ -277,37 +272,35 @@ impl And { #[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, S)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] -/// CanonicalFormRegionConstraints always have constraints shared between every OR element moved +/// RegionConstraints 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`. +/// this is handled when constructing And/Ors rather than when constructing `RegionConstraint`. /// /// 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 struct RegionConstraint { pub and_constraint: And, pub or_constraint: Or, } -impl CanonicalFormRegionConstraint { +impl RegionConstraint { pub fn with_spans( self, span: S, - ) -> CanonicalFormRegionConstraint { - CanonicalFormRegionConstraint { + ) -> RegionConstraint { + RegionConstraint { and_constraint: self.and_constraint.with_spans(span.clone()), or_constraint: self.or_constraint.with_spans(span.clone()), } } } -impl - CanonicalFormRegionConstraint -{ +impl RegionConstraint { pub fn new_from_or(or: Or) -> Self { let Some(fst) = or.0.get(0).clone() else { - return CanonicalFormRegionConstraint::new_false(); + return RegionConstraint::new_false(); }; let mut and_constraint = fst.0.to_vec(); @@ -332,10 +325,7 @@ impl })) } - pub fn new_and( - a: CanonicalFormRegionConstraint, - b: CanonicalFormRegionConstraint, - ) -> Self { + pub fn new_and(a: RegionConstraint, b: RegionConstraint) -> 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); @@ -345,10 +335,7 @@ impl } } - pub fn new_or( - a: CanonicalFormRegionConstraint, - b: CanonicalFormRegionConstraint, - ) -> Self { + pub fn new_or(a: RegionConstraint, b: RegionConstraint) -> Self { Self::new_from_or(Or::new_or(a.splatted_and_constraints(), b.splatted_and_constraints())) } @@ -386,22 +373,19 @@ impl } } - pub fn without_spans(self) -> CanonicalFormRegionConstraint { - CanonicalFormRegionConstraint { + pub fn without_spans(self) -> RegionConstraint { + RegionConstraint { and_constraint: self.and_constraint.without_spans(), or_constraint: self.or_constraint.without_spans(), } } pub fn new_leaf(l: LeafRegionConstraint) -> Self { - CanonicalFormRegionConstraint { - and_constraint: And(Box::new([l])), - or_constraint: Or::new_true(), - } + RegionConstraint { and_constraint: And(Box::new([l])), or_constraint: Or::new_true() } } } -impl Default for CanonicalFormRegionConstraint { +impl Default for RegionConstraint { fn default() -> Self { Self::new_true() } @@ -429,9 +413,9 @@ impl LeafRegionConstraint { #[instrument(level = "debug", skip(infcx), ret)] pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraint: CanonicalFormRegionConstraint, + constraint: RegionConstraint, u: UniverseIndex, -) -> CanonicalFormRegionConstraint { +) -> RegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); // 1. rewrite type outlives constraints involving things from `u` into either region constraints @@ -469,9 +453,9 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( infcx: &Infcx, - constraint: CanonicalFormRegionConstraint, + constraint: RegionConstraint, u: UniverseIndex, -) -> CanonicalFormRegionConstraint { +) -> RegionConstraint { use LeafRegionConstraint::*; let extend_from_and = |builder: &mut TransitiveRelationBuilder<_>, @@ -533,7 +517,7 @@ fn compute_new_region_constraints, I: Interne new_ands.push(Or::new([And::new(constraints)])) } - CanonicalFormRegionConstraint::new_from_or( + RegionConstraint::new_from_or( new_ands.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)), ) } @@ -557,10 +541,10 @@ fn compute_new_region_constraints, I: Interne /// matters at trait solver query boundaries. We currently call it in more than just that location #[instrument(level = "debug", ret)] pub fn propagate_ambiguity( - constraint: CanonicalFormRegionConstraint, -) -> CanonicalFormRegionConstraint { + constraint: RegionConstraint, +) -> RegionConstraint { if let Some(ambig) = constraint.and_constraint.0.iter().find(|c| c.is_ambig()) { - return CanonicalFormRegionConstraint::new_leaf(ambig.clone()); + return RegionConstraint::new_leaf(ambig.clone()); } for and in constraint.or_constraint.0.iter() { @@ -575,7 +559,7 @@ pub fn propagate_ambiguity( infcx: &Infcx, - constraint: CanonicalFormRegionConstraint, + constraint: RegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> CanonicalFormRegionConstraint { +) -> RegionConstraint { assert!(max_universe(infcx, constraint.clone()) <= u); // FIXME(-Zassumptions-on-binders): we don't lower universes of region variables when exiting `u` @@ -680,7 +664,7 @@ fn pull_region_outlives_constraints_out_of_universe< .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)) + RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of @@ -694,9 +678,9 @@ pub fn destructure_type_outlives_constraints_in_root< S: Clone + std::fmt::Debug + Eq + std::hash::Hash, >( infcx: &Infcx, - constraint: CanonicalFormRegionConstraint, + constraint: RegionConstraint, assumptions: &Assumptions, -) -> CanonicalFormRegionConstraint { +) -> RegionConstraint { use LeafRegionConstraint::*; let destructure_and = |and: &And| { @@ -740,7 +724,7 @@ pub fn destructure_type_outlives_constraints_in_root< .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)) + RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into either region outlives constraints, or type outlives @@ -759,10 +743,10 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< I: Interner, >( infcx: &Infcx, - constraint: CanonicalFormRegionConstraint, + constraint: RegionConstraint, u: UniverseIndex, assumptions: &Option>, -) -> CanonicalFormRegionConstraint { +) -> RegionConstraint { use LeafRegionConstraint::*; assert!( @@ -795,7 +779,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< .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)) + RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) } fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< @@ -1087,7 +1071,7 @@ fn alias_outlives_candidates_from_assumptions } }); - let constraint = CanonicalFormRegionConstraint::new_from_or(Or::new(candidates)); + let constraint = RegionConstraint::new_from_or(Or::new(candidates)); let largest_universe = infcx.universe(); debug!(?prev_universe, ?largest_universe); diff --git a/compiler/rustc_type_ir/src/solve/mod.rs b/compiler/rustc_type_ir/src/solve/mod.rs index 9c5ffa7971b13..6de031ed1bd51 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::CanonicalFormRegionConstraint; +use crate::region_constraint::RegionConstraint; 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(CanonicalFormRegionConstraint), + NextGen(RegionConstraint), } 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(CanonicalFormRegionConstraint::new_true()), + true => ExternalRegionConstraints::NextGen(RegionConstraint::new_true()), false => ExternalRegionConstraints::Old(vec![]), }; From 518ebc917a474fe379024bcbdfcb346696b7652c Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:10:19 +0100 Subject: [PATCH 15/24] import whoopsies --- compiler/rustc_infer/src/infer/outlives/obligations.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 8770cb9864195..0d8565ec74295 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -143,13 +143,11 @@ impl<'tcx> InferCtxt<'tcx> { pub fn register_solver_region_constraint(&self, c: SolverRegionConstraint<'tcx>) { 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::RegionConstraint::new_and(c, old_constraint.clone()); - use crate::infer::UndoLog; inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); inner.solver_region_constraint_storage.overwrite(new_constraint); } From 666fbdb78b371e74164a2d5f274185edf229ce82 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:30:22 +0100 Subject: [PATCH 16/24] use reduce --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 5e1d311de13b2..441c1a572a63d 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2446,15 +2446,11 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { } let span_of_and = |c: &And<_, _>| { - let mut spans = c.0.iter().map(|leaf| leaf.span()); - let fst = spans.next()?; - Some(spans.fold(fst, |snd, acc: rustc_span::Span| acc.to(snd))) + c.0.iter().map(|leaf| leaf.span()).reduce(|span: Span, acc| acc.to(span)) }; let span_of_or = |c: &Or<_, _>| { - let mut spans = c.0.iter().flat_map(|and| span_of_and(and)); - let fst = spans.next()?; - Some(spans.fold(fst, |snd, acc| acc.to(snd))) + c.0.iter().flat_map(|and| span_of_and(and)).reduce(|span, acc| acc.to(span)) }; let check_leaf_constraint = From c523918be32d11d7e8372626eba78ea11bffe9a3 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:32:33 +0100 Subject: [PATCH 17/24] empty or is falsey :3 --- compiler/rustc_hir_analysis/src/collect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index aaa056de19673..64dc54a9c30ac 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -442,7 +442,7 @@ impl<'tcx> ItemCtxt<'tcx> { .into_iter() .map(|item| self.lower_test_binder_constraint(item)) .reduce(SolverRegionConstraint::new_or) - .unwrap_or(SolverRegionConstraint::new_true()), + .unwrap_or(SolverRegionConstraint::new_false()), hir::TestBinderConstraint::Lifetime { lhs, rhs } => { let span = lhs.ident.span.to(rhs.ident.span); let lhs = self.lowerer().lower_lifetime(lhs, RegionInferReason::RegionPredicate); From 1482fead952fcda609ac3f442dfd4418bcbe26bb Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:34:23 +0100 Subject: [PATCH 18/24] add perf fixme for region constraint undolog --- compiler/rustc_infer/src/infer/outlives/obligations.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index 0d8565ec74295..cdbb78b0373d7 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -148,6 +148,9 @@ impl<'tcx> InferCtxt<'tcx> { let new_constraint = rustc_type_ir::region_constraint::RegionConstraint::new_and(c, old_constraint.clone()); + // FIXME(-Zassumptions-on-binders): This is pretty bad for perf, we don't make incremental + // changes to the region constraints, instead we just rewrite the entire thing every time + // and store the old version. inner.undo_log.push(UndoLog::OverwriteSolverRegionConstraint { old_constraint }); inner.solver_region_constraint_storage.overwrite(new_constraint); } From 1058ab7cfa1b3378aff7f8d97f835856e32d9bdf Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:36:18 +0100 Subject: [PATCH 19/24] do type annotation better :3 --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 441c1a572a63d..fd8bba12584c5 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2354,17 +2354,13 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { ) -> Result<(), ErrorGuaranteed> { let mut r = Ok(()); - let mut validate_and = |and: &And<_, _>| { + let mut validate_and = |and: &And, _>| { for c in and.0.iter() { match c { LeafRegionConstraint::Ambiguity(_) | LeafRegionConstraint::RegionOutlives(..) | LeafRegionConstraint::AliasTyOutlivesViaEnv(..) => (), // OK LeafRegionConstraint::PlaceholderTyOutlives(ty, _, span) => { - // I couldn't tell you why without this line `ty.kind()` is an error - // because of not being able to infer the type of `ty`... Lmao - // - BoxyUwU - let ty: Ty<'_> = *ty; // we can't check this during lowering, because the ty is a ty::Bound that gets // instantiated with a placeholder when entering the containing forall. if let ty::Placeholder(_) | ty::Param(_) = ty.kind() { From 6eebdecba102883a76274c576c44a78314e48c83 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 13:46:06 +0100 Subject: [PATCH 20/24] dont clone for no reason --- compiler/rustc_hir_analysis/src/check/wfcheck.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index fd8bba12584c5..fe95c03251171 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2407,12 +2407,8 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { u, ) .with_spans(forall.span); - if let Some(assert_on_exit) = forall.assert_on_exit { - self.check_test_binder_region_constraints( - forall.span, - &assert_on_exit.clone(), - &constraint.clone(), - ); + if let Some(assert_on_exit) = &forall.assert_on_exit { + self.check_test_binder_region_constraints(forall.span, assert_on_exit, &constraint); } self.infcx.overwrite_solver_region_constraint(constraint); }); From 8794caeb55b724cac02466d7e9ac5dced793a290 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 14:08:50 +0100 Subject: [PATCH 21/24] better docs on region constraints --- .../rustc_type_ir/src/region_constraint.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index ace238f79d668..da7f04b6f37b6 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -162,6 +162,9 @@ impl LeafRegionC #[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +/// An OR of AND of LEAF constraints. Always in "canonical form" meaning: +/// - No two ANDs are equivalent +/// - All ANDs are in canonical form pub struct Or(pub Box<[And]>); impl Or { pub fn with_spans( @@ -241,6 +244,8 @@ impl Or { #[derive_where(Clone, Hash, PartialEq, Eq, Debug; I: Interner, S)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +/// An AND of leaf constraints. Always in "canonical form", meaning: +/// - No leaf constraints are present twice in this AND pub struct And(pub Box<[LeafRegionConstraint]>); impl And { pub fn with_spans( @@ -272,15 +277,15 @@ impl And { #[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, S)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] #[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] -/// RegionConstraints 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. +/// An `And` and an `Or` constraint both in canonical forms, with two additional constraints: +/// - If the `or_constraint` is false then the `and_constraint` is empty +/// - The `or_constraint` does not have any constraints present in all of its inner `And`s +/// - i.e. `OR ( AND ('a: 'b, 'b: 'c), AND ('a: 'b, 'b: 'd))` is not a thing +/// - The OR constraint is in canonical form +/// - The AND constraint is in canonical form /// -/// 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 `RegionConstraint`. -/// -/// 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. +/// This should be thought of as an AND consisting of a set of LEAF constraints as well +/// as a single OR constraint. pub struct RegionConstraint { pub and_constraint: And, pub or_constraint: Or, From ba7c1dd9ddf341868af859f6a186e29d8725d1bd Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 14:21:16 +0100 Subject: [PATCH 22/24] split first! --- compiler/rustc_type_ir/src/region_constraint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index da7f04b6f37b6..b25975771bfdf 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -309,7 +309,7 @@ impl RegionConst }; let mut and_constraint = fst.0.to_vec(); - for and in or.0.clone() { + for and in or.0.split_first().unwrap().1 { and_constraint.retain(|c| and.0.iter().any(|c2| c == c2)); } let and_constraint = And::new(and_constraint); From b93ea278db942d45dc25c72018de489c8b0ad728 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 14:25:17 +0100 Subject: [PATCH 23/24] rename `Or::new_and/or` and `RegionConstraint::new_and/or` --- compiler/rustc_hir_analysis/src/collect.rs | 4 +- .../src/infer/outlives/obligations.rs | 6 ++- .../eval_ctxt/solver_region_constraints.rs | 6 +-- .../rustc_type_ir/src/region_constraint.rs | 39 ++++++++++--------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 64dc54a9c30ac..248e7aa583a19 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -436,12 +436,12 @@ impl<'tcx> ItemCtxt<'tcx> { hir::TestBinderConstraint::And { items } => items .into_iter() .map(|item| self.lower_test_binder_constraint(item)) - .reduce(SolverRegionConstraint::new_and) + .reduce(SolverRegionConstraint::build_and) .unwrap_or(SolverRegionConstraint::new_true()), hir::TestBinderConstraint::Or { items } => items .into_iter() .map(|item| self.lower_test_binder_constraint(item)) - .reduce(SolverRegionConstraint::new_or) + .reduce(SolverRegionConstraint::build_or) .unwrap_or(SolverRegionConstraint::new_false()), hir::TestBinderConstraint::Lifetime { lhs, rhs } => { let span = lhs.ident.span.to(rhs.ident.span); diff --git a/compiler/rustc_infer/src/infer/outlives/obligations.rs b/compiler/rustc_infer/src/infer/outlives/obligations.rs index cdbb78b0373d7..42f686b39136b 100644 --- a/compiler/rustc_infer/src/infer/outlives/obligations.rs +++ b/compiler/rustc_infer/src/infer/outlives/obligations.rs @@ -145,8 +145,10 @@ impl<'tcx> InferCtxt<'tcx> { let mut inner = self.inner.borrow_mut(); let old_constraint = inner.solver_region_constraint_storage.get_constraint(); - let new_constraint = - rustc_type_ir::region_constraint::RegionConstraint::new_and(c, old_constraint.clone()); + let new_constraint = rustc_type_ir::region_constraint::RegionConstraint::build_and( + c, + old_constraint.clone(), + ); // FIXME(-Zassumptions-on-binders): This is pretty bad for perf, we don't make incremental // changes to the region constraints, instead we just rewrite the entire thing every time 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 86cd608455d28..88044b2a78b79 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 @@ -164,7 +164,7 @@ where 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))) + .fold(Or::new_true(), |acc, c| Or::build_and(acc, self.destructure_component(c, r))) } fn destructure_component(&mut self, c: &Component, r: Region) -> Or { @@ -209,7 +209,7 @@ where ); let components_outlives = self.destructure_components(&components, r); - let assumption_outlives = Or::new_or(item_bound_outlives, where_clause_outlives); - Or::new_or(assumption_outlives, components_outlives) + let assumption_outlives = Or::build_or(item_bound_outlives, where_clause_outlives); + Or::build_or(assumption_outlives, components_outlives) } } diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index b25975771bfdf..d3d026ca49403 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -220,7 +220,7 @@ impl Or { Or(Box::new([And(Box::new([l]))])) } - pub fn new_and(a: Or, b: Or) -> Self { + pub fn build_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 { @@ -232,7 +232,7 @@ impl Or { Or::new(ands) } - pub fn new_or(a: Or, b: Or) -> Self { + pub fn build_or(a: Or, b: Or) -> Self { Or::new(a.0.into_iter().chain(b.0)) } @@ -330,9 +330,9 @@ impl RegionConst })) } - pub fn new_and(a: RegionConstraint, b: RegionConstraint) -> Self { + pub fn build_and(a: RegionConstraint, b: RegionConstraint) -> 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); + let or_constraint = Or::build_and(a.or_constraint, b.or_constraint); Self { and_constraint: if or_constraint.is_false() { And::new([]) } else { and_constraint }, @@ -340,8 +340,8 @@ impl RegionConst } } - pub fn new_or(a: RegionConstraint, b: RegionConstraint) -> Self { - Self::new_from_or(Or::new_or(a.splatted_and_constraints(), b.splatted_and_constraints())) + pub fn build_or(a: RegionConstraint, b: RegionConstraint) -> Self { + Self::new_from_or(Or::build_or(a.splatted_and_constraints(), b.splatted_and_constraints())) } pub fn new_true() -> Self { @@ -523,7 +523,7 @@ fn compute_new_region_constraints, I: Interne } RegionConstraint::new_from_or( - new_ands.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)), + new_ands.into_iter().fold(Or::new_false(), |acc, c| Or::build_or(acc, c)), ) } @@ -660,7 +660,7 @@ fn pull_region_outlives_constraints_out_of_universe< }; } - pulled_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) + pulled_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::build_and(acc, c)) }; let and_constraint = pull_and(constraint.and_constraint); @@ -668,8 +668,8 @@ fn pull_region_outlives_constraints_out_of_universe< .or_constraint .0 .into_iter() - .fold(Or::new_false(), |acc, c| Or::new_or(acc, pull_and(c))); - RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) + .fold(Or::new_false(), |acc, c| Or::build_or(acc, pull_and(c))); + RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into region outlives constraints. This assumes the *complete* set of @@ -716,8 +716,9 @@ pub fn destructure_type_outlives_constraints_in_root< } } debug!(?destructured_constraints); - let merged_constraints = - destructured_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)); + let merged_constraints = destructured_constraints + .into_iter() + .fold(Or::new_true(), |acc, c| Or::build_and(acc, c)); debug!(?merged_constraints); merged_constraints }; @@ -727,9 +728,9 @@ pub fn destructure_type_outlives_constraints_in_root< .or_constraint .0 .into_iter() - .fold(Or::new_false(), |acc, c| Or::new_or(acc, destructure_and(&c))); + .fold(Or::new_false(), |acc, c| Or::build_or(acc, destructure_and(&c))); - RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) + RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } /// Converts type outlives constraints into either region outlives constraints, or type outlives @@ -774,7 +775,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< } } } - rewritten_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::new_and(acc, c)) + rewritten_constraints.into_iter().fold(Or::new_true(), |acc, c| Or::build_and(acc, c)) }; let and_constraint = rewrite_and(constraint.and_constraint); @@ -782,9 +783,9 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< .or_constraint .0 .into_iter() - .fold(Or::new_false(), |acc, c| Or::new_or(acc, rewrite_and(c))); + .fold(Or::new_false(), |acc, c| Or::build_or(acc, rewrite_and(c))); - RegionConstraint::new_from_or(Or::new_and(and_constraint, or_constraint)) + RegionConstraint::new_from_or(Or::build_and(and_constraint, or_constraint)) } fn rewrite_placeholder_ty_outlives_constraints_in_universe_for_eager_placeholder_handling< @@ -895,7 +896,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl Some(assumptions) => assumptions, None => { candidates.push(Or::new_ambig(())); - return candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)); + return candidates.into_iter().fold(Or::new_false(), |acc, c| Or::build_or(acc, c)); } }; @@ -958,7 +959,7 @@ fn rewrite_alias_ty_outlives_constraints_in_universe_for_eager_placeholder_handl | TypingMode::Codegen => (), }; - candidates.into_iter().fold(Or::new_false(), |acc, c| Or::new_or(acc, c)) + candidates.into_iter().fold(Or::new_false(), |acc, c| Or::build_or(acc, c)) } /// Returns all regions `r2` for which `r: r2` is known to hold in From be614567c1b89e7b8df23bad916b5d4d811bd991 Mon Sep 17 00:00:00 2001 From: Boxy Uwu Date: Fri, 28 Aug 2026 14:49:20 +0100 Subject: [PATCH 24/24] whoops everything is span sensitive now --- .../src/infer/solver_region_constraints.rs | 3 - .../infer/solver_region_constraints/tests.rs | 58 ------------------- 2 files changed, 61 deletions(-) delete mode 100644 compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints.rs b/compiler/rustc_infer/src/infer/solver_region_constraints.rs index 09baae2ca8a42..1e6eced91752d 100644 --- a/compiler/rustc_infer/src/infer/solver_region_constraints.rs +++ b/compiler/rustc_infer/src/infer/solver_region_constraints.rs @@ -22,6 +22,3 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> { self.0 = constraint; } } - -#[cfg(test)] -mod tests; diff --git a/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs b/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs deleted file mode 100644 index 339a454753f0b..0000000000000 --- a/compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs +++ /dev/null @@ -1,58 +0,0 @@ -use rustc_span::{BytePos, DUMMY_SP, Span}; -use rustc_type_ir::region_constraint::evaluate_solver_constraint; - -use super::SolverRegionConstraint; - -fn and(constraints: Vec>) -> SolverRegionConstraint<'static> { - SolverRegionConstraint::And(constraints.into_boxed_slice()) -} - -fn or(constraints: Vec>) -> SolverRegionConstraint<'static> { - SolverRegionConstraint::Or(constraints.into_boxed_slice()) -} - -fn ambiguity() -> SolverRegionConstraint<'static> { - SolverRegionConstraint::Ambiguity(DUMMY_SP) -} - -#[test] -fn evaluation_is_span_agnostic() { - let constraints = [ - ambiguity(), - and(vec![]), - or(vec![]), - and(vec![and(vec![]), ambiguity()]), - and(vec![ambiguity(), or(vec![])]), - or(vec![or(vec![]), ambiguity()]), - or(vec![ambiguity(), and(vec![])]), - and(vec![or(vec![or(vec![]), ambiguity()]), or(vec![ambiguity(), and(vec![])])]), - ]; - - for constraint in constraints { - let expected = evaluate_solver_constraint(&constraint.clone().without_spans()); - let actual = evaluate_solver_constraint(&constraint).without_spans(); - assert_eq!(actual, expected); - } -} - -#[test] -fn evaluation_preserves_first_ambiguity_span() { - let first = Span::with_root_ctxt(BytePos(1), BytePos(2)); - let second = Span::with_root_ctxt(BytePos(3), BytePos(4)); - - for constraint in [ - and(vec![ - SolverRegionConstraint::Ambiguity(first), - SolverRegionConstraint::Ambiguity(second), - ]), - or(vec![ - SolverRegionConstraint::Ambiguity(first), - SolverRegionConstraint::Ambiguity(second), - ]), - ] { - assert!(matches!( - evaluate_solver_constraint(&constraint), - SolverRegionConstraint::Ambiguity(span) if span == first - )); - } -}