@@ -320,7 +320,6 @@ impl<'tcx> FfiResult<'tcx> {
320320 /// if the note at their core reason is one in a provided list.
321321 /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
322322 /// then return FfiSafe.
323- #[ expect( unused) ]
324323 fn take_with_core_note ( & mut self , notes : & [ DiagMessage ] ) -> Self {
325324 match self {
326325 Self :: FfiUnsafe ( this) => {
@@ -733,6 +732,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
733732 all_ffires
734733 }
735734
735+ /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
736+ fn visit_uninhabited ( & self , state : VisitorState , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
737+ if state. is_in_function_return ( ) {
738+ FfiResult :: FfiSafe
739+ } else {
740+ let desc = match ty. kind ( ) {
741+ ty:: Adt ( ..) => fluent:: lint_improper_ctypes_uninhabited_enum,
742+ ty:: Never => fluent:: lint_improper_ctypes_uninhabited_never,
743+ r @ _ => bug ! ( "unexpected ty_kind in uninhabited type handling: {:?}" , r) ,
744+ } ;
745+ FfiResult :: new_with_reason ( ty, desc, None )
746+ }
747+ }
748+
736749 /// Checks if a simple numeric (int, float) type has an actual portable definition
737750 /// for the compile target.
738751 fn visit_numeric ( & self , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
@@ -883,6 +896,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
883896 if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
884897 // determine if there is 0 or 1 non-1ZST field, and which it is.
885898 // (note: for enums, "transparent" means 1-variant)
899+ if ty. is_privately_uninhabited ( self . cx . tcx , self . cx . typing_env ( ) ) {
900+ // let's consider transparent structs to be maybe unsafe if uninhabited,
901+ // even if that is because of fields otherwise ignored in FFI-safety checks
902+ // FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where?
903+ ffires_accumulator += variant
904+ . fields
905+ . iter ( )
906+ . map ( |field| {
907+ let field_ty = get_type_from_field ( self . cx , field, args) ;
908+ let mut field_res = self . visit_type ( state, Some ( ty) , field_ty) ;
909+ field_res. take_with_core_note ( & [
910+ fluent:: lint_improper_ctypes_uninhabited_enum,
911+ fluent:: lint_improper_ctypes_uninhabited_never,
912+ ] )
913+ } )
914+ . reduce ( |r1, r2| r1 + r2)
915+ . unwrap ( ) // if uninhabited, then >0 fields
916+ }
886917 if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
887918 // Transparent newtypes have at most one non-ZST field which needs to be checked later
888919 ( false , vec ! [ field] )
@@ -1073,8 +1104,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10731104 use FfiResult :: * ;
10741105
10751106 if def. variants ( ) . is_empty ( ) {
1076- // Empty enums are okay... although sort of useless.
1077- return FfiSafe ;
1107+ // Empty enums are implicitly handled as the never type:
1108+ return self . visit_uninhabited ( state , ty ) ;
10781109 }
10791110 // Check for a repr() attribute to specify the size of the
10801111 // discriminant.
@@ -1115,18 +1146,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
11151146 None ,
11161147 )
11171148 } else {
1118- let ffires = def
1149+ // small caveat to checking the variants: we authorise up to n-1 invariants
1150+ // to be unsafe because uninhabited.
1151+ // so for now let's isolate those unsafeties
1152+ let mut variants_uninhabited_ffires = vec ! [ FfiSafe ; def. variants( ) . len( ) ] ;
1153+
1154+ let mut ffires = def
11191155 . variants ( )
11201156 . iter ( )
1121- . map ( |variant| {
1122- let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1157+ . enumerate ( )
1158+ . map ( |( variant_i, variant) | {
1159+ let mut variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1160+ variants_uninhabited_ffires[ variant_i] = variant_res. take_with_core_note ( & [
1161+ fluent:: lint_improper_ctypes_uninhabited_enum,
1162+ fluent:: lint_improper_ctypes_uninhabited_never,
1163+ ] ) ;
11231164 // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
11241165 // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
11251166 variant_res. forbid_phantom ( )
11261167 } )
11271168 . reduce ( |r1, r2| r1 + r2)
11281169 . unwrap ( ) ; // always at least one variant if we hit this branch
11291170
1171+ if variants_uninhabited_ffires. iter ( ) . all ( |res| matches ! ( res, FfiUnsafe ( ..) ) ) {
1172+ // if the enum is uninhabited, because all its variants are uninhabited
1173+ ffires += variants_uninhabited_ffires. into_iter ( ) . reduce ( |r1, r2| r1 + r2) . unwrap ( ) ;
1174+ }
1175+
11301176 // this enum is visited in the middle of another lint,
11311177 // so we override the "cause type" of the lint
11321178 // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1303,7 +1349,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13031349
13041350 ty:: Foreign ( ..) => FfiSafe ,
13051351
1306- ty:: Never => FfiSafe ,
1352+ ty:: Never => self . visit_uninhabited ( state , ty ) ,
13071353
13081354 // While opaque types are checked for earlier, if a projection in a struct field
13091355 // normalizes to an opaque type, then it will reach this branch.
0 commit comments