Skip to content

Commit 5f6abd8

Browse files
committed
Auto merge of #156114 - RalfJung:array-nontrivial, r=<try>
repr(transparent): never consider arrays trivial for the ABI
2 parents 818811b + 9adbd90 commit 5f6abd8

18 files changed

Lines changed: 383 additions & 876 deletions

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 109 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;
@@ -1689,39 +1689,6 @@ pub(super) fn check_packed_inner(
16891689
}
16901690

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

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

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

1790-
fn check_unsuited<'tcx>(
1737+
/// Check if this type is "trivial" for `repr(transparent)`. If not, return the reason why
1738+
/// and the problematic type.
1739+
fn is_trivial<'tcx>(
17911740
tcx: TyCtxt<'tcx>,
17921741
typing_env: ty::TypingEnv<'tcx>,
17931742
ty: Ty<'tcx>,
1794-
) -> ControlFlow<UnsuitedInfo<'tcx>> {
1743+
) -> ControlFlow<NonTrivialReason<'tcx>> {
17951744
// We can encounter projections during traversal, so ensure the type is normalized.
17961745
let ty =
17971746
tcx.try_normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty)).unwrap_or(ty);
17981747
match ty.kind() {
1799-
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
1800-
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
1748+
ty::Tuple(list) => list.iter().try_for_each(|t| is_trivial(tcx, typing_env, t)),
1749+
ty::Array(..) => {
1750+
// Arrays are meant to be ABI-compatible with what happens in other languages,
1751+
// so we should not promise that they are "trivial".
1752+
return ControlFlow::Break(NonTrivialReason::Array);
1753+
}
18011754
ty::Adt(def, args) => {
18021755
if !def.did().is_local() && !find_attr!(tcx, def.did(), RustcPubTransparent(_)) {
18031756
let non_exhaustive = def.is_variant_list_non_exhaustive()
18041757
|| def.variants().iter().any(ty::VariantDef::is_field_list_non_exhaustive);
1758+
if non_exhaustive {
1759+
return ControlFlow::Break(NonTrivialReason::NonExhaustive { ty });
1760+
}
18051761
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
1806-
if non_exhaustive || has_priv {
1807-
return ControlFlow::Break(UnsuitedInfo {
1808-
ty,
1809-
reason: if non_exhaustive {
1810-
UnsuitedReason::NonExhaustive
1811-
} else {
1812-
UnsuitedReason::PrivateField
1813-
},
1814-
});
1762+
if has_priv {
1763+
return ControlFlow::Break(NonTrivialReason::PrivateField { inside: ty });
18151764
}
18161765
}
18171766
if def.repr().c() {
1818-
return ControlFlow::Break(UnsuitedInfo { ty, reason: UnsuitedReason::ReprC });
1767+
return ControlFlow::Break(NonTrivialReason::ReprC { ty });
18191768
}
18201769
def.all_fields()
18211770
.map(|field| field.ty(tcx, args))
1822-
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
1771+
.try_for_each(|t| is_trivial(tcx, typing_env, t))
18231772
}
18241773
_ => ControlFlow::Continue(()),
18251774
}
18261775
}
18271776

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

compiler/rustc_hir_analysis/src/check/mod.rs

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

593-
/// Emit an error when encountering two or more non-zero-sized fields in a transparent
594-
/// enum.
595-
fn bad_non_zero_sized_fields<'tcx>(
596-
tcx: TyCtxt<'tcx>,
597-
adt: ty::AdtDef<'tcx>,
598-
field_count: usize,
599-
field_spans: impl Iterator<Item = Span>,
600-
sp: Span,
601-
) {
602-
if adt.is_enum() {
603-
tcx.dcx().emit_err(errors::TransparentNonZeroSizedEnum {
604-
span: sp,
605-
spans: field_spans.collect(),
606-
field_count,
607-
desc: adt.descr(),
608-
});
609-
} else {
610-
tcx.dcx().emit_err(errors::TransparentNonZeroSized {
611-
span: sp,
612-
spans: field_spans.collect(),
613-
field_count,
614-
desc: adt.descr(),
615-
});
616-
}
617-
}
618-
619593
// FIXME: Consider moving this method to a more fitting place.
620594
pub fn potentially_plural_count(count: usize, word: &str) -> String {
621595
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
@@ -1006,30 +1006,6 @@ pub(crate) struct TransparentEnumVariant {
10061006
pub path: String,
10071007
}
10081008

1009-
#[derive(Diagnostic)]
1010-
#[diag("the variant of a transparent {$desc} needs at most one field with non-trivial size or alignment, but has {$field_count}", code = E0690)]
1011-
pub(crate) struct TransparentNonZeroSizedEnum<'a> {
1012-
#[primary_span]
1013-
#[label("needs at most one field with non-trivial size or alignment, but has {$field_count}")]
1014-
pub span: Span,
1015-
#[label("this field has non-zero size or requires alignment")]
1016-
pub spans: Vec<Span>,
1017-
pub field_count: usize,
1018-
pub desc: &'a str,
1019-
}
1020-
1021-
#[derive(Diagnostic)]
1022-
#[diag("transparent {$desc} needs at most one field with non-trivial size or alignment, but has {$field_count}", code = E0690)]
1023-
pub(crate) struct TransparentNonZeroSized<'a> {
1024-
#[primary_span]
1025-
#[label("needs at most one field with non-trivial size or alignment, but has {$field_count}")]
1026-
pub span: Span,
1027-
#[label("this field has non-zero size or requires alignment")]
1028-
pub spans: Vec<Span>,
1029-
pub field_count: usize,
1030-
pub desc: &'a str,
1031-
}
1032-
10331009
#[derive(Diagnostic)]
10341010
#[diag("extern static is too large for the target architecture")]
10351011
pub(crate) struct TooLargeStatic {

compiler/rustc_lint/src/lib.rs

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

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

649655
fn register_internals(store: &mut LintStore) {

0 commit comments

Comments
 (0)