Skip to content

Commit aa78e00

Browse files
committed
do not complain about enums where all discriminants fit into a c_uint
1 parent ba9f51e commit aa78e00

8 files changed

+116
-18
lines changed

compiler/rustc_hir_analysis/src/collect.rs

Lines changed: 20 additions & 5 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,16 +635,27 @@ 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);
641-
let mut d = tcx
642-
.dcx()
643-
.struct_span_err(span, "`repr(C)` enum discriminant does not fit into C `int`");
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+
};
658+
let mut d = tcx.dcx().struct_span_err(span, msg);
644659
d.note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C")
645660
.help("use `repr($int_ty)` instead to explicitly set the size of this enum");
646661
d.emit();

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]);

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,21 @@ LL | A = -2147483649, // i32::MIN-1
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:30: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:38: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: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: `repr(C)` enum discriminant does not fit into C `int`
1+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
22
--> $DIR/repr-c-big-discriminant1.rs:16:5
33
|
44
LL | A = 9223372036854775807, // i64::MAX
@@ -7,7 +7,7 @@ LL | A = 9223372036854775807, // i64::MAX
77
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
88
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
99

10-
error: `repr(C)` enum discriminant does not fit into C `int`
10+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
1111
--> $DIR/repr-c-big-discriminant1.rs:23:5
1212
|
1313
LL | A = -2147483649, // i32::MIN-1
@@ -16,14 +16,32 @@ LL | A = -2147483649, // i32::MIN-1
1616
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
1717
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
1818

19-
error: `repr(C)` enum discriminant does not fit into C `int`
19+
error: `repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`
2020
--> $DIR/repr-c-big-discriminant1.rs:32:5
2121
|
22+
LL | B = -1,
23+
| ^
24+
|
25+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
26+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
27+
28+
error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`
29+
--> $DIR/repr-c-big-discriminant1.rs:38:5
30+
|
31+
LL | A = 2147483648, // i32::MAX+1
32+
| ^
33+
|
34+
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
35+
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
36+
37+
error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`
38+
--> $DIR/repr-c-big-discriminant1.rs:47:5
39+
|
2240
LL | A = I64_MAX as isize,
2341
| ^
2442
|
2543
= note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C
2644
= help: use `repr($int_ty)` instead to explicitly set the size of this enum
2745

28-
error: aborting due to 3 previous errors
46+
error: aborting due to 5 previous errors
2947

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,46 @@ use minicore::*;
1515
enum OverflowingEnum1 {
1616
A = 9223372036854775807, // i64::MAX
1717
//[ptr32]~^ ERROR: literal out of range
18-
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
18+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
1919
}
2020

2121
#[repr(C)]
2222
enum OverflowingEnum2 {
2323
A = -2147483649, // i32::MIN-1
2424
//[ptr32]~^ ERROR: literal out of range
25-
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`
25+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
26+
}
27+
28+
#[repr(C)]
29+
enum OverflowingEnum3a {
30+
A = 2147483648, // i32::MAX+1
31+
//[ptr32]~^ ERROR: literal out of range
32+
B = -1,
33+
//[ptr64]~^ ERROR: discriminant does not fit into C `unsigned int`, and a previous
34+
}
35+
#[repr(C)]
36+
enum OverflowingEnum3b {
37+
B = -1,
38+
A = 2147483648, // i32::MAX+1
39+
//[ptr32]~^ ERROR: literal out of range
40+
//[ptr64]~^^ ERROR: discriminant does not fit into C `int`, and a previous
2641
}
2742

2843
const I64_MAX: i64 = 9223372036854775807;
2944

3045
#[repr(C)]
31-
enum OverflowingEnum3 {
46+
enum OverflowingEnum4 {
3247
A = I64_MAX as isize,
33-
//[ptr64]~^ ERROR: discriminant does not fit into C `int`
48+
//[ptr64]~^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int`
3449
// No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring.
3550
}
3651

52+
// Enums like this are found in the ecosystem, let's make sure they get accepted.
53+
#[repr(C)]
54+
#[allow(overflowing_literals)]
55+
enum OkayEnum {
56+
A = 0,
57+
O = 0xffffffff,
58+
}
59+
3760
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:22:5
33
|
44
LL | B, // +1
55
| ^ overflowed on value after 2147483647

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

Lines changed: 2 additions & 2 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:22:5
33
|
44
LL | B, // +1
55
| ^

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
extern crate minicore;
1212
use minicore::*;
1313

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

1618
#[repr(C)]
1719
enum OverflowingEnum {
20+
NEG = -1,
1821
A = 2147483647, // i32::MAX
1922
B, // +1
2023
//[ptr32]~^ ERROR: enum discriminant overflowed

0 commit comments

Comments
 (0)