Skip to content

Commit 3bb6c15

Browse files
committed
make repr_transparent_non_zst_fields a hard error
1 parent 12f35ad commit 3bb6c15

18 files changed

Lines changed: 375 additions & 876 deletions

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 101 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_hir::def::{CtorKind, DefKind};
1111
use rustc_hir::{LangItem, Node, find_attr, intravisit};
1212
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1313
use rustc_infer::traits::{Obligation, ObligationCauseCode, WellFormedLoc};
14-
use rustc_lint_defs::builtin::{REPR_TRANSPARENT_NON_ZST_FIELDS, UNSUPPORTED_CALLING_CONVENTIONS};
14+
use rustc_lint_defs::builtin::UNSUPPORTED_CALLING_CONVENTIONS;
1515
use rustc_macros::Diagnostic;
1616
use rustc_middle::hir::nested_filter;
1717
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
@@ -1686,39 +1686,6 @@ pub(super) fn check_packed_inner(
16861686
}
16871687

16881688
pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) {
1689-
struct ZeroSizedFieldReprTransparentIncompatibility<'tcx> {
1690-
unsuited: UnsuitedInfo<'tcx>,
1691-
}
1692-
1693-
impl<'a, 'tcx> Diagnostic<'a, ()> for ZeroSizedFieldReprTransparentIncompatibility<'tcx> {
1694-
fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
1695-
let Self { unsuited } = self;
1696-
let (title, note) = match unsuited.reason {
1697-
UnsuitedReason::NonExhaustive => (
1698-
"external non-exhaustive types",
1699-
"is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.",
1700-
),
1701-
UnsuitedReason::PrivateField => (
1702-
"external types with private fields",
1703-
"contains private fields, so it could become non-zero-sized in the future.",
1704-
),
1705-
UnsuitedReason::ReprC => (
1706-
"`repr(C)` types",
1707-
"is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.",
1708-
),
1709-
};
1710-
Diag::new(
1711-
dcx,
1712-
level,
1713-
format!("zero-sized fields in `repr(transparent)` cannot contain {title}"),
1714-
)
1715-
.with_note(format!(
1716-
"this field contains `{field_ty}`, which {note}",
1717-
field_ty = unsuited.ty,
1718-
))
1719-
}
1720-
}
1721-
17221689
if !adt.repr().transparent() {
17231690
return;
17241691
}
@@ -1738,107 +1705,136 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
17381705
// Don't bother checking the fields.
17391706
return;
17401707
}
1708+
let variant = adt.variant(VariantIdx::ZERO);
17411709

