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_type_ir:: TyKind :: * ;
4750use tracing:: instrument;
4851
@@ -54,7 +57,12 @@ pub mod inhabited_predicate;
5457pub use inhabited_predicate:: InhabitedPredicate ;
5558
5659pub ( crate ) fn provide ( providers : & mut Providers ) {
57- * providers = Providers { inhabited_predicate_adt, inhabited_predicate_type, ..* providers } ;
60+ * providers = Providers {
61+ inhabited_predicate_adt,
62+ inhabited_predicate_type,
63+ is_opsem_inhabited_raw,
64+ ..* providers
65+ } ;
5866}
5967
6068/// Returns an `InhabitedPredicate` that is generic over type parameters and
@@ -186,14 +194,27 @@ impl<'tcx> Ty<'tcx> {
186194 self . inhabited_predicate ( tcx) . apply ( tcx, typing_env, module)
187195 }
188196
189- /// Returns true if the type is uninhabited without regard to visibility
197+ /// Returns true if the type is uninhabited without regard to visibility.
198+ ///
199+ /// This is still conservative; for instance, a `#[non_exhaustive]` enum *in another crate*
200+ /// is always considered inhabited.
190201 pub fn is_privately_uninhabited (
191202 self ,
192203 tcx : TyCtxt < ' tcx > ,
193204 typing_env : ty:: TypingEnv < ' tcx > ,
194205 ) -> bool {
195206 !self . inhabited_predicate ( tcx) . apply_ignore_module ( tcx, typing_env)
196207 }
208+
209+ /// Returns whether `self` is considered inhabited on the opsem level, i.e., its validity
210+ /// invariant might be satisfiable. `self` is expected to be monomorphic and normalized.
211+ pub fn is_opsem_inhabited ( self , tcx : TyCtxt < ' tcx > , typing_env : ty:: TypingEnv < ' tcx > ) -> bool {
212+ // Handle simple cases directly, use the query with its cache for the rest.
213+ is_opsem_inhabited_recursor ( self , tcx, & mut ( ) , /* stop_at_ref */ false , & |ty, _, _| {
214+ // ADT handler: stop recursing, invoke the query.
215+ tcx. is_opsem_inhabited_raw ( typing_env. as_query_input ( ty) )
216+ } )
217+ }
197218}
198219
199220/// N.B. this query should only be called through `Ty::inhabited_predicate`
@@ -216,3 +237,159 @@ fn inhabited_predicate_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> InhabitedP
216237 _ => bug ! ( "unexpected TyKind, use `Ty::inhabited_predicate`" ) ,
217238 }
218239}
240+
241+ /// Recurse over a type to determine whether it is inhabited on the opsem level.
242+ /// Key constraints are:
243+ /// - if a type's validity invariant is satisfiable, it must be opsem-inhabited.
244+ /// - if a type's layout is marked uninhabited, it must be opsem-uninhabited.
245+ ///
246+ /// Beyond that, the value returned by this function is not a stable guarantee.
247+ ///
248+ /// When we encounter an ADT, we call `adt_handler`, giving it as its last argument a closure that
249+ /// it can invoke to continue the recursion. This lets us share the logic for "simple" cases
250+ /// (i.e., everything except for ADTs) between `Ty::is_opsem_inhabited` and the query.
251+ ///
252+ /// `seen` is used to detect infinite recursion: the set contains all ADTs that we encountered
253+ /// on our path to the current type.
254+ /// If `stop_at_ref` is true, we stop recursing at the next reference we encounter.
255+ fn is_opsem_inhabited_recursor < ' tcx , SEEN > (
256+ ty : Ty < ' tcx > ,
257+ tcx : TyCtxt < ' tcx > ,
258+ seen : & mut SEEN ,
259+ stop_at_ref : bool ,
260+ adt_handler : & impl Fn (
261+ Ty < ' tcx > ,
262+ & mut SEEN ,
263+ & dyn Fn ( Ty < ' tcx > , & mut SEEN , /* stop_at_ref */ bool ) -> bool ,
264+ ) -> bool ,
265+ ) -> bool {
266+ match * ty. kind ( ) {
267+ // Trivially (un)inhabited types
268+ ty:: Int ( _)
269+ | ty:: Uint ( _)
270+ | ty:: Float ( _)
271+ | ty:: Bool
272+ | ty:: Char
273+ | ty:: Str
274+ | ty:: Foreign ( ..)
275+ | ty:: RawPtr ( ..)
276+ | ty:: FnPtr ( ..)
277+ | ty:: FnDef ( ..) => true ,
278+ ty:: Dynamic ( ..) => true , // We can't reason about traits, assume they are inhabited
279+ ty:: Slice ( ..) => true , // Slices can always be empty
280+ ty:: Never => false ,
281+
282+ // Types where we recurse
283+ ty:: Ref ( _, pointee, _) => {
284+ if stop_at_ref {
285+ // Bailing out here is safe as the layout code always considers references
286+ // inhabited, so the implication ("layout uninhabited => opsem uninhabited")
287+ // is upheld.
288+ return true ;
289+ }
290+ is_opsem_inhabited_recursor ( pointee, tcx, seen, stop_at_ref, adt_handler)
291+ }
292+ ty:: Tuple ( tys) => tys
293+ . iter ( )
294+ . all ( |ty| is_opsem_inhabited_recursor ( ty, tcx, seen, stop_at_ref, adt_handler) ) ,
295+ ty:: Array ( elem, len) => {
296+ len. try_to_target_usize ( tcx) . unwrap ( ) == 0
297+ || is_opsem_inhabited_recursor ( elem, tcx, seen, stop_at_ref, adt_handler)
298+ }
299+ ty:: Pat ( inner, _pat) => {
300+ is_opsem_inhabited_recursor ( inner, tcx, seen, stop_at_ref, adt_handler)
301+ }
302+ ty:: Closure ( _def, args) => {
303+ let args = args. as_closure ( ) ;
304+ args. upvar_tys ( )
305+ . iter ( )
306+ . all ( |ty| is_opsem_inhabited_recursor ( ty, tcx, seen, stop_at_ref, adt_handler) )
307+ }
308+ ty:: Coroutine ( _def, args) => {
309+ let args = args. as_coroutine ( ) ;
310+ args. upvar_tys ( )
311+ . iter ( )
312+ . all ( |ty| is_opsem_inhabited_recursor ( ty, tcx, seen, stop_at_ref, adt_handler) )
313+ }
314+ ty:: CoroutineClosure ( _def, args) => {
315+ let args = args. as_coroutine_closure ( ) ;
316+ args. upvar_tys ( )
317+ . iter ( )
318+ . all ( |ty| is_opsem_inhabited_recursor ( ty, tcx, seen, stop_at_ref, adt_handler) )
319+ }
320+ ty:: UnsafeBinder ( base) => {
321+ let base = tcx. instantiate_bound_regions_with_erased ( ( * base) . into ( ) ) ;
322+ is_opsem_inhabited_recursor ( base, tcx, seen, stop_at_ref, adt_handler)
323+ }
324+ ty:: Adt ( ..) => {
325+ // ADTs need a special handler to avoid infinite recursion. That handler is meant to
326+ // call back into the recursor. Ideally it'd just call `is_opsem_inhabited_recursor` but
327+ // then it would have to pass itself as the adt_handler argument which is not possible
328+ // in Rust... so we provide the handler with a callback that it can use to continue the
329+ // recursion with the same `adt_handler`.
330+ adt_handler ( ty, seen, & |ty, seen, stop_at_ref| {
331+ is_opsem_inhabited_recursor ( ty, tcx, seen, stop_at_ref, adt_handler)
332+ } )
333+ }
334+
335+ ty:: Error ( _)
336+ | ty:: Infer ( ..)
337+ | ty:: Placeholder ( ..)
338+ | ty:: Bound ( ..)
339+ | ty:: Param ( ..)
340+ | ty:: Alias ( ..)
341+ | ty:: CoroutineWitness ( ..) => {
342+ bug ! ( "non-normalized type in `is_opsem_uninhabited`: `{ty}`" )
343+ }
344+ }
345+ }
346+
347+ fn is_opsem_inhabited_raw < ' tcx > (
348+ tcx : TyCtxt < ' tcx > ,
349+ env : ty:: PseudoCanonicalInput < ' tcx , Ty < ' tcx > > ,
350+ ) -> bool {
351+ let ( ty, typing_env) = ( env. value , env. typing_env ) ;
352+ assert_matches ! (
353+ ty. kind( ) ,
354+ ty:: Adt ( ..) ,
355+ "the query should only be invoked by `Ty::is_opsem_inhabited`"
356+ ) ;
357+
358+ is_opsem_inhabited_recursor (
359+ ty,
360+ tcx,
361+ & mut FxHashSet :: < DefId > :: default ( ) ,
362+ /* stop_at_ref */ false ,
363+ & |ty, seen, rec| {
364+ let ty:: Adt ( adt_def, adt_args) = * ty. kind ( ) else {
365+ unreachable ! { }
366+ } ;
367+ if adt_def. is_union ( ) {
368+ // Unions are always inhabited.
369+ return true ;
370+ }
371+
372+ let new_adt = seen. insert ( adt_def. did ( ) ) ;
373+ // If we have seen this ADT before, stop at the next reference to avoid infinite
374+ // recursion. We can't stop here since we have to ensure that "layout inhabited"
375+ // implies "opsem inhabited".
376+ let stop_at_ref = !new_adt;
377+
378+ // We are inhabited if in some variant all fields are inhabited.
379+ let inhabited = adt_def. variants ( ) . iter ( ) . any ( |variant| {
380+ variant. fields . iter ( ) . all ( |field| {
381+ let ty = field. ty ( tcx, adt_args) ;
382+ let ty = tcx. normalize_erasing_regions ( typing_env, ty) ;
383+ rec ( ty, seen, stop_at_ref)
384+ } )
385+ } ) ;
386+
387+ // Remove the type again so that we allow it to appear on other branches.
388+ if new_adt {
389+ seen. remove ( & adt_def. did ( ) ) ;
390+ }
391+
392+ inhabited
393+ } ,
394+ )
395+ }
0 commit comments