diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index b0a6922ff72bb..b49467ea1a168 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -10,7 +10,8 @@ use rustc_hir::{Node, intravisit}; use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt}; use rustc_infer::traits::{Obligation, ObligationCauseCode}; use rustc_lint_defs::builtin::{ - REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, UNSUPPORTED_FN_PTR_CALLING_CONVENTIONS, + REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, REPR_TRANSPARENT_UNINHABITED_FIELDS, + UNSUPPORTED_FN_PTR_CALLING_CONVENTIONS, }; use rustc_middle::hir::nested_filter; use rustc_middle::middle::resolve_bound_vars::ResolvedArg; @@ -1359,7 +1360,16 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) return; } - // For each field, figure out if it's known to have "trivial" layout (i.e., is a 1-ZST), with + let descr = adt.descr(); + + enum DisallowedFieldCause<'tcx> { + AdtNonExhaustive(AdtDef<'tcx>, GenericArgsRef<'tcx>), + AdtPrivateFields(AdtDef<'tcx>, GenericArgsRef<'tcx>), + AdtUninhabited(AdtDef<'tcx>, GenericArgsRef<'tcx>), + NeverTypeUninhabited, + } + + // For each field, figure out if it's known to have "trivial" layout (i.e., is an inhabited 1-ZST), with // "known" respecting #[non_exhaustive] attributes. let field_infos = adt.all_fields().map(|field| { let ty = field.ty(tcx, GenericArgs::identity_for_item(tcx, field.did)); @@ -1371,12 +1381,12 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) if !trivial { return (span, trivial, None); } - // Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`. + // Even some 1-ZST fields are not allowed though, if they have `non_exhaustive`, or are uninhabited. fn check_non_exhaustive<'tcx>( tcx: TyCtxt<'tcx>, t: Ty<'tcx>, - ) -> ControlFlow<(&'static str, DefId, GenericArgsRef<'tcx>, bool)> { + ) -> ControlFlow> { match t.kind() { ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)), ty::Array(ty, _) => check_non_exhaustive(tcx, *ty), @@ -1388,13 +1398,15 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) .variants() .iter() .any(ty::VariantDef::is_field_list_non_exhaustive); + if non_exhaustive { + return ControlFlow::Break(DisallowedFieldCause::AdtNonExhaustive( + *def, args, + )); + } let has_priv = def.all_fields().any(|f| !f.vis.is_public()); - if non_exhaustive || has_priv { - return ControlFlow::Break(( - def.descr(), - def.did(), - args, - non_exhaustive, + if has_priv { + return ControlFlow::Break(DisallowedFieldCause::AdtPrivateFields( + *def, args, )); } } @@ -1406,7 +1418,72 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) } } - (span, trivial, check_non_exhaustive(tcx, ty).break_value()) + fn check_uninhabited<'tcx>( + tcx: TyCtxt<'tcx>, + t: Ty<'tcx>, + ) -> ControlFlow> { + match t.kind() { + ty::Tuple(list) => list.iter().try_for_each(|t| check_uninhabited(tcx, t)), + ty::Array(ty, len) => { + // Allow length-0 arrays of uninhabited types + if let Some(0) = len.try_to_target_usize(tcx) { + return ControlFlow::Continue(()); + } + check_uninhabited(tcx, *ty) + } + ty::Adt(def, args) => { + // Union validity requirements are not decided yet. + // Currently, unions are always inhabited, but to be safe, + // here we check that at least one field is inhabited + if def.is_union() { + for field in def.all_fields() { + let t = field.ty(tcx, args); + let check_result = check_uninhabited(tcx, t); + if check_result.is_continue() { + return ControlFlow::Continue(()); + } + } + return ControlFlow::Break(DisallowedFieldCause::AdtUninhabited( + *def, args, + )); + } + + // If there is exactly one variant, recurse into it to give a more specific error message + // pointing at the uninhabited field instead of at the whole struct/enum. + if def.variants().len() == 1 { + return def + .all_fields() + .map(|field| field.ty(tcx, args)) + .try_for_each(|t| check_uninhabited(tcx, t)); + } + + // If every variant is uninhabited, then the whole ADT is uninhabited. + // This vacuously includes the case of an enum with no variants. + if def.variants().iter().all(|variant| { + variant + .fields + .iter() + .map(|field| field.ty(tcx, args)) + .any(|t| check_uninhabited(tcx, t).is_break()) + }) { + return ControlFlow::Break(DisallowedFieldCause::AdtUninhabited( + *def, args, + )); + } + ControlFlow::Continue(()) + } + ty::Never => ControlFlow::Break(DisallowedFieldCause::NeverTypeUninhabited), + _ => ControlFlow::Continue(()), + } + } + + ( + span, + trivial, + check_non_exhaustive(tcx, ty) + .break_value() + .or_else(|| check_uninhabited(tcx, ty).break_value()), + ) }); let non_trivial_fields = field_infos @@ -1423,36 +1500,60 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, adt: ty::AdtDef<'tcx>) ); return; } - let mut prev_non_exhaustive_1zst = false; - for (span, _trivial, non_exhaustive_1zst) in field_infos { - if let Some((descr, def_id, args, non_exhaustive)) = non_exhaustive_1zst { - // If there are any non-trivial fields, then there can be no non-exhaustive 1-zsts. - // Otherwise, it's only an issue if there's >1 non-exhaustive 1-zst. - if non_trivial_count > 0 || prev_non_exhaustive_1zst { + let mut prev_disallowed_1zst = false; + for (span, _trivial, disallowed_1zst) in field_infos { + if let Some(cause) = disallowed_1zst { + // If there are any non-trivial fields, then there can be no disallowed 1-zsts. + // Otherwise, it's only an issue if there's >1 disallowed 1-zst. + if non_trivial_count > 0 || prev_disallowed_1zst { + use DisallowedFieldCause as DFC; + + let (lint, msg) = match cause { + DFC::AdtNonExhaustive(..) | DFC::AdtPrivateFields(..) => ( + REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, + "zero-sized fields in `repr(transparent)` cannot \ + contain external non-exhaustive types", + ), + DFC::AdtUninhabited(..) | DFC::NeverTypeUninhabited => ( + REPR_TRANSPARENT_UNINHABITED_FIELDS, + "zero-sized fields in `repr(transparent)` cannot \ + be uninhabited", + ), + }; tcx.node_span_lint( - REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS, + lint, tcx.local_def_id_to_hir_id(adt.did().expect_local()), span, |lint| { - lint.primary_message( - "zero-sized fields in `repr(transparent)` cannot \ - contain external non-exhaustive types", - ); - let note = if non_exhaustive { - "is marked with `#[non_exhaustive]`" - } else { - "contains private fields" + lint.primary_message(msg); + let field_ty = match cause { + DFC::AdtNonExhaustive(adt_def, args) + | DFC::AdtPrivateFields(adt_def, args) + | DFC::AdtUninhabited(adt_def, args) => { + &tcx.def_path_str_with_args(adt_def.did(), args) + } + DFC::NeverTypeUninhabited => "!", }; - let field_ty = tcx.def_path_str_with_args(def_id, args); - lint.note(format!( - "this {descr} contains `{field_ty}`, which {note}, \ + let note = match cause { + DFC::AdtNonExhaustive(..) => { + "is marked with #[non_exhaustive], \ and makes it not a breaking change to become \ non-zero-sized in the future." - )); + } + DFC::AdtPrivateFields(..) => { + "contains private fields, \ + and makes it not a breaking change to become \ + non-zero-sized in the future." + } + DFC::AdtUninhabited(..) | DFC::NeverTypeUninhabited => { + "is uninhabited and affects its ABI." + } + }; + lint.note(format!("this {descr} contains `{field_ty}`, which {note}")); }, ) } else { - prev_non_exhaustive_1zst = true; + prev_disallowed_1zst = true; } } } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 9fc527a6a3ab3..701c940d24691 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3279,6 +3279,59 @@ declare_lint! { }; } +declare_lint! { + /// The `repr_transparent_uninhabited_fields` lint + /// detects types marked `#[repr(transparent)]` that (transitively) + /// contain an uninhabited ZST type. + /// + /// ### Example + /// + /// ```rust + /// #![deny(repr_transparent_uninhabited_fields)] + /// enum UninhabitedZst {} + /// + /// #[repr(transparent)] + /// struct Bar(u32, ([u32; 0], UninhabitedZst)); + /// ``` + /// + /// This will produce: + /// + /// ```text + /// error: zero-sized fields in repr(transparent) cannot be uninhabited + /// --> src/main.rs:5:28 + /// | + /// 5 | struct Bar(u32, ([u32; 0], UninhabitedZst)); + /// | ^^^^^^^^^^^^^^ + /// | + /// note: the lint level is defined here + /// --> src/main.rs:1:9 + /// | + /// 1 | #![deny(repr_transparent_uninhabited_fields)] + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + /// = note: for more information, see issue #135802 + /// = note: this struct contains `UninhabitedZst`, which is uninhabited and affects its ABI. + /// ``` + /// + /// ### Explanation + /// + /// Previous, Rust accepted fields that contain uninhabited zero-sized types, + /// even though such types are not intended to be allowed. + /// + /// This is a [future-incompatible] lint to transition this + /// to a hard error in the future. See [issue #135802] for more details. + /// + /// [issue #135802]: https://github.com/rust-lang/rust/issues/135802 + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub REPR_TRANSPARENT_UNINHABITED_FIELDS, + Warn, + "transparent type contains an uninhabited ZST", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reference: "issue #135802 ", + }; +} + declare_lint! { /// The `unstable_syntax_pre_expansion` lint detects the use of unstable /// syntax that is discarded during attribute expansion. diff --git a/tests/ui/repr/repr-transparent-uninhabited.rs b/tests/ui/repr/repr-transparent-uninhabited.rs new file mode 100644 index 0000000000000..23a3cab4ec5c8 --- /dev/null +++ b/tests/ui/repr/repr-transparent-uninhabited.rs @@ -0,0 +1,162 @@ +//@ check-pass +#![feature(never_type)] + +#[derive(Clone, Copy)] +enum EmptyEnum {} + +union InhabitedUnion { + pub unit: (), + pub never: EmptyEnum, +} + +union MaybeUninhabitedUnion { + pub never1: EmptyEnum, + pub never2: !, +} + +enum InhabitedEnum { + A, + B(EmptyEnum), + C(EmptyEnum, [u8; 0]), + D(EmptyEnum, EmptyEnum), +} + +enum UninhabitedOneVariantEnum { + A(EmptyEnum), +} + +enum UninhabitedMultiVariantEnum { + A(EmptyEnum), + B(EmptyEnum, [u8; 0]), + C(EmptyEnum, EmptyEnum), +} + +// Errors involving EmptyEnum should directly reference it + +#[repr(transparent)] +struct EmptyEnum1ZstDisallowed1(u32, EmptyEnum); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. +//~| NOTE: `#[warn(repr_transparent_uninhabited_fields)]` on by default + +#[repr(transparent)] +struct EmptyEnum1ZstDisallowed2(EmptyEnum, EmptyEnum); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct EmptyEnum1ZstValidAsWrapped([u8; 0], EmptyEnum); + +// Errors involving `UninhabitedOneVariantEnum` should refer to its field, +// since it has only one variant. + +#[repr(transparent)] +struct UninhabitedOneVariantEnum1ZstDisallowed1(u32, UninhabitedOneVariantEnum); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct UninhabitedOneVariantEnum1ZstDisallowed2( + UninhabitedOneVariantEnum, + UninhabitedOneVariantEnum, + //~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited + //~| WARN: this was previously accepted + //~| NOTE: for more information + //~| NOTE: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. +); + +#[repr(transparent)] +struct UninhabitedOneVariantEnum1ZstValidAsWrapped([u8; 0], UninhabitedOneVariantEnum); + +// Errors involving `UninhabitedMultiVariantEnum` should refer to itself, +// since it has multiple variants so no single variant is responsible for its uninhabitedness. + +#[repr(transparent)] +struct UninhabitedMultiVariantEnum1ZstDisallowed1(u32, UninhabitedMultiVariantEnum); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `UninhabitedMultiVariantEnum`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct UninhabitedMultiVariantEnum1ZstDisallowed2( + UninhabitedMultiVariantEnum, + UninhabitedMultiVariantEnum, + //~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited + //~| WARN: this was previously accepted + //~| NOTE: for more information + //~| NOTE: this struct contains `UninhabitedMultiVariantEnum`, which is uninhabited and affects its ABI. +); + +#[repr(transparent)] +struct UninhabitedMultiVariantEnum1ZstValidAsWrapped([u8; 0], UninhabitedMultiVariantEnum); + +// Errors involving the never type should refer to it. + +#[repr(transparent)] +struct NeverDisallowed1(u32, !); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `!`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct NeverDisallowed2(!, !); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `!`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct NeverValidAsWrapped([u8; 0], !); + +// Zero-length arrays of uninhabited 1-ZSTs should be allowed, +// as they are inhabited. + +#[repr(transparent)] +struct ArrayZeroValid1(u32, [EmptyEnum; 0]); + +#[repr(transparent)] +struct ArrayZeroValid2([EmptyEnum; 0], [EmptyEnum; 0]); + +// A 1-ZST enum with one inhabited variant can have other uninhabited variants +// and still be valid. + +#[repr(transparent)] +struct OneInhabitedVariantValid(u32, InhabitedEnum); + +// A union with an inhabited field is definitely inhabited. + +#[repr(transparent)] +struct InhabitedUnion1ZstValid1(u32, InhabitedUnion); + +#[repr(transparent)] +struct InhabitedUnion1ZstValid2(InhabitedUnion, InhabitedUnion); + +// A union without any inhabited fields *might* be considered uninhabited in the future. +// Currently all unions are treated as inhabited, so this is just future-proofing. + +#[repr(transparent)] +struct MaybeUninhabitedUnionUnion1ZstDisallowed1(u32, MaybeUninhabitedUnion); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `MaybeUninhabitedUnion`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct MaybeUninhabitedUnionUnion1ZstDisallowed2(MaybeUninhabitedUnion, MaybeUninhabitedUnion); +//~^ WARN: zero-sized fields in `repr(transparent)` cannot be uninhabited +//~| WARN: this was previously accepted +//~| NOTE: for more information +//~| NOTE: this struct contains `MaybeUninhabitedUnion`, which is uninhabited and affects its ABI. + +#[repr(transparent)] +struct MaybeUninhabitedUnionUnion1ZstValidAsWrapped([u8; 0], MaybeUninhabitedUnion); + +fn main() {} diff --git a/tests/ui/repr/repr-transparent-uninhabited.stderr b/tests/ui/repr/repr-transparent-uninhabited.stderr new file mode 100644 index 0000000000000..a276f0cc1f096 --- /dev/null +++ b/tests/ui/repr/repr-transparent-uninhabited.stderr @@ -0,0 +1,103 @@ +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:37:38 + | +LL | struct EmptyEnum1ZstDisallowed1(u32, EmptyEnum); + | ^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + = note: `#[warn(repr_transparent_uninhabited_fields)]` on by default + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:45:44 + | +LL | struct EmptyEnum1ZstDisallowed2(EmptyEnum, EmptyEnum); + | ^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:58:54 + | +LL | struct UninhabitedOneVariantEnum1ZstDisallowed1(u32, UninhabitedOneVariantEnum); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:67:5 + | +LL | UninhabitedOneVariantEnum, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `EmptyEnum`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:81:56 + | +LL | struct UninhabitedMultiVariantEnum1ZstDisallowed1(u32, UninhabitedMultiVariantEnum); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `UninhabitedMultiVariantEnum`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:90:5 + | +LL | UninhabitedMultiVariantEnum, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `UninhabitedMultiVariantEnum`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:103:30 + | +LL | struct NeverDisallowed1(u32, !); + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `!`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:110:28 + | +LL | struct NeverDisallowed2(!, !); + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `!`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:146:55 + | +LL | struct MaybeUninhabitedUnionUnion1ZstDisallowed1(u32, MaybeUninhabitedUnion); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `MaybeUninhabitedUnion`, which is uninhabited and affects its ABI. + +warning: zero-sized fields in `repr(transparent)` cannot be uninhabited + --> $DIR/repr-transparent-uninhabited.rs:153:73 + | +LL | struct MaybeUninhabitedUnionUnion1ZstDisallowed2(MaybeUninhabitedUnion, MaybeUninhabitedUnion); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #135802 + = note: this struct contains `MaybeUninhabitedUnion`, which is uninhabited and affects its ABI. + +warning: 10 warnings emitted +