Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signed types for enum tags #2459

Merged
merged 5 commits into from
May 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 61 additions & 47 deletions src/analyze.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,18 @@ static Error resolve_union_type(CodeGen *g, ZigType *union_type) {
return ErrorNone;
}

static bool type_is_valid_extern_enum_tag(CodeGen *g, ZigType *ty) {
// Only integer types are allowed by the C ABI
if(ty->id != ZigTypeIdInt)
return false;

// According to the ANSI C standard the enumeration type should be either a
// signed char, a signed integer or an unsigned one. But GCC/Clang allow
// other integral types as a compiler extension so let's accomodate them
// aswell.
return type_allowed_in_extern(g, ty);
}

static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
assert(enum_type->id == ZigTypeIdEnum);

Expand Down Expand Up @@ -1965,7 +1977,6 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
enum_type->abi_size = tag_int_type->abi_size;
enum_type->abi_align = tag_int_type->abi_align;

// TODO: Are extern enums allowed to have an init_arg_expr?
if (decl_node->data.container_decl.init_arg_expr != nullptr) {
ZigType *wanted_tag_int_type = analyze_type_expr(g, scope, decl_node->data.container_decl.init_arg_expr);
if (type_is_invalid(wanted_tag_int_type)) {
Expand All @@ -1974,24 +1985,29 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
enum_type->data.enumeration.is_invalid = true;
add_node_error(g, decl_node->data.container_decl.init_arg_expr,
buf_sprintf("expected integer, found '%s'", buf_ptr(&wanted_tag_int_type->name)));
} else if (wanted_tag_int_type->data.integral.is_signed) {
} else if (enum_type->data.enumeration.layout == ContainerLayoutExtern &&
!type_is_valid_extern_enum_tag(g, wanted_tag_int_type)) {
enum_type->data.enumeration.is_invalid = true;
add_node_error(g, decl_node->data.container_decl.init_arg_expr,
buf_sprintf("expected unsigned integer, found '%s'", buf_ptr(&wanted_tag_int_type->name)));
} else if (wanted_tag_int_type->data.integral.bit_count < tag_int_type->data.integral.bit_count) {
enum_type->data.enumeration.is_invalid = true;
add_node_error(g, decl_node->data.container_decl.init_arg_expr,
buf_sprintf("'%s' too small to hold all bits; must be at least '%s'",
buf_ptr(&wanted_tag_int_type->name), buf_ptr(&tag_int_type->name)));
ErrorMsg *msg = add_node_error(g, decl_node->data.container_decl.init_arg_expr,
buf_sprintf("'%s' is not a valid tag type for an extern enum",
buf_ptr(&wanted_tag_int_type->name)));
add_error_note(g, msg, decl_node->data.container_decl.init_arg_expr,
buf_sprintf("any integral type of size 8, 16, 32, 64 or 128 bit is valid"));
} else {
tag_int_type = wanted_tag_int_type;
}
}

enum_type->data.enumeration.tag_int_type = tag_int_type;
enum_type->size_in_bits = tag_int_type->size_in_bits;
enum_type->abi_size = tag_int_type->abi_size;
enum_type->abi_align = tag_int_type->abi_align;

BigInt bi_one;
bigint_init_unsigned(&bi_one, 1);

TypeEnumField *last_enum_field = nullptr;

for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
AstNode *field_node = decl_node->data.container_decl.fields.at(field_i);
TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i];
Expand All @@ -2017,60 +2033,58 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {

AstNode *tag_value = field_node->data.struct_field.value;

// In this first pass we resolve explicit tag values.
// In a second pass we will fill in the unspecified ones.
if (tag_value != nullptr) {
// A user-specified value is available
ConstExprValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, nullptr);
if (type_is_invalid(result->type)) {
enum_type->data.enumeration.is_invalid = true;
continue;
}

assert(result->special != ConstValSpecialRuntime);
assert(result->type->id == ZigTypeIdInt ||
result->type->id == ZigTypeIdComptimeInt);
auto entry = occupied_tag_values.put_unique(result->data.x_bigint, tag_value);
if (entry == nullptr) {
bigint_init_bigint(&type_enum_field->value, &result->data.x_bigint);
assert(result->type->id == ZigTypeIdInt || result->type->id == ZigTypeIdComptimeInt);

bigint_init_bigint(&type_enum_field->value, &result->data.x_bigint);
} else {
// No value was explicitly specified: allocate the last value + 1
// or, if this is the first element, zero
if (last_enum_field != nullptr) {
bigint_add(&type_enum_field->value, &last_enum_field->value, &bi_one);
} else {
Buf *val_buf = buf_alloc();
bigint_append_buf(val_buf, &result->data.x_bigint, 10);
bigint_init_unsigned(&type_enum_field->value, 0);
}

ErrorMsg *msg = add_node_error(g, tag_value,
buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf)));
add_error_note(g, msg, entry->value,
buf_sprintf("other occurrence here"));
// Make sure we can represent this number with tag_int_type
if (!bigint_fits_in_bits(&type_enum_field->value,
tag_int_type->size_in_bits,
tag_int_type->data.integral.is_signed)) {
enum_type->data.enumeration.is_invalid = true;
continue;

Buf *val_buf = buf_alloc();
bigint_append_buf(val_buf, &type_enum_field->value, 10);
add_node_error(g, field_node,
buf_sprintf("enumeration value %s too large for type '%s'",
buf_ptr(val_buf), buf_ptr(&tag_int_type->name)));

break;
}
}
}

