Skip to content

Commit 2de69d2

Browse files
committed
do not complain about enums where all discriminants fit into a c_uint
1 parent e3420ce commit 2de69d2

File tree

9 files changed

+145
-33
lines changed

9 files changed

+145
-33
lines changed

compiler/rustc_hir_analysis/src/collect.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,10 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) {
610610
let repr_type = def.repr().discr_type();
611611
let initial = repr_type.initial_discriminant(tcx);
612612
let mut prev_discr = None::<Discr<'_>>;
613+
// Some of the logic below relies on `i128` being able to hold all c_int and c_uint values.
614+
assert!(tcx.sess.target.c_int_width < 128);
615+
let mut min_discr = i128::MAX;
616+
let mut max_discr = i128::MIN;
613617

614618
// fill the discriminant values and field types
615619
for variant in def.variants() {
@@ -631,19 +635,32 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) {
631635
.unwrap_or(wrapped_discr);
632636

633637
if def.repr().c() {
638+
let c_int = Size::from_bits(tcx.sess.target.c_int_width);
639+
let c_uint_max = i128::try_from(c_int.unsigned_int_max()).unwrap();
634640
// c_int is a signed type, so get a proper signed version of the discriminant
635641
let discr_size = cur_discr.ty.int_size_and_signed(tcx).0;
636642
let discr_val = discr_size.sign_extend(cur_discr.val);
643+
min_discr = min_discr.min(discr_val);
644+
max_discr = max_discr.max(discr_val);
637645

638-
let c_int = Size::from_bits(tcx.sess.target.c_int_width);
639-
if discr_val < c_int.signed_int_min() || discr_val > c_int.signed_int_max() {
646+
// The discriminant range must either fit into c_int or c_uint.
647+
if !(min_discr >= c_int.signed_int_min() && max_discr <= c_int.signed_int_max())
648+
&& !(min_discr >= 0 && max_discr <= c_uint_max)
649+
{
640650
let span = tcx.def_span(variant.def_id);
651+
let msg = if discr_val < c_int.signed_int_min() || discr_val > c_uint_max {
652+
"`repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`"
653+
} else if discr_val < 0 {
654+
"`repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`"
655+
} else {
656+
"`repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`"
657+
};
641658
tcx.node_span_lint(
642659
rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT,
643660
tcx.local_def_id_to_hir_id(def_id),
644661
span,
645662
|d| {
646-
d.primary_message("`repr(C)` enum discriminant does not fit into C `int`")
663+
d.primary_message(msg)
647664
.note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C")
648665
.help("use `repr($int_ty)` instead to explicitly set the size of this enum");
649666
}

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5217,7 +5217,7 @@ declare_lint! {
52175217

52185218
declare_lint! {
52195219
/// The `repr_c_enums_larger_than_int` lint detects `repr(C)` enums with discriminant
5220-
/// values that do not fit into a C `int`.
5220+
/// values that do not fit into a C `int` or `unsigned int`.
52215221
///
52225222
/// ### Example
52235223
///
@@ -5231,7 +5231,7 @@ declare_lint! {
52315231
/// This will produce:
52325232
///
52335233
/// ```text
5234-
/// error: `repr(C)` enum discriminant does not fit into C `int`
5234+
/// error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
52355235
/// --> $DIR/repr-c-big-discriminant1.rs:16:5
52365236
/// |
52375237
/// LL | A = 9223372036854775807, // i64::MAX
@@ -5243,15 +5243,17 @@ declare_lint! {
52435243
///
52445244
/// ### Explanation
52455245
///
5246-
/// In C, enums with discriminants that do not fit into an `int` are a portability hazard: such
5247-
/// enums are only permitted since C23, and not supported e.g. by MSVC. Furthermore, Rust
5248-
/// interprets the discriminant values of `repr(C)` enums as expressions of type `isize`, which
5249-
/// cannot be changed in a backwards-compatible way. If the discriminant is given as a literal
5250-
/// that does not fit into `isize`, it is wrapped (with a warning). This makes it impossible to
5251-
/// implement the C23 behavior of enums where the enum discriminants have no predefined type and
5252-
/// instead the enum uses a type large enough to hold all discriminants.
5246+
/// In C, enums with discriminants that do not all fit into an `int` or all fit into an
5247+
/// `unsigned int` are a portability hazard: such enums are only permitted since C23, and not
5248+
/// supported e.g. by MSVC.
52535249
///
5254-
/// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`.
5250+
/// Furthermore, Rust interprets the discriminant values of `repr(C)` enums as expressions of
5251+
/// type `isize`. This makes it impossible to implement the C23 behavior of enums where the enum
5252+
/// discriminants have no predefined type and instead the enum uses a type large enough to hold
5253+
/// all discriminants.
5254+
///
5255+
/// Therefore, `repr(C)` enums in Rust require that either all discriminants to fit into a C
5256+
/// `int` or they all fit into an `unsigned int`.
52555257
pub REPR_C_ENUMS_LARGER_THAN_INT,
52565258
Warn,
52575259
"repr(C) enums with discriminant values that do not fit into a C int",

tests/ui/enum-discriminant/discriminant_size.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![feature(core_intrinsics)]
33

44
use std::intrinsics::discriminant_value;
5+
use std::mem::size_of;
56

67
enum E1 {
78
A,
@@ -20,31 +21,53 @@ enum E3 {
2021
B = 100,
2122
}
2223

24+
// Enums like this are found in the ecosystem, let's make sure they get the right size.
25+
#[repr(C)]
26+
#[allow(overflowing_literals)]
27+
enum UnsignedIntEnum {
28+
A = 0,
29+
O = 0xffffffff, // doesn't fit into `int`, but fits into `unsigned int`
30+
}
31+
2332
#[repr(i128)]
2433
enum E4 {
2534
A = 0x1223_3445_5667_7889,
2635
B = -0x1223_3445_5667_7889,
2736
}
2837

2938
fn main() {
39+
assert_eq!(size_of::<E1>(), 1);
3040
let mut target: [isize; 3] = [0, 0, 0];
3141
target[1] = discriminant_value(&E1::A);
3242
assert_eq!(target, [0, 0, 0]);
3343
target[1] = discriminant_value(&E1::B);
3444
assert_eq!(target, [0, 1, 0]);
3545

46+
assert_eq!(size_of::<E2>(), 1);
3647
let mut target: [i8; 3] = [0, 0, 0];
3748
target[1] = discriminant_value(&E2::A);
3849
assert_eq!(target, [0, 7, 0]);
3950
target[1] = discriminant_value(&E2::B);
4051
assert_eq!(target, [0, -2, 0]);
4152

53+
// E3's size is target-dependent
4254
let mut target: [isize; 3] = [0, 0, 0];
4355
target[1] = discriminant_value(&E3::A);
4456
assert_eq!(target, [0, 42, 0]);
4557
target[1] = discriminant_value(&E3::B);
4658
assert_eq!(target, [0, 100, 0]);
4759

60+
#[allow(overflowing_literals)]
61+
{
62+
assert_eq!(size_of::<UnsignedIntEnum>(), 4);
63+
let mut target: [isize; 3] = [0, -1, 0];
64+
target[1] = discriminant_value(&UnsignedIntEnum::A);
65+
assert_eq!(target, [0, 0, 0]);
66+
target[1] = discriminant_value(&UnsignedIntEnum::O);
67+
assert_eq!(target, [0, 0xffffffff as isize, 0]);
68+
}
69+
70+
assert_eq!(size_of::<E4>(), 16);
4871
let mut target: [i128; 3] = [0, 0, 0];
4972
target[1] = discriminant_value(&E4::A);
5073
assert_eq!(target, [0, 0x1223_3445_5667_7889, 0]);
Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: literal out of range for `isize`
2-
--> $DIR/repr-c-big-discriminant1.rs:16:9
2+
--> $DIR/repr-c-big-discriminant1.rs:18:9
33
|
44
LL | A = 9223372036854775807, // i64::MAX
55
| ^^^^^^^^^^^^^^^^^^^
@@ -8,12 +8,28 @@ LL | A = 9223372036854775807, // i64::MAX
88
= note: `#[deny(overflowing_literals)]` on by default
99

1010
error: literal out of range for `isize`
11-
--> $DIR/repr-c-big-discriminant1.rs:24:9
11+
--> $DIR/repr-c-big-discriminant1.rs:26:9
1212
|
1313
LL | A = -2147483649, // i32::MIN-1
1414
| ^^^^^^^^^^^
1515
|
1616
= note: the literal `-2147483649` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
1717

18-
error: aborting due to 2 previous errors
18+
error: literal out of range for `isize`
19+
--> $DIR/repr-c-big-discriminant1.rs:34:9
20+
|
21+
LL | A = 2147483648, // i32::MAX+1
22+
| ^^^^^^^^^^
23+
|
24+
= note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
25+
26+
error: literal out of range for `isize`
27+
--> $DIR/repr-c-big-discriminant1.rs:43:9
28+
|
29+
LL | A = 2147483648, // i32::MAX+1
30+
| ^^^^^^^^^^
31+
|
32+
= note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647`
33+
34+
error: aborting due to 4 previous errors
1935

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
error: `repr(C)` enum discriminant does not fit into C `int`
2-
--> $DIR/repr-c-big-discriminant1.rs:16:5
1+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
2+
--> $DIR/repr-c-big-discriminant1.rs:18:5
33
|
44
LL | A = 9223372036854775807, // i64::MAX
55
| ^
@@ -9,13 +9,13 @@ LL | A = 9223372036854775807, // i64::MAX
99
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
1010
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
1111
note: the lint level is defined here
12-
--> $DIR/repr-c-big-discriminant1.rs:6:9
12+
--> $DIR/repr-c-big-discriminant1.rs:8:9
1313
|
1414
LL | #![deny(repr_c_enums_larger_than_int)]
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1616

17-
error: `repr(C)` enum discriminant does not fit into C `int`
18-
--> $DIR/repr-c-big-discriminant1.rs:24:5
17+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
18+
--> $DIR/repr-c-big-discriminant1.rs:26:5
1919
|
2020
LL | A = -2147483649, // i32::MIN-1
2121
| ^
@@ -25,8 +25,30 @@ LL | A = -2147483649, // i32::MIN-1
2525
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
2626
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
2727

28-
error: `repr(C)` enum discriminant does not fit into C `int`
29-
--> $DIR/repr-c-big-discriminant1.rs:34:5
28+
error: `repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`
29+
--> $DIR/repr-c-big-discriminant1.rs:36:5
30+
|
31+
LL | B = -1,
32+
| ^
33+
|
34+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
35+
= note: for more information, see issue #124403 <https://github.com/rust-lang/rust/issues/124403>
36+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
37+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
38+
39+
error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`
40+
--> $DIR/repr-c-big-discriminant1.rs:43:5
41+
|
42+
LL | A = 2147483648, // i32::MAX+1
43+
| ^
44+
|
45+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
46+
= note: for more information, see issue #124403 <https://github.com/rust-lang/rust/issues/124403>
47+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
48+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
49+
50+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
51+
--> $DIR/repr-c-big-discriminant1.rs:53:5
3052
|
3153
LL | A = I64_MAX as isize,
3254
| ^
@@ -36,5 +58,5 @@ LL | A = I64_MAX as isize,
3658
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
3759
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
3860

39-
error: aborting due to 3 previous errors
61+
error: aborting due to 5 previous errors
4062

tests/ui/enum-discriminant/repr-c-big-discriminant1.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//@[ptr32] needs-llvm-components: x86
44
//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu
55
//@[ptr64] needs-llvm-components: x86
6+
// GCC doesn't like cross-compilation
7+
//@ ignore-backends: gcc
68
#![deny(repr_c_enums_larger_than_int)]
79

810
//@ add-minicore
@@ -15,26 +17,51 @@ use minicore::*;
1517
enum OverflowingEnum1 {
1618
A = 9223372036854775807, // i64::MAX
1719
//[ptr32]~^ ERROR: literal out of range
18-
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
20+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
1921
//[ptr64]~^^^ WARN: previously accepted
2022
}
2123

2224
#[repr(C)]
2325
enum OverflowingEnum2 {
2426
A = -2147483649, // i32::MIN-1
2527
//[ptr32]~^ ERROR: literal out of range
26-
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
28+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
29+
//[ptr64]~^^^ WARN: previously accepted
30+
}
31+
32+
#[repr(C)]
33+
enum OverflowingEnum3a {
34+
A = 2147483648, // i32::MAX+1
35+
//[ptr32]~^ ERROR: literal out of range
36+
B = -1,
37+
//[ptr64]~^ ERROR: discriminant does not fit into C `unsigned int`, and a previous
38+
//[ptr64]~^^ WARN: previously accepted
39+
}
40+
#[repr(C)]
41+
enum OverflowingEnum3b {
42+
B = -1,
43+
A = 2147483648, // i32::MAX+1
44+
//[ptr32]~^ ERROR: literal out of range
45+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`, and a previous
2746
//[ptr64]~^^^ WARN: previously accepted
2847
}
2948

3049
const I64_MAX: i64 = 9223372036854775807;
3150

3251
#[repr(C)]
33-
enum OverflowingEnum3 {
52+
enum OverflowingEnum4 {
3453
A = I64_MAX as isize,
35-
//[ptr64]~^ ERROR: discriminant does not fit into C `int`
54+
//[ptr64]~^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
3655
//[ptr64]~^^ WARN: previously accepted
3756
// No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring.
3857
}
3958

59+
// Enums like this are found in the ecosystem, let's make sure they get accepted.
60+
#[repr(C)]
61+
#[allow(overflowing_literals)]
62+
enum OkayEnum {
63+
A = 0,
64+
O = 0xffffffff,
65+
}
66+
4067
fn main() {}

tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0370]: enum discriminant overflowed
2-
--> $DIR/repr-c-big-discriminant2.rs:19:5
2+
--> $DIR/repr-c-big-discriminant2.rs:24:5
33
|
44
LL | B, // +1
55
| ^ overflowed on value after 2147483647

tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
error: `repr(C)` enum discriminant does not fit into C `int`
2-
--> $DIR/repr-c-big-discriminant2.rs:19:5
1+
error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`
2+
--> $DIR/repr-c-big-discriminant2.rs:24:5
33
|
44
LL | B, // +1
55
| ^
@@ -9,7 +9,7 @@ LL | B, // +1
99
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
1010
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
1111
note: the lint level is defined here
12-
--> $DIR/repr-c-big-discriminant2.rs:6:9
12+
--> $DIR/repr-c-big-discriminant2.rs:8:9
1313
|
1414
LL | #![deny(repr_c_enums_larger_than_int)]
1515
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/enum-discriminant/repr-c-big-discriminant2.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//@[ptr32] needs-llvm-components: x86
44
//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu
55
//@[ptr64] needs-llvm-components: x86
6+
// GCC doesn't like cross-compilation
7+
//@ ignore-backends: gcc
68
#![deny(repr_c_enums_larger_than_int)]
79

810
//@ add-core-stubs
@@ -11,10 +13,13 @@
1113
extern crate minicore;
1214
use minicore::*;
1315

14-
// Separate test since it suppresses other errors on ptr32
16+
// Separate test since it suppresses other errors on ptr32:
17+
// ensure we find the bad discriminant when it is implicitly computed by incrementing
18+
// the previous discriminant.
1519

1620
#[repr(C)]
1721
enum OverflowingEnum {
22+
NEG = -1,
1823
A = 2147483647, // i32::MAX
1924
B, // +1
2025
//[ptr32]~^ ERROR: enum discriminant overflowed

0 commit comments

Comments
 (0)