Skip to content

Commit 5bd164b

Browse files
committed
repr(transparent): do not consider repr(C) types to be 1-ZST
1 parent a2db928 commit 5bd164b

File tree

4 files changed

+146
-57
lines changed

4 files changed

+146
-57
lines changed

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 76 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,58 +1510,15 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15101510
return;
15111511
}
15121512

1513-
// For each field, figure out if it's known to have "trivial" layout (i.e., is a 1-ZST), with
1514-
// "known" respecting #[non_exhaustive] attributes.
1513+
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
15151514
let field_infos = adt.all_fields().map(|field| {
15161515
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
15171516
let typing_env = ty::TypingEnv::non_body_analysis(tcx, field.did);
15181517
let layout = tcx.layout_of(typing_env.as_query_input(ty));
15191518
// We are currently checking the type this field came from, so it must be local
15201519
let span = tcx.hir_span_if_local(field.did).unwrap();
15211520
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
1522-
if !trivial {
1523-
return (span, trivial, None);
1524-
}
1525-
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`.
1526-
1527-
fn check_non_exhaustive<'tcx>(
1528-
tcx: TyCtxt<'tcx>,
1529-
t: Ty<'tcx>,
1530-
) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> {
1531-
match t.kind() {
1532-
ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)),
1533-
ty::Array(ty, _) => check_non_exhaustive(tcx, *ty),
1534-
ty::Adt(def, args) => {
1535-
if !def.did().is_local()
1536-
&& !find_attr!(
1537-
tcx.get_all_attrs(def.did()),
1538-
AttributeKind::PubTransparent(_)
1539-
)
1540-
{
1541-
let non_exhaustive = def.is_variant_list_non_exhaustive()
1542-
|| def
1543-
.variants()
1544-
.iter()
1545-
.any(ty::VariantDef::is_field_list_non_exhaustive);
1546-
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
1547-
if non_exhaustive || has_priv {
1548-
return ControlFlow::Break((
1549-
def.descr(),
1550-
def.did(),
1551-
args,
1552-
non_exhaustive,
1553-
));
1554-
}
1555-
}
1556-
def.all_fields()
1557-
.map(|field| field.ty(tcx, args))
1558-
.try_for_each(|t| check_non_exhaustive(tcx, t))
1559-
}
1560-
_ => ControlFlow::Continue(()),
1561-
}
1562-
}
1563-
1564-
(span, trivial, check_non_exhaustive(tcx, ty).break_value())
1521+
(span, trivial, ty)
15651522
});
15661523

15671524
let non_trivial_fields = field_infos
@@ -1578,12 +1535,74 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15781535
);
15791536
return;
15801537
}
1581-
let mut prev_non_exhaustive_1zst = false;
1582-
for (span, _trivial, non_exhaustive_1zst) in field_infos {
1583-
if let Some((descr, def_id, args, non_exhaustive)) = non_exhaustive_1zst {
1538+
1539+
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
1540+
// fields or `repr(C)`. We call those fields "unsuited". Search for unsuited fields and
1541+
// error if the repr(transparent) condition relies on them.
1542+
enum UnsuitedReason {
1543+
NonExhaustive,
1544+
PrivateField,
1545+
ReprC,
1546+
}
1547+
struct UnsuitedInfo<'tcx> {
1548+
descr: &'static str,
1549+
def_id: DefId,
1550+
args: GenericArgsRef<'tcx>,
1551+
reason: UnsuitedReason,
1552+
}
1553+
1554+
fn check_unsuited_1zst<'tcx>(
1555+
tcx: TyCtxt<'tcx>,
1556+
t: Ty<'tcx>,
1557+
) -> ControlFlow<UnsuitedInfo<'tcx>> {
1558+
match t.kind() {
1559+
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited_1zst(tcx, t)),
1560+
ty::Array(ty, _) => check_unsuited_1zst(tcx, *ty),
1561+
ty::Adt(def, args) => {
1562+
if !def.did().is_local()
1563+
&& !find_attr!(tcx.get_all_attrs(def.did()), AttributeKind::PubTransparent(_))
1564+
{
1565+
let non_exhaustive = def.is_variant_list_non_exhaustive()
1566+
|| def.variants().iter().any(ty::VariantDef::is_field_list_non_exhaustive);
1567+
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
1568+
if non_exhaustive || has_priv {
1569+
return ControlFlow::Break(UnsuitedInfo {
1570+
descr: def.descr(),
1571+
def_id: def.did(),
1572+
args,
1573+
reason: if non_exhaustive {
1574+
UnsuitedReason::NonExhaustive
1575+
} else {
1576+
UnsuitedReason::PrivateField
1577+
},
1578+
});
1579+
}
1580+
}
1581+
if def.repr().c() {
1582+
return ControlFlow::Break(UnsuitedInfo {
1583+
descr: def.descr(),
1584+
def_id: def.did(),
1585+
args,
1586+
reason: UnsuitedReason::ReprC,
1587+
});
1588+
}
1589+
def.all_fields()
1590+
.map(|field| field.ty(tcx, args))
1591+
.try_for_each(|t| check_unsuited_1zst(tcx, t))
1592+
}
1593+
_ => ControlFlow::Continue(()),
1594+
}
1595+
}
1596+
1597+
let mut prev_unsuited_1zst = false;
1598+
for (span, trivial, ty) in field_infos {
1599+
if !trivial {
1600+
continue;
1601+
}
1602+
if let Some(unsuited) = check_unsuited_1zst(tcx, ty).break_value() {
15841603
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
15851604
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1586-
if non_trivial_count > 0 || prev_non_exhaustive_1zst {
1605+
if non_trivial_count > 0 || prev_unsuited_1zst {
15871606
tcx.node_span_lint(
15881607
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
15891608
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
@@ -1593,21 +1612,22 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15931612
"zero-sized fields in `repr(transparent)` cannot \
15941613
contain external non-exhaustive types",
15951614
);
1596-
let note = if non_exhaustive {
1597-
"is marked with `#[non_exhaustive]`"
1598-
} else {
1599-
"contains private fields"
1615+
let note = match unsuited.reason {
1616+
UnsuitedReason::NonExhaustive => "is marked with `#[non_exhaustive]`",
1617+
UnsuitedReason::PrivateField => "contains private fields",
1618+
UnsuitedReason::ReprC => "is marked with `#[repr(C)]`",
16001619
};
1601-
let field_ty = tcx.def_path_str_with_args(def_id, args);
1620+
let field_ty = tcx.def_path_str_with_args(unsuited.def_id, unsuited.args);
16021621
lint.note(format!(
16031622
"this {descr} contains `{field_ty}`, which {note}, \
16041623
and makes it not a breaking change to become \
1605-
non-zero-sized in the future."
1624+
non-zero-sized in the future.",
1625+
descr = unsuited.descr,
16061626
));
16071627
},
16081628
)
16091629
} else {
1610-
prev_non_exhaustive_1zst = true;
1630+
prev_unsuited_1zst = true;
16111631
}
16121632
}
16131633
}

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
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![deny(repr_transparent_external_private_fields)]
2+
3+
#[repr(C)]
4+
pub struct ReprC1Zst {
5+
pub _f: (),
6+
}
7+
8+
pub type Sized = i32;
9+
10+
#[repr(transparent)]
11+
pub struct T1(ReprC1Zst);
12+
#[repr(transparent)]
13+
pub struct T2((), ReprC1Zst);
14+
#[repr(transparent)]
15+
pub struct T3(ReprC1Zst, ());
16+
17+
#[repr(transparent)]
18+
pub struct T5(Sized, ReprC1Zst);
19+
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
20+
//~| WARN this was previously accepted by the compiler
21+
22+
#[repr(transparent)]
23+
pub struct T6(ReprC1Zst, Sized);
24+
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
25+
//~| WARN this was previously accepted by the compiler
26+
27+
#[repr(transparent)]
28+
pub struct T7(T1, Sized); // still wrong, even when the repr(C) is hidden inside another type
29+
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
30+
//~| WARN this was previously accepted by the compiler
31+
32+
fn main() {}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
error: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
2+
--> $DIR/repr-transparent-repr-c.rs:18:22
3+
|
4+
LL | pub struct T5(Sized, ReprC1Zst);
5+
| ^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
9+
= note: this struct contains `ReprC1Zst`, which is marked with `#[repr(C)]`, and makes it not a breaking change to become non-zero-sized in the future.
10+
note: the lint level is defined here
11+
--> $DIR/repr-transparent-repr-c.rs:1:9
12+
|
13+
LL | #![deny(repr_transparent_external_private_fields)]
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
16+
error: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
17+
--> $DIR/repr-transparent-repr-c.rs:23:15
18+
|
19+
LL | pub struct T6(ReprC1Zst, Sized);
20+
| ^^^^^^^^^
21+
|
22+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
23+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
24+
= note: this struct contains `ReprC1Zst`, which is marked with `#[repr(C)]`, and makes it not a breaking change to become non-zero-sized in the future.
25+
26+
error: zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
27+
--> $DIR/repr-transparent-repr-c.rs:28:15
28+
|
29+
LL | pub struct T7(T1, Sized); // still wrong, even when the repr(C) is hidden inside another type
30+
| ^^
31+
|
32+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
33+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
34+
= note: this struct contains `ReprC1Zst`, which is marked with `#[repr(C)]`, and makes it not a breaking change to become non-zero-sized in the future.
35+
36+
error: aborting due to 3 previous errors
37+

0 commit comments

Comments
 (0)