Skip to content

Commit 091a9d5

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

File tree

13 files changed

+582
-123
lines changed

13 files changed

+582
-123
lines changed

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 68 additions & 43 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::{REPR_TRANSPARENT_NON_ZST_FIELDS, 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;
@@ -1511,31 +1509,46 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15111509
}
15121510

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

1527-
fn check_non_exhaustive<'tcx>(
1542+
fn check_unsuited<'tcx>(
15281543
tcx: TyCtxt<'tcx>,
15291544
typing_env: ty::TypingEnv<'tcx>,
1530-
t: Ty<'tcx>,
1531-
) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> {
1545+
ty: Ty<'tcx>,
1546+
) -> ControlFlow<UnsuitedInfo<'tcx>> {
15321547
// 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),
1548+
let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
1549+
match ty.kind() {
1550+
ty::Tuple(list) => list.iter().try_for_each(|t| check_unsuited(tcx, typing_env, t)),
1551+
ty::Array(ty, _) => check_unsuited(tcx, typing_env, *ty),
15391552
ty::Adt(def, args) => {
15401553
if !def.did().is_local()
15411554
&& !find_attr!(
@@ -1550,28 +1563,36 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15501563
.any(ty::VariantDef::is_field_list_non_exhaustive);
15511564
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
15521565
if non_exhaustive || has_priv {
1553-
return ControlFlow::Break((
1554-
def.descr(),
1555-
def.did(),
1556-
args,
1557-
non_exhaustive,
1558-
));
1566+
return ControlFlow::Break(UnsuitedInfo {
1567+
ty,
1568+
reason: if non_exhaustive {
1569+
UnsuitedReason::NonExhaustive
1570+
} else {
1571+
UnsuitedReason::PrivateField
1572+
},
1573+
});
15591574
}
15601575
}
1576+
if def.repr().c() {
1577+
return ControlFlow::Break(UnsuitedInfo {
1578+
ty,
1579+
reason: UnsuitedReason::ReprC,
1580+
});
1581+
}
15611582
def.all_fields()
15621583
.map(|field| field.ty(tcx, args))
1563-
.try_for_each(|t| check_non_exhaustive(tcx, typing_env, t))
1584+
.try_for_each(|t| check_unsuited(tcx, typing_env, t))
15641585
}
15651586
_ => ControlFlow::Continue(()),
15661587
}
15671588
}
15681589

1569-
(span, trivial, check_non_exhaustive(tcx, typing_env, ty).break_value())
1590+
FieldInfo { span, trivial, unsuited: check_unsuited(tcx, typing_env, ty).break_value() }
15701591
});
15711592

15721593
let non_trivial_fields = field_infos
15731594
.clone()
1574-
.filter_map(|(span, trivial, _non_exhaustive)| if !trivial { Some(span) } else { None });
1595+
.filter_map(|field| if !field.trivial { Some(field.span) } else { None });
15751596
let non_trivial_count = non_trivial_fields.clone().count();
15761597
if non_trivial_count >= 2 {
15771598
bad_non_zero_sized_fields(
@@ -1583,36 +1604,40 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>)
15831604
);
15841605
return;
15851606
}
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 {
1607+
1608+
let mut prev_unsuited_1zst = false;
1609+
for field in field_infos {
1610+
if let Some(unsuited) = field.unsuited {
1611+
assert!(field.trivial);
15891612
// If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts.
15901613
// Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst.
1591-
if non_trivial_count > 0 || prev_non_exhaustive_1zst {
1614+
if non_trivial_count > 0 || prev_unsuited_1zst {
15921615
tcx.node_span_lint(
1593-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
1616+
REPR_TRANSPARENT_NON_ZST_FIELDS,
15941617
tcx.local_def_id_to_hir_id(adt.did().expect_local()),
1595-
span,
1618+
field.span,
15961619
|lint| {
1620+
let title = match unsuited.reason {
1621+
UnsuitedReason::NonExhaustive => "external non-exhaustive types",
1622+
UnsuitedReason::PrivateField => "external types with private fields",
1623+
UnsuitedReason::ReprC => "`repr(C)` types",
1624+
};
15971625
lint.primary_message(
1598-
"zero-sized fields in `repr(transparent)` cannot \
1599-
contain external non-exhaustive types",
1626+
format!("zero-sized fields in `repr(transparent)` cannot contain {title}"),
16001627
);
1601-
let note = if non_exhaustive {
1602-
"is marked with `#[non_exhaustive]`"
1603-
} else {
1604-
"contains private fields"
1628+
let note = match unsuited.reason {
1629+
UnsuitedReason::NonExhaustive => "is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.",
1630+
UnsuitedReason::PrivateField => "contains private fields, so it could become non-zero-sized in the future.",
1631+
UnsuitedReason::ReprC => "is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.",
16051632
};
1606-
let field_ty = tcx.def_path_str_with_args(def_id, args);
16071633
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."
1634+
"this field contains `{field_ty}`, which {note}",
1635+
field_ty = unsuited.ty,
16111636
));
16121637
},
1613-
)
1638+
);
16141639
} else {
1615-
prev_non_exhaustive_1zst = true;
1640+
prev_unsuited_1zst = true;
16161641
}
16171642
}
16181643
}

