@@ -173,7 +173,6 @@ impl<'tcx> FfiResult<'tcx> {
173
173
/// if the note at their core reason is one in a provided list.
174
174
/// If the FfiResult is not FfiUnsafe, or if no reasons are plucked,
175
175
/// then return FfiSafe.
176
- #[ expect( unused) ]
177
176
fn take_with_core_note ( & mut self , notes : & [ DiagMessage ] ) -> Self {
178
177
match self {
179
178
Self :: FfiUnsafe ( this) => {
@@ -574,6 +573,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
574
573
all_ffires
575
574
}
576
575
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
+
577
590
/// Checks if a simple numeric (int, float) type has an actual portable definition
578
591
/// for the compile target.
579
592
fn visit_numeric ( & self , ty : Ty < ' tcx > ) -> FfiResult < ' tcx > {
@@ -724,6 +737,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
724
737
if !matches ! ( def. adt_kind( ) , AdtKind :: Enum ) && def. repr ( ) . transparent ( ) {
725
738
// determine if there is 0 or 1 non-1ZST field, and which it is.
726
739
// (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
+ }
727
758
if let Some ( field) = super :: transparent_newtype_field ( self . cx . tcx , variant) {
728
759
// Transparent newtypes have at most one non-ZST field which needs to be checked later
729
760
( false , vec ! [ field] )
@@ -918,8 +949,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
918
949
use FfiResult :: * ;
919
950
920
951
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 ) ;
923
954
}
924
955
// Check for a repr() attribute to specify the size of the
925
956
// discriminant.
@@ -960,19 +991,33 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
960
991
None ,
961
992
)
962
993
} 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( ) ] ;
963
998
964
- let ffires = def
999
+ let mut ffires = def
965
1000
. variants ( )
966
1001
. 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
+ ] ) ;
969
1009
// FIXME(ctypes): check that enums allow any (up to all) variants to be phantoms?
970
1010
// (previous code says no, but I don't know why? the problem with phantoms is that they're ZSTs, right?)
971
1011
variant_res. forbid_phantom ( )
972
1012
} )
973
1013
. reduce ( |r1, r2| r1 + r2)
974
1014
. unwrap ( ) ; // always at least one variant if we hit this branch
975
1015
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
+
976
1021
// this enum is visited in the middle of another lint,
977
1022
// so we override the "cause type" of the lint
978
1023
// (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> {
1154
1199
1155
1200
ty:: Foreign ( ..) => FfiSafe ,
1156
1201
1157
- ty:: Never => FfiSafe ,
1202
+ ty:: Never => self . visit_uninhabited ( state , ty ) ,
1158
1203
1159
1204
// While opaque types are checked for earlier, if a projection in a struct field
1160
1205
// normalizes to an opaque type, then it will reach this branch.
0 commit comments