mirror of
https://github.com/ziglang/zig.git
synced 2024-11-26 23:22:44 +00:00
Merge pull request #4191 from Vexu/non-exhaustive-enums
Implement non-exhaustive enums
This commit is contained in:
commit
b5ac079f88
@ -2893,6 +2893,47 @@ test "switch using enum literals" {
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|Non-exhaustive enum#}
|
||||
<p>
|
||||
A Non-exhaustive enum can be created by adding a trailing '_' field.
|
||||
It must specify a tag type and cannot consume every enumeration value.
|
||||
</p>
|
||||
<p>
|
||||
{#link|@intToEnum#} on a non-exhaustive enum cannot fail.
|
||||
</p>
|
||||
<p>
|
||||
A switch on a non-exhaustive enum can include a '_' prong as an alternative to an {#syntax#}else{#endsyntax#} prong
|
||||
with the difference being that it makes it a compile error if all the known tag names are not handled by the switch.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Number = enum(u8) {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
_,
|
||||
};
|
||||
|
||||
test "switch on non-exhaustive enum" {
|
||||
const number = Number.One;
|
||||
const result = switch (number) {
|
||||
.One => true,
|
||||
.Two,
|
||||
.Three => false,
|
||||
_ => false,
|
||||
};
|
||||
assert(result);
|
||||
const is_one = switch (number) {
|
||||
.One => true,
|
||||
else => false,
|
||||
};
|
||||
assert(is_one);
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|union#}
|
||||
|
@ -254,6 +254,7 @@ pub const TypeInfo = union(enum) {
|
||||
tag_type: type,
|
||||
fields: []EnumField,
|
||||
decls: []Declaration,
|
||||
is_exhaustive: bool,
|
||||
};
|
||||
|
||||
/// This data structure is used by the Zig language code generation and
|
||||
|
@ -289,8 +289,7 @@ pub fn translate(
|
||||
tree.errors = ast.Tree.ErrorList.init(arena);
|
||||
|
||||
tree.root_node = try arena.create(ast.Node.Root);
|
||||
tree.root_node.* = ast.Node.Root{
|
||||
.base = ast.Node{ .id = ast.Node.Id.Root },
|
||||
tree.root_node.* = .{
|
||||
.decls = ast.Node.Root.DeclList.init(arena),
|
||||
// initialized with the eof token at the end
|
||||
.eof_token = undefined,
|
||||
@ -440,7 +439,6 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void {
|
||||
.PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern", .{}),
|
||||
.Auto => unreachable, // Not legal on functions
|
||||
.Register => unreachable, // Not legal on functions
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
|
||||
@ -877,25 +875,23 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
|
||||
// types, while that's not ISO-C compliant many compilers allow this and
|
||||
// default to the usual integer type used for all the enums.
|
||||
|
||||
// TODO only emit this tag type if the enum tag type is not the default.
|
||||
// I don't know what the default is, need to figure out how clang is deciding.
|
||||
// it appears to at least be different across gcc/msvc
|
||||
if (int_type.ptr != null and
|
||||
!isCBuiltinType(int_type, .UInt) and
|
||||
!isCBuiltinType(int_type, .Int))
|
||||
{
|
||||
_ = try appendToken(c, .LParen, "(");
|
||||
container_node.init_arg_expr = .{
|
||||
.Type = transQualType(rp, int_type, enum_loc) catch |err| switch (err) {
|
||||
// default to c_int since msvc and gcc default to different types
|
||||
_ = try appendToken(c, .LParen, "(");
|
||||
container_node.init_arg_expr = .{
|
||||
.Type = if (int_type.ptr != null and
|
||||
!isCBuiltinType(int_type, .UInt) and
|
||||
!isCBuiltinType(int_type, .Int))
|
||||
transQualType(rp, int_type, enum_loc) catch |err| switch (err) {
|
||||
error.UnsupportedType => {
|
||||
try failDecl(c, enum_loc, name, "unable to translate enum tag type", .{});
|
||||
return null;
|
||||
},
|
||||
else => |e| return e,
|
||||
},
|
||||
};
|
||||
_ = try appendToken(c, .RParen, ")");
|
||||
}
|
||||
}
|
||||
else
|
||||
try transCreateNodeIdentifier(c, "c_int"),
|
||||
};
|
||||
_ = try appendToken(c, .RParen, ")");
|
||||
|
||||
container_node.lbrace_token = try appendToken(c, .LBrace, "{");
|
||||
|
||||
@ -953,6 +949,19 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No
|
||||
tld_node.semicolon_token = try appendToken(c, .Semicolon, ";");
|
||||
try addTopLevelDecl(c, field_name, &tld_node.base);
|
||||
}
|
||||
// make non exhaustive
|
||||
const field_node = try c.a().create(ast.Node.ContainerField);
|
||||
field_node.* = .{
|
||||
.doc_comments = null,
|
||||
.comptime_token = null,
|
||||
.name_token = try appendIdentifier(c, "_"),
|
||||
.type_expr = null,
|
||||
.value_expr = null,
|
||||
.align_expr = null,
|
||||
};
|
||||
|
||||
try container_node.fields_and_decls.push(&field_node.base);
|
||||
_ = try appendToken(c, .Comma, ",");
|
||||
container_node.rbrace_token = try appendToken(c, .RBrace, "}");
|
||||
|
||||
break :blk &container_node.base;
|
||||
@ -1231,18 +1240,6 @@ fn transBinaryOperator(
|
||||
op_id = .BitOr;
|
||||
op_token = try appendToken(rp.c, .Pipe, "|");
|
||||
},
|
||||
.Assign,
|
||||
.MulAssign,
|
||||
.DivAssign,
|
||||
.RemAssign,
|
||||
.AddAssign,
|
||||
.SubAssign,
|
||||
.ShlAssign,
|
||||
.ShrAssign,
|
||||
.AndAssign,
|
||||
.XorAssign,
|
||||
.OrAssign,
|
||||
=> unreachable,
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
@ -1678,7 +1675,6 @@ fn transStringLiteral(
|
||||
"TODO: support string literal kind {}",
|
||||
.{kind},
|
||||
),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2206,6 +2202,19 @@ fn transDoWhileLoop(
|
||||
.id = .Loop,
|
||||
};
|
||||
|
||||
// if (!cond) break;
|
||||
const if_node = try transCreateNodeIf(rp.c);
|
||||
var cond_scope = Scope{
|
||||
.parent = scope,
|
||||
.id = .Condition,
|
||||
};
|
||||
const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
|
||||
prefix_op.rhs = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true);
|
||||
_ = try appendToken(rp.c, .RParen, ")");
|
||||
if_node.condition = &prefix_op.base;
|
||||
if_node.body = &(try transCreateNodeBreak(rp.c, null)).base;
|
||||
_ = try appendToken(rp.c, .Semicolon, ";");
|
||||
|
||||
const body_node = if (ZigClangStmt_getStmtClass(ZigClangDoStmt_getBody(stmt)) == .CompoundStmtClass) blk: {
|
||||
// there's already a block in C, so we'll append our condition to it.
|
||||
// c: do {
|
||||
@ -2217,10 +2226,7 @@ fn transDoWhileLoop(
|
||||
// zig: b;
|
||||
// zig: if (!cond) break;
|
||||
// zig: }
|
||||
const body = (try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)).cast(ast.Node.Block).?;
|
||||
// if this is used as an expression in Zig it needs to be immediately followed by a semicolon
|
||||
_ = try appendToken(rp.c, .Semicolon, ";");
|
||||
break :blk body;
|
||||
break :blk (try transStmt(rp, &loop_scope, ZigClangDoStmt_getBody(stmt), .unused, .r_value)).cast(ast.Node.Block).?;
|
||||
} else blk: {
|
||||
// the C statement is without a block, so we need to create a block to contain it.
|
||||
// c: do
|
||||
@ -2236,19 +2242,6 @@ fn transDoWhileLoop(
|
||||
break :blk block;
|
||||
};
|
||||
|
||||
// if (!cond) break;
|
||||
const if_node = try transCreateNodeIf(rp.c);
|
||||
var cond_scope = Scope{
|
||||
.parent = scope,
|
||||
.id = .Condition,
|
||||
};
|
||||
const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!");
|
||||
prefix_op.rhs = try transBoolExpr(rp, &cond_scope, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true);
|
||||
_ = try appendToken(rp.c, .RParen, ")");
|
||||
if_node.condition = &prefix_op.base;
|
||||
if_node.body = &(try transCreateNodeBreak(rp.c, null)).base;
|
||||
_ = try appendToken(rp.c, .Semicolon, ";");
|
||||
|
||||
try body_node.statements.push(&if_node.base);
|
||||
if (new)
|
||||
body_node.rbrace = try appendToken(rp.c, .RBrace, "}");
|
||||
@ -4783,8 +4776,7 @@ fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex {
|
||||
fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {
|
||||
const token_index = try appendIdentifier(c, name);
|
||||
const identifier = try c.a().create(ast.Node.Identifier);
|
||||
identifier.* = ast.Node.Identifier{
|
||||
.base = ast.Node{ .id = ast.Node.Id.Identifier },
|
||||
identifier.* = .{
|
||||
.token = token_index,
|
||||
};
|
||||
return &identifier.base;
|
||||
@ -4923,8 +4915,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
|
||||
|
||||
const token_index = try appendToken(c, .Keyword_var, "var");
|
||||
const identifier = try c.a().create(ast.Node.Identifier);
|
||||
identifier.* = ast.Node.Identifier{
|
||||
.base = ast.Node{ .id = ast.Node.Id.Identifier },
|
||||
identifier.* = .{
|
||||
.token = token_index,
|
||||
};
|
||||
|
||||
|
@ -1385,6 +1385,7 @@ struct ZigTypeEnum {
|
||||
ContainerLayout layout;
|
||||
ResolveStatus resolve_status;
|
||||
|
||||
bool non_exhaustive;
|
||||
bool resolve_loop_flag;
|
||||
};
|
||||
|
||||
@ -3669,6 +3670,7 @@ struct IrInstructionCheckSwitchProngs {
|
||||
IrInstructionCheckSwitchProngsRange *ranges;
|
||||
size_t range_count;
|
||||
bool have_else_prong;
|
||||
bool have_underscore_prong;
|
||||
};
|
||||
|
||||
struct IrInstructionCheckStatementIsVoid {
|
||||
|
@ -2569,15 +2569,8 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
|
||||
enum_type->data.enumeration.src_field_count = field_count;
|
||||
enum_type->data.enumeration.fields = allocate<TypeEnumField>(field_count);
|
||||
enum_type->data.enumeration.fields_by_name.init(field_count);
|
||||
|
||||
Scope *scope = &enum_type->data.enumeration.decls_scope->base;
|
||||
|
||||
HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> occupied_tag_values = {};
|
||||
occupied_tag_values.init(field_count);
|
||||
|
||||
ZigType *tag_int_type;
|
||||
if (enum_type->data.enumeration.layout == ContainerLayoutExtern) {
|
||||
tag_int_type = get_c_int_type(g, CIntTypeInt);
|
||||
@ -2619,6 +2612,7 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
|
||||
}
|
||||
}
|
||||
|
||||
enum_type->data.enumeration.non_exhaustive = false;
|
||||
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;
|
||||
@ -2627,6 +2621,31 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
|
||||
BigInt bi_one;
|
||||
bigint_init_unsigned(&bi_one, 1);
|
||||
|
||||
AstNode *last_field_node = decl_node->data.container_decl.fields.at(field_count - 1);
|
||||
if (buf_eql_str(last_field_node->data.struct_field.name, "_")) {
|
||||
field_count -= 1;
|
||||
if (field_count > 1 && log2_u64(field_count) == enum_type->size_in_bits) {
|
||||
add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum specifies every value"));
|
||||
enum_type->data.enumeration.resolve_status = ResolveStatusInvalid;
|
||||
}
|
||||
if (decl_node->data.container_decl.init_arg_expr == nullptr) {
|
||||
add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum must specify size"));
|
||||
enum_type->data.enumeration.resolve_status = ResolveStatusInvalid;
|
||||
}
|
||||
if (last_field_node->data.struct_field.value != nullptr) {
|
||||
add_node_error(g, last_field_node, buf_sprintf("value assigned to '_' field of non-exhaustive enum"));
|
||||
enum_type->data.enumeration.resolve_status = ResolveStatusInvalid;
|
||||
}
|
||||
enum_type->data.enumeration.non_exhaustive = true;
|
||||
}
|
||||
|
||||
enum_type->data.enumeration.src_field_count = field_count;
|
||||
enum_type->data.enumeration.fields = allocate<TypeEnumField>(field_count);
|
||||
enum_type->data.enumeration.fields_by_name.init(field_count);
|
||||
|
||||
HashMap<BigInt, AstNode *, bigint_hash, bigint_eql> occupied_tag_values = {};
|
||||
occupied_tag_values.init(field_count);
|
||||
|
||||
TypeEnumField *last_enum_field = nullptr;
|
||||
|
||||
for (uint32_t field_i = 0; field_i < field_count; field_i += 1) {
|
||||
@ -2648,6 +2667,11 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) {
|
||||
buf_sprintf("consider 'union(enum)' here"));
|
||||
}
|
||||
|
||||
if (buf_eql_str(type_enum_field->name, "_")) {
|
||||
add_node_error(g, field_node, buf_sprintf("'_' field of non-exhaustive enum must be last"));
|
||||
enum_type->data.enumeration.resolve_status = ResolveStatusInvalid;
|
||||
}
|
||||
|
||||
auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field);
|
||||
if (field_entry != nullptr) {
|
||||
ErrorMsg *msg = add_node_error(g, field_node,
|
||||
|
@ -3356,7 +3356,7 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable,
|
||||
LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
|
||||
instruction->target->value->type, tag_int_type, target_val);
|
||||
|
||||
if (ir_want_runtime_safety(g, &instruction->base) && wanted_type->data.enumeration.layout != ContainerLayoutExtern) {
|
||||
if (ir_want_runtime_safety(g, &instruction->base) && !wanted_type->data.enumeration.non_exhaustive) {
|
||||
LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue");
|
||||
LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue");
|
||||
size_t field_count = wanted_type->data.enumeration.src_field_count;
|
||||
@ -5065,6 +5065,11 @@ static LLVMValueRef ir_render_enum_tag_name(CodeGen *g, IrExecutable *executable
|
||||
{
|
||||
ZigType *enum_type = instruction->target->value->type;
|
||||
assert(enum_type->id == ZigTypeIdEnum);
|
||||
if (enum_type->data.enumeration.non_exhaustive) {
|
||||
add_node_error(g, instruction->base.source_node,
|
||||
buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991"));
|
||||
codegen_report_errors_and_exit(g);
|
||||
}
|
||||
|
||||
LLVMValueRef enum_name_function = get_enum_tag_name_function(g, enum_type);
|
||||
|
||||
|
123
src/ir.cpp
123
src/ir.cpp
@ -3452,7 +3452,7 @@ static IrInstruction *ir_build_err_to_int(IrBuilder *irb, Scope *scope, AstNode
|
||||
|
||||
static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, AstNode *source_node,
|
||||
IrInstruction *target_value, IrInstructionCheckSwitchProngsRange *ranges, size_t range_count,
|
||||
bool have_else_prong)
|
||||
bool have_else_prong, bool have_underscore_prong)
|
||||
{
|
||||
IrInstructionCheckSwitchProngs *instruction = ir_build_instruction<IrInstructionCheckSwitchProngs>(
|
||||
irb, scope, source_node);
|
||||
@ -3460,6 +3460,7 @@ static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope,
|
||||
instruction->ranges = ranges;
|
||||
instruction->range_count = range_count;
|
||||
instruction->have_else_prong = have_else_prong;
|
||||
instruction->have_underscore_prong = have_underscore_prong;
|
||||
|
||||
ir_ref_instruction(target_value, irb->current_basic_block);
|
||||
for (size_t i = 0; i < range_count; i += 1) {
|
||||
@ -8092,34 +8093,11 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
|
||||
Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
|
||||
Scope *comptime_scope = create_comptime_scope(irb->codegen, node, scope);
|
||||
AstNode *else_prong = nullptr;
|
||||
AstNode *underscore_prong = nullptr;
|
||||
for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
|
||||
AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
|
||||
size_t prong_item_count = prong_node->data.switch_prong.items.length;
|
||||
if (prong_item_count == 0) {
|
||||
ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent);
|
||||
if (else_prong) {
|
||||
ErrorMsg *msg = add_node_error(irb->codegen, prong_node,
|
||||
buf_sprintf("multiple else prongs in switch expression"));
|
||||
add_error_note(irb->codegen, msg, else_prong,
|
||||
buf_sprintf("previous else prong is here"));
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
else_prong = prong_node;
|
||||
|
||||
IrBasicBlock *prev_block = irb->current_basic_block;
|
||||
if (peer_parent->peers.length > 0) {
|
||||
peer_parent->peers.last()->next_bb = else_block;
|
||||
}
|
||||
peer_parent->peers.append(this_peer_result_loc);
|
||||
ir_set_cursor_at_end_and_append_block(irb, else_block);
|
||||
if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
|
||||
is_comptime, var_is_comptime, target_value_ptr, nullptr, 0, &incoming_blocks, &incoming_values,
|
||||
&switch_else_var, LValNone, &this_peer_result_loc->base))
|
||||
{
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
ir_set_cursor_at_end(irb, prev_block);
|
||||
} else if (prong_node->data.switch_prong.any_items_are_range) {
|
||||
if (prong_node->data.switch_prong.any_items_are_range) {
|
||||
ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent);
|
||||
|
||||
IrInstruction *ok_bit = nullptr;
|
||||
@ -8197,6 +8175,56 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
|
||||
}
|
||||
|
||||
ir_set_cursor_at_end_and_append_block(irb, range_block_no);
|
||||
} else {
|
||||
if (prong_item_count == 0) {
|
||||
if (else_prong) {
|
||||
ErrorMsg *msg = add_node_error(irb->codegen, prong_node,
|
||||
buf_sprintf("multiple else prongs in switch expression"));
|
||||
add_error_note(irb->codegen, msg, else_prong,
|
||||
buf_sprintf("previous else prong is here"));
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
else_prong = prong_node;
|
||||
} else if (prong_item_count == 1 &&
|
||||
prong_node->data.switch_prong.items.at(0)->type == NodeTypeSymbol &&
|
||||
buf_eql_str(prong_node->data.switch_prong.items.at(0)->data.symbol_expr.symbol, "_")) {
|
||||
if (underscore_prong) {
|
||||
ErrorMsg *msg = add_node_error(irb->codegen, prong_node,
|
||||
buf_sprintf("multiple '_' prongs in switch expression"));
|
||||
add_error_note(irb->codegen, msg, underscore_prong,
|
||||
buf_sprintf("previous '_' prong is here"));
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
underscore_prong = prong_node;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (underscore_prong && else_prong) {
|
||||
ErrorMsg *msg = add_node_error(irb->codegen, prong_node,
|
||||
buf_sprintf("else and '_' prong in switch expression"));
|
||||
if (underscore_prong == prong_node)
|
||||
add_error_note(irb->codegen, msg, else_prong,
|
||||
buf_sprintf("else prong is here"));
|
||||
else
|
||||
add_error_note(irb->codegen, msg, underscore_prong,
|
||||
buf_sprintf("'_' prong is here"));
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent);
|
||||
|
||||
IrBasicBlock *prev_block = irb->current_basic_block;
|
||||
if (peer_parent->peers.length > 0) {
|
||||
peer_parent->peers.last()->next_bb = else_block;
|
||||
}
|
||||
peer_parent->peers.append(this_peer_result_loc);
|
||||
ir_set_cursor_at_end_and_append_block(irb, else_block);
|
||||
if (!ir_gen_switch_prong_expr(irb, subexpr_scope, node, prong_node, end_block,
|
||||
is_comptime, var_is_comptime, target_value_ptr, nullptr, 0, &incoming_blocks, &incoming_values,
|
||||
&switch_else_var, LValNone, &this_peer_result_loc->base))
|
||||
{
|
||||
return irb->codegen->invalid_instruction;
|
||||
}
|
||||
ir_set_cursor_at_end(irb, prev_block);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8208,6 +8236,8 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
|
||||
continue;
|
||||
if (prong_node->data.switch_prong.any_items_are_range)
|
||||
continue;
|
||||
if (underscore_prong == prong_node)
|
||||
continue;
|
||||
|
||||
ResultLocPeer *this_peer_result_loc = create_peer_result(peer_parent);
|
||||
|
||||
@ -8251,7 +8281,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
|
||||
}
|
||||
|
||||
IrInstruction *switch_prongs_void = ir_build_check_switch_prongs(irb, scope, node, target_value,
|
||||
check_ranges.items, check_ranges.length, else_prong != nullptr);
|
||||
check_ranges.items, check_ranges.length, else_prong != nullptr, underscore_prong != nullptr);
|
||||
|
||||
IrInstruction *br_instruction;
|
||||
if (cases.length == 0) {
|
||||
@ -8271,7 +8301,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
|
||||
peer_parent->peers.at(i)->base.source_instruction = peer_parent->base.source_instruction;
|
||||
}
|
||||
|
||||
if (!else_prong) {
|
||||
if (!else_prong && !underscore_prong) {
|
||||
if (peer_parent->peers.length != 0) {
|
||||
peer_parent->peers.last()->next_bb = else_block;
|
||||
}
|
||||
@ -12792,7 +12822,7 @@ static IrInstruction *ir_analyze_int_to_enum(IrAnalyze *ira, IrInstruction *sour
|
||||
return ira->codegen->invalid_instruction;
|
||||
|
||||
TypeEnumField *field = find_enum_field_by_tag(wanted_type, &val->data.x_bigint);
|
||||
if (field == nullptr && wanted_type->data.enumeration.layout != ContainerLayoutExtern) {
|
||||
if (field == nullptr && !wanted_type->data.enumeration.non_exhaustive) {
|
||||
Buf *val_buf = buf_alloc();
|
||||
bigint_append_buf(val_buf, &val->data.x_bigint, 10);
|
||||
ErrorMsg *msg = ir_add_error(ira, source_instr,
|
||||
@ -22327,6 +22357,11 @@ static IrInstruction *ir_analyze_instruction_enum_tag_name(IrAnalyze *ira, IrIns
|
||||
if (instr_is_comptime(target)) {
|
||||
if ((err = type_resolve(ira->codegen, target->value->type, ResolveStatusZeroBitsKnown)))
|
||||
return ira->codegen->invalid_instruction;
|
||||
if (target->value->type->data.enumeration.non_exhaustive) {
|
||||
add_node_error(ira->codegen, instruction->base.source_node,
|
||||
buf_sprintf("TODO @tagName on non-exhaustive enum https://github.com/ziglang/zig/issues/3991"));
|
||||
return ira->codegen->invalid_instruction;
|
||||
}
|
||||
TypeEnumField *field = find_enum_field_by_tag(target->value->type, &target->value->data.x_bigint);
|
||||
ZigValue *array_val = create_const_str_lit(ira->codegen, field->name)->data.x_ptr.data.ref.pointee;
|
||||
IrInstruction *result = ir_const(ira, &instruction->base, nullptr);
|
||||
@ -23077,7 +23112,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInstruction *source_instr
|
||||
result->special = ConstValSpecialStatic;
|
||||
result->type = ir_type_info_get_type(ira, "Enum", nullptr);
|
||||
|
||||
ZigValue **fields = alloc_const_vals_ptrs(4);
|
||||
ZigValue **fields = alloc_const_vals_ptrs(5);
|
||||
result->data.x_struct.fields = fields;
|
||||
|
||||
// layout: ContainerLayout
|
||||
@ -23123,6 +23158,11 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInstruction *source_instr
|
||||
{
|
||||
return err;
|
||||
}
|
||||
// is_exhaustive: bool
|
||||
ensure_field_index(result->type, "is_exhaustive", 4);
|
||||
fields[4]->special = ConstValSpecialStatic;
|
||||
fields[4]->type = ira->codegen->builtin_types.entry_bool;
|
||||
fields[4]->data.x_bool = !type_entry->data.enumeration.non_exhaustive;
|
||||
|
||||
break;
|
||||
}
|
||||
@ -26442,10 +26482,27 @@ static IrInstruction *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira,
|
||||
bigint_incr(&field_index);
|
||||
}
|
||||
}
|
||||
if (!instruction->have_else_prong) {
|
||||
if (switch_type->data.enumeration.layout == ContainerLayoutExtern) {
|
||||
if (instruction->have_underscore_prong) {
|
||||
if (!switch_type->data.enumeration.non_exhaustive){
|
||||
ir_add_error(ira, &instruction->base,
|
||||
buf_sprintf("switch on an extern enum must have an else prong"));
|
||||
buf_sprintf("switch on non-exhaustive enum has `_` prong"));
|
||||
}
|
||||
for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) {
|
||||
TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];
|
||||
if (buf_eql_str(enum_field->name, "_"))
|
||||
continue;
|
||||
|
||||
auto entry = field_prev_uses.maybe_get(enum_field->value);
|
||||
if (!entry) {
|
||||
ir_add_error(ira, &instruction->base,
|
||||
buf_sprintf("enumeration value '%s.%s' not handled in switch", buf_ptr(&switch_type->name),
|
||||
buf_ptr(enum_field->name)));
|
||||
}
|
||||
}
|
||||
} else if (!instruction->have_else_prong) {
|
||||
if (switch_type->data.enumeration.non_exhaustive) {
|
||||
ir_add_error(ira, &instruction->base,
|
||||
buf_sprintf("switch on non-exhaustive enum must include `else` or `_` prong"));
|
||||
}
|
||||
for (uint32_t i = 0; i < switch_type->data.enumeration.src_field_count; i += 1) {
|
||||
TypeEnumField *enum_field = &switch_type->data.enumeration.fields[i];
|
||||
|
@ -2,6 +2,56 @@ const tests = @import("tests.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
cases.addTest("non-exhaustive enums",
|
||||
\\const A = enum {
|
||||
\\ a,
|
||||
\\ b,
|
||||
\\ _ = 1,
|
||||
\\};
|
||||
\\const B = enum(u1) {
|
||||
\\ a,
|
||||
\\ _,
|
||||
\\ b,
|
||||
\\};
|
||||
\\const C = enum(u1) {
|
||||
\\ a,
|
||||
\\ b,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub export fn entry() void {
|
||||
\\ _ = A;
|
||||
\\ _ = B;
|
||||
\\ _ = C;
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
"tmp.zig:4:5: error: non-exhaustive enum must specify size",
|
||||
"error: value assigned to '_' field of non-exhaustive enum",
|
||||
"error: non-exhaustive enum specifies every value",
|
||||
"error: '_' field of non-exhaustive enum must be last",
|
||||
});
|
||||
|
||||
cases.addTest("switching with non-exhaustive enums",
|
||||
\\const E = enum(u8) {
|
||||
\\ a,
|
||||
\\ b,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub export fn entry() void {
|
||||
\\ var e: E = .b;
|
||||
\\ switch (e) { // error: switch not handling the tag `b`
|
||||
\\ .a => {},
|
||||
\\ _ => {},
|
||||
\\ }
|
||||
\\ switch (e) { // error: switch on non-exhaustive enum must include `else` or `_` prong
|
||||
\\ .a => {},
|
||||
\\ .b => {},
|
||||
\\ }
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
"tmp.zig:8:5: error: enumeration value 'E.b' not handled in switch",
|
||||
"tmp.zig:12:5: error: switch on non-exhaustive enum must include `else` or `_` prong",
|
||||
});
|
||||
|
||||
cases.addTest("@export with empty name string",
|
||||
\\pub export fn entry() void { }
|
||||
\\comptime {
|
||||
@ -139,25 +189,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
"tmp.zig:2:13: error: pointer type '[*]align(4) u8' requires aligned address",
|
||||
});
|
||||
|
||||
cases.add("switch on extern enum missing else prong",
|
||||
\\const i = extern enum {
|
||||
\\ n = 0,
|
||||
\\ o = 2,
|
||||
\\ p = 4,
|
||||
\\ q = 4,
|
||||
\\};
|
||||
\\pub fn main() void {
|
||||
\\ var x = @intToEnum(i, 52);
|
||||
\\ switch (x) {
|
||||
\\ .n,
|
||||
\\ .o,
|
||||
\\ .p => unreachable,
|
||||
\\ }
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
"tmp.zig:9:5: error: switch on an extern enum must have an else prong",
|
||||
});
|
||||
|
||||
cases.add("invalid float literal",
|
||||
\\const std = @import("std");
|
||||
\\
|
||||
|
@ -618,7 +618,6 @@ test "peer resolution of string literals" {
|
||||
.b => "two",
|
||||
.c => "three",
|
||||
.d => "four",
|
||||
else => unreachable,
|
||||
};
|
||||
expect(mem.eql(u8, cmd, "two"));
|
||||
}
|
||||
|
@ -11,16 +11,9 @@ test "extern enum" {
|
||||
};
|
||||
fn doTheTest(y: c_int) void {
|
||||
var x = i.o;
|
||||
expect(@enumToInt(x) == 2);
|
||||
x = @intToEnum(i, 12);
|
||||
expect(@enumToInt(x) == 12);
|
||||
x = @intToEnum(i, y);
|
||||
expect(@enumToInt(x) == 52);
|
||||
switch (x) {
|
||||
.n,
|
||||
.o,
|
||||
.p => unreachable,
|
||||
else => {},
|
||||
.n, .p => unreachable,
|
||||
.o => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -28,6 +21,50 @@ test "extern enum" {
|
||||
comptime S.doTheTest(52);
|
||||
}
|
||||
|
||||
test "non-exhaustive enum" {
|
||||
const S = struct {
|
||||
const E = enum(u8) {
|
||||
a,
|
||||
b,
|
||||
_,
|
||||
};
|
||||
fn doTheTest(y: u8) void {
|
||||
var e: E = .b;
|
||||
expect(switch (e) {
|
||||
.a => false,
|
||||
.b => true,
|
||||
_ => false,
|
||||
});
|
||||
e = @intToEnum(E, 12);
|
||||
expect(switch (e) {
|
||||
.a => false,
|
||||
.b => false,
|
||||
_ => true,
|
||||
});
|
||||
|
||||
expect(switch (e) {
|
||||
.a => false,
|
||||
.b => false,
|
||||
else => true,
|
||||
});
|
||||
e = .b;
|
||||
expect(switch (e) {
|
||||
.a => false,
|
||||
else => true,
|
||||
});
|
||||
|
||||
expect(@typeInfo(E).Enum.fields.len == 2);
|
||||
e = @intToEnum(E, 12);
|
||||
expect(@enumToInt(e) == 12);
|
||||
e = @intToEnum(E, y);
|
||||
expect(@enumToInt(e) == 52);
|
||||
expect(@typeInfo(E).Enum.is_exhaustive == false);
|
||||
}
|
||||
};
|
||||
S.doTheTest(52);
|
||||
comptime S.doTheTest(52);
|
||||
}
|
||||
|
||||
test "enum type" {
|
||||
const foo1 = Foo{ .One = 13 };
|
||||
const foo2 = Foo{
|
||||
|
@ -629,6 +629,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ VAL21 = 6917529027641081853,
|
||||
\\ VAL22 = 0,
|
||||
\\ VAL23 = -1,
|
||||
\\ _,
|
||||
\\};
|
||||
});
|
||||
}
|
||||
@ -988,8 +989,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\enum enum_ty { FOO };
|
||||
, &[_][]const u8{
|
||||
\\pub const FOO = @enumToInt(enum_enum_ty.FOO);
|
||||
\\pub const enum_enum_ty = extern enum {
|
||||
\\pub const enum_enum_ty = extern enum(c_int) {
|
||||
\\ FOO,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub extern var my_enum: enum_enum_ty;
|
||||
});
|
||||
@ -1102,28 +1104,31 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\pub const a = @enumToInt(enum_unnamed_1.a);
|
||||
\\pub const b = @enumToInt(enum_unnamed_1.b);
|
||||
\\pub const c = @enumToInt(enum_unnamed_1.c);
|
||||
\\const enum_unnamed_1 = extern enum {
|
||||
\\const enum_unnamed_1 = extern enum(c_int) {
|
||||
\\ a,
|
||||
\\ b,
|
||||
\\ c,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub const d = enum_unnamed_1;
|
||||
\\pub const e = @enumToInt(enum_unnamed_2.e);
|
||||
\\pub const f = @enumToInt(enum_unnamed_2.f);
|
||||
\\pub const g = @enumToInt(enum_unnamed_2.g);
|
||||
\\const enum_unnamed_2 = extern enum {
|
||||
\\const enum_unnamed_2 = extern enum(c_int) {
|
||||
\\ e = 0,
|
||||
\\ f = 4,
|
||||
\\ g = 5,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub export var h: enum_unnamed_2 = @intToEnum(enum_unnamed_2, e);
|
||||
\\pub const i = @enumToInt(enum_unnamed_3.i);
|
||||
\\pub const j = @enumToInt(enum_unnamed_3.j);
|
||||
\\pub const k = @enumToInt(enum_unnamed_3.k);
|
||||
\\const enum_unnamed_3 = extern enum {
|
||||
\\const enum_unnamed_3 = extern enum(c_int) {
|
||||
\\ i,
|
||||
\\ j,
|
||||
\\ k,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub const struct_Baz = extern struct {
|
||||
\\ l: enum_unnamed_3,
|
||||
@ -1132,10 +1137,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\pub const n = @enumToInt(enum_i.n);
|
||||
\\pub const o = @enumToInt(enum_i.o);
|
||||
\\pub const p = @enumToInt(enum_i.p);
|
||||
\\pub const enum_i = extern enum {
|
||||
\\pub const enum_i = extern enum(c_int) {
|
||||
\\ n,
|
||||
\\ o,
|
||||
\\ p,
|
||||
\\ _,
|
||||
\\};
|
||||
,
|
||||
\\pub const Baz = struct_Baz;
|
||||
@ -1563,9 +1569,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
, &[_][]const u8{
|
||||
\\pub const One = @enumToInt(enum_unnamed_1.One);
|
||||
\\pub const Two = @enumToInt(enum_unnamed_1.Two);
|
||||
\\const enum_unnamed_1 = extern enum {
|
||||
\\const enum_unnamed_1 = extern enum(c_int) {
|
||||
\\ One,
|
||||
\\ Two,
|
||||
\\ _,
|
||||
\\};
|
||||
});
|
||||
|
||||
@ -1665,10 +1672,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ return ((((((((((e + f) + g) + h) + i) + j) + k) + l) + m) + o) + p);
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
\\pub const enum_Foo = extern enum {
|
||||
\\pub const enum_Foo = extern enum(c_int) {
|
||||
\\ A,
|
||||
\\ B,
|
||||
\\ C,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub const SomeTypedef = c_int;
|
||||
\\pub export fn and_or_non_bool(arg_a: c_int, arg_b: f32, arg_c: ?*c_void) c_int {
|
||||
@ -1710,9 +1718,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ y: c_int,
|
||||
\\};
|
||||
,
|
||||
\\pub const enum_Bar = extern enum {
|
||||
\\pub const enum_Bar = extern enum(c_int) {
|
||||
\\ A,
|
||||
\\ B,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub extern fn func(a: [*c]struct_Foo, b: [*c][*c]enum_Bar) void;
|
||||
,
|
||||
@ -1973,10 +1982,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\ return 4;
|
||||
\\}
|
||||
, &[_][]const u8{
|
||||
\\pub const enum_SomeEnum = extern enum {
|
||||
\\pub const enum_SomeEnum = extern enum(c_int) {
|
||||
\\ A,
|
||||
\\ B,
|
||||
\\ C,
|
||||
\\ _,
|
||||
\\};
|
||||
\\pub export fn if_none_bool(arg_a: c_int, arg_b: f32, arg_c: ?*c_void, arg_d: enum_SomeEnum) c_int {
|
||||
\\ var a = arg_a;
|
||||
@ -2414,10 +2424,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
|
||||
\\pub const FooA = @enumToInt(enum_Foo.A);
|
||||
\\pub const FooB = @enumToInt(enum_Foo.B);
|
||||
\\pub const Foo1 = @enumToInt(enum_Foo.@"1");
|
||||
\\pub const enum_Foo = extern enum {
|
||||
\\pub const enum_Foo = extern enum(c_int) {
|
||||
\\ A = 2,
|
||||
\\ B = 5,
|
||||
\\ @"1" = 6,
|
||||
\\ _,
|
||||
\\};
|
||||
,
|
||||
\\pub const Foo = enum_Foo;
|
||||
|
Loading…
Reference in New Issue
Block a user