diff --git a/doc/langref.html.in b/doc/langref.html.in
index 24bf6e1b16..19f023f6e1 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -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#}
+
@enumToInt(enum_value: var) var
+
+ Converts an enumeration value into its integer tag type.
+
+ {#see_also|@intToEnum#}
+ {#header_close#}
+
{#header_open|@errSetCast#}
@errSetCast(comptime T: DestType, value: var) DestType
@@ -5095,6 +5103,18 @@ fn add(a: i32, b: i32) i32 { return a + b; }
{#header_close#}
+ {#header_open|@intToEnum#}
+ @intToEnum(comptime DestType: type, int_value: @TagType(DestType)) DestType
+
+ Converts an integer into an {#link|enum#} value.
+
+
+ Attempting to convert an integer which represents no value in the chosen enum type invokes
+ safety-checked {#link|Undefined Behavior#}.
+
+ {#see_also|@enumToInt#}
+ {#header_close#}
+
{#header_open|@intToError#}
@intToError(value: @IntType(false, @sizeOf(error) * 8)) error
@@ -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];
diff --git a/src/all_types.hpp b/src/all_types.hpp
index e1a4ed7510..41c9fe18c3 100644
--- a/src/all_types.hpp
+++ b/src/all_types.hpp
@@ -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;
};
diff --git a/src/codegen.cpp b/src/codegen.cpp
index c9b4ade4c6..21520e0dd0 100644
--- a/src/codegen.cpp
+++ b/src/codegen.cpp
@@ -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
diff --git a/src/ir.cpp b/src/ir.cpp
index 350c80017e..a8599e7aae 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -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(
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(
+ 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:
diff --git a/src/ir_print.cpp b/src/ir_print.cpp
index 1b35ecf57f..5e5a71382c 100644
--- a/src/ir_print.cpp
+++ b/src/ir_print.cpp
@@ -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");
}
diff --git a/std/json.zig b/std/json.zig
index 2930cd21bb..8986034fb4 100644
--- a/std/json.zig
+++ b/std/json.zig
@@ -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));
}
};
diff --git a/test/cases/enum.zig b/test/cases/enum.zig
index 6a02a47784..50edfda536 100644
--- a/test/cases/enum.zig
+++ b/test/cases/enum.zig
@@ -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" {
diff --git a/test/cases/union.zig b/test/cases/union.zig
index bdcbbdb452..78b2dc8dd7 100644
--- a/test/cases/union.zig
+++ b/test/cases/union.zig
@@ -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,
diff --git a/test/compile_errors.zig b/test/compile_errors.zig
index 866b303082..609e3f103f 100644
--- a/test/compile_errors.zig
+++ b/test/compile_errors.zig
@@ -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",
);