Skip to content

Commit 944c0e2

Browse files
committed
check non_exhaustive attr and private fields for transparent types
1 parent b3f4c31 commit 944c0e2

File tree

5 files changed

+254
-5
lines changed

5 files changed

+254
-5
lines changed

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3132,6 +3132,56 @@ declare_lint! {
31323132
"detects unexpected names and values in `#[cfg]` conditions",
31333133
}
31343134

3135+
declare_lint! {
3136+
/// The `repr_transparent_external_private_fields` lint
3137+
/// detects types marked #[repr(trasparent)] that (transitively)
3138+
/// contain an external ZST type marked #[non_exhaustive]
3139+
///
3140+
/// ### Example
3141+
///
3142+
/// ```rust,ignore (needs external crate)
3143+
/// #![deny(repr_transparent_external_private_fields)]
3144+
/// use foo::NonExhaustiveZst;
3145+
///
3146+
/// #[repr(transparent)]
3147+
/// struct Bar(u32, ([u32; 0], NonExhaustiveZst));
3148+
/// ```
3149+
///
3150+
/// This will produce:
3151+
///
3152+
/// ```text
3153+
/// error: deprecated `#[macro_use]` attribute used to import macros should be replaced at use sites with a `use` item to import the macro instead
3154+
/// --> src/main.rs:3:1
3155+
/// |
3156+
/// 3 | #[macro_use]
3157+
/// | ^^^^^^^^^^^^
3158+
/// |
3159+
/// note: the lint level is defined here
3160+
/// --> src/main.rs:1:9
3161+
/// |
3162+
/// 1 | #![deny(repr_transparent_external_private_fields)]
3163+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3164+
/// ```
3165+
///
3166+
/// ### Explanation
3167+
///
3168+
/// Previous, Rust accepted fields that contain external private zero-sized types,
3169+
/// even though it should not be a breaking change to add a non-zero-sized field to
3170+
/// that private type.
3171+
///
3172+
/// This is a [future-incompatible] lint to transition this
3173+
/// to a hard error in the future. See [issue #78586] for more details.
3174+
///
3175+
/// [issue #78586]: https://github.com/rust-lang/rust/issues/78586
3176+
/// [future-incompatible]: ../index.md#future-incompatible-lints
3177+
pub REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
3178+
Warn,
3179+
"tranparent type contains an external ZST that is marked #[non_exhaustive] or contains private fields",
3180+
@future_incompatible = FutureIncompatibleInfo {
3181+
reference: "issue #78586 <https://github.com/rust-lang/rust/issues/78586>",
3182+
};
3183+
}
3184+
31353185
declare_lint_pass! {
31363186
/// Does nothing as a lint pass, but registers some `Lint`s
31373187
/// that are used by other parts of the compiler.
@@ -3237,6 +3287,7 @@ declare_lint_pass! {
32373287
DEPRECATED_WHERE_CLAUSE_LOCATION,
32383288
TEST_UNSTABLE_LINT,
32393289
FFI_UNWIND_CALLS,
3290+
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
32403291
]
32413292
}
32423293

compiler/rustc_typeck/src/check/check.rs

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_infer::infer::outlives::env::OutlivesEnvironment;
1717
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1818
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1919
use rustc_infer::traits::Obligation;
20+
use rustc_lint::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS;
2021
use rustc_middle::hir::nested_filter;
2122
use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
2223
use rustc_middle::ty::subst::GenericArgKind;
@@ -1318,7 +1319,8 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
13181319
}
13191320
}
13201321