1742-
let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
1743-
// For each field, figure out if it has "trivial" layout (i.e., is a 1-ZST).
1744-
struct FieldInfo<'tcx> {
1745-
span: Span,
1746-
trivial: bool,
1747-
ty: Ty<'tcx>,
1748-
}
1749-
1750-
let field_infos = adt.all_fields().map(|field| {
1751-
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
1752-
let layout = tcx.layout_of(typing_env.as_query_input(ty));
1753-
// We are currently checking the type this field came from, so it must be local
1754-
let span = tcx.hir_span_if_local(field.did).unwrap();
1755-
let trivial = layout.is_ok_and(|layout| layout.is_1zst());
1756-
FieldInfo { span, trivial, ty }
1757-
});
1758-
1759-
let non_trivial_fields = field_infos
1760-
.clone()
1761-
.filter_map(|field| if !field.trivial { Some(field.span) } else { None });
1762-
let non_trivial_count = non_trivial_fields.clone().count();
1763-
if non_trivial_count >= 2 {
1764-
bad_non_zero_sized_fields(
1765-
tcx,
1766-
adt,
1767-
non_trivial_count,
1768-
non_trivial_fields,
1769-
tcx.def_span(adt.did()),
1770-
);
1710+
if variant.fields.len() <= 1 {
1711+
// No need to check when there's at most one field.
17711712
return;
17721713
}
17731714

1774-
// Even some 1-ZST fields are not allowed though, if they have `non_exhaustive` or private
1775-
// fields or `repr(C)`. We call those fields "unsuited".
1776-
struct UnsuitedInfo<'tcx> {
1777-
/// The source of the problem, a type that is found somewhere within the field type.
1778-
ty: Ty<'tcx>,
1779-
reason: UnsuitedReason,
1780-
}
1781-
enum UnsuitedReason {
1782-
NonExhaustive,
1783-
PrivateField,
1784-
ReprC,
1715+
let typing_env = ty::TypingEnv::non_body_analysis(tcx, adt.did());
1716+
1717+
/// We call a field "trivial" for `repr(transparent)` purposes if it can be ignored.
1718+
/// IOW, `repr(transparent)` is allowed if there is at most one non-trivial field.
1719+
/// This enum captuers all the reasons why a field might not be "trivial".
1720+
enum NonTrivialReason<'tcx> {
1721+
UnknownLayout,
1722+
NonZeroSized,
1723+
NonTrivialAlignment,
1724+
PrivateField { inside: Ty<'tcx> },
1725+
NonExhaustive { ty: Ty<'tcx> },
1726+
ReprC { ty: Ty<'tcx> },
1727+
}
1728+
struct NonTrivialFieldInfo<'tcx> {
1729+
span: Span,
1730+
reason: NonTrivialReason<'tcx>,
17851731
}
17861732

1787-
fn check_unsuited<'tcx>(
1733+
/// Check if this type is "trivial" for `repr(transparent)`. If not, return the reason why
1734+
/// and the problematic type.
1735+
fn is_trivial<'tcx>(
17881736
tcx: TyCtxt<'tcx>,
17891737
typing_env: ty::TypingEnv<'tcx>,
17901738
ty: Ty<'tcx>,
1791-
) -> ControlFlow<UnsuitedInfo<'tcx>> {
1739+
) -> ControlFlow<NonTrivialReason<'tcx>> {
17921740
// We can encounter projections during traversal, so ensure the type is normalized.
17931741
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
17941742
match ty.kind() {
1795-
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
1796-
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
1743+
ty::Tuple(list) => list.iter().try_for_each(|t| is_trivial(tcx, typing_env, t)),
1744+
ty::Array(ty, _) => is_trivial(tcx, typing_env, *ty),
17971745
ty::Adt(def, args) => {
17981746
if !def.did().is_local() && !find_attr!(tcx, def.did(), RustcPubTransparent(_)) {
17991747
let non_exhaustive = def.is_variant_list_non_exhaustive()
18001748
|| def.variants().iter().any(ty::VariantDef::is_field_list_non_exhaustive);
1749+
if non_exhaustive {
1750+
return ControlFlow::Break(NonTrivialReason::NonExhaustive { ty });
1751+
}
18011752
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
1802-
if non_exhaustive || has_priv {
1803-
return ControlFlow::Break(UnsuitedInfo {
1804-
ty,
1805-
reason: if non_exhaustive {
1806-
UnsuitedReason::NonExhaustive
1807-
} else {
1808-
UnsuitedReason::PrivateField
1809-
},
1810-
});
1753+
if has_priv {
1754+
return ControlFlow::Break(NonTrivialReason::PrivateField { inside: ty });
18111755
}
18121756
}
18131757
if def.repr().c() {
1814-
return ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::ReprC });
1758+
return ControlFlow::Break(NonTrivialReason::ReprC { ty });
18151759
}
18161760
def.all_fields()
18171761
.map(|field| field.ty(tcx, args))
1818-
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
1762+
.try_for_each(|t| is_trivial(tcx, typing_env, t))
18191763
}
18201764
_ => ControlFlow::Continue(()),
18211765
}
18221766
}
18231767