// Now iterate again and populate the unspecified tag values
uint32_t next_maybe_unoccupied_index = 0;
// Make sure the value is unique
auto entry = occupied_tag_values.put_unique(type_enum_field->value, field_node);
if (entry != nullptr) {
enum_type->data.enumeration.is_invalid = true;

for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
AstNode *field_node = decl_node->data.container_decl.fields.at(field_i);
TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i];
AstNode *tag_value = field_node->data.struct_field.value;
Buf *val_buf = buf_alloc();
bigint_append_buf(val_buf, &type_enum_field->value, 10);

if (tag_value == nullptr) {
if (occupied_tag_values.size() == 0) {
bigint_init_unsigned(&type_enum_field->value, next_maybe_unoccupied_index);
next_maybe_unoccupied_index += 1;
} else {
BigInt proposed_value;
for (;;) {
bigint_init_unsigned(&proposed_value, next_maybe_unoccupied_index);
next_maybe_unoccupied_index += 1;
auto entry = occupied_tag_values.put_unique(proposed_value, field_node);
if (entry != nullptr) {
continue;
}
break;
}
bigint_init_bigint(&type_enum_field->value, &proposed_value);
}
ErrorMsg *msg = add_node_error(g, field_node,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you make this field_node->data.struct_field.value then the compile error test won't need updated, right? It seems better to point at the value than the field. Likewise with entry->value->data.struct_field.value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

field_node->data.struct_field.value is null if the user didn't specify it, I've made it point to the field declaration because that's always present.

I can make it choose either of them based on whether value is null or not but I'm not very fond of this "asymmetry" in the error messages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, that makes sense!

buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf)));
add_error_note(g, msg, entry->value,
buf_sprintf("other occurrence here"));
}

last_enum_field = type_enum_field;
}

enum_type->data.enumeration.zero_bits_loop_flag = false;
Expand Down
40 changes: 17 additions & 23 deletions test/compile_errors.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:3:32: error: cast discards const qualifier",
);

cases.add(
"overflow in enum value allocation",
\\const Moo = enum(u8) {
\\ Last = 255,
\\ Over,
\\};
\\pub fn main() void {
\\ var y = Moo.Last;
\\}
,
"tmp.zig:3:5: error: enumeration value 256 too large for type 'u8'",
);

cases.add(
"attempt to cast enum literal to error",
\\export fn entry() void {
Expand Down Expand Up @@ -5383,8 +5396,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:12:20: note: referenced here",
);

cases.add(
"specify enum tag type that is too small",
cases.add("specify enum tag type that is too small",
\\const Small = enum (u2) {
\\ One,
\\ Two,
Expand All @@ -5396,9 +5408,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\export fn entry() void {
\\ var x = Small.One;
\\}
,
"tmp.zig:1:21: error: 'u2' too small to hold all bits; must be at least 'u3'",
);
, "tmp.zig:6:5: error: enumeration value 4 too large for type 'u2'");

cases.add(
"specify non-integer enum tag type",
Expand Down Expand Up @@ -5448,22 +5458,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"tmp.zig:10:31: error: expected type 'u2', found 'u3'",
);

cases.add(
"non unsigned integer enum tag type",
\\const Small = enum(i2) {
\\ One,
\\ Two,
\\ Three,
\\ Four,
\\};
\\
\\export fn entry() void {
\\ var y = Small.Two;
\\}
,
"tmp.zig:1:20: error: expected unsigned integer, found 'i2'",
);

cases.add(
"struct fields with value assignments",
\\const MultipleChoice = struct {
Expand Down Expand Up @@ -5522,8 +5516,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\ var x = MultipleChoice.C;
\\}
,
"tmp.zig:6:9: error: enum tag value 60 already taken",
"tmp.zig:4:9: note: other occurrence here",
"tmp.zig:6:5: error: enum tag value 60 already taken",
"tmp.zig:4:5: note: other occurrence here",
);

cases.add(
Expand Down
24 changes: 24 additions & 0 deletions test/stage1/behavior/enum.zig
Original file line number Diff line number Diff line change
Expand Up @@ -938,3 +938,27 @@ test "enum literal in array literal" {
expect(array[0] == .one);
expect(array[1] == .two);
}

test "signed integer as enum tag" {
const SignedEnum = enum(i2) {
A0 = -1,
A1 = 0,
A2 = 1,
};

expect(@enumToInt(SignedEnum.A0) == -1);
expect(@enumToInt(SignedEnum.A1) == 0);
expect(@enumToInt(SignedEnum.A2) == 1);
}

test "enum value allocation" {
const LargeEnum = enum(u32) {
A0 = 0x80000000,
A1,
A2,
};

expect(@enumToInt(LargeEnum.A0) == 0x80000000);
expect(@enumToInt(LargeEnum.A1) == 0x80000001);
expect(@enumToInt(LargeEnum.A2) == 0x80000002);
}