Skip to content

Commit a2b6928

Browse files
committed
repr(transparent): do not consider repr(C) types to be 1-ZST
1 parent 9b82646 commit a2b6928

File tree

8 files changed

+150
-57
lines changed

8 files changed

+150
-57
lines changed

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,31 +1511,46 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15111511
}
15121512

15131513
let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
1514-
// For each field, figure out if it's known to have "trivial" layout (i.e., is a 1-ZST), with
1515-
// "known" respecting #[non_exhaustive] attributes.
1514+
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
1515+
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
1516+
// fields or `repr(C)` or uninhabited. We call those fields "unsuited".
1517+
struct FieldInfo<'tcx> {
1518+
span: Span,
1519+
trivial: bool,
1520+
unsuited: Option<UnsuitedInfo<'tcx>>,
1521+
}
1522+
struct UnsuitedInfo<'tcx> {
1523+
/// The source of the problem, a type that is found somewhere within the field type.
1524+
ty: Ty<'tcx>,
1525+
reason: UnsuitedReason,
1526+
}
1527+
enum UnsuitedReason {
1528+
NonExhaustive,
1529+
PrivateField,
1530+
ReprC,
1531+
}
1532+
15161533
let field_infos = adt.all_fields().map(|field| {
15171534
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
15181535
let layout = tcx.layout_of(typing_env.as_query_input(ty));
15191536
// We are currently checking the type this field came from, so it must be local
15201537
let span = tcx.hir_span_if_local(field.did).unwrap();
15211538
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
15221539
if !trivial {
1523-
return (span, trivial, None);
1540+
// No need to even compute `unsuited`.
1541+
return FieldInfo { span, trivial, unsuited: None };
15241542
}
1525-
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`.
15261543

1527-
fn check_non_exhaustive<'tcx>(
1544+
fn check_unsuited<'tcx>(
15281545
tcx: TyCtxt<'tcx>,
15291546
typing_env: ty::TypingEnv<'tcx>,
1530-
t: Ty<'tcx>,
1531-
) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> {
1547+
ty: Ty<'tcx>,
1548+
) -> ControlFlow<UnsuitedInfo<'tcx>> {
15321549
// We can encounter projections during traversal, so ensure the type is normalized.
1533-
let t = tcx.try_normalize_erasing_regions(typing_env, t).unwrap_or(t);
1534-
match t.kind() {
1535-
ty::Tuple(list) => {
1536-
list.iter().try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1537-
}
1538-
ty::Array(ty, _) => check_non_exhaustive(tcx, typing_env, *ty),
1550+
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
1551+
match ty.kind() {
1552+
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
1553+
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
15391554
ty::Adt(def, args) => {
15401555
if !def.did().is_local()
15411556
&& !find_attr!(
@@ -1550,28 +1565,36 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15501565
.any(ty::VariantDef::is_field_list_non_exhaustive);
15511566
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
15521567
if non_exhaustive || has_priv {
1553-
return ControlFlow::Break((
1554-
def.descr(),
1555-
def.did(),
1556-
args,
1557-
non_exhaustive,
1558-
));
1568+
return ControlFlow::Break(UnsuitedInfo {
1569+
ty,
1570+
reason: if non_exhaustive {
1571+
UnsuitedReason::NonExhaustive
1572+
} else {
1573+
UnsuitedReason::PrivateField
1574+
},
1575+
});
15591576
}
15601577
}
1578+
if def.repr().c() {
1579+
return ControlFlow::Break(UnsuitedInfo {
1580+
ty,
1581+
reason: UnsuitedReason::ReprC,
1582+
});
1583+
}
15611584
def.all_fields()
15621585
.map(|field| field.ty(tcx, args))
1563-
.try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1586+
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
15641587
}
15651588
_ => ControlFlow::Continue(()),
15661589
}
15671590
}
15681591

1569-
(span, trivial, check_non_exhaustive(tcx, typing_env, ty).break_value())
1592+
FieldInfo { span, trivial, unsuited: check_unsuited(tcx, typing_env, ty).break_value() }
15701593
});
15711594

15721595
let non_trivial_fields = field_infos
15731596
.clone()
1574-
.filter_map(|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None });
1597+
.filter_map(|field| if !field.trivial { Some(field.span) } else { None });
15751598
let non_trivial_count = non_trivial_fields.clone().count();
15761599
if non_trivial_count >= 2 {
15771600
bad_non_zero_sized_fields(
@@ -1583,36 +1606,36 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15831606
);
15841607
return;
15851608
}
1586-
let mut prev_non_exhaustive_1zst = false;
1587-
for (span, _trivial, non_exhaustive_1zst) in field_infos {
1588-
if let Some((descr, def_id, args, non_exhaustive)) = non_exhaustive_1zst {
1609+
1610+
let mut prev_unsuited_1zst = false;
1611+
for field in field_infos {
1612+
if let Some(unsuited) = field.unsuited {
1613+
assert!(field.trivial);
15891614
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
15901615
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1591-
if non_trivial_count > 0 || prev_non_exhaustive_1zst {
1616+
if non_trivial_count > 0 || prev_unsuited_1zst {
15921617
tcx.node_span_lint(
15931618
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
15941619
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
1595-
span,
1620+
field.span,
15961621
|lint| {
15971622
lint.primary_message(
15981623
"zero-sized fields in `repr(transparent)` cannot \
15991624
contain external non-exhaustive types",
16001625
);
1601-
let note = if non_exhaustive {
1602-
"is marked with `#[non_exhaustive]`"
1603-
} else {
1604-
"contains private fields"
1626+
let note = match unsuited.reason {
1627+
UnsuitedReason::NonExhaustive => "is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.",
1628+
UnsuitedReason::PrivateField => "contains private fields, so it could become non-zero-sized in the future.",
1629+
UnsuitedReason::ReprC => "is marked with `#[repr(C)]`, which means it cannot be ignored for all ABIs.",
16051630
};
1606-
let field_ty = tcx.def_path_str_with_args(def_id, args);
16071631
lint.note(format!(
1608-
"this {descr} contains `{field_ty}`, which {note}, \
1609-
and makes it not a breaking change to become \
1610-
non-zero-sized in the future."
1632+
"this field contains `{field_ty}`, which {note}",
1633+
field_ty = unsuited.ty,
16111634
));
16121635
},
1613-
)
1636+
);
16141637
} else {
1615-
prev_non_exhaustive_1zst = true;
1638+
prev_unsuited_1zst = true;
16161639
}
16171640
}
16181641
}

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ pub enum FutureIncompatibilityReason {
403403
///
404404
/// After a lint has been in this state for a while and you feel like it is ready to graduate
405405
/// to warning everyone, consider setting [`FutureIncompatibleInfo::report_in_deps`] to true.
406-
/// (see it's documentation for more guidance)
406+
/// (see its documentation for more guidance)
407407
///
408408
/// After some period of time, lints with this variant can be turned into
409409
/// hard errors (and the lint removed). Preferably when there is some

tests/ui/lint/improper-ctypes/lint-ctypes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![feature(rustc_private)]
22

3-
#![allow(private_interfaces)]
3+
#![allow(private_interfaces, repr_transparent_external_private_fields)]
44
#![deny(improper_ctypes)]
55

66
use std::cell::UnsafeCell;

tests/ui/lint/improper-ctypes/lint-fn.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(private_interfaces)]
1+
#![allow(private_interfaces, repr_transparent_external_private_fields)]
22
#![deny(improper_ctypes_definitions)]
33

44
use std::default::Default;

0 commit comments

Comments
 (0)