@@ -1099,6 +1099,32 @@ fn get_nullable_type<'tcx>(
10991099 })
11001100}
11011101
1102+ /// A type is niche_optimization_candiate iff:
1103+ /// - Is a zero-sized type with alignment 1 (a “1-ZST”).
1104+ /// - Has no fields.
1105+ /// - Does not have the #[non_exhaustive] attribute.
1106+ fn is_niche_optimization_candidate<'tcx>(
1107+ tcx: TyCtxt<'tcx>,
1108+ param_env: ty::ParamEnv<'tcx>,
1109+ ty: Ty<'tcx>,
1110+ ) -> bool {
1111+ if !tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| layout.is_1zst()) {
1112+ return false;
1113+ }
1114+
1115+ match ty.kind() {
1116+ ty::Adt(ty_def, _) => {
1117+ let non_exhaustive = ty_def.is_variant_list_non_exhaustive()
1118+ || ty_def.variants().iter().any(|variant| variant.is_field_list_non_exhaustive());
1119+ let contains_no_fields = ty_def.all_fields().next().is_none();
1120+
1121+ !non_exhaustive && contains_no_fields
1122+ }
1123+ ty::Tuple(tys) => tys.is_empty(),
1124+ _ => false,
1125+ }
1126+ }
1127+
11021128/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
11031129/// can, return the type that `ty` can be safely converted to, otherwise return `None`.
11041130/// Currently restricted to function pointers, boxes, references, `core::num::NonZero`,
@@ -1115,6 +1141,26 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
11151141 let field_ty = match &ty_def.variants().raw[..] {
11161142 [var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) {
11171143 ([], [field]) | ([field], []) => field.ty(tcx, args),
1144+ ([field1], [field2]) => {
1145+ // TODO: We pass all the checks here although individual enum variants has
1146+ // checks for FFI safety even when niche optimized which needs to be
1147+ // suppressed. for types like `Result<PhantomData<()>, E>`, PhantomData has
1148+ // it's own lint for FFI which needs to be suppressed: `composed only of
1149+ // `PhantomData``. This is true for other custom types as well `struct
1150+ // Example;` which emits `this struct has unspecified layout` and suggests to
1151+ // add `#[repr(C)]` and when that is done, linter emits `this struct has no
1152+ // fields`, all under the `improper_ctypes_definitions` lint group
1153+ let ty1 = field1.ty(tcx, args);
1154+ let ty2 = field2.ty(tcx, args);
1155+
1156+ if is_niche_optimization_candidate(tcx, param_env, ty1) {
1157+ ty2
1158+ } else if is_niche_optimization_candidate(tcx, param_env, ty2) {
1159+ ty1
1160+ } else {
1161+ return None;
1162+ }
1163+ }
11181164 _ => return None,
11191165 },
11201166 _ => return None,
@@ -1331,7 +1377,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
13311377 // discriminant.
13321378 if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
13331379 {
1334- // Special-case types like `Option<extern fn()>`.
1380+ // Special-case types like `Option<extern fn()>` and `Result<extern fn(), ()>`
13351381 if repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode)
13361382 .is_none()
13371383 {
0 commit comments