compiler/rustc_lint/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ fn register_builtins(store: &mut LintStore) {
362362
store.register_renamed("static_mut_ref", "static_mut_refs");
363363
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
364364
store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes");
365+
store.register_renamed(
366+
"repr_transparent_external_private_fields",
367+
"repr_transparent_non_zst_fields",
368+
);
365369

366370
// These were moved to tool lints, but rustc still sees them when compiling normally, before
367371
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ declare_lint_pass! {
8686
REFINING_IMPL_TRAIT_INTERNAL,
8787
REFINING_IMPL_TRAIT_REACHABLE,
8888
RENAMED_AND_REMOVED_LINTS,
89-
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
89+
REPR_TRANSPARENT_NON_ZST_FIELDS,
9090
RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
9191
RUST_2021_INCOMPATIBLE_OR_PATTERNS,
9292
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
@@ -3011,19 +3011,23 @@ declare_lint! {
30113011
}
30123012

30133013
declare_lint! {
3014-
/// The `repr_transparent_external_private_fields` lint
3014+
/// The `repr_transparent_non_zst_fields` lint
30153015
/// detects types marked `#[repr(transparent)]` that (transitively)
3016-
/// contain an external ZST type marked `#[non_exhaustive]` or containing
3017-
/// private fields
3016+
/// contain a type that is not guaranteed to remain a ZST type under all configurations.
30183017
///
30193018
/// ### Example
30203019
///
30213020
/// ```rust,ignore (needs external crate)
30223021
/// #![deny(repr_transparent_external_private_fields)]
30233022
/// use foo::NonExhaustiveZst;
30243023
///
3024+
/// #[repr(C)]
3025+
/// struct CZst([u8; 0]);
3026+
///
30253027
/// #[repr(transparent)]
30263028
/// struct Bar(u32, ([u32; 0], NonExhaustiveZst));
3029+
/// #[repr(transparent)]
3030+
/// struct Baz(u32, CZst);
30273031
/// ```
30283032
///
30293033
/// This will produce:
@@ -3042,26 +3046,39 @@ declare_lint! {
30423046
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30433047
/// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
30443048
/// = note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
3045-
/// = note: this struct contains `NonExhaustiveZst`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
3049+
/// = note: this field contains `NonExhaustiveZst`, which is marked with `#[non_exhaustive]`, so it could become non-zero-sized in the future.
3050+
///
3051+
/// error: zero-sized fields in repr(transparent) cannot contain `#[repr(C)]` types
3052+
/// --> src/main.rs:5:28
3053+
/// |
3054+
/// 5 | struct Baz(u32, CZst);
3055+
/// | ^^^^
3056+
/// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
3057+
/// = note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
3058+
/// = note: this field contains `CZst`, which is a `#[repr(C)]` type, so it is not guaranteed to be zero-sized on all targets.
30463059
/// ```
30473060
///
30483061
/// ### Explanation
30493062
///
3050-
/// Previous, Rust accepted fields that contain external private zero-sized types,
3051-
/// even though it should not be a breaking change to add a non-zero-sized field to
3052-
/// that private type.
3063+
/// Previous, Rust accepted fields that contain external private zero-sized types, even though
3064+
/// those types could gain a non-zero-sized field in a future, semver-compatible update.
3065+
///
3066+
/// Rust also accepted fields that contain `repr(C)` zero-sized types, even though those types
3067+
/// are not guaranteed to be zero-sized on all targets, and even though those types can
3068+
/// make a difference for the ABI (and therefore cannot be ignored by `repr(transparent)`).
30533069
///
30543070
/// This is a [future-incompatible] lint to transition this
30553071
/// to a hard error in the future. See [issue #78586] for more details.
30563072
///
30573073
/// [issue #78586]: https://github.com/rust-lang/rust/issues/78586
30583074
/// [future-incompatible]: ../index.md#future-incompatible-lints
3059-
pub REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
3060-
Warn,
3075+
pub REPR_TRANSPARENT_NON_ZST_FIELDS,
3076+
Deny,
30613077
"transparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields",
30623078
@future_incompatible = FutureIncompatibleInfo {
30633079
reason: FutureIncompatibilityReason::FutureReleaseError,
30643080
reference: "issue #78586 <https://github.com/rust-lang/rust/issues/78586>",
3081+
report_in_deps: true,
30653082
};
30663083
}
30673084

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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>);
3838
#[repr(transparent)]
3939
pub struct TransparentUnit<U>(f32, PhantomData<U>);
4040
#[repr(transparent)]
41+
#[allow(repr_transparent_non_zst_fields)]
4142
pub struct TransparentCustomZst(i32, ZeroSize);
4243

4344
#[repr(C)]

0 commit comments

Comments
 (0)