From 1b3da3e318d7837d62cb7f7e6ae486b0a4b0b537 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Tue, 10 Feb 2026 14:33:06 +0100 Subject: [PATCH 1/4] Remove the empty variant of `VerifyBound` This variant is not encountered in region inference at all. Where it is used, it is replaced with an empty any (or) bound, which is checked for as a special case with the equivalent logic where necessary. Such a bound will always fail in region inference, so this should not introduce any soundness issues. --- compiler/rustc_borrowck/src/region_infer/mod.rs | 5 ----- .../rustc_infer/src/infer/lexical_region_resolve/mod.rs | 3 ++- compiler/rustc_infer/src/infer/outlives/verify.rs | 9 +-------- compiler/rustc_infer/src/infer/region_constraints/mod.rs | 7 +------ 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 6ed70b39c5b7f..db1397b14486f 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -853,11 +853,6 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.eval_if_eq(infcx, generic_ty, lower_bound, *verify_if_eq_b) } - VerifyBound::IsEmpty => { - let lower_bound_scc = self.constraint_sccs.scc(lower_bound); - self.scc_values.elements_contained_in(lower_bound_scc).next().is_none() - } - VerifyBound::OutlivedBy(r) => { let r_vid = self.to_region_vid(*r); self.eval_outlives(r_vid, lower_bound) diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index 5134b7b7ca8f1..28fec5dd9d610 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -952,7 +952,8 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { self.sub_region_values(a, b) } - VerifyBound::IsEmpty => match min.kind() { + // An empty bound holds for an empty variable. + VerifyBound::AnyBound(bs) if bs.is_empty() => match min.kind() { ty::ReVar(rid) => match var_values.values[rid] { VarValue::ErrorValue => false, VarValue::Empty(_) => true, diff --git a/compiler/rustc_infer/src/infer/outlives/verify.rs b/compiler/rustc_infer/src/infer/outlives/verify.rs index affeb01e6d052..46fc712157736 100644 --- a/compiler/rustc_infer/src/infer/outlives/verify.rs +++ b/compiler/rustc_infer/src/infer/outlives/verify.rs @@ -63,17 +63,10 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> { param_bounds.push(VerifyBound::OutlivedBy(r)); } - if param_bounds.is_empty() { - // We know that all types `T` outlive `'empty`, so if we - // can find no other bound, then check that the region - // being tested is `'empty`. - VerifyBound::IsEmpty - } else if param_bounds.len() == 1 { + if param_bounds.len() == 1 { // Micro-opt: no need to store the vector if it's just len 1 param_bounds.pop().unwrap() } else { - // If we can find any other bound `R` such that `T: R`, then - // we don't need to check for `'empty`, because `R: 'empty`. VerifyBound::AnyBound(param_bounds) } } diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index ae7481b5d1e76..83c621bd81786 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -163,9 +163,6 @@ pub enum VerifyBound<'tcx> { /// if `R: min`, then by transitivity `G: min`. OutlivedBy(Region<'tcx>), - /// Given a region `R`, true if it is `'empty`. - IsEmpty, - /// Given a set of bounds `B`, expands to the function: /// /// ```ignore (pseudo-rust) @@ -690,7 +687,6 @@ impl<'tcx> VerifyBound<'tcx> { match self { VerifyBound::IfEq(..) => false, VerifyBound::OutlivedBy(re) => re.is_static(), - VerifyBound::IsEmpty => false, VerifyBound::AnyBound(bs) => bs.iter().any(|b| b.must_hold()), VerifyBound::AllBounds(bs) => bs.iter().all(|b| b.must_hold()), } @@ -699,9 +695,8 @@ impl<'tcx> VerifyBound<'tcx> { pub fn cannot_hold(&self) -> bool { match self { VerifyBound::IfEq(..) => false, - VerifyBound::IsEmpty => false, VerifyBound::OutlivedBy(_) => false, - VerifyBound::AnyBound(bs) => bs.iter().all(|b| b.cannot_hold()), + VerifyBound::AnyBound(bs) => !bs.is_empty() && bs.iter().all(|b| b.cannot_hold()), VerifyBound::AllBounds(bs) => bs.iter().any(|b| b.cannot_hold()), } } From e83f56117417a6759b0586ef37ce3884d9fcbd3b Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Tue, 10 Feb 2026 14:50:07 +0100 Subject: [PATCH 2/4] Catch a few extra case of empty any bounds, just to be safe --- compiler/rustc_borrowck/src/region_infer/mod.rs | 9 ++++++--- compiler/rustc_infer/src/infer/region_constraints/mod.rs | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index db1397b14486f..45a3a376a952a 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -858,9 +858,12 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.eval_outlives(r_vid, lower_bound) } - VerifyBound::AnyBound(verify_bounds) => verify_bounds.iter().any(|verify_bound| { - self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) - }), + VerifyBound::AnyBound(verify_bounds) => { + !verify_bounds.is_empty() + && verify_bounds.iter().any(|verify_bound| { + self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) + }) + } VerifyBound::AllBounds(verify_bounds) => verify_bounds.iter().all(|verify_bound| { self.eval_verify_bound(infcx, generic_ty, lower_bound, verify_bound) diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index 83c621bd81786..7b5a39febdbc7 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -687,7 +687,7 @@ impl<'tcx> VerifyBound<'tcx> { match self { VerifyBound::IfEq(..) => false, VerifyBound::OutlivedBy(re) => re.is_static(), - VerifyBound::AnyBound(bs) => bs.iter().any(|b| b.must_hold()), + VerifyBound::AnyBound(bs) => !bs.is_empty() && bs.iter().any(|b| b.must_hold()), VerifyBound::AllBounds(bs) => bs.iter().all(|b| b.must_hold()), } } From 2c4a74325221b5886a22068b9fbc6a8cd9741b3d Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Tue, 10 Feb 2026 15:07:10 +0100 Subject: [PATCH 3/4] Add an assertion just to be sure --- .../rustc_borrowck/src/type_check/constraint_conversion.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 703223e2e54a3..80488d140d674 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -6,10 +6,10 @@ use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_infer::infer::outlives::obligations::{TypeOutlives, TypeOutlivesDelegate}; use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound}; use rustc_infer::traits::query::type_op::DeeplyNormalize; -use rustc_middle::bug; use rustc_middle::ty::{ self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, elaborate, fold_regions, }; +use rustc_middle::{bug, span_bug}; use rustc_span::Span; use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; use tracing::{debug, instrument}; @@ -240,6 +240,11 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { verify_bound: VerifyBound<'tcx>, ) -> TypeTest<'tcx> { let lower_bound = self.to_region_vid(region); + if let VerifyBound::AnyBound(bs) = &verify_bound + && bs.is_empty() + { + span_bug!(self.span, "No empty any bound should make it to borrow check!"); + } TypeTest { generic_kind, lower_bound, span: self.span, verify_bound } } From 699edbb470196dd4ba95ddcb8215ae80af168ef0 Mon Sep 17 00:00:00 2001 From: Amanda Stjerna Date: Fri, 13 Feb 2026 15:54:05 +0100 Subject: [PATCH 4/4] Treat an empty any bound as a failure: this currently errors --- .../rustc_infer/src/infer/lexical_region_resolve/mod.rs | 9 ++++++++- compiler/rustc_infer/src/infer/region_constraints/mod.rs | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs index 28fec5dd9d610..73d3558e368af 100644 --- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs +++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs @@ -956,13 +956,20 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> { VerifyBound::AnyBound(bs) if bs.is_empty() => match min.kind() { ty::ReVar(rid) => match var_values.values[rid] { VarValue::ErrorValue => false, - VarValue::Empty(_) => true, + VarValue::Empty(_) => { + debug!( + "This bound was empty which should be true: {:?} {min:?}, {generic_ty:?}", + var_values.values + ); + false + } VarValue::Value(_) => false, }, _ => false, }, VerifyBound::AnyBound(bs) => { + // Edge case: an empty any bound does not hold. bs.iter().any(|b| self.bound_is_met(b, var_values, generic_ty, min)) } diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs index 7b5a39febdbc7..7bb18ee186959 100644 --- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs +++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs @@ -687,7 +687,7 @@ impl<'tcx> VerifyBound<'tcx> { match self { VerifyBound::IfEq(..) => false, VerifyBound::OutlivedBy(re) => re.is_static(), - VerifyBound::AnyBound(bs) => !bs.is_empty() && bs.iter().any(|b| b.must_hold()), + VerifyBound::AnyBound(bs) => bs.iter().any(|b| b.must_hold()), VerifyBound::AllBounds(bs) => bs.iter().all(|b| b.must_hold()), } } @@ -696,7 +696,8 @@ impl<'tcx> VerifyBound<'tcx> { match self { VerifyBound::IfEq(..) => false, VerifyBound::OutlivedBy(_) => false, - VerifyBound::AnyBound(bs) => !bs.is_empty() && bs.iter().all(|b| b.cannot_hold()), + // Edge case: am empty any (or all) bound cannot hold: + VerifyBound::AnyBound(bs) => bs.iter().all(|b| b.cannot_hold()), VerifyBound::AllBounds(bs) => bs.iter().any(|b| b.cannot_hold()), } }