From ca08edb609290ead05b42e477e5dbf3c558487cd Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 26 Nov 2023 12:54:52 +0100 Subject: [PATCH] Detect cycles in `InhabitedPredicate::apply` This is for post-monomorphization cycles. These are only caught later (in drop elaboration for the example that I saw), so we need to handle them here. This issue wasn't noticed before because exhaustiveness only checked inhabitedness when `exhaustive_patterns` was on. The preceding commit now check inhabitedness always, which revealed the problem. --- .../ty/inhabitedness/inhabited_predicate.rs | 40 ++++++++++++++----- .../rustc_middle/src/ty/inhabitedness/mod.rs | 1 + 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs b/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs index f278cace99dc4..ae17942785ff8 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/inhabited_predicate.rs @@ -1,3 +1,5 @@ +use smallvec::SmallVec; + use crate::ty::context::TyCtxt; use crate::ty::{self, DefId, ParamEnv, Ty}; @@ -31,27 +33,31 @@ impl<'tcx> InhabitedPredicate<'tcx> { /// Returns true if the corresponding type is inhabited in the given /// `ParamEnv` and module pub fn apply(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, module_def_id: DefId) -> bool { - let Ok(result) = self - .apply_inner::(tcx, param_env, &|id| Ok(tcx.is_descendant_of(module_def_id, id))); + let Ok(result) = self.apply_inner::(tcx, param_env, &mut Default::default(), &|id| { + Ok(tcx.is_descendant_of(module_def_id, id)) + }); result } /// Same as `apply`, but returns `None` if self contains a module predicate pub fn apply_any_module(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Option { - self.apply_inner(tcx, param_env, &|_| Err(())).ok() + self.apply_inner(tcx, param_env, &mut Default::default(), &|_| Err(())).ok() } /// Same as `apply`, but `NotInModule(_)` predicates yield `false`. That is, /// privately uninhabited types are considered always uninhabited. pub fn apply_ignore_module(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> bool { - let Ok(result) = self.apply_inner::(tcx, param_env, &|_| Ok(true)); + let Ok(result) = + self.apply_inner::(tcx, param_env, &mut Default::default(), &|_| Ok(true)); result } - fn apply_inner( + #[instrument(level = "debug", skip(tcx, param_env, in_module), ret)] + fn apply_inner( self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, + eval_stack: &mut SmallVec<[Ty<'tcx>; 1]>, // for cycle detection in_module: &impl Fn(DefId) -> Result, ) -> Result { match self { @@ -71,11 +77,25 @@ impl<'tcx> InhabitedPredicate<'tcx> { match normalized_pred { // We don't have more information than we started with, so consider inhabited. Self::GenericType(_) => Ok(true), - pred => pred.apply_inner(tcx, param_env, in_module), + pred => { + // A type which is cyclic when monomorphized can happen here since the + // layout error would only trigger later. See e.g. `tests/ui/sized/recursive-type-2.rs`. + if eval_stack.contains(&t) { + return Ok(true); // Recover; this will error later. + } + eval_stack.push(t); + let ret = pred.apply_inner(tcx, param_env, eval_stack, in_module); + eval_stack.pop(); + ret + } } } - Self::And([a, b]) => try_and(a, b, |x| x.apply_inner(tcx, param_env, in_module)), - Self::Or([a, b]) => try_or(a, b, |x| x.apply_inner(tcx, param_env, in_module)), + Self::And([a, b]) => { + try_and(a, b, |x| x.apply_inner(tcx, param_env, eval_stack, in_module)) + } + Self::Or([a, b]) => { + try_or(a, b, |x| x.apply_inner(tcx, param_env, eval_stack, in_module)) + } } } @@ -197,7 +217,7 @@ impl<'tcx> InhabitedPredicate<'tcx> { // this is basically like `f(a)? && f(b)?` but different in the case of // `Ok(false) && Err(_) -> Ok(false)` -fn try_and(a: T, b: T, f: impl Fn(T) -> Result) -> Result { +fn try_and(a: T, b: T, mut f: impl FnMut(T) -> Result) -> Result { let a = f(a); if matches!(a, Ok(false)) { return Ok(false); @@ -209,7 +229,7 @@ fn try_and(a: T, b: T, f: impl Fn(T) -> Result) -> Result(a: T, b: T, f: impl Fn(T) -> Result) -> Result { +fn try_or(a: T, b: T, mut f: impl FnMut(T) -> Result) -> Result { let a = f(a); if matches!(a, Ok(true)) { return Ok(true); diff --git a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs index 68ac54e899ac3..d67fb9fd0b734 100644 --- a/compiler/rustc_middle/src/ty/inhabitedness/mod.rs +++ b/compiler/rustc_middle/src/ty/inhabitedness/mod.rs @@ -103,6 +103,7 @@ impl<'tcx> VariantDef { } impl<'tcx> Ty<'tcx> { + #[instrument(level = "debug", skip(tcx), ret)] pub fn inhabited_predicate(self, tcx: TyCtxt<'tcx>) -> InhabitedPredicate<'tcx> { match self.kind() { // For now, unions are always considered inhabited