1321-
// For each field, figure out if it's known to be a ZST and align(1)
1322+
// For each field, figure out if it's known to be a ZST and align(1), with "known"
1323+
// respecting #[non_exhaustive] attributes.
13221324
let field_infos = adt.all_fields().map(|field| {
13231325
let ty = field.ty(tcx, InternalSubsts::identity_for_item(tcx, field.did));
13241326
let param_env = tcx.param_env(field.did);
@@ -1327,16 +1329,56 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
13271329
let span = tcx.hir().span_if_local(field.did).unwrap();
13281330
let zst = layout.map_or(false, |layout| layout.is_zst());
13291331
let align1 = layout.map_or(false, |layout| layout.align.abi.bytes() == 1);
1330-
(span, zst, align1)
1332+
if !zst {
1333+
return (span, zst, align1, None);
1334+
}
1335+
1336+
fn check_non_exhaustive<'tcx>(
1337+
tcx: TyCtxt<'tcx>,
1338+
t: Ty<'tcx>,
1339+
) -> ControlFlow<(&'static str, DefId, SubstsRef<'tcx>, bool)> {
1340+
match t.kind() {
1341+
ty::Tuple(list) => list.iter().try_for_each(|t| check_non_exhaustive(tcx, t)),
1342+
ty::Array(ty, _) => check_non_exhaustive(tcx, *ty),
1343+
ty::Adt(def, subst) => {
1344+
if !def.did().is_local() {
1345+
let non_exhaustive = def.is_variant_list_non_exhaustive()
1346+
|| def
1347+
.variants()
1348+
.iter()
1349+
.any(ty::VariantDef::is_field_list_non_exhaustive);
1350+
let has_priv = def.all_fields().any(|f| !f.vis.is_public());
1351+
if non_exhaustive || has_priv {
1352+
return ControlFlow::Break((
1353+
def.descr(),
1354+
def.did(),
1355+
subst,
1356+
non_exhaustive,
1357+
));
1358+
}
1359+
}
1360+
def.all_fields()
1361+
.map(|field| field.ty(tcx, subst))
1362+
.try_for_each(|t| check_non_exhaustive(tcx, t))
1363+
}
1364+
_ => ControlFlow::Continue(()),
1365+
}
1366+
}
1367+
1368+
(span, zst, align1, check_non_exhaustive(tcx, ty).break_value())
13311369
});
13321370

