remove enum to/from int casting syntax; add `@enumToInt`/`@intToEnum`

see #1061
master
Andrew Kelley 2018-06-19 03:50:38 -04:00
parent 626b73e8be
commit a3ddd0826b
9 changed files with 174 additions and 51 deletions

View File

@ -1900,9 +1900,9 @@ const Value = enum(u2) {
// Now you can cast between u2 and Value.
// The ordinal value starts from 0, counting up for each member.
test "enum ordinal value" {
assert(u2(Value.Zero) == 0);
assert(u2(Value.One) == 1);
assert(u2(Value.Two) == 2);
assert(@enumToInt(Value.Zero) == 0);
assert(@enumToInt(Value.One) == 1);
assert(@enumToInt(Value.Two) == 2);
}
// You can override the ordinal value for an enum.
@ -1912,9 +1912,9 @@ const Value2 = enum(u32) {
Million = 1000000,
};
test "set enum ordinal value" {
assert(u32(Value2.Hundred) == 100);
assert(u32(Value2.Thousand) == 1000);
assert(u32(Value2.Million) == 1000000);
assert(@enumToInt(Value2.Hundred) == 100);
assert(@enumToInt(Value2.Thousand) == 1000);
assert(@enumToInt(Value2.Million) == 1000000);
}
// Enums can have methods, the same as structs and unions.
@ -4931,6 +4931,14 @@ test "main" {
{#see_also|@import#}
{#header_close#}
{#header_open|@enumToInt#}
<pre><code class="zig">@enumToInt(enum_value: var) var</code></pre>
<p>
Converts an enumeration value into its integer tag type.
</p>
{#see_also|@intToEnum#}
{#header_close#}
{#header_open|@errSetCast#}
<pre><code class="zig">@errSetCast(comptime T: DestType, value: var) DestType</code></pre>
<p>
@ -5095,6 +5103,18 @@ fn add(a: i32, b: i32) i32 { return a + b; }
</p>
{#header_close#}
{#header_open|@intToEnum#}
<pre><code class="zig">@intToEnum(comptime DestType: type, int_value: @TagType(DestType)) DestType</code></pre>
<p>
Converts an integer into an {#link|enum#} value.
</p>
<p>
Attempting to convert an integer which represents no value in the chosen enum type invokes
safety-checked {#link|Undefined Behavior#}.
</p>
{#see_also|@enumToInt#}
{#header_close#}
{#header_open|@intToError#}
<pre><code class="zig">@intToError(value: @IntType(false, @sizeOf(error) * 8)) error</code></pre>
<p>
@ -6867,7 +6887,7 @@ hljs.registerLanguage("zig", function(t) {
a = t.IR + "\\s*\\(",
c = {
keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong resume cancel await async orelse",
built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError",
built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage setGlobalSection divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError enumToInt intToEnum",
literal: "true false null undefined"
},
n = [e, t.CLCM, t.CBCM, s, r];

View File

@ -1378,6 +1378,8 @@ enum BuiltinFnId {
BuiltinFnIdBoolToInt,
BuiltinFnIdErrToInt,
BuiltinFnIdIntToErr,
BuiltinFnIdEnumToInt,
BuiltinFnIdIntToEnum,
BuiltinFnIdIntType,
BuiltinFnIdSetCold,
BuiltinFnIdSetRuntimeSafety,
@ -2092,6 +2094,7 @@ enum IrInstructionId {
IrInstructionIdIntToPtr,
IrInstructionIdPtrToInt,
IrInstructionIdIntToEnum,
IrInstructionIdEnumToInt,
IrInstructionIdIntToErr,
IrInstructionIdErrToInt,
IrInstructionIdCheckSwitchProngs,
@ -2905,6 +2908,13 @@ struct IrInstructionIntToPtr {
struct IrInstructionIntToEnum {
IrInstruction base;
IrInstruction *dest_type;
IrInstruction *target;
};
struct IrInstructionEnumToInt {
IrInstruction base;
IrInstruction *target;
};

View File

@ -4730,6 +4730,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
case IrInstructionIdErrSetCast:
case IrInstructionIdFromBytes:
case IrInstructionIdToBytes:
case IrInstructionIdEnumToInt:
zig_unreachable();
case IrInstructionIdReturn:
@ -6325,6 +6326,8 @@ static void define_builtin_fns(CodeGen *g) {
create_builtin_fn(g, BuiltinFnIdBoolToInt, "boolToInt", 1);
create_builtin_fn(g, BuiltinFnIdErrToInt, "errorToInt", 1);
create_builtin_fn(g, BuiltinFnIdIntToErr, "intToError", 1);
create_builtin_fn(g, BuiltinFnIdEnumToInt, "enumToInt", 1);
create_builtin_fn(g, BuiltinFnIdIntToEnum, "intToEnum", 2);
create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1);
create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int

View File

@ -600,6 +600,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToEnum *) {
return IrInstructionIdIntToEnum;
}
static constexpr IrInstructionId ir_instruction_id(IrInstructionEnumToInt *) {
return IrInstructionIdEnumToInt;
}
static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToErr *) {
return IrInstructionIdIntToErr;
}
@ -2378,10 +2382,26 @@ static IrInstruction *ir_build_ptr_to_int(IrBuilder *irb, Scope *scope, AstNode
}
static IrInstruction *ir_build_int_to_enum(IrBuilder *irb, Scope *scope, AstNode *source_node,
IrInstruction *target)
IrInstruction *dest_type, IrInstruction *target)
{
IrInstructionIntToEnum *instruction = ir_build_instruction<IrInstructionIntToEnum>(
irb, scope, source_node);
instruction->dest_type = dest_type;
instruction->target = target;
if (dest_type) ir_ref_instruction(dest_type, irb->current_basic_block);
ir_ref_instruction(target, irb->current_basic_block);
return &instruction->base;
}
static IrInstruction *ir_build_enum_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node,
IrInstruction *target)
{
IrInstructionEnumToInt *instruction = ir_build_instruction<IrInstructionEnumToInt>(
irb, scope, source_node);
instruction->target = target;
ir_ref_instruction(target, irb->current_basic_block);
@ -4708,6 +4728,31 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
// this value does not mean anything since we passed non-null values for other arg
AtomicOrderMonotonic);
}
case BuiltinFnIdIntToEnum:
{
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
if (arg0_value == irb->codegen->invalid_instruction)
return arg0_value;
AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
if (arg1_value == irb->codegen->invalid_instruction)
return arg1_value;
IrInstruction *result = ir_build_int_to_enum(irb, scope, node, arg0_value, arg1_value);
return ir_lval_wrap(irb, scope, result, lval);
}
case BuiltinFnIdEnumToInt:
{
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
if (arg0_value == irb->codegen->invalid_instruction)
return arg0_value;
IrInstruction *result = ir_build_enum_to_int(irb, scope, node, arg0_value);
return ir_lval_wrap(irb, scope, result, lval);
}
}
zig_unreachable();
}
@ -9951,7 +9996,7 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour
}
IrInstruction *result = ir_build_int_to_enum(&ira->new_irb, source_instr->scope,
source_instr->source_node, target);
source_instr->source_node, nullptr, target);
result->value.type = wanted_type;
return result;
}
@ -10485,16 +10530,6 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type);
}
// explicit cast from integer to enum type with no payload
if (actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdEnum) {
return ir_analyze_int_to_enum(ira, source_instr, value, wanted_type);
}
// explicit cast from enum type with no payload to integer
if (wanted_type->id == TypeTableEntryIdInt && actual_type->id == TypeTableEntryIdEnum) {
return ir_analyze_enum_to_int(ira, source_instr, value, wanted_type);
}
// explicit cast from union to the enum type of the union
if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) {
type_ensure_zero_bits_known(ira->codegen, actual_type);
@ -20262,11 +20297,63 @@ static TypeTableEntry *ir_analyze_instruction_sqrt(IrAnalyze *ira, IrInstruction
return result->value.type;
}
static TypeTableEntry *ir_analyze_instruction_enum_to_int(IrAnalyze *ira, IrInstructionEnumToInt *instruction) {
IrInstruction *target = instruction->target->other;
if (type_is_invalid(target->value.type))
return ira->codegen->builtin_types.entry_invalid;
if (target->value.type->id != TypeTableEntryIdEnum) {
ir_add_error(ira, instruction->target,
buf_sprintf("expected enum, found type '%s'", buf_ptr(&target->value.type->name)));
return ira->codegen->builtin_types.entry_invalid;
}
type_ensure_zero_bits_known(ira->codegen, target->value.type);
if (type_is_invalid(target->value.type))
return ira->codegen->builtin_types.entry_invalid;
TypeTableEntry *tag_type = target->value.type->data.enumeration.tag_int_type;
IrInstruction *result = ir_analyze_enum_to_int(ira, &instruction->base, target, tag_type);
ir_link_new_instruction(result, &instruction->base);
return result->value.type;
}
static TypeTableEntry *ir_analyze_instruction_int_to_enum(IrAnalyze *ira, IrInstructionIntToEnum *instruction) {
IrInstruction *dest_type_value = instruction->dest_type->other;
TypeTableEntry *dest_type = ir_resolve_type(ira, dest_type_value);
if (type_is_invalid(dest_type))
return ira->codegen->builtin_types.entry_invalid;
if (dest_type->id != TypeTableEntryIdEnum) {
ir_add_error(ira, instruction->dest_type,
buf_sprintf("expected enum, found type '%s'", buf_ptr(&dest_type->name)));
return ira->codegen->builtin_types.entry_invalid;
}
type_ensure_zero_bits_known(ira->codegen, dest_type);
if (type_is_invalid(dest_type))
return ira->codegen->builtin_types.entry_invalid;
TypeTableEntry *tag_type = dest_type->data.enumeration.tag_int_type;
IrInstruction *target = instruction->target->other;
if (type_is_invalid(target->value.type))
return ira->codegen->builtin_types.entry_invalid;
IrInstruction *casted_target = ir_implicit_cast(ira, target, tag_type);
if (type_is_invalid(casted_target->value.type))
return ira->codegen->builtin_types.entry_invalid;
IrInstruction *result = ir_analyze_int_to_enum(ira, &instruction->base, casted_target, dest_type);
ir_link_new_instruction(result, &instruction->base);
return result->value.type;
}
static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
switch (instruction->id) {
case IrInstructionIdInvalid:
case IrInstructionIdWidenOrShorten:
case IrInstructionIdIntToEnum:
case IrInstructionIdStructInit:
case IrInstructionIdUnionInit:
case IrInstructionIdStructFieldPtr:
@ -20531,6 +20618,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction);
case IrInstructionIdErrToInt:
return ir_analyze_instruction_err_to_int(ira, (IrInstructionErrToInt *)instruction);
case IrInstructionIdIntToEnum:
return ir_analyze_instruction_int_to_enum(ira, (IrInstructionIntToEnum *)instruction);
case IrInstructionIdEnumToInt:
return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction);
}
zig_unreachable();
}
@ -20754,6 +20845,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
case IrInstructionIdBoolToInt:
case IrInstructionIdFromBytes:
case IrInstructionIdToBytes:
case IrInstructionIdEnumToInt:
return false;
case IrInstructionIdAsm:

View File

@ -928,6 +928,17 @@ static void ir_print_int_to_ptr(IrPrint *irp, IrInstructionIntToPtr *instruction
static void ir_print_int_to_enum(IrPrint *irp, IrInstructionIntToEnum *instruction) {
fprintf(irp->f, "@intToEnum(");
if (instruction->dest_type == nullptr) {
fprintf(irp->f, "(null)");
} else {
ir_print_other_instruction(irp, instruction->dest_type);
}
ir_print_other_instruction(irp, instruction->target);
fprintf(irp->f, ")");
}
static void ir_print_enum_to_int(IrPrint *irp, IrInstructionEnumToInt *instruction) {
fprintf(irp->f, "@enumToInt(");
ir_print_other_instruction(irp, instruction->target);
fprintf(irp->f, ")");
}
@ -1717,6 +1728,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
case IrInstructionIdAtomicLoad:
ir_print_atomic_load(irp, (IrInstructionAtomicLoad *)instruction);
break;
case IrInstructionIdEnumToInt:
ir_print_enum_to_int(irp, (IrInstructionEnumToInt *)instruction);
break;
}
fprintf(irp->f, "\n");
}

View File

@ -180,7 +180,7 @@ pub const StreamingParser = struct {
pub fn fromInt(x: var) State {
debug.assert(x == 0 or x == 1);
const T = @TagType(State);
return State(@intCast(T, x));
return @intToEnum(State, @intCast(T, x));
}
};

View File

@ -92,14 +92,14 @@ test "enum to int" {
}
fn shouldEqual(n: Number, expected: u3) void {
assert(u3(n) == expected);
assert(@enumToInt(n) == expected);
}
test "int to enum" {
testIntToEnumEval(3);
}
fn testIntToEnumEval(x: i32) void {
assert(IntToEnumNumber(@intCast(u3, x)) == IntToEnumNumber.Three);
assert(@intToEnum(IntToEnumNumber, @intCast(u3, x)) == IntToEnumNumber.Three);
}
const IntToEnumNumber = enum {
Zero,
@ -768,7 +768,7 @@ test "casting enum to its tag type" {
}
fn testCastEnumToTagType(value: Small2) void {
assert(u2(value) == 1);
assert(@enumToInt(value) == 1);
}
const MultipleChoice = enum(u32) {
@ -784,7 +784,7 @@ test "enum with specified tag values" {
}
fn testEnumWithSpecifiedTagValues(x: MultipleChoice) void {
assert(u32(x) == 60);
assert(@enumToInt(x) == 60);
assert(1234 == switch (x) {
MultipleChoice.A => 1,
MultipleChoice.B => 2,
@ -811,7 +811,7 @@ test "enum with specified and unspecified tag values" {
}
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void {
assert(u32(x) == 1000);
assert(@enumToInt(x) == 1000);
assert(1234 == switch (x) {
MultipleChoice2.A => 1,
MultipleChoice2.B => 2,
@ -826,8 +826,8 @@ fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void {
}
test "cast integer literal to enum" {
assert(MultipleChoice2(0) == MultipleChoice2.Unspecified1);
assert(MultipleChoice2(40) == MultipleChoice2.B);
assert(@intToEnum(MultipleChoice2, 0) == MultipleChoice2.Unspecified1);
assert(@intToEnum(MultipleChoice2, 40) == MultipleChoice2.B);
}
const EnumWithOneMember = enum {
@ -865,7 +865,7 @@ const EnumWithTagValues = enum(u4) {
D = 1 << 3,
};
test "enum with tag values don't require parens" {
assert(u4(EnumWithTagValues.C) == 0b0100);
assert(@enumToInt(EnumWithTagValues.C) == 0b0100);
}
test "enum with 1 field but explicit tag type should still have the tag type" {

View File

@ -126,7 +126,7 @@ const MultipleChoice = union(enum(u32)) {
test "simple union(enum(u32))" {
var x = MultipleChoice.C;
assert(x == MultipleChoice.C);
assert(u32(@TagType(MultipleChoice)(x)) == 60);
assert(@enumToInt(@TagType(MultipleChoice)(x)) == 60);
}
const MultipleChoice2 = union(enum(u32)) {
@ -148,7 +148,7 @@ test "union(enum(u32)) with specified and unspecified tag values" {
}
fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: *const MultipleChoice2) void {
assert(u32(@TagType(MultipleChoice2)(x.*)) == 60);
assert(@enumToInt(@TagType(MultipleChoice2)(x.*)) == 60);
assert(1123 == switch (x.*) {
MultipleChoice2.A => 1,
MultipleChoice2.B => 2,

View File

@ -3709,22 +3709,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
".tmp_source.zig:9:22: error: expected type 'u2', found 'Small'",
);
cases.add(
"explicitly casting enum to non tag type",
\\const Small = enum(u2) {
\\ One,
\\ Two,
\\ Three,
\\ Four,
\\};
\\
\\export fn entry() void {
\\ var x = u3(Small.Two);
\\}
,
".tmp_source.zig:9:15: error: enum to integer cast to 'u3' instead of its tag type, 'u2'",
);
cases.add(
"explicitly casting non tag type to enum",
\\const Small = enum(u2) {
@ -3736,10 +3720,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\
\\export fn entry() void {
\\ var y = u3(3);
\\ var x = Small(y);
\\ var x = @intToEnum(Small, y);
\\}
,
".tmp_source.zig:10:18: error: integer to enum cast from 'u3' instead of its tag type, 'u2'",
".tmp_source.zig:10:31: error: expected type 'u2', found 'u3'",
);
cases.add(
@ -4020,10 +4004,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
\\ B = 11,
\\};
\\export fn entry() void {
\\ var x = Foo(0);
\\ var x = @intToEnum(Foo, 0);
\\}
,
".tmp_source.zig:6:16: error: enum 'Foo' has no tag matching integer value 0",
".tmp_source.zig:6:13: error: enum 'Foo' has no tag matching integer value 0",
".tmp_source.zig:1:13: note: 'Foo' declared here",
);