aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--decl.c12
-rw-r--r--doc/extensions.md24
-rw-r--r--test/enum-large-value.c4
-rw-r--r--test/enum-large-value.qbe1
4 files changed, 39 insertions, 2 deletions
diff --git a/decl.c b/decl.c
index cca767a..ae4f765 100644
--- a/decl.c
+++ b/decl.c
@@ -156,6 +156,7 @@ tagspec(struct scope *s)
struct expr *e;
struct structbuilder b;
uint64_t i;
+ bool large;
switch (tok.kind) {
case TSTRUCT: kind = TYPESTRUCT; break;
@@ -216,6 +217,7 @@ tagspec(struct scope *s)
t->incomplete = false;
break;
case TYPEENUM:
+ large = false;
for (i = 0; tok.kind == TIDENT; ++i) {
name = tok.lit;
next();
@@ -228,15 +230,21 @@ tagspec(struct scope *s)
if (i < -1ull << 31)
goto invalid;
t->basic.issigned = true;
- } else if (i >= 1ull << 31) {
+ } else if (i >= 1ull << 32) {
goto invalid;
}
- } else if (i == 1ull << 31) {
+ } else if (i == 1ull << 32) {
invalid:
error(&tok.loc, "enumerator '%s' value cannot be represented as 'int'", name);
}
d = mkdecl(DECLCONST, &typeint, QUALNONE, LINKNONE);
d->value = mkintconst(t->repr, i);
+ if (i >= 1ull << 31 && i < 1ull << 63) {
+ large = true;
+ d->type = &typeuint;
+ }
+ if (large && t->basic.issigned)
+ error(&tok.loc, "neither 'int' nor 'unsigned' can represent all enumerator values");
scopeputdecl(s, name, d);
if (!consume(TCOMMA))
break;
diff --git a/doc/extensions.md b/doc/extensions.md
index 9a7b81d..9d07972 100644
--- a/doc/extensions.md
+++ b/doc/extensions.md
@@ -32,6 +32,30 @@ rules. The name may contain characters not allowed in regular identifiers.
- **`__builtin_va_list`**: Built-in suitable for implementing the `va_list` type.
- **`__builtin_va_start`**: Built-in suitable for implementing the `va_start` macro.
+### Enumerator values outside the range of `int`
+
+ISO C requires that enumerator values be in the range of `int`. GNU C
+allows any integer value, at the cost of enumerator constants possibly
+having different types inside and outside the `enum` specifier.
+
+In the following example, `A` has type `unsigned` inside the `enum`
+specifier, and `long` outside, so both of the assertions fail.
+
+```c
+enum E {
+ A = 0x80000000,
+ B = sizeof(A),
+ C = A < -1,
+ D = -1,
+};
+_Static_assert(B == sizeof(A), "sizeof(A) changed");
+_Static_assert(C == A < -1, "signedness of typeof(A) changed");
+```
+
+As a compromise, we allow enumerator values larger than `INT_MAX` and
+less than or equal to `UINT_MAX`, but only if no other enumerators have
+a negative value. This way, enumerator constants have a fixed type.
+
## Missing
### Statement expressions
diff --git a/test/enum-large-value.c b/test/enum-large-value.c
new file mode 100644
index 0000000..3532062
--- /dev/null
+++ b/test/enum-large-value.c
@@ -0,0 +1,4 @@
+enum {
+ A = 0x80000000,
+};
+int x = A < 0;
diff --git a/test/enum-large-value.qbe b/test/enum-large-value.qbe
new file mode 100644
index 0000000..a67781d
--- /dev/null
+++ b/test/enum-large-value.qbe
@@ -0,0 +1 @@
+export data $x = align 4 { w 0, }