4343//! This code should only compile in modules where the uninhabitedness of `Foo`
4444//! is visible.
4545
46+ use std:: assert_matches;
47+
48+ use rustc_data_structures:: fx:: FxHashSet ;
4649use rustc_span:: def_id:: LocalModId ;
4750use rustc_type_ir:: TyKind :: * ;
4851use tracing:: instrument;
@@ -55,7 +58,12 @@ pub mod inhabited_predicate;
5558pub use inhabited_predicate:: InhabitedPredicate ;
5659
5760pub ( crate ) fn provide ( providers : & mut Providers ) {
58- * providers = Providers { inhabited_predicate_adt, inhabited_predicate_type, ..* providers } ;
61+ * providers = Providers {
62+ inhabited_predicate_adt,
63+ inhabited_predicate_type,
64+ is_opsem_inhabited_raw,
65+ ..* providers
66+ } ;
5967}
6068
6169/// Returns an `InhabitedPredicate` that is generic over type parameters and
@@ -191,14 +199,33 @@ impl<'tcx> Ty<'tcx> {
191199 self . inhabited_predicate ( tcx) . apply ( tcx, typing_env, module)
192200 }
193201
194- /// Returns true if the type is uninhabited without regard to visibility
202+ /// Returns true if the type is uninhabited without regard to visibility.
203+ ///
204+ /// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate*
205+ /// is always considered inhabited.
195206 pub fn is_privately_uninhabited (
196207 self ,
197208 tcx : TyCtxt < ' tcx > ,
198209 typing_env : ty:: TypingEnv < ' tcx > ,
199210 ) -> bool {
200211 !self . inhabited_predicate ( tcx) . apply_ignore_module ( tcx, typing_env)
201212 }
213+
214+ /// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity
215+ /// invariant might be satisfiable. `self` is expected to be monomorphic and normalized.
216+ ///
217+ /// Key constraints are:
218+ /// - if a type's validity invariant is satisfiable, it must be opsem-inhabited.
219+ /// - if a type's layout is marked uninhabited, it must be opsem-uninhabited.
220+ ///
221+ /// Beyond that, the value returned by this function is not a stable guarantee.
222+ pub fn is_opsem_inhabited ( self , tcx : TyCtxt < ' tcx > , typing_env : ty:: TypingEnv < ' tcx > ) -> bool {
223+ // 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+ } )
228+ }
202229}
203230
204231/// N.B. this query should only be called through `Ty::inhabited_predicate`
@@ -221,3 +248,156 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
221248 _ => bug ! ( "unexpected TyKind, use `Ty::inhabited_predicate`" ) ,
222249 }
223250}
251+
252+ /// Recurse over a type to determine whether it is inhabited on the opsem level.
253+ /// 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 > ,
264+ tcx : TyCtxt < ' tcx > ,
265+ seen : & mut SEEN ,
266+ 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 ,
288+
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 ;
296+ }
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) )
326+ }
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+ 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)
339+ } )
340+ }
341+
342+ ty:: Error ( _)
343+ | ty:: Infer ( ..)
344+ | ty:: Placeholder ( ..)
345+ | ty:: Bound ( ..)
346+ | ty:: Param ( ..)
347+ | ty:: Alias ( ..)
348+ | ty:: CoroutineWitness ( ..) => {
349+ bug ! ( "non-normalized type in `is_opsem_uninhabited`: `{ty}`" )
350+ }
351+ }
352+ }
353+
354+ fn is_opsem_inhabited_raw < ' tcx > (
355+ tcx : TyCtxt < ' tcx > ,
356+ env : ty:: PseudoCanonicalInput < ' tcx , Ty < ' tcx > > ,
357+ ) -> bool {
358+ let ( ty, typing_env) = ( env. value , env. typing_env ) ;
359+ assert_matches ! (
360+ ty. kind( ) ,
361+ ty:: Adt ( ..) ,
362+ "the query should only be invoked by `Ty::is_opsem_inhabited`"
363+ ) ;
364+
365+ is_opsem_inhabited_recursor (
366+ ty,
367+ tcx,
368+ & mut FxHashSet :: < DefId > :: default ( ) ,
369+ /* stop_at_ref */ false ,
370+ & |ty, seen, rec| {
371+ let ty:: Adt ( adt_def, adt_args) = * ty. kind ( ) else {
372+ unreachable ! { }
373+ } ;
374+ if adt_def. is_union ( ) {
375+ // Unions are always inhabited.
376+ return true ;
377+ }
378+
379+ let new_adt = seen. insert ( adt_def. did ( ) ) ;
380+ // If we have seen this ADT before, stop at the next reference to avoid infinite
381+ // recursion. We can't stop here since we have to ensure that "layout uninhabited"
382+ // implies "opsem uninhabited". References are always layout-inhabited so the
383+ // implication is vacuously true.
384+ let stop_at_ref = !new_adt;
385+
386+ // We are inhabited if in some variant all fields are inhabited.
387+ let inhabited = adt_def. variants ( ) . iter ( ) . any ( |variant| {
388+ variant. fields . iter ( ) . all ( |field| {
389+ let ty = field. ty ( tcx, adt_args) ;
390+ let ty = tcx. normalize_erasing_regions ( typing_env, ty) ;
391+ rec ( ty, seen, stop_at_ref)
392+ } )
393+ } ) ;
394+
395+ // Remove the type again so that we allow it to appear on other branches.
396+ if new_adt {
397+ seen. remove ( & adt_def. did ( ) ) ;
398+ }
399+
400+ inhabited
401+ } ,
402+ )
403+ }
0 commit comments