repr(transparent) check: do not compute check_unsuited more than once

This commit is contained in:
Ralf Jung 2025-10-30 08:07:56 +01:00
parent 42ebbd2356
commit c23182eb24

View file

@ -1542,22 +1542,10 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
// fields or `repr(C)`. We call those fields "unsuited".
struct FieldInfo<'tcx> {
span: Span,
trivial: bool,
unsuited: Option<UnsuitedInfo<'tcx>>,
}
struct UnsuitedInfo<'tcx> {
/// The source of the problem, a type that is found somewhere within the field type.
ty: Ty<'tcx>,
reason: UnsuitedReason,
}
enum UnsuitedReason {
NonExhaustive,
PrivateField,
ReprC,
}
let field_infos = adt.all_fields().map(|field| {
@ -1566,60 +1554,7 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
// We are currently checking the type this field came from, so it must be local
let span = tcx.hir_span_if_local(field.did).unwrap();
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
if !trivial {
// No need to even compute `unsuited`.
return FieldInfo { span, trivial, unsuited: None };
}
fn check_unsuited<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
) -> ControlFlow<UnsuitedInfo<'tcx>> {
// We can encounter projections during traversal, so ensure the type is normalized.
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
match ty.kind() {
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
ty::Adt(def, args) => {
if !def.did().is_local()
&& !find_attr!(
tcx.get_all_attrs(def.did()),
AttributeKind::PubTransparent(_)
)
{
let non_exhaustive = def.is_variant_list_non_exhaustive()
|| def
.variants()
.iter()
.any(ty::VariantDef::is_field_list_non_exhaustive);
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
if non_exhaustive || has_priv {
return ControlFlow::Break(UnsuitedInfo {
ty,
reason: if non_exhaustive {
UnsuitedReason::NonExhaustive
} else {
UnsuitedReason::PrivateField
},
});
}
}
if def.repr().c() {
return ControlFlow::Break(UnsuitedInfo {
ty,
reason: UnsuitedReason::ReprC,
});
}
def.all_fields()
.map(|field| field.ty(tcx, args))
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
}
_ => ControlFlow::Continue(()),
}
}
FieldInfo { span, trivial, unsuited: check_unsuited(tcx, typing_env, ty).break_value() }
FieldInfo { span, trivial, ty }
});
let non_trivial_fields = field_infos
@ -1637,10 +1572,63 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
return;
}
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
// fields or `repr(C)`. We call those fields "unsuited".
struct UnsuitedInfo<'tcx> {
/// The source of the problem, a type that is found somewhere within the field type.
ty: Ty<'tcx>,
reason: UnsuitedReason,
}
enum UnsuitedReason {
NonExhaustive,
PrivateField,
ReprC,
}
fn check_unsuited<'tcx>(
tcx: TyCtxt<'tcx>,
typing_env: ty::TypingEnv<'tcx>,
ty: Ty<'tcx>,
) -> ControlFlow<UnsuitedInfo<'tcx>> {
// We can encounter projections during traversal, so ensure the type is normalized.
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
match ty.kind() {
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
ty::Adt(def, args) => {
if !def.did().is_local()
&& !find_attr!(tcx.get_all_attrs(def.did()), AttributeKind::PubTransparent(_))
{
let non_exhaustive = def.is_variant_list_non_exhaustive()
|| def.variants().iter().any(ty::VariantDef::is_field_list_non_exhaustive);
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
if non_exhaustive || has_priv {
return ControlFlow::Break(UnsuitedInfo {
ty,
reason: if non_exhaustive {
UnsuitedReason::NonExhaustive
} else {
UnsuitedReason::PrivateField
},
});
}
}
if def.repr().c() {
return ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::ReprC });
}
def.all_fields()
.map(|field| field.ty(tcx, args))
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
}
_ => ControlFlow::Continue(()),
}
}
let mut prev_unsuited_1zst = false;
for field in field_infos {
if let Some(unsuited) = field.unsuited {
assert!(field.trivial);
if field.trivial
&& let Some(unsuited) = check_unsuited(tcx, typing_env, field.ty).break_value()
{
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
if non_trivial_count > 0 || prev_unsuited_1zst {