1824-
let mut prev_unsuited_1zst = false;
1825-
for field in field_infos {
1826-
if field.trivial
1827-
&& let Some(unsuited) = check_unsuited(tcx, typing_env, field.ty).break_value()
1828-
{
1829-
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
1830-
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1831-
if non_trivial_count > 0 || prev_unsuited_1zst {
1832-
tcx.emit_node_span_lint(
1833-
REPR_TRANSPARENT_NON_ZST_FIELDS,
1834-
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
1835-
field.span,
1836-
ZeroSizedFieldReprTransparentIncompatibility { unsuited },
1837-
);
1838-
} else {
1839-
prev_unsuited_1zst = true;
1768+
let non_trivial_fields = variant
1769+
.fields
1770+
.iter()
1771+
.filter_map(|field| {
1772+
let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did));
1773+
let layout = tcx.layout_of(typing_env.as_query_input(ty));
1774+
// We are currently checking the type this field came from, so it must be local
1775+
let span = tcx.hir_span_if_local(field.did).unwrap();
1776+
// Rule out non-1ZST
1777+
if !layout.is_ok_and(|layout| layout.is_1zst()) {
1778+
let reason = match layout {
1779+
Err(_) => NonTrivialReason::UnknownLayout,
1780+
Ok(layout) => {
1781+
if !(layout.is_sized() && layout.size.bytes() == 0) {
1782+
NonTrivialReason::NonZeroSized
1783+
} else {
1784+
NonTrivialReason::NonTrivialAlignment
1785+
}
1786+
}
1787+
};
1788+
return Some(NonTrivialFieldInfo { span, reason });
1789+
}
1790+
// Recursively check for other things that have to be ruled out.
1791+
if let Some(reason) = is_trivial(tcx, typing_env, ty).break_value() {
1792+
return Some(NonTrivialFieldInfo { span, reason });
18401793
}
1794+
// Otherwise,
1795+
None
1796+
})
1797+
.collect::<Vec<_>>();
1798+
1799+
if non_trivial_fields.len() >= 2 {
1800+
let count = non_trivial_fields.len();
1801+
let desc = if adt.is_enum() {
1802+
format_args!("the variant of a transparent {}", adt.descr())
1803+
} else {
1804+
format_args!("transparent {}", adt.descr())
1805+
};
1806+
let ty_span = tcx.def_span(adt.did());
1807+
let mut diag = tcx.dcx().struct_span_err(
1808+
ty_span,
1809+
format!("{desc} needs at most one non-trivial field, but has {count}"),
1810+
);
1811+
diag.code(E0690);
1812+
1813+
// Label for the type.
1814+
diag.span_label(ty_span, format!("needs at most one non-trivial field, but has {count}"));
1815+
// Label for each non-trivial field.
1816+
for field in non_trivial_fields {
1817+
let msg = match field.reason {
1818+
NonTrivialReason::UnknownLayout => {
1819+
format!("this field is generic and hence may have non-zero size")
1820+
}
1821+
NonTrivialReason::NonZeroSized => format!("this field has non-zero size"),
1822+
NonTrivialReason::NonTrivialAlignment => format!("this field requires alignment"),
1823+
NonTrivialReason::PrivateField { inside } => format!(
1824+
"this field contains `{inside}`, which has private fields, so it could become non-zero-sized in the future"
1825+
),
1826+
NonTrivialReason::NonExhaustive { ty } => format!(
1827+
"this field contains `{ty}`, which is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future"
1828+
),
1829+
NonTrivialReason::ReprC { ty } => format!(
1830+
"this field contains `{ty}`, which is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets"
1831+
),
1832+
};
1833+
diag.span_label(field.span, msg);
18411834
}
1835+
1836+
diag.emit();
1837+
return;
18421838
}
18431839
}
18441840

compiler/rustc_hir_analysis/src/check/mod.rs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -584,32 +584,6 @@ fn bad_variant_count<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>, sp: Span, d
584584
});
585585
}
586586

