@@ -173,7 +173,6 @@ impl<'tcx> FfiResult<'tcx> {
173173 /// if the note at their core reason is one in a provided list.
174174 /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
175175 /// then return FfiSafe.
176- #[ expect( unused) ]
177176 fn take_with_core_note ( & mut self , notes : & [ DiagMessage ] ) -> Self {
178177 match self {
179178 Self :: FfiUnsafe ( this) => {
@@ -574,6 +573,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
574573 all_ffires
575574 }
576575
576+ /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
577+ fn visit_uninhabited ( & self , state : VisitorState , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
578+ if state. is_in_function_return ( ) {
579+ FfiResult :: FfiSafe
580+ } else {
581+ let desc = match ty. kind ( ) {
582+ ty:: Adt ( ..) => fluent:: lint_improper_ctypes_uninhabited_enum,
583+ ty:: Never => fluent:: lint_improper_ctypes_uninhabited_never,
584+ r @ _ => bug ! ( "unexpected ty_kind in uninhabited type handling: {:?}" , r) ,
585+ } ;
586+ FfiResult :: new_with_reason ( ty, desc, None )
587+ }
588+ }
589+
577590 /// Checks if a simple numeric (int, float) type has an actual portable definition
578591 /// for the compile target.
579592 fn visit_numeric ( & self , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
@@ -724,6 +737,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
724737 if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
725738 // determine if there is 0 or 1 non-1ZST field, and which it is.
726739 // (note: for enums, "transparent" means 1-variant)
740+ if ty. is_privately_uninhabited ( self . cx . tcx , self . cx . typing_env ( ) ) {
741+ // let's consider transparent structs to be maybe unsafe if uninhabited,
742+ // even if that is because of fields otherwise ignored in FFI-safety checks
743+ // FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where?
744+ ffires_accumulator += variant
745+ . fields
746+ . iter ( )
747+ . map ( |field| {
748+ let field_ty = get_type_from_field ( self . cx , field, args) ;
749+ let mut field_res = self . visit_type ( state, Some ( ty) , field_ty) ;
750+ field_res. take_with_core_note ( & [
751+ fluent:: lint_improper_ctypes_uninhabited_enum,
752+ fluent:: lint_improper_ctypes_uninhabited_never,
753+ ] )
754+ } )
755+ . reduce ( |r1, r2| r1 + r2)
756+ . unwrap ( ) // if uninhabited, then >0 fields
757+ }
727758 if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
728759 // Transparent newtypes have at most one non-ZST field which needs to be checked later
729760 ( false , vec ! [ field] )
@@ -918,8 +949,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
918949 use FfiResult :: * ;
919950
920951 if def. variants ( ) . is_empty ( ) {
921- // Empty enums are okay... although sort of useless.
922- return FfiSafe ;
952+ // Empty enums are implicitly handled as the never type:
953+ return self . visit_uninhabited ( state , ty ) ;
923954 }
924955 // Check for a repr() attribute to specify the size of the
925956 // discriminant.
@@ -960,19 +991,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
960991 None ,
961992 )
962993 } else {
994+ // small caveat to checking the variants: we authorise up to n-1 invariants
995+ // to be unsafe because uninhabited.
996+ // so for now let's isolate those unsafeties
997+ let mut variants_uninhabited_ffires = vec ! [ FfiSafe ; def. variants( ) . len( ) ] ;
963998
964- let ffires = def
999+ let mut ffires = def
9651000 . variants ( )
9661001 . iter ( )
967- . map ( |variant| {
968- let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1002+ . enumerate ( )
1003+ . map ( |( variant_i, variant) | {
1004+ let mut variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1005+ variants_uninhabited_ffires[ variant_i] = variant_res. take_with_core_note ( & [
1006+ fluent:: lint_improper_ctypes_uninhabited_enum,
1007+ fluent:: lint_improper_ctypes_uninhabited_never,
1008+ ] ) ;
9691009 // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
9701010 // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
9711011 variant_res. forbid_phantom ( )
9721012 } )
9731013 . reduce ( |r1, r2| r1 + r2)
9741014 . unwrap ( ) ; // always at least one variant if we hit this branch
9751015
1016+ if variants_uninhabited_ffires. iter ( ) . all ( |res| matches ! ( res, FfiUnsafe ( ..) ) ) {
1017+ // if the enum is uninhabited, because all its variants are uninhabited
1018+ ffires += variants_uninhabited_ffires. into_iter ( ) . reduce ( |r1, r2| r1 + r2) . unwrap ( ) ;
1019+ }
1020+
9761021 // this enum is visited in the middle of another lint,
9771022 // so we override the "cause type" of the lint
9781023 // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1154,7 +1199,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11541199
11551200 ty:: Foreign ( ..) => FfiSafe ,
11561201
1157- ty:: Never => FfiSafe ,
1202+ ty:: Never => self . visit_uninhabited ( state , ty ) ,
11581203
11591204 // While opaque types are checked for earlier, if a projection in a struct field
11601205 // normalizes to an opaque type, then it will reach this branch.
0 commit comments