Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 56 additions & 68 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1508,22 +1508,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| {
Expand All @@ -1532,60 +1520,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());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still some code here we are running twice, in particular the layout_of query. The alternative would be to simply collect the results into a Vec, which will cost us an allocation but then avoids running the iterator twice.

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() }
return FieldInfo { span, trivial, ty };
});

let non_trivial_fields = field_infos
Expand All @@ -1603,10 +1538,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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but why does the function return a ControlFlow if its only call immediately calls break_value, and answering my own question: because it is used internally in recursive calls.

Perhaps this implementation detail should still be pushed inside with check_unsuited returning Option, while calling an internal function that returns ControlFlow?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already a purely internal helper. I don't see the point of having even more nested functions here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough.

{
// 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 {
Expand Down
Loading