diff --git a/compiler/rustc_next_trait_solver/src/canonical/mod.rs b/compiler/rustc_next_trait_solver/src/canonical/mod.rs index 0d8620c3614a2..986bdaa7ddbd9 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/mod.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/mod.rs @@ -14,6 +14,9 @@ use std::iter; use canonicalizer::Canonicalizer; use rustc_index::IndexVec; use rustc_type_ir::inherent::*; +use rustc_type_ir::region_constraint::{ + LeafRegionConstraint, RegionConstraint as SolverRegionConstraint, +}; use rustc_type_ir::relate::{ self, Relate, RelateResult, TypeRelation, VarianceDiagInfo, relate_args_invariantly, }; @@ -416,7 +419,17 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { - self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); + if self.cx().assumptions_on_binders() { + if a != b { + let region_outlives = |a, b| { + SolverRegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b, ())) + }; + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span); + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span); + } + } else { + self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span); + } Ok(a) } diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index 9a643b538d93f..0ece6ceacff67 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -46,7 +46,7 @@ impl Default for TransitiveRelationBuilder { } } -use crate::data_structures::IndexMap; +use crate::data_structures::{HashMap, HashSet, IndexMap}; use crate::fold::TypeSuperFoldable; use crate::inherent::*; use crate::relate::{Relate, RelateResult, TypeRelation, VarianceDiagInfo}; @@ -446,6 +446,12 @@ pub fn eagerly_handle_placeholders_in_universe RegionConstraint { let assumptions = infcx.get_placeholder_assumptions(u); + // Replace current-universe `'?x` with a non-var it's equated with in this `And` + // (`'?x: '!a` and `'!a: '?x` → `'!a`). Alias/env matching has to see that shape + // or `alias_outlives.rs` / `implied_higher_ranked_alias_outlives_assumption.rs` + // go ambiguous. + let constraint = normalize_equated_region_vars(infcx, constraint, u); + // 1. rewrite type outlives constraints involving things from `u` into either region constraints // involving things from `u` or type outlives constraints not involving things from `u` // @@ -470,6 +476,155 @@ pub fn eagerly_handle_placeholders_in_universe, I: Interner>( + infcx: &Infcx, + constraint: RegionConstraint, + u: UniverseIndex, +) -> RegionConstraint { + // Every `And` in the `Or` is a separate branch, so regions equated inside one of + // them may only be replaced within that branch. + let or_constraint = Or(constraint + .or_constraint + .0 + .into_iter() + .map(|and| normalize_equated_region_vars_in_and(infcx, and, u)) + .collect()); + + // The outer `And` is conjoined with every branch, so its replacements apply to the + // whole constraint. + let replacements = compute_equated_region_var_replacements( + infcx, + &conjunctive_region_outlives(&constraint.and_constraint), + u, + ); + let constraint = RegionConstraint { and_constraint: constraint.and_constraint, or_constraint }; + let constraint = if replacements.is_empty() { + constraint + } else { + constraint.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }) + }; + + // Replacing regions can make previously distinct leaves equal, so rebuild the + // canonical form instead of handing back a constraint with duplicates in it. + RegionConstraint::new_from_or(constraint.splatted_and_constraints()) +} + +fn normalize_equated_region_vars_in_and, I: Interner>( + infcx: &Infcx, + and: And, + u: UniverseIndex, +) -> And { + let replacements = + compute_equated_region_var_replacements(infcx, &conjunctive_region_outlives(&and), u); + + if replacements.is_empty() { + and + } else { + And::new(and.fold_with(&mut EquatedRegionVarReplacer { cx: infcx.cx(), replacements }).0) + } +} + +fn compute_equated_region_var_replacements, I: Interner>( + infcx: &Infcx, + region_outlives: &[(Region, Region)], + u: UniverseIndex, +) -> HashMap, Region> { + compute_equated_region_var_replacements_from( + region_outlives, + |r| is_current_universe_region_var(infcx, r, u), + is_region_var::, + ) +} + +fn compute_equated_region_var_replacements_from( + region_outlives: &[(R, R)], + mut is_current_universe_region_var: impl FnMut(R) -> bool, + mut is_region_var: impl FnMut(R) -> bool, +) -> HashMap +where + R: Copy + Eq + std::hash::Hash, +{ + let edges: HashSet<(R, R)> = region_outlives.iter().copied().collect(); + + let mut equated_regions_builder = TransitiveRelationBuilder::default(); + let mut has_equated_regions = false; + for (r1, r2) in region_outlives.iter().copied() { + // Paired outlives constraints represent region equality. Build a transitive relation so + // current-universe variables equated through other variables still find a non-var partner. + if edges.contains(&(r2, r1)) { + equated_regions_builder.add(r1, r2); + equated_regions_builder.add(r2, r1); + has_equated_regions = true; + } + } + + if !has_equated_regions { + return HashMap::default(); + } + + let equated_regions = equated_regions_builder.freeze(); + let mut seen = HashSet::default(); + let mut replacements = HashMap::default(); + for (r1, r2) in region_outlives.iter().copied() { + for candidate in [r1, r2] { + if !seen.insert(candidate) || !is_current_universe_region_var(candidate) { + continue; + } + + // `reachable_from` already includes `candidate` when both equality edges exist. + // Candidates are always revars, so the partner has to come from that closure. + // If a var has several non-var partners, `find` just picks one; the remaining + // folded constraints still relate those partners, so first-match only affects + // representation. + if let Some(partner) = + equated_regions.reachable_from(candidate).into_iter().find(|r| !is_region_var(*r)) + { + replacements.insert(candidate, partner); + } + } + } + replacements +} + +fn conjunctive_region_outlives(and: &And) -> Vec<(Region, Region)> { + use LeafRegionConstraint::*; + + and.0 + .iter() + .filter_map(|c| match c { + RegionOutlives(r1, r2, ()) => Some((*r1, *r2)), + Ambiguity(_) | PlaceholderTyOutlives(..) | AliasTyOutlivesViaEnv(..) => None, + }) + .collect() +} + +fn is_current_universe_region_var, I: Interner>( + infcx: &Infcx, + region: Region, + u: UniverseIndex, +) -> bool { + is_region_var::(region) && max_universe(infcx, region) == u +} + +fn is_region_var(region: Region) -> bool { + matches!(region.kind(), RegionKind::ReVar(_)) +} + +struct EquatedRegionVarReplacer { + cx: I, + replacements: HashMap, Region>, +} + +impl TypeFolder for EquatedRegionVarReplacer { + fn cx(&self) -> I { + self.cx + } + + fn fold_region(&mut self, r: Region) -> Region { + self.replacements.get(&r).copied().unwrap_or(r) + } +} + /// Filter our region constraints to not include constraints between region variables from `u` and /// other regions as those are always satisfied. This requires some care to handle correctly for example: /// `'!a_u1: '?x_u1: '!b_u1` should result in us requiring `'!a_u1: '!b_u1` rather than dropping the two @@ -645,6 +800,13 @@ fn pull_region_outlives_constraints_out_of_universe< pulled_constraints.push(Or::new_leaf(c.clone())); } RegionOutlives(region_1, region_2, ()) => { + if region_1 == region_2 { + // `'r: 'r` is always true, including for current-universe regions, so + // there's nothing left to pull out of the universe. Relating a region to + // itself, component destructure, and normalize rewriting `'?x: '!a` + + // `'!a: '?x` into `'!a: '!a` can all produce this. + continue; + } let region_1_u = max_universe(infcx, region_1); let region_2_u = max_universe(infcx, region_2); @@ -1190,3 +1352,6 @@ impl<'a, Infcx: InferCtxtLike, I: Interner> TypeRelation Ok(a) } } + +#[cfg(all(test, feature = "nightly"))] +mod tests; diff --git a/compiler/rustc_type_ir/src/region_constraint/tests.rs b/compiler/rustc_type_ir/src/region_constraint/tests.rs new file mode 100644 index 0000000000000..831d98f590162 --- /dev/null +++ b/compiler/rustc_type_ir/src/region_constraint/tests.rs @@ -0,0 +1,29 @@ +use super::compute_equated_region_var_replacements_from; + +#[test] +fn equated_region_var_replacements_follow_transitive_region_var_chains() { + const REVAR_1: u8 = 1; + const REVAR_2: u8 = 2; + const PLACEHOLDER: u8 = 3; + // Equated with REVAR_1, but not a current-universe candidate and not a valid partner. + const OTHER_REVAR: u8 = 4; + + let region_outlives = [ + (REVAR_1, REVAR_2), + (REVAR_2, REVAR_1), + (REVAR_2, PLACEHOLDER), + (PLACEHOLDER, REVAR_2), + (REVAR_1, OTHER_REVAR), + (OTHER_REVAR, REVAR_1), + ]; + + let replacements = compute_equated_region_var_replacements_from( + ®ion_outlives, + |r| matches!(r, REVAR_1 | REVAR_2), + |r| matches!(r, REVAR_1 | REVAR_2 | OTHER_REVAR), + ); + + assert_eq!(replacements.len(), 2); + assert_eq!(replacements.get(&REVAR_1), Some(&PLACEHOLDER)); + assert_eq!(replacements.get(&REVAR_2), Some(&PLACEHOLDER)); +} diff --git a/compiler/rustc_type_ir/src/relate/solver_relating.rs b/compiler/rustc_type_ir/src/relate/solver_relating.rs index 1e8ff77e4d395..ddb94d6483f31 100644 --- a/compiler/rustc_type_ir/src/relate/solver_relating.rs +++ b/compiler/rustc_type_ir/src/relate/solver_relating.rs @@ -2,6 +2,7 @@ use tracing::{debug, instrument}; use self::combine::{PredicateEmittingRelation, super_combine_consts, super_combine_tys}; use crate::data_structures::DelayedSet; +use crate::region_constraint::{LeafRegionConstraint, RegionConstraint}; use crate::relate::combine::combine_ty_args; pub use crate::relate::*; use crate::solve::{Goal, VisibleForLeakCheck}; @@ -238,14 +239,38 @@ where #[instrument(skip(self), level = "trace")] fn regions(&mut self, a: Region, b: Region) -> RelateResult> { - match self.ambient_variance { - // Subtype(&'a u8, &'b u8) => Outlives('a: 'b) => SubRegion('b, 'a) - ty::Covariant => self.infcx.sub_regions(b, a, VisibleForLeakCheck::Yes, self.span), - // Suptype(&'a u8, &'b u8) => Outlives('b: 'a) => SubRegion('a, 'b) - ty::Contravariant => self.infcx.sub_regions(a, b, VisibleForLeakCheck::Yes, self.span), - ty::Invariant => self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span), - ty::Bivariant => { - unreachable!("Expected bivariance to be handled in relate_with_variance") + if self.cx().assumptions_on_binders() { + let region_outlives = + |a, b| RegionConstraint::new_leaf(LeafRegionConstraint::RegionOutlives(a, b, ())); + + match self.ambient_variance { + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") + } + _ if a == b => return Ok(a), + ty::Covariant => { + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span) + } + ty::Contravariant => { + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span) + } + ty::Invariant => { + self.infcx.register_solver_region_constraint(region_outlives(a, b), self.span); + self.infcx.register_solver_region_constraint(region_outlives(b, a), self.span); + } + } + } else { + match self.ambient_variance { + ty::Covariant => self.infcx.sub_regions(b, a, VisibleForLeakCheck::Yes, self.span), + ty::Contravariant => { + self.infcx.sub_regions(a, b, VisibleForLeakCheck::Yes, self.span) + } + ty::Invariant => { + self.infcx.equate_regions(a, b, VisibleForLeakCheck::Yes, self.span) + } + ty::Bivariant => { + unreachable!("Expected bivariance to be handled in relate_with_variance") + } } } diff --git a/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr b/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr index 5e8e131addd28..3ebb61b92c5ad 100644 --- a/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr +++ b/tests/ui/assumptions_on_binders/placeholder-assumptions-issue-157840.stderr @@ -4,10 +4,11 @@ error[E0277]: the trait bound `(): Trait fn(>::Assoc))> LL | (): Trait<>::Assoc>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Trait fn(>::Assoc))>` is not implemented for `()` | -help: consider extending the `where` clause, but there might be an alternative better way to express this requirement +help: this trait has no implementations, consider adding one + --> $DIR/placeholder-assumptions-issue-157840.rs:3:1 | -LL | (): Trait<>::Assoc>, (): Trait fn(>::Assoc))> - | +++++++++++++++++++++++++++++++++++++++++++++++++ +LL | trait Trait {} + | ^^^^^^^^^^^^^^ error: aborting due to 1 previous error diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs new file mode 100644 index 0000000000000..f3050fe336eb1 --- /dev/null +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.rs @@ -0,0 +1,19 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +trait Super { + fn a(&self) { + let a: &dyn Sub = &(); + let b: &dyn Super fn(&'a ())> = a; + //~^ ERROR the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + } +} + +impl Super for () {} + +trait Sub: Super {} + +impl Sub for () {} + +fn main() { + let a: &dyn Sub = &(); +} diff --git a/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr new file mode 100644 index 0000000000000..afc1f56491503 --- /dev/null +++ b/tests/ui/assumptions_on_binders/principal-upcast-region-eq-issue-157859.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + --> $DIR/principal-upcast-region-eq-issue-157859.rs:6:49 + | +LL | let b: &dyn Super fn(&'a ())> = a; + | ^ the nightly-only, unstable trait `Unsize fn(&'a ())>>` is not implemented for `dyn Sub` + | + = note: all implementations of `Unsize` are provided automatically by the compiler, see for more information + = note: required for `&dyn Sub` to implement `CoerceUnsized<&dyn Super fn(&'a ())>>` + = note: required for the cast from `&dyn Sub` to `&dyn Super fn(&'a ())>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs new file mode 100644 index 0000000000000..d32cdd33e6e8f --- /dev/null +++ b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.rs @@ -0,0 +1,23 @@ +//@ compile-flags: -Zassumptions-on-binders -Znext-solver=globally + +trait Super { + type Assoc; + + fn a(&self) { + let a: &dyn Sub = &(); + let b: &dyn Super fn(&'a ())> = a; + //~^ ERROR the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + } +} + +impl Super for () { + type Assoc = fn(&'static ()); +} + +trait Sub: Super {} + +impl Sub for () {} + +fn main() { + let a: &dyn Sub = &(); +} diff --git a/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr new file mode 100644 index 0000000000000..fb55bab0ad20c --- /dev/null +++ b/tests/ui/assumptions_on_binders/trait-upcast-projection-region-eq.stderr @@ -0,0 +1,13 @@ +error[E0277]: the trait bound `&dyn Sub: CoerceUnsized<&dyn Super fn(&'a ())>>` is not satisfied + --> $DIR/trait-upcast-projection-region-eq.rs:8:57 + | +LL | let b: &dyn Super fn(&'a ())> = a; + | ^ the nightly-only, unstable trait `Unsize fn(&'a ())>>` is not implemented for `dyn Sub` + | + = note: all implementations of `Unsize` are provided automatically by the compiler, see for more information + = note: required for `&dyn Sub` to implement `CoerceUnsized<&dyn Super fn(&'a ())>>` + = note: required for the cast from `&dyn Sub` to `&dyn Super fn(&'a ())>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.