587-
/// Emit an error when encountering two or more non-zero-sized fields in a transparent
588-
/// enum.
589-
fn bad_non_zero_sized_fields<'tcx>(
590-
tcx: TyCtxt<'tcx>,
591-
adt: ty::AdtDef<'tcx>,
592-
field_count: usize,
593-
field_spans: impl Iterator<Item = Span>,
594-
sp: Span,
595-
) {
596-
if adt.is_enum() {
597-
tcx.dcx().emit_err(errors::TransparentNonZeroSizedEnum {
598-
span: sp,
599-
spans: field_spans.collect(),
600-
field_count,
601-
desc: adt.descr(),
602-
});
603-
} else {
604-
tcx.dcx().emit_err(errors::TransparentNonZeroSized {
605-
span: sp,
606-
spans: field_spans.collect(),
607-
field_count,
608-
desc: adt.descr(),
609-
});
610-
}
611-
}
612-
613587
// FIXME: Consider moving this method to a more fitting place.
614588
pub fn potentially_plural_count(count: usize, word: &str) -> String {
615589
format!("{} {}{}", count, word, pluralize!(count))

compiler/rustc_hir_analysis/src/errors.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -996,30 +996,6 @@ pub(crate) struct TransparentEnumVariant {
996996
pub path: String,
997997
}
998998

999-
#[derive(Diagnostic)]
1000-
#[diag("the variant of a transparent {$desc} needs at most one field with non-trivial size or alignment, but has {$field_count}", code = E0690)]
1001-
pub(crate) struct TransparentNonZeroSizedEnum<'a> {
1002-
#[primary_span]
1003-
#[label("needs at most one field with non-trivial size or alignment, but has {$field_count}")]
1004-
pub span: Span,
1005-
#[label("this field has non-zero size or requires alignment")]
1006-
pub spans: Vec<Span>,
1007-
pub field_count: usize,
1008-
pub desc: &'a str,
1009-
}
1010-
1011-
#[derive(Diagnostic)]
1012-
#[diag("transparent {$desc} needs at most one field with non-trivial size or alignment, but has {$field_count}", code = E0690)]
1013-
pub(crate) struct TransparentNonZeroSized<'a> {
1014-
#[primary_span]
1015-
#[label("needs at most one field with non-trivial size or alignment, but has {$field_count}")]
1016-
pub span: Span,
1017-
#[label("this field has non-zero size or requires alignment")]
1018-
pub spans: Vec<Span>,
1019-
pub field_count: usize,
1020-
pub desc: &'a str,
1021-
}
1022-
1023999
#[derive(Diagnostic)]
10241000
#[diag("extern static is too large for the target architecture")]
10251001
pub(crate) struct TooLargeStatic {

compiler/rustc_lint/src/lib.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,6 @@ fn register_builtins(store: &mut LintStore) {
369369
store.register_renamed("static_mut_ref", "static_mut_refs");
370370
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
371371
store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes");
372-
store.register_renamed(
373-
"repr_transparent_external_private_fields",
374-
"repr_transparent_non_zst_fields",
375-
);
376372

377373
// These were moved to tool lints, but rustc still sees them when compiling normally, before
378374
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use
@@ -643,6 +639,16 @@ fn register_builtins(store: &mut LintStore) {
643639
);
644640
store.register_removed("wasm_c_abi", "the wasm C ABI has been fixed");
645641
store.register_removed("soft_unstable", "the general soft-unstable mechanism has been removed");
642+
store.register_removed(
643+
"repr_transparent_external_private_fields",
644+
"converted into hard error, \
645+
see <https://github.com/rust-lang/rust/issues/78586> for more information",
646+
);
647+
store.register_removed(
648+
"repr_transparent_non_zst_fields",
649+
"converted into hard error, \
650+
see <https://github.com/rust-lang/rust/issues/78586> for more information",
651+
);
646652
}
647653

648654
fn register_internals(store: &mut LintStore) {

0 commit comments

Comments
 (0)