Skip to content

Commit 56b10ba

Browse files
committed
repr(transparent): do not consider repr(C) or uninhabited types to be 1-ZST
1 parent 28d0a4a commit 56b10ba

File tree

8 files changed

+207
-148
lines changed

8 files changed

+207
-148
lines changed

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use rustc_hir::def::{CtorKind, DefKind};
1212
use rustc_hir::{LangItem, Node, attrs, find_attr, intravisit};
1313
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1414
use rustc_infer::traits::{Obligation, ObligationCauseCode, WellFormedLoc};
15-
use rustc_lint_defs::builtin::{
16-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, UNSUPPORTED_CALLING_CONVENTIONS,
17-
};
15+
use rustc_lint_defs::builtin::UNSUPPORTED_CALLING_CONVENTIONS;
1816
use rustc_middle::hir::nested_filter;
1917
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
2018
use rustc_middle::middle::stability::EvalResult;
@@ -1510,32 +1508,46 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15101508
return;
15111509
}
15121510

1511+
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
1512+
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
1513+
// fields or `repr(C)`. We call those fields "unsuited".
1514+
struct FieldInfo<'tcx> {
1515+
span: Span,
1516+
trivial: bool,
1517+
unsuited: Option<UnsuitedInfo<'tcx>>,
1518+
}
1519+
struct UnsuitedInfo<'tcx> {
1520+
/// The source of the problem, a type that is found somewhere within the field type.
1521+
ty: Ty<'tcx>,
1522+
reason: UnsuitedReason,
1523+
}
1524+
enum UnsuitedReason {
1525+
NonExhaustive,
1526+
PrivateField,
1527+
ReprC,
1528+
Uninhabited,
1529+
}
1530+
15131531
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.
15161532
let field_infos = adt.all_fields().map(|field| {
15171533
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
15181534
let layout = tcx.layout_of(typing_env.as_query_input(ty));
15191535
// We are currently checking the type this field came from, so it must be local
15201536
let span = tcx.hir_span_if_local(field.did).unwrap();
15211537
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
15221538
if !trivial {
1523-
return (span, trivial, None);
1539+
// No need to even compute `unsuited`.
1540+
return FieldInfo { span, trivial, unsuited: None };
15241541
}
1525-
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`.
15261542

1527-
fn check_non_exhaustive<'tcx>(
1543+
fn check_unsuited<'tcx>(
15281544
tcx: TyCtxt<'tcx>,
1529-
typing_env: ty::TypingEnv<'tcx>,
1530-
t: Ty<'tcx>,
1531-
) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> {
1532-
// 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),
1545+
adt: DefId,
1546+
ty: Ty<'tcx>,
1547+
) -> ControlFlow<UnsuitedInfo<'tcx>> {
1548+
match ty.kind() {
1549+
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, adt, t)),
1550+
ty::Array(ty, _) => check_unsuited(tcx, adt, *ty),
15391551
ty::Adt(def, args) => {
15401552
if !def.did().is_local()
15411553
&& !find_attr!(
@@ -1550,28 +1562,44 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15501562
.any(ty::VariantDef::is_field_list_non_exhaustive);
15511563
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
15521564
if non_exhaustive || has_priv {
1553-
return ControlFlow::Break((
1554-
def.descr(),
1555-
def.did(),
1556-
args,
1557-
non_exhaustive,
1558-
));
1565+
return ControlFlow::Break(UnsuitedInfo {
1566+
ty,
1567+
reason: if non_exhaustive {
1568+
UnsuitedReason::NonExhaustive
1569+
} else {
1570+
UnsuitedReason::PrivateField
1571+
},
1572+
});
15591573
}
15601574
}
1575+
if def.repr().c() {
1576+
return ControlFlow::Break(UnsuitedInfo {
1577+
ty,
1578+
reason: UnsuitedReason::ReprC,
1579+
});
1580+
}
15611581
def.all_fields()
15621582
.map(|field| field.ty(tcx, args))
1563-
.try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1583+
.try_for_each(|t| check_unsuited(tcx, adt, t))
15641584
}
15651585
_ => ControlFlow::Continue(()),
15661586
}
15671587
}
15681588

1569-
(span, trivial, check_non_exhaustive(tcx, typing_env, ty).break_value())
1589+
FieldInfo {
1590+
span,
1591+
trivial,
1592+
unsuited: check_unsuited(tcx, adt.did(), ty).break_value().or_else(|| {
1593+
// We don't need to check this recursively, a single top-level check suffices.
1594+
let uninhabited = layout.is_ok_and(|layout| layout.is_uninhabited());
1595+
uninhabited.then_some(UnsuitedInfo { ty, reason: UnsuitedReason::Uninhabited })
1596+
}),
1597+
}
15701598
});
15711599

15721600
let non_trivial_fields = field_infos
15731601
.clone()
1574-
.filter_map(|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None });
1602+
.filter_map(|field| if !field.trivial { Some(field.span) } else { None });
15751603
let non_trivial_count = non_trivial_fields.clone().count();
15761604
if non_trivial_count >= 2 {
15771605
bad_non_zero_sized_fields(
@@ -1583,36 +1611,34 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15831611
);
15841612
return;
15851613
}
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 {
1614+
1615+
let mut prev_unsuited_1zst = false;
1616+
for field in field_infos {
1617+
if let Some(unsuited) = field.unsuited {
1618+
assert!(field.trivial);
15891619
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
15901620
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1591-
if non_trivial_count > 0 || prev_non_exhaustive_1zst {
1592-
tcx.node_span_lint(
1593-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
1594-
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
1595-
span,
1596-
|lint| {
1597-
lint.primary_message(
1598-
"zero-sized fields in `repr(transparent)` cannot \
1621+
if non_trivial_count > 0 || prev_unsuited_1zst {
1622+
let mut diag = tcx.dcx().struct_span_err(
1623+
field.span,
1624+
"zero-sized fields in `repr(transparent)` cannot \
15991625
contain external non-exhaustive types",
1600-
);
1601-
let note = if non_exhaustive {
1602-
"is marked with `#[non_exhaustive]`"
1603-
} else {
1604-
"contains private fields"
1605-
};
1606-
let field_ty = tcx.def_path_str_with_args(def_id, args);
1607-
lint.note(format!(
1608-
"this {descr} contains `{field_ty}`, which {note}, \
1626+
);
1627+
let note = match unsuited.reason {
1628+
UnsuitedReason::NonExhaustive => "is marked with `#[non_exhaustive]`",
1629+
UnsuitedReason::PrivateField => "contains private fields",
1630+
UnsuitedReason::ReprC => "is marked with `#[repr(C)]`",
1631+
UnsuitedReason::Uninhabited => "is not inhabited",
1632+
};
1633+
diag.note(format!(
1634+
"this field contains `{field_ty}`, which {note}, \
16091635
and makes it not a breaking change to become \
1610-
non-zero-sized in the future."
1611-
));
1612-
},
1613-
)
1636+
non-zero-sized in the future.",
1637+
field_ty = unsuited.ty,
1638+
));
1639+
diag.emit();
16141640
} else {
1615-
prev_non_exhaustive_1zst = true;
1641+
prev_unsuited_1zst = true;
16161642
}
16171643
}
16181644
}

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/repr/repr-transparent-non-exhaustive.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,10 @@ pub struct T4(Sized, ExternalIndirection<(InternalPrivate, InternalNonExhaustive
4343
#[repr(transparent)]
4444
pub struct T5(Sized, Private);
4545
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
46-
//~| WARN this was previously accepted by the compiler
4746

4847
#[repr(transparent)]
4948
pub struct T6(Sized, NonExhaustive);
5049
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
51-
//~| WARN this was previously accepted by the compiler
5250

5351
#[repr(transparent)]
5452
pub struct T6a(Sized, <i32 as Trait>::Assoc); // normalizes to `NonExhaustive`
@@ -58,72 +56,58 @@ pub struct T6a(Sized, <i32 as Trait>::Assoc); // normalizes to `NonExhaustive`
5856
#[repr(transparent)]
5957
pub struct T7(Sized, NonExhaustiveEnum);
6058
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
61-
//~| WARN this was previously accepted by the compiler
6259

6360
#[repr(transparent)]
6461
pub struct T8(Sized, NonExhaustiveVariant);
6562
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
66-
//~| WARN this was previously accepted by the compiler
6763

6864
#[repr(transparent)]
6965
pub struct T9(Sized, InternalIndirection<Private>);
7066
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
71-
//~| WARN this was previously accepted by the compiler
7267

7368
#[repr(transparent)]
7469
pub struct T10(Sized, InternalIndirection<NonExhaustive>);
7570
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
76-
//~| WARN this was previously accepted by the compiler
7771

7872
#[repr(transparent)]
7973
pub struct T11(Sized, InternalIndirection<NonExhaustiveEnum>);
8074
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
81-
//~| WARN this was previously accepted by the compiler
8275

8376
#[repr(transparent)]
8477
pub struct T12(Sized, InternalIndirection<NonExhaustiveVariant>);
8578
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
86-
//~| WARN this was previously accepted by the compiler
8779

8880
#[repr(transparent)]
8981
pub struct T13(Sized, ExternalIndirection<Private>);
9082
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
91-
//~| WARN this was previously accepted by the compiler
9283

9384
#[repr(transparent)]
9485
pub struct T14(Sized, ExternalIndirection<NonExhaustive>);
9586
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
96-
//~| WARN this was previously accepted by the compiler
9787

9888
#[repr(transparent)]
9989
pub struct T15(Sized, ExternalIndirection<NonExhaustiveEnum>);
10090
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
101-
//~| WARN this was previously accepted by the compiler
10291

10392
#[repr(transparent)]
10493
pub struct T16(Sized, ExternalIndirection<NonExhaustiveVariant>);
10594
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
106-
//~| WARN this was previously accepted by the compiler
10795

10896
#[repr(transparent)]
10997
pub struct T17(NonExhaustive, Sized);
11098
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
111-
//~| WARN this was previously accepted by the compiler
11299

113100
#[repr(transparent)]
114101
pub struct T18(NonExhaustive, NonExhaustive);
115102
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
116-
//~| WARN this was previously accepted by the compiler
117103

118104
#[repr(transparent)]
119105
pub struct T19(NonExhaustive, Private);
120106
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
121-
//~| WARN this was previously accepted by the compiler
122107

123108
#[repr(transparent)]
124109
pub struct T19Flipped(Private, NonExhaustive);
125110
//~^ ERROR zero-sized fields in `repr(transparent)` cannot contain external non-exhaustive types
126-
//~| WARN this was previously accepted by the compiler
127111

128112
#[repr(transparent)]
129113
pub struct T20(NonExhaustive);

0 commit comments

Comments
 (0)