1333-
let non_zst_fields =
1334-
field_infos.clone().filter_map(|(span, zst, _align1)| if !zst { Some(span) } else { None });
1371+
let non_zst_fields = field_infos
1372+
.clone()
1373+
.filter_map(|(span, zst, _align1, _non_exhaustive)| if !zst { Some(span) } else { None });
13351374
let non_zst_count = non_zst_fields.clone().count();
13361375
if non_zst_count >= 2 {
13371376
bad_non_zero_sized_fields(tcx, adt, non_zst_count, non_zst_fields, sp);
13381377
}
1339-
for (span, zst, align1) in field_infos {
1378+
let incompatible_zst_fields =
1379+
field_infos.clone().filter(|(_, _, _, opt)| opt.is_some()).count();
1380+
let incompat = incompatible_zst_fields + non_zst_count >= 2 && non_zst_count < 2;
1381+
for (span, zst, align1, non_exhaustive) in field_infos {
13401382
if zst && !align1 {
13411383
struct_span_err!(
13421384
tcx.sess,
@@ -1348,6 +1390,25 @@ pub(super) fn check_transparent<'tcx>(tcx: TyCtxt<'tcx>, sp: Span, adt: ty::AdtD
13481390
.span_label(span, "has alignment larger than 1")
13491391
.emit();
13501392
}
1393+
if incompat && let Some((descr, def_id, substs, non_exhaustive)) = non_exhaustive {
1394+
tcx.struct_span_lint_hir(
1395+
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
1396+
tcx.hir().local_def_id_to_hir_id(adt.did().expect_local()),
1397+
span,
1398+
|lint| {
1399+
let note = if non_exhaustive {
1400+
"is marked with `#[non_exhaustive]`"
1401+
} else {
1402+
"contains private fields"
1403+
};
1404+
let field_ty = tcx.def_path_str_with_substs(def_id, substs);
1405+
lint.build("zero-sized fields in repr(transparent) cannot contain external non-exhaustive types")
1406+
.note(format!("this {descr} contains `{field_ty}`, which {note}, \
1407+
and makes it not a breaking change to become non-zero-sized in the future."))
1408+
.emit();
1409+
},
1410+
)
1411+
}
13511412
}
13521413
}
13531414

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![crate_type = "lib"]
2+
3+
pub struct Private { _priv: () }
4+
5+
#[non_exhaustive]
6+
pub struct NonExhaustive {}
7+
8+
pub struct ExternalIndirection<T> {
9+
pub x: T,
10+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#![deny(repr_transparent_external_private_fields)]
2+
3+
// aux-build: repr-transparent-non-exhaustive.rs
4+
extern crate repr_transparent_non_exhaustive;
5+
6+
use repr_transparent_non_exhaustive::{Private, NonExhaustive, ExternalIndirection};
7+
8+
pub struct InternalPrivate {
9+
_priv: (),
10+
}
11+
12+
#[non_exhaustive]
13+
pub struct InternalNonExhaustive;
14+
15+
pub struct InternalIndirection<T> {
16+
x: T,
17+
}
18+
19+
pub type Sized = i32;
20+
21+
#[repr(transparent)]
22+
pub struct T1(Sized, InternalPrivate);
23+
#[repr(transparent)]
24+
pub struct T2(Sized, InternalNonExhaustive);
25+
#[repr(transparent)]
26+
pub struct T3(Sized, InternalIndirection<(InternalPrivate, InternalNonExhaustive)>);
27+
#[repr(transparent)]
28+
pub struct T4(Sized, ExternalIndirection<(InternalPrivate, InternalNonExhaustive)>);
29+
30+
#[repr(transparent)]
31+
pub struct T5(Sized, Private);
32+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
33+
//~| WARN this was previously accepted by the compiler
34+
35+
#[repr(transparent)]
36+
pub struct T6(Sized, NonExhaustive);
37+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
38+
//~| WARN this was previously accepted by the compiler
39+
40+
#[repr(transparent)]
41+
pub struct T7(Sized, InternalIndirection<Private>);
42+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
43+
//~| WARN this was previously accepted by the compiler
44+
45+
#[repr(transparent)]
46+
pub struct T8(Sized, InternalIndirection<NonExhaustive>);
47+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
48+
//~| WARN this was previously accepted by the compiler
49+
50+
#[repr(transparent)]
51+
pub struct T9(Sized, ExternalIndirection<Private>);
52+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
53+
//~| WARN this was previously accepted by the compiler
54+
55+
#[repr(transparent)]
56+
pub struct T10(Sized, ExternalIndirection<NonExhaustive>);
57+
//~^ ERROR zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
58+
//~| WARN this was previously accepted by the compiler
59+
60+
fn main() {}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
2+
--> $DIR/repr-transparent-non-exhaustive.rs:31:22
3+
|
4+
LL | pub struct T5(Sized, Private);
5+
| ^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/repr-transparent-non-exhaustive.rs:1:9
9+
|
10+
LL | #![deny(repr_transparent_external_private_fields)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
13+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
14+
= note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
15+
16+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
17+
--> $DIR/repr-transparent-non-exhaustive.rs:36:22
18+
|
19+
LL | pub struct T6(Sized, NonExhaustive);
20+
| ^^^^^^^^^^^^^
21+
|
22+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
23+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
24+
= note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
25+
26+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
27+
--> $DIR/repr-transparent-non-exhaustive.rs:41:22
28+
|
29+
LL | pub struct T7(Sized, InternalIndirection<Private>);
30+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31+
|
32+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
33+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
34+
= note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
35+
36+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
37+
--> $DIR/repr-transparent-non-exhaustive.rs:46:22
38+
|
39+
LL | pub struct T8(Sized, InternalIndirection<NonExhaustive>);
40+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41+
|
42+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
43+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
44+
= note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
45+
46+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
47+
--> $DIR/repr-transparent-non-exhaustive.rs:51:22
48+
|
49+
LL | pub struct T9(Sized, ExternalIndirection<Private>);
50+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
51+
|
52+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
53+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
54+
= note: this struct contains `Private`, which contains private fields, and makes it not a breaking change to become non-zero-sized in the future.
55+
56+
error: zero-sized fields in repr(transparent) cannot contain external non-exhaustive types
57+
--> $DIR/repr-transparent-non-exhaustive.rs:56:23
58+
|
59+
LL | pub struct T10(Sized, ExternalIndirection<NonExhaustive>);
60+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61+
|
62+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
63+
= note: for more information, see issue #78586 <https://github.com/rust-lang/rust/issues/78586>
64+
= note: this struct contains `NonExhaustive`, which is marked with `#[non_exhaustive]`, and makes it not a breaking change to become non-zero-sized in the future.
65+
66+
error: aborting due to 6 previous errors
67+

0 commit comments

Comments
 (0)