@@ -389,7 +389,6 @@ impl<'tcx> FfiResult<'tcx> {
389389 /// if the note at their core reason is one in a provided list.
390390 /// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
391391 /// then return FfiSafe.
392- #[ expect( unused) ]
393392 fn take_with_core_note ( & mut self , notes : & [ DiagMessage ] ) -> Self {
394393 match self {
395394 Self :: FfiUnsafe ( this) => {
@@ -902,6 +901,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
902901 all_ffires
903902 }
904903
904+ /// Checks whether an uninhabited type (one without valid values) is safe-ish to have here.
905+ fn visit_uninhabited ( & self , state : VisitorState , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
906+ if state. is_in_function_return ( ) {
907+ FfiResult :: FfiSafe
908+ } else {
909+ let desc = match ty. kind ( ) {
910+ ty:: Adt ( ..) => fluent:: lint_improper_ctypes_uninhabited_enum,
911+ ty:: Never => fluent:: lint_improper_ctypes_uninhabited_never,
912+ r @ _ => bug ! ( "unexpected ty_kind in uninhabited type handling: {:?}" , r) ,
913+ } ;
914+ FfiResult :: new_with_reason ( ty, desc, None )
915+ }
916+ }
917+
905918 /// Return the right help for Cstring and Cstr-linked unsafety.
906919 fn visit_cstr ( & mut self , state : VisitorState , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
907920 debug_assert ! ( matches!( ty. kind( ) , ty:: Adt ( def, _)
@@ -1022,6 +1035,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
10221035 if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
10231036 // determine if there is 0 or 1 non-1ZST field, and which it is.
10241037 // (note: for enums, "transparent" means 1-variant)
1038+ if ty. is_privately_uninhabited ( self . cx . tcx , self . cx . typing_env ( ) ) {
1039+ // let's consider transparent structs to be maybe unsafe if uninhabited,
1040+ // even if that is because of fields otherwise ignored in FFI-safety checks
1041+ // FIXME(ctypes): and also maybe this should be "!is_inhabited_from" but from where?
1042+ ffires_accumulator += variant
1043+ . fields
1044+ . iter ( )
1045+ . map ( |field| {
1046+ let field_ty = get_type_from_field ( self . cx , field, args) ;
1047+ let mut field_res = self . visit_type ( state. get_next ( ty) , field_ty) ;
1048+ field_res. take_with_core_note ( & [
1049+ fluent:: lint_improper_ctypes_uninhabited_enum,
1050+ fluent:: lint_improper_ctypes_uninhabited_never,
1051+ ] )
1052+ } )
1053+ . reduce ( |r1, r2| r1 + r2)
1054+ . unwrap ( ) // if uninhabited, then >0 fields
1055+ }
10251056 if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
10261057 // Transparent newtypes have at most one non-ZST field which needs to be checked later
10271058 ( false , vec ! [ field] )
@@ -1212,8 +1243,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12121243 use FfiResult :: * ;
12131244
12141245 if def. variants ( ) . is_empty ( ) {
1215- // Empty enums are okay... although sort of useless.
1216- return FfiSafe ;
1246+ // Empty enums are implicitly handled as the never type:
1247+ return self . visit_uninhabited ( state , ty ) ;
12171248 }
12181249 // Check for a repr() attribute to specify the size of the
12191250 // discriminant.
@@ -1242,7 +1273,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12421273 nonexhaustive_flag |= nonex_enum;
12431274 nonexhaustive_variant_flag |= nonex_var;
12441275 } ) ;
1245-
12461276 // "nonexhaustive" lints only happen outside of the crate defining the enum, so no CItemKind override
12471277 // (meaning: the fault lies in the function call, not the enum)
12481278 if nonexhaustive_flag {
@@ -1254,18 +1284,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
12541284 None ,
12551285 )
12561286 } else {
1257- let ffires = def
1287+ // small caveat to checking the variants: we authorise up to n-1 invariants
1288+ // to be unsafe because uninhabited.
1289+ // so for now let's isolate those unsafeties
1290+ let mut variants_uninhabited_ffires = vec ! [ FfiSafe ; def. variants( ) . len( ) ] ;
1291+
1292+ let mut ffires = def
12581293 . variants ( )
12591294 . iter ( )
1260- . map ( |variant| {
1261- let variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1295+ . enumerate ( )
1296+ . map ( |( variant_i, variant) | {
1297+ let mut variant_res = self . visit_variant_fields ( state, ty, def, variant, args) ;
1298+ variants_uninhabited_ffires[ variant_i] = variant_res. take_with_core_note ( & [
1299+ fluent:: lint_improper_ctypes_uninhabited_enum,
1300+ fluent:: lint_improper_ctypes_uninhabited_never,
1301+ ] ) ;
12621302 // FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
12631303 // (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
12641304 variant_res. forbid_phantom ( )
12651305 } )
12661306 . reduce ( |r1, r2| r1 + r2)
12671307 . unwrap ( ) ; // always at least one variant if we hit this branch
12681308
1309+ if variants_uninhabited_ffires. iter ( ) . all ( |res| matches ! ( res, FfiUnsafe ( ..) ) ) {
1310+ // if the enum is uninhabited, because all its variants are uninhabited
1311+ ffires += variants_uninhabited_ffires. into_iter ( ) . reduce ( |r1, r2| r1 + r2) . unwrap ( ) ;
1312+ }
1313+
12691314 // this enum is visited in the middle of another lint,
12701315 // so we override the "cause type" of the lint
12711316 // (for more detail, see comment in ``visit_struct_union`` before its call to ``ffires.with_overrides``)
@@ -1432,7 +1477,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
14321477
14331478 ty:: Foreign ( ..) => FfiSafe ,
14341479
1435- ty:: Never => FfiSafe ,
1480+ ty:: Never => self . visit_uninhabited ( state , ty ) ,
14361481
14371482 // While opaque types are checked for earlier, if a projection in a struct field
14381483 // normalizes to an opaque type, then it will reach this branch.
0 commit comments