Change:
While reviewing I noticed for the first time that the improperctypes lint normalizes with the "revealall" mode. I don't have a nuanced understanding of what that means exactly but based on the general description in the rustc docs and experimentation (see below) I think that's probably the wrong choice for this lint, because it exposes details to the user that are normally hidden during type checking. For example, using existential types, the lint pierces through otherwise-opaque existentials to look at the underlying (hidden) type, thus it both accepts code that is only FFI-safe because of the particular type underlying the existential, which users of that type shouldn't know or rely on leaks the underlying type to the user in error messages when (correctly) rejecting uses of an existential type as FFI-unsafe I can't quickly find an equivalent using only stable , because we don't have , but if there's a way to write "Option of the return type of this function" in stable Rust, then that would presumably have the same issue. cc for fact-checking my understanding that this is because of reveal_all
User-facing diagnostics should avoid the mode, yeah. In fact, the other variant of is literally - also the doc comments are pretty explicit about this: cc
I've been using 's over in recent PRs that modify the lint (see , , and ). In each recent PR, I've ensured that opaque types are prohibited (in the spirit of "FFI-safety of a type not relying on particular type behind an opaque type being FFI-safe") - with the exception of when a opaque type is used in a projection (). Unless there are specific examples of behavior that still need to be changed as a result of this issue - should this issue be closed?
My litmus test for closing this would be "all the normalizing done in improper_ctypes uses ", which is hard to audit for while there's multiple PRs in flight that address parts of the problem. After those PRs are all merged, we can see if there's any remaining and if so, their location should tell us how to exercise any remaining questionable behavior.
I've opened to fix the remaining two places that is used now that the other PRs have landed.

Discussion:
use rustc_middle::mir::interpret::{sign_extend, truncate}; use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton}; use rustc_middle::ty::subst::SubstsRef; <del> use rustc_middle::ty::{self, AdtKind, ParamEnv, Ty, TyCtxt, TypeFoldable}; </del> <ins> use rustc_middle::ty::{self, AdtKind, Ty, TypeFoldable}; </ins> use rustc_span::source_map; use rustc_span::symbol::sym; use rustc_span::{Span, DUMMY_SP};

FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> }, } <del> fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind { ty::FnPtr(_) => true, ty::Ref(..) => true, ty::Adt(field_def, substs) if field_def.repr.transparent() && !field_def.is_union() => { for field in field_def.all_fields() { let field_ty = tcx.normalize_erasing_regions(ParamEnv::reveal_all(), field.ty(tcx, substs)); if field_ty.is_zst(tcx, field.did) { continue; } </del> <ins> impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Is type known to be non-null? fn ty_is_known_nonnull(&self, ty: Ty<'tcx>) -> bool { match ty.kind { ty::FnPtr(_) => true, ty::Ref(..) => true, ty::Adt(field_def, substs) if field_def.repr.transparent() && !field_def.is_union() => { for field in field_def.all_fields() { let field_ty = self.cx.tcx.normalize_erasing_regions( self.cx.param_env, field.ty(self.cx.tcx, substs), ); if field_ty.is_zst(self.cx.tcx, field.did) { continue; } </ins> <del> let attrs = tcx.get_attrs(field_def.did); if attrs.iter().any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed)) || ty_is_known_nonnull(tcx, field_ty) { return true; </del> <ins> let attrs = self.cx.tcx.get_attrs(field_def.did); if attrs .iter() .any(|a| a.check_name(sym::rustc_nonnull_optimization_guaranteed)) || self.ty_is_known_nonnull(field_ty) { return true; } </ins> } <del> } false </del> <ins> false } _ => false, </ins> } <del> _ => false, </del> } <del> } /// Check if this enum can be safely exported based on the /// "nullable pointer optimization". Currently restricted /// to function pointers, references, core::num::NonZero*, /// core::ptr::NonNull, and #[repr(transparent)] newtypes. /// FIXME: This duplicates code in codegen. fn is_repr_nullable_ptr<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, ty_def: &'tcx ty::AdtDef, substs: SubstsRef<'tcx>, ) -> bool { if ty_def.variants.len() != 2 { return false; } </del> <ins> /// Check if this enum can be safely exported based on the "nullable pointer optimization". /// Currently restricted to function pointers, references, `core::num::NonZero*`, /// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. fn is_repr_nullable_ptr( &self, ty: Ty<'tcx>, ty_def: &'tcx ty::AdtDef, substs: SubstsRef<'tcx>, ) -> bool { if ty_def.variants.len() != 2 { return false; } </ins> <del> let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields; let variant_fields = [get_variant_fields(0), get_variant_fields(1)]; let fields = if variant_fields[0].is_empty() { &variant_fields[1] } else if variant_fields[1].is_empty() { &variant_fields[0] } else { return false; }; </del> <ins> let get_variant_fields = |index| &ty_def.variants[VariantIdx::new(index)].fields; let variant_fields = [get_variant_fields(0), get_variant_fields(1)]; let fields = if variant_fields[0].is_empty() { &variant_fields[1] } else if variant_fields[1].is_empty() { &variant_fields[0] } else { return false; }; </ins> <del> if fields.len() != 1 { return false; } </del> <ins> if fields.len() != 1 { return false; } </ins> <del> let field_ty = fields[0].ty(tcx, substs); if !ty_is_known_nonnull(tcx, field_ty) { return false; } </del> <ins> let field_ty = fields[0].ty(self.cx.tcx, substs); if !self.ty_is_known_nonnull(field_ty) { return false; } </ins> <del> // At this point, the field's type is known to be nonnull and the parent enum is Option-like. // If the computed size for the field and the enum are different, the nonnull optimization isn't // being applied (and we've got a problem somewhere). let compute_size_skeleton = |t| SizeSkeleton::compute(t, tcx, ParamEnv::reveal_all()).unwrap(); if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { bug!("improper_ctypes: Option nonnull optimization not applied?"); } </del> <ins> // At this point, the field's type is known to be nonnull and the parent enum is // Option-like. If the computed size for the field and the enum are different, the non-null // optimization isn't being applied (and we've got a problem somewhere). let compute_size_skeleton = |t| SizeSkeleton::compute(t, self.cx.tcx, self.cx.param_env).unwrap(); if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { bug!("improper_ctypes: Option nonnull optimization not applied?"); } </ins> <del> true } </del> <ins> true } </ins> <del> impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { </del> /// Check if the type is array and emit an unsafe type lint. fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { if let ty::Array(..) = ty.kind {

// discriminant. if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() { // Special-case types like `Option<extern fn()>`. <del> if !is_repr_nullable_ptr(cx, ty, def, substs) { </del> <ins> if !self.is_repr_nullable_ptr(ty, def, substs) { </ins> return FfiUnsafe { ty, reason: "enum has no representation hint".into(),