Skip to content

Commit 0cccaa0

Browse files
committed
lint: unify enum variant, union and struct logic
This commit applies the changes introduced in rust-lang#72890 to both enum variants and unions - where the logic prior to rust-lang#72890 was duplicated. Signed-off-by: David Wood <david@davidtw.co>
1 parent 76ad38d commit 0cccaa0

File tree

2 files changed

+107
-152
lines changed

2 files changed

+107
-152
lines changed

src/librustc_lint/types.rs

+82-125
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ struct ImproperCTypesVisitor<'a, 'tcx> {
507507
enum FfiResult<'tcx> {
508508
FfiSafe,
509509
FfiPhantom(Ty<'tcx>),
510-
FfiUnsafe { ty: Ty<'tcx>, reason: &'static str, help: Option<&'static str> },
510+
FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> },
511511
}
512512

513513
fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
@@ -613,6 +613,50 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
613613
}
614614
}
615615

616+
/// Checks if the given `VariantDef`'s field types are "ffi-safe".
617+
fn check_variant_for_ffi(
618+
&self,
619+
cache: &mut FxHashSet<Ty<'tcx>>,
620+
ty: Ty<'tcx>,
621+
def: &ty::AdtDef,
622+
variant: &ty::VariantDef,
623+
substs: SubstsRef<'tcx>,
624+
) -> FfiResult<'tcx> {
625+
use FfiResult::*;
626+
627+
if def.repr.transparent() {
628+
// Can assume that only one field is not a ZST, so only check
629+
// that field's type for FFI-safety.
630+
if let Some(field) = variant.transparent_newtype_field(self.cx.tcx, self.cx.param_env) {
631+
self.check_field_type_for_ffi(cache, field, substs)
632+
} else {
633+
bug!("malformed transparent type");
634+
}
635+
} else {
636+
// We can't completely trust repr(C) markings; make sure the fields are
637+
// actually safe.
638+
let mut all_phantom = !variant.fields.is_empty();
639+
for field in &variant.fields {
640+
match self.check_field_type_for_ffi(cache, &field, substs) {
641+
FfiSafe => {
642+
all_phantom = false;
643+
}
644+
FfiPhantom(..) if def.is_enum() => {
645+
return FfiUnsafe {
646+
ty,
647+
reason: "this enum contains a PhantomData field".into(),
648+
help: None,
649+
};
650+
}
651+
FfiPhantom(..) => {}
652+
r => return r,
653+
}
654+
}
655+
656+
if all_phantom { FfiPhantom(ty) } else { FfiSafe }
657+
}
658+
}
659+
616660
/// Checks if the given type is "ffi-safe" (has a stable, well-defined
617661
/// representation which can be exported to C code).
618662
fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
@@ -634,15 +678,18 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
634678
return FfiPhantom(ty);
635679
}
636680
match def.adt_kind() {
637-
AdtKind::Struct => {
681+
AdtKind::Struct | AdtKind::Union => {
682+
let kind = if def.is_struct() { "struct" } else { "union" };
683+
638684
if !def.repr.c() && !def.repr.transparent() {
639685
return FfiUnsafe {
640686
ty,
641-
reason: "this struct has unspecified layout",
642-
help: Some(
687+
reason: format!("this {} has unspecified layout", kind),
688+
help: Some(format!(
643689
"consider adding a `#[repr(C)]` or \
644-
`#[repr(transparent)]` attribute to this struct",
645-
),
690+
`#[repr(transparent)]` attribute to this {}",
691+
kind
692+
)),
646693
};
647694
}
648695

@@ -651,93 +698,20 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
651698
if is_non_exhaustive && !def.did.is_local() {
652699
return FfiUnsafe {
653700
ty,
654-
reason: "this struct is non-exhaustive",
701+
reason: format!("this {} is non-exhaustive", kind),
655702
help: None,
656703
};
657704
}
658705

659706
if def.non_enum_variant().fields.is_empty() {
660707
return FfiUnsafe {
661708
ty,
662-
reason: "this struct has no fields",
663-
help: Some("consider adding a member to this struct"),
709+
reason: format!("this {} has no fields", kind),
710+
help: Some(format!("consider adding a member to this {}", kind)),
664711
};
665712
}
666713

667-
if def.repr.transparent() {
668-
// Can assume that only one field is not a ZST, so only check
669-
// that field's type for FFI-safety.
670-
if let Some(field) =
671-
def.transparent_newtype_field(cx, self.cx.param_env)
672-
{
673-
self.check_field_type_for_ffi(cache, field, substs)
674-
} else {
675-
FfiSafe
676-
}
677-
} else {
678-
// We can't completely trust repr(C) markings; make sure the fields are
679-
// actually safe.
680-
let mut all_phantom = true;
681-
for field in &def.non_enum_variant().fields {
682-
let r = self.check_field_type_for_ffi(cache, field, substs);
683-
match r {
684-
FfiSafe => {
685-
all_phantom = false;
686-
}
687-
FfiPhantom(..) => {}
688-
FfiUnsafe { .. } => {
689-
return r;
690-
}
691-
}
692-
}
693-
694-
if all_phantom { FfiPhantom(ty) } else { FfiSafe }
695-
}
696-
}
697-
AdtKind::Union => {
698-
if !def.repr.c() && !def.repr.transparent() {
699-
return FfiUnsafe {
700-
ty,
701-
reason: "this union has unspecified layout",
702-
help: Some(
703-
"consider adding a `#[repr(C)]` or \
704-
`#[repr(transparent)]` attribute to this union",
705-
),
706-
};
707-
}
708-
709-
if def.non_enum_variant().fields.is_empty() {
710-
return FfiUnsafe {
711-
ty,
712-
reason: "this union has no fields",
713-
help: Some("consider adding a field to this union"),
714-
};
715-
}
716-
717-
let mut all_phantom = true;
718-
for field in &def.non_enum_variant().fields {
719-
let field_ty = cx.normalize_erasing_regions(
720-
ParamEnv::reveal_all(),
721-
field.ty(cx, substs),
722-
);
723-
// repr(transparent) types are allowed to have arbitrary ZSTs, not just
724-
// PhantomData -- skip checking all ZST fields.
725-
if def.repr.transparent() && field_ty.is_zst(cx, field.did) {
726-
continue;
727-
}
728-
let r = self.check_type_for_ffi(cache, field_ty);
729-
match r {
730-
FfiSafe => {
731-
all_phantom = false;
732-
}
733-
FfiPhantom(..) => {}
734-
FfiUnsafe { .. } => {
735-
return r;
736-
}
737-
}
738-
}
739-
740-
if all_phantom { FfiPhantom(ty) } else { FfiSafe }
714+
self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs)
741715
}
742716
AdtKind::Enum => {
743717
if def.variants.is_empty() {
@@ -752,11 +726,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
752726
if !is_repr_nullable_ptr(cx, ty, def, substs) {
753727
return FfiUnsafe {
754728
ty,
755-
reason: "enum has no representation hint",
729+
reason: "enum has no representation hint".into(),
756730
help: Some(
757731
"consider adding a `#[repr(C)]`, \
758732
`#[repr(transparent)]`, or integer `#[repr(...)]` \
759-
attribute to this enum",
733+
attribute to this enum"
734+
.into(),
760735
),
761736
};
762737
}
@@ -765,7 +740,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
765740
if def.is_variant_list_non_exhaustive() && !def.did.is_local() {
766741
return FfiUnsafe {
767742
ty,
768-
reason: "this enum is non-exhaustive",
743+
reason: "this enum is non-exhaustive".into(),
769744
help: None,
770745
};
771746
}
@@ -776,51 +751,31 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
776751
if is_non_exhaustive && !variant.def_id.is_local() {
777752
return FfiUnsafe {
778753
ty,
779-
reason: "this enum has non-exhaustive variants",
754+
reason: "this enum has non-exhaustive variants".into(),
780755
help: None,
781756
};
782757
}
783758

784-
for field in &variant.fields {
785-
let field_ty = cx.normalize_erasing_regions(
786-
ParamEnv::reveal_all(),
787-
field.ty(cx, substs),
788-
);
789-
// repr(transparent) types are allowed to have arbitrary ZSTs, not
790-
// just PhantomData -- skip checking all ZST fields.
791-
if def.repr.transparent() && field_ty.is_zst(cx, field.did) {
792-
continue;
793-
}
794-
let r = self.check_type_for_ffi(cache, field_ty);
795-
match r {
796-
FfiSafe => {}
797-
FfiUnsafe { .. } => {
798-
return r;
799-
}
800-
FfiPhantom(..) => {
801-
return FfiUnsafe {
802-
ty,
803-
reason: "this enum contains a PhantomData field",
804-
help: None,
805-
};
806-
}
807-
}
759+
match self.check_variant_for_ffi(cache, ty, def, variant, substs) {
760+
FfiSafe => (),
761+
r => return r,
808762
}
809763
}
764+
810765
FfiSafe
811766
}
812767
}
813768
}
814769

815770
ty::Char => FfiUnsafe {
816771
ty,
817-
reason: "the `char` type has no C equivalent",
818-
help: Some("consider using `u32` or `libc::wchar_t` instead"),
772+
reason: "the `char` type has no C equivalent".into(),
773+
help: Some("consider using `u32` or `libc::wchar_t` instead".into()),
819774
},
820775

821776
ty::Int(ast::IntTy::I128) | ty::Uint(ast::UintTy::U128) => FfiUnsafe {
822777
ty,
823-
reason: "128-bit integers don't currently have a known stable ABI",
778+
reason: "128-bit integers don't currently have a known stable ABI".into(),
824779
help: None,
825780
},
826781

@@ -829,24 +784,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
829784

830785
ty::Slice(_) => FfiUnsafe {
831786
ty,
832-
reason: "slices have no C equivalent",
833-
help: Some("consider using a raw pointer instead"),
787+
reason: "slices have no C equivalent".into(),
788+
help: Some("consider using a raw pointer instead".into()),
834789
},
835790

836791
ty::Dynamic(..) => {
837-
FfiUnsafe { ty, reason: "trait objects have no C equivalent", help: None }
792+
FfiUnsafe { ty, reason: "trait objects have no C equivalent".into(), help: None }
838793
}
839794

840795
ty::Str => FfiUnsafe {
841796
ty,
842-
reason: "string slices have no C equivalent",
843-
help: Some("consider using `*const u8` and a length instead"),
797+
reason: "string slices have no C equivalent".into(),
798+
help: Some("consider using `*const u8` and a length instead".into()),
844799
},
845800

846801
ty::Tuple(..) => FfiUnsafe {
847802
ty,
848-
reason: "tuples have unspecified layout",
849-
help: Some("consider using a struct instead"),
803+
reason: "tuples have unspecified layout".into(),
804+
help: Some("consider using a struct instead".into()),
850805
},
851806

852807
ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
@@ -860,10 +815,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
860815
Abi::Rust | Abi::RustIntrinsic | Abi::PlatformIntrinsic | Abi::RustCall => {
861816
return FfiUnsafe {
862817
ty,
863-
reason: "this function pointer has Rust-specific calling convention",
818+
reason: "this function pointer has Rust-specific calling convention"
819+
.into(),
864820
help: Some(
865821
"consider using an `extern fn(...) -> ...` \
866-
function pointer instead",
822+
function pointer instead"
823+
.into(),
867824
),
868825
};
869826
}
@@ -897,7 +854,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
897854
// While opaque types are checked for earlier, if a projection in a struct field
898855
// normalizes to an opaque type, then it will reach this branch.
899856
ty::Opaque(..) => {
900-
FfiUnsafe { ty, reason: "opaque types have no C equivalent", help: None }
857+
FfiUnsafe { ty, reason: "opaque types have no C equivalent".into(), help: None }
901858
}
902859

903860
ty::Param(..)
@@ -1004,7 +961,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
1004961
// argument, which after substitution, is `()`, then this branch can be hit.
1005962
FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => return,
1006963
FfiResult::FfiUnsafe { ty, reason, help } => {
1007-
self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
964+
self.emit_ffi_unsafe_type_lint(ty, sp, &reason, help.as_deref());
1008965
}
1009966
}
1010967
}

src/librustc_middle/ty/mod.rs

+25-27
Original file line numberDiff line numberDiff line change
@@ -1807,6 +1807,31 @@ impl<'tcx> VariantDef {
18071807
pub fn is_field_list_non_exhaustive(&self) -> bool {
18081808
self.flags.intersects(VariantFlags::IS_FIELD_LIST_NON_EXHAUSTIVE)
18091809
}
1810+
1811+
/// `repr(transparent)` structs can have a single non-ZST field, this function returns that
1812+
/// field.
1813+
pub fn transparent_newtype_field(
1814+
&self,
1815+
tcx: TyCtxt<'tcx>,
1816+
param_env: ParamEnv<'tcx>,
1817+
) -> Option<&FieldDef> {
1818+
for field in &self.fields {
1819+
let field_ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, self.def_id));
1820+
1821+
// `normalize_erasing_regions` will fail for projections that contain generic
1822+
// parameters, so check these before normalizing.
1823+
if field_ty.has_projections() && field_ty.needs_subst() {
1824+
return Some(field);
1825+
}
1826+
1827+
let field_ty = tcx.normalize_erasing_regions(param_env, field_ty);
1828+
if !field_ty.is_zst(tcx, self.def_id) {
1829+
return Some(field);
1830+
}
1831+
}
1832+
1833+
None
1834+
}
18101835
}
18111836

18121837
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, HashStable)]
@@ -2376,33 +2401,6 @@ impl<'tcx> AdtDef {
23762401
pub fn sized_constraint(&self, tcx: TyCtxt<'tcx>) -> &'tcx [Ty<'tcx>] {
23772402
tcx.adt_sized_constraint(self.did).0
23782403
}
2379-
2380-
/// `repr(transparent)` structs can have a single non-ZST field, this function returns that
2381-
/// field.
2382-
pub fn transparent_newtype_field(
2383-
&self,
2384-
tcx: TyCtxt<'tcx>,
2385-
param_env: ParamEnv<'tcx>,
2386-
) -> Option<&FieldDef> {
2387-
assert!(self.is_struct() && self.repr.transparent());
2388-
2389-
for field in &self.non_enum_variant().fields {
2390-
let field_ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, self.did));
2391-
2392-
// `normalize_erasing_regions` will fail for projections that contain generic
2393-
// parameters, so check these before normalizing.
2394-
if field_ty.has_projections() && field_ty.needs_subst() {
2395-
return Some(field);
2396-
}
2397-
2398-
let field_ty = tcx.normalize_erasing_regions(param_env, field_ty);
2399-
if !field_ty.is_zst(tcx, self.did) {
2400-
return Some(field);
2401-
}
2402-
}
2403-
2404-
None
2405-
}
24062404
}
24072405

24082406
impl<'tcx> FieldDef {

0 commit comments

Comments
 (0)