Skip to content
/ rust Public
forked from rust-lang/rust

Commit 7f42495

Browse files
authored
Rollup merge of rust-lang#159625 - camsteffen:opsem-inhabited-refactor, r=RalfJung
Refactor is_opsem_inhabited Refactor of rust-lang#156977 Introduce a struct to generally simplify the recursion. r? WaffleLapkin cc @RalfJung
2 parents 6589af4 + 7f2417b commit 7f42495

1 file changed

Lines changed: 116 additions & 135 deletions

File tree

  • compiler/rustc_middle/src/ty/inhabitedness

compiler/rustc_middle/src/ty/inhabitedness/mod.rs

Lines changed: 116 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use rustc_type_ir::TyKind::*;
5151
use tracing::instrument;
5252

5353
use crate::query::Providers;
54-
use crate::ty::{self, DefId, Ty, TyCtxt, TypeVisitableExt, VariantDef, Visibility};
54+
use crate::ty::{self, DefId, Ty, TyCtxt, TypeVisitableExt, TypingEnv, VariantDef, Visibility};
5555

5656
pub mod inhabited_predicate;
5757

@@ -221,10 +221,7 @@ impl<'tcx> Ty<'tcx> {
221221
/// Beyond that, the value returned by this function is not a stable guarantee.
222222
pub fn is_opsem_inhabited(self, tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> bool {
223223
// Handle simple cases directly, use the query with its cache for the rest.
224-
is_opsem_inhabited_recursor(self, tcx, &mut (), /* stop_at_ref */ false, &|ty, _, _| {
225-
// ADT handler: stop recursing, invoke the query.
226-
tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty))
227-
})
224+
OpsemInhabitedCtx { tcx, typing_env, seen: None, stop_at_ref: false }.is_inhabited_ty(self)
228225
}
229226
}
230227

@@ -249,109 +246,129 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
249246
}
250247
}
251248

252-
/// Recurse over a type to determine whether it is inhabited on the opsem level.
249+
/// Context for computing whether a type is inhabited on the opsem level.
253250
/// See `is_opsem_inhabited` above for the spec of what we compute.
254-
///
255-
/// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that
256-
/// it can invoke to continue the recursion. This lets us share the logic for "simple" cases
257-
/// (i.e., everything except for ADTs) between `Ty::is_opsem_inhabited` and the query.
258-
///
259-
/// `seen` is used to detect infinite recursion: the set contains all ADTs that we encountered
260-
/// on our path to the current type.
261-
/// If `stop_at_ref` is true, we stop recursing at the next reference we encounter.
262-
fn is_opsem_inhabited_recursor<'tcx, SEEN>(
263-
ty: Ty<'tcx>,
251+
struct OpsemInhabitedCtx<'tcx> {
264252
tcx: TyCtxt<'tcx>,
265-
seen: &mut SEEN,
253+
typing_env: TypingEnv<'tcx>,
254+
/// IDs of ADTs that have been encountered in the current stack.
255+
/// It's `None` unless we are inside the `is_opsem_inhabited_raw` query,
256+
/// which is only invoked for more complex types.
257+
seen: Option<FxHashSet<DefId>>,
258+
/// If an ADT is encountered recursively within itself, then `stop_at_ref`
259+
/// is set to `true`, and then any nested references are considered inhabited.
266260
stop_at_ref: bool,
267-
adt_handler: &impl Fn(
268-
Ty<'tcx>,
269-
&mut SEEN,
270-
&dyn Fn(Ty<'tcx>, &mut SEEN, /* stop_at_ref */ bool) -> bool,
271-
) -> bool,
272-
) -> bool {
273-
match *ty.kind() {
274-
// Trivially (un)inhabited types
275-
ty::Int(_)
276-
| ty::Uint(_)
277-
| ty::Float(_)
278-
| ty::Bool
279-
| ty::Char
280-
| ty::Str
281-
| ty::Foreign(..)
282-
| ty::RawPtr(..)
283-
| ty::FnPtr(..)
284-
| ty::FnDef(..) => true,
285-
ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited
286-
ty::Slice(..) => true, // Slices can always be empty
287-
ty::Never => false,
261+
}
288262

289-
// Types where we recurse
290-
ty::Ref(_, pointee, _) => {
291-
if stop_at_ref {
292-
// Bailing out here is safe as the layout code always considers references
293-
// inhabited, so the implication ("layout uninhabited => opsem uninhabited")
294-
// is upheld.
295-
return true;
263+
impl<'tcx> OpsemInhabitedCtx<'tcx> {
264+
/// See `is_opsem_inhabited` above for the spec of what we compute.
265+
fn is_inhabited_ty(&mut self, ty: Ty<'tcx>) -> bool {
266+
let tcx = self.tcx;
267+
match *ty.kind() {
268+
// Trivially (un)inhabited types
269+
ty::Int(_)
270+
| ty::Uint(_)
271+
| ty::Float(_)
272+
| ty::Bool
273+
| ty::Char
274+
| ty::Str
275+
| ty::Foreign(..)
276+
| ty::RawPtr(..)
277+
| ty::FnPtr(..)
278+
| ty::FnDef(..) => true,
279+
ty::Dynamic(..) => true, // We can't reason about traits, assume they are inhabited
280+
ty::Slice(..) => true, // Slices can always be empty
281+
ty::Never => false,
282+
283+
// Types where we recurse
284+
ty::Ref(_, pointee, _) => {
285+
if self.stop_at_ref {
286+
// Bailing out here is safe as the layout code always considers references
287+
// inhabited, so the implication ("layout uninhabited => opsem uninhabited")
288+
// is upheld.
289+
return true;
290+
}
291+
self.is_inhabited_ty(pointee)
292+
}
293+
ty::Tuple(tys) => tys.iter().all(|ty| self.is_inhabited_ty(ty)),
294+
ty::Array(elem, len) => {
295+
len.try_to_target_usize(tcx).unwrap() == 0 || self.is_inhabited_ty(elem)
296+
}
297+
ty::Pat(inner, _pat) => self.is_inhabited_ty(inner),
298+
ty::Closure(_def, args) => {
299+
let args = args.as_closure();
300+
args.upvar_tys().iter().all(|ty| self.is_inhabited_ty(ty))
301+
}
302+
ty::Coroutine(_def, args) => {
303+
let args = args.as_coroutine();
304+
args.upvar_tys().iter().all(|ty| self.is_inhabited_ty(ty))
305+
}
306+
ty::CoroutineClosure(_def, args) => {
307+
let args = args.as_coroutine_closure();
308+
args.upvar_tys().iter().all(|ty| self.is_inhabited_ty(ty))
309+
}
310+
ty::UnsafeBinder(base) => {
311+
let base = tcx.instantiate_bound_regions_with_erased((*base).into());
312+
self.is_inhabited_ty(base)
313+
}
314+
ty::Adt(..) => self.is_inhabited_adt_ty(ty),
315+
316+
ty::Error(_error_guaranteed) => {
317+
// We have a token proving there was an error, so we can return a dummy value.
318+
true
319+
}
320+
321+
ty::Infer(..)
322+
| ty::Placeholder(..)
323+
| ty::Bound(..)
324+
| ty::Param(..)
325+
| ty::Alias(..)
326+
| ty::CoroutineWitness(..) => {
327+
bug!("non-normalized type in `is_opsem_uninhabited`: `{ty}`")
296328
}
297-
is_opsem_inhabited_recursor(pointee, tcx, seen, stop_at_ref, adt_handler)
298-
}
299-
ty::Tuple(tys) => tys
300-
.iter()
301-
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)),
302-
ty::Array(elem, len) => {
303-
len.try_to_target_usize(tcx).unwrap() == 0
304-
|| is_opsem_inhabited_recursor(elem, tcx, seen, stop_at_ref, adt_handler)
305-
}
306-
ty::Pat(inner, _pat) => {
307-
is_opsem_inhabited_recursor(inner, tcx, seen, stop_at_ref, adt_handler)
308-
}
309-
ty::Closure(_def, args) => {
310-
let args = args.as_closure();
311-
args.upvar_tys()
312-
.iter()
313-
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
314-
}
315-
ty::Coroutine(_def, args) => {
316-
let args = args.as_coroutine();
317-
args.upvar_tys()
318-
.iter()
319-
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
320-
}
321-
ty::CoroutineClosure(_def, args) => {
322-
let args = args.as_coroutine_closure();
323-
args.upvar_tys()
324-
.iter()
325-
.all(|ty| is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler))
326329
}
327-
ty::UnsafeBinder(base) => {
328-
let base = tcx.instantiate_bound_regions_with_erased((*base).into());
329-
is_opsem_inhabited_recursor(base, tcx, seen, stop_at_ref, adt_handler)
330+
}
331+
332+
fn is_inhabited_adt_ty(&mut self, ty: Ty<'tcx>) -> bool {
333+
let ty::Adt(adt_def, adt_args) = *ty.kind() else {
334+
unreachable! {}
335+
};
336+
let Self { tcx, typing_env, .. } = *self;
337+
338+
if adt_def.is_union() {
339+
// Unions are always inhabited.
340+
return true;
330341
}
331-
ty::Adt(..) => {
332-
// ADTs need a special handler to avoid infinite recursion. That handler is meant to
333-
// call back into the recursor. Ideally it'd just call `is_opsem_inhabited_recursor` but
334-
// then it would have to pass itself as the adt_handler argument which is not possible
335-
// in Rust... so we provide the handler with a callback that it can use to continue the
336-
// recursion with the same `adt_handler`.
337-
adt_handler(ty, seen, &|ty, seen, stop_at_ref| {
338-
is_opsem_inhabited_recursor(ty, tcx, seen, stop_at_ref, adt_handler)
342+
343+
let Some(seen) = self.seen.as_mut() else {
344+
// stop recursing, invoke the query.
345+
return tcx.is_opsem_inhabited_raw(typing_env.as_query_input(ty));
346+
};
347+
348+
let new_adt = seen.insert(adt_def.did());
349+
// If we have seen this ADT before, stop at the next reference to avoid infinite
350+
// recursion. We can't stop here since we have to ensure that "layout uninhabited"
351+
// implies "opsem uninhabited". References are always layout-inhabited so the
352+
// implication is vacuously true.
353+
let stop_at_ref_prev = self.stop_at_ref;
354+
self.stop_at_ref |= !new_adt;
355+
356+
// We are inhabited if in some variant all fields are inhabited.
357+
let inhabited = adt_def.variants().iter().any(|variant| {
358+
variant.fields.iter().all(|field| {
359+
let ty = field.ty(tcx, adt_args);
360+
let ty = tcx.normalize_erasing_regions(typing_env, ty);
361+
self.is_inhabited_ty(ty)
339362
})
340-
}
363+
});
341364

342-
ty::Error(_error_guaranteed) => {
343-
// We have a token proving there was an error, so we can return a dummy value.
344-
true
365+
self.stop_at_ref = stop_at_ref_prev;
366+
// Remove the type again so that we allow it to appear on other branches.
367+
if new_adt {
368+
self.seen.as_mut().unwrap().remove(&adt_def.did());
345369
}
346370

347-
ty::Infer(..)
348-
| ty::Placeholder(..)
349-
| ty::Bound(..)
350-
| ty::Param(..)
351-
| ty::Alias(..)
352-
| ty::CoroutineWitness(..) => {
353-
bug!("non-normalized type in `is_opsem_uninhabited`: `{ty}`")
354-
}
371+
inhabited
355372
}
356373
}
357374

@@ -366,42 +383,6 @@ fn is_opsem_inhabited_raw<'tcx>(
366383
"the query should only be invoked by `Ty::is_opsem_inhabited`"
367384
);
368385

369-
is_opsem_inhabited_recursor(
370-
ty,
371-
tcx,
372-
&mut FxHashSet::<DefId>::default(),
373-
/* stop_at_ref */ false,
374-
&|ty, seen, rec| {
375-
let ty::Adt(adt_def, adt_args) = *ty.kind() else {
376-
unreachable! {}
377-
};
378-
if adt_def.is_union() {
379-
// Unions are always inhabited.
380-
return true;
381-
}
382-
383-
let new_adt = seen.insert(adt_def.did());
384-
// If we have seen this ADT before, stop at the next reference to avoid infinite
385-
// recursion. We can't stop here since we have to ensure that "layout uninhabited"
386-
// implies "opsem uninhabited". References are always layout-inhabited so the
387-
// implication is vacuously true.
388-
let stop_at_ref = !new_adt;
389-
390-
// We are inhabited if in some variant all fields are inhabited.
391-
let inhabited = adt_def.variants().iter().any(|variant| {
392-
variant.fields.iter().all(|field| {
393-
let ty = field.ty(tcx, adt_args);
394-
let ty = tcx.normalize_erasing_regions(typing_env, ty);
395-
rec(ty, seen, stop_at_ref)
396-
})
397-
});
398-
399-
// Remove the type again so that we allow it to appear on other branches.
400-
if new_adt {
401-
seen.remove(&adt_def.did());
402-
}
403-
404-
inhabited
405-
},
406-
)
386+
OpsemInhabitedCtx { tcx, typing_env, seen: Some(FxHashSet::default()), stop_at_ref: false }
387+
.is_inhabited_adt_ty(ty)
407388
}

0 commit comments

Comments
 (0)