Merge pull request #12462 from Vexu/stage2-noreturn

Stage2: improve behavior of noreturn
This commit is contained in:
Veikka Tuominen 2022-08-18 17:18:57 +03:00 committed by GitHub
commit b038dba06b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 576 additions and 215 deletions

View File

@ -4557,9 +4557,6 @@ fn unionDeclInner(
wip_members.appendToField(@enumToInt(tag_value));
}
}
if (field_count == 0) {
return astgen.failNode(node, "union declarations must have at least one tag", .{});
}
if (!block_scope.isEmpty()) {
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
@ -4715,12 +4712,6 @@ fn containerDecl(
.nonexhaustive_node = nonexhaustive_node,
};
};
if (counts.total_fields == 0 and counts.nonexhaustive_node == 0) {
// One can construct an enum with no tags, and it functions the same as `noreturn`. But
// this is only useful for generic code; when explicitly using `enum {}` syntax, there
// must be at least one tag.
try astgen.appendErrorNode(node, "enum declarations must have at least one tag", .{});
}
if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) {
try astgen.appendErrorNodeNotes(
node,

View File

@ -3773,6 +3773,7 @@ fn validateStructInit(
}
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
const struct_ptr = try sema.resolveInst(struct_ptr_zir_ref);
if ((is_comptime or block.is_comptime) and
@ -3948,6 +3949,7 @@ fn validateStructInit(
}
if (root_msg) |msg| {
root_msg = null;
if (struct_ty.castTag(.@"struct")) |struct_obj| {
const fqn = try struct_obj.data.getFullyQualifiedName(sema.mod);
defer gpa.free(fqn);
@ -4006,6 +4008,8 @@ fn zirValidateArrayInit(
if (instrs.len != array_len and array_ty.isTuple()) {
const struct_obj = array_ty.castTag(.tuple).?.data;
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
for (struct_obj.values) |default_val, i| {
if (i < instrs.len) continue;
@ -4020,6 +4024,7 @@ fn zirValidateArrayInit(
}
if (root_msg) |msg| {
root_msg = null;
return sema.failWithOwnedErrorMsg(msg);
}
}
@ -6702,8 +6707,13 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
const child_type = try sema.resolveType(block, src, inst_data.operand);
const operand_src: LazySrcLoc = .{ .node_offset_un_op = inst_data.src_node };
const child_type = try sema.resolveType(block, operand_src, inst_data.operand);
if (child_type.zigTypeTag() == .Opaque) {
return sema.fail(block, operand_src, "opaque type '{}' cannot be optional", .{child_type.fmt(sema.mod)});
} else if (child_type.zigTypeTag() == .Null) {
return sema.fail(block, operand_src, "type '{}' cannot be optional", .{child_type.fmt(sema.mod)});
}
const opt_type = try Type.optional(sema.arena, child_type);
return sema.addType(opt_type);
@ -6802,6 +6812,15 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
error_set.fmt(sema.mod),
});
}
if (payload.zigTypeTag() == .Opaque) {
return sema.fail(block, rhs_src, "error union with payload of opaque type '{}' not allowed", .{
payload.fmt(sema.mod),
});
} else if (payload.zigTypeTag() == .ErrorSet) {
return sema.fail(block, rhs_src, "error union with payload of error set type '{}' not allowed", .{
payload.fmt(sema.mod),
});
}
const err_union_ty = try Type.errorUnion(sema.arena, error_set, payload, sema.mod);
return sema.addType(err_union_ty);
}
@ -8968,12 +8987,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
},
};
const union_originally = blk: {
const maybe_union_ty = blk: {
const zir_data = sema.code.instructions.items(.data);
const cond_index = Zir.refToIndex(extra.data.operand).?;
const raw_operand = sema.resolveInst(zir_data[cond_index].un_node.operand) catch unreachable;
break :blk sema.typeOf(raw_operand).zigTypeTag() == .Union;
break :blk sema.typeOf(raw_operand);
};
const union_originally = maybe_union_ty.zigTypeTag() == .Union;
var seen_union_fields: []?Module.SwitchProngSrc = &.{};
defer gpa.free(seen_union_fields);
var empty_enum = false;
const operand_ty = sema.typeOf(operand);
@ -9008,7 +9032,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
.Union => unreachable, // handled in zirSwitchCond
.Enum => {
var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount());
defer gpa.free(seen_fields);
empty_enum = seen_fields.len == 0 and !operand_ty.isNonexhaustiveEnum();
defer if (!union_originally) gpa.free(seen_fields);
if (union_originally) seen_union_fields = seen_fields;
mem.set(?Module.SwitchProngSrc, seen_fields, null);
// This is used for non-exhaustive enum values that do not correspond to any tags.
@ -9602,6 +9628,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
}
if (scalar_cases_len + multi_cases_len == 0) {
if (empty_enum) {
return Air.Inst.Ref.void_value;
}
if (special_prong == .none) {
return sema.fail(block, src, "switch must handle all possibilities", .{});
}
@ -9641,6 +9670,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
const item = try sema.resolveInst(item_ref);
// `item` is already guaranteed to be constant known.
const analyze_body = if (union_originally) blk: {
const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod);
break :blk field_ty.zigTypeTag() != .NoReturn;
} else true;
if (analyze_body) {
_ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
@ -9653,6 +9689,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
},
else => |e| return e,
};
} else {
_ = try case_block.addNoOp(.unreach);
}
try wip_captures.finalize();
@ -9693,8 +9732,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
if (ranges_len == 0) {
cases_len += 1;
const analyze_body = if (union_originally)
for (items) |item_ref| {
const item = try sema.resolveInst(item_ref);
const item_val = sema.resolveConstValue(block, .unneeded, item, undefined) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod);
if (field_ty.zigTypeTag() != .NoReturn) break true;
} else false
else
true;
const body = sema.code.extra[extra_index..][0..body_len];
extra_index += body_len;
if (analyze_body) {
_ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
@ -9707,6 +9757,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
},
else => |e| return e,
};
} else {
_ = try case_block.addNoOp(.unreach);
}
try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len +
case_block.instructions.items.len);
@ -9828,7 +9881,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
case_block.instructions.shrinkRetainingCapacity(0);
case_block.wip_capture_scope = wip_captures.scope;
if (special.body.len != 0) {
const analyze_body = if (union_originally)
for (seen_union_fields) |seen_field, index| {
if (seen_field != null) continue;
const union_obj = maybe_union_ty.cast(Type.Payload.Union).?.data;
const field_ty = union_obj.fields.values()[index].ty;
if (field_ty.zigTypeTag() != .NoReturn) break true;
} else false
else
true;
if (special.body.len != 0 and analyze_body) {
_ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
@ -13244,6 +13307,14 @@ fn analyzeCmpUnionTag(
const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src);
const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src);
if (try sema.resolveMaybeUndefVal(block, tag_src, coerced_tag)) |enum_val| {
if (enum_val.isUndef()) return sema.addConstUndef(Type.bool);
const field_ty = union_ty.unionFieldType(enum_val, sema.mod);
if (field_ty.zigTypeTag() == .NoReturn) {
return Air.Inst.Ref.bool_false;
}
}
return sema.cmpSelf(block, src, coerced_union, coerced_tag, op, un_src, tag_src);
}
@ -15598,6 +15669,8 @@ fn finishStructInit(
const gpa = sema.gpa;
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
if (struct_ty.isAnonStruct()) {
const struct_obj = struct_ty.castTag(.anon_struct).?.data;
for (struct_obj.values) |default_val, i| {
@ -15653,6 +15726,7 @@ fn finishStructInit(
}
if (root_msg) |msg| {
root_msg = null;
if (struct_ty.castTag(.@"struct")) |struct_obj| {
const fqn = try struct_obj.data.getFullyQualifiedName(sema.mod);
defer gpa.free(fqn);
@ -16655,7 +16729,6 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
// Fields
const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod));
if (fields_len > 0) {
try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len);
try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{
.ty = enum_obj.tag_ty,
@ -16690,9 +16763,6 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
.mod = mod,
});
}
} else {
return sema.fail(block, src, "enums must have at least one field", .{});
}
try new_decl.finalizeNewArena(&new_decl_arena);
return sema.analyzeDeclVal(block, src, new_decl_index);
@ -16816,7 +16886,6 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
}
// Fields
if (fields_len > 0) {
try union_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len);
var i: usize = 0;
@ -16866,9 +16935,6 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
.abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)),
};
}
} else {
return sema.fail(block, src, "unions must have at least one field", .{});
}
if (tag_ty_field_names) |names| {
if (names.count() > 0) {
@ -21701,6 +21767,18 @@ fn unionFieldPtr(
.@"addrspace" = union_ptr_ty.ptrAddressSpace(),
});
if (initializing and field.ty.zigTypeTag() == .NoReturn) {
const msg = msg: {
const msg = try sema.errMsg(block, src, "cannot initialize 'noreturn' field of union", .{});
errdefer msg.destroy(sema.gpa);
try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' declared here", .{field_name});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
}
if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: {
switch (union_obj.layout) {
.Auto => if (!initializing) {
@ -21753,6 +21831,10 @@ fn unionFieldPtr(
const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
try sema.addSafetyCheck(block, ok, .inactive_union_field);
}
if (field.ty.zigTypeTag() == .NoReturn) {
_ = try block.addNoOp(.unreach);
return Air.Inst.Ref.unreachable_value;
}
return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty);
}
@ -21821,6 +21903,10 @@ fn unionFieldVal(
const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag);
try sema.addSafetyCheck(block, ok, .inactive_union_field);
}
if (field.ty.zigTypeTag() == .NoReturn) {
_ = try block.addNoOp(.unreach);
return Air.Inst.Ref.unreachable_value;
}
return block.addStructFieldVal(union_byval, field_index, field.ty);
}
@ -25021,6 +25107,18 @@ fn coerceEnumToUnion(
};
const field = union_obj.fields.values()[field_index];
const field_ty = try sema.resolveTypeFields(block, inst_src, field.ty);
if (field_ty.zigTypeTag() == .NoReturn) {
const msg = msg: {
const msg = try sema.errMsg(block, inst_src, "cannot initialize 'noreturn' field of union", .{});
errdefer msg.destroy(sema.gpa);
const field_name = union_obj.fields.keys()[field_index];
try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' declared here", .{field_name});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
}
const opv = (try sema.typeHasOnePossibleValue(block, inst_src, field_ty)) orelse {
const msg = msg: {
const field_name = union_obj.fields.keys()[field_index];
@ -25056,13 +25154,37 @@ fn coerceEnumToUnion(
return sema.failWithOwnedErrorMsg(msg);
}
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
{
var msg: ?*Module.ErrorMsg = null;
errdefer if (msg) |some| some.destroy(sema.gpa);
for (union_obj.fields.values()) |field, i| {
if (field.ty.zigTypeTag() == .NoReturn) {
const err_msg = msg orelse try sema.errMsg(
block,
inst_src,
"runtime coercion from enum '{}' to union '{}' which has a 'noreturn' field",
.{ tag_ty.fmt(sema.mod), union_ty.fmt(sema.mod) },
);
msg = err_msg;
try sema.addFieldErrNote(block, union_ty, i, err_msg, "'noreturn' field here", .{});
}
}
if (msg) |some| {
msg = null;
try sema.addDeclaredHereNote(some, union_ty);
return sema.failWithOwnedErrorMsg(some);
}
}
// If the union has all fields 0 bits, the union value is just the enum value.
if (union_ty.unionHasAllZeroBitFieldTypes()) {
return block.addBitCast(union_ty, enum_tag);
}
const msg = msg: {
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
const msg = try sema.errMsg(
block,
inst_src,
@ -25073,11 +25195,11 @@ fn coerceEnumToUnion(
var it = union_obj.fields.iterator();
var field_index: usize = 0;
while (it.next()) |field| {
while (it.next()) |field| : (field_index += 1) {
const field_name = field.key_ptr.*;
const field_ty = field.value_ptr.ty;
if (!field_ty.hasRuntimeBits()) continue;
try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' has type '{}'", .{ field_name, field_ty.fmt(sema.mod) });
field_index += 1;
}
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
@ -25380,6 +25502,7 @@ fn coerceTupleToStruct(
// Populate default field values and report errors for missing fields.
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
for (field_refs) |*field_ref, i| {
if (field_ref.* != .none) continue;
@ -25405,6 +25528,7 @@ fn coerceTupleToStruct(
}
if (root_msg) |msg| {
root_msg = null;
try sema.addDeclaredHereNote(msg, struct_ty);
return sema.failWithOwnedErrorMsg(msg);
}
@ -25474,6 +25598,7 @@ fn coerceTupleToTuple(
// Populate default field values and report errors for missing fields.
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
for (field_refs) |*field_ref, i| {
if (field_ref.* != .none) continue;
@ -25509,6 +25634,7 @@ fn coerceTupleToTuple(
}
if (root_msg) |msg| {
root_msg = null;
try sema.addDeclaredHereNote(msg, tuple_ty);
return sema.failWithOwnedErrorMsg(msg);
}
@ -25747,6 +25873,12 @@ fn analyzeIsNull(
return Air.Inst.Ref.bool_false;
}
}
const operand_ty = sema.typeOf(operand);
var buf: Type.Payload.ElemType = undefined;
if (operand_ty.zigTypeTag() == .Optional and operand_ty.optionalChild(&buf).zigTypeTag() == .NoReturn) {
return Air.Inst.Ref.bool_true;
}
try sema.requireRuntimeBlock(block, src, null);
const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;
return block.addUnOp(air_tag, operand);
@ -25785,6 +25917,11 @@ fn analyzeIsNonErrComptimeOnly(
if (ot == .ErrorSet) return Air.Inst.Ref.bool_false;
assert(ot == .ErrorUnion);
const payload_ty = operand_ty.errorUnionPayload();
if (payload_ty.zigTypeTag() == .NoReturn) {
return Air.Inst.Ref.bool_false;
}
if (Air.refToIndex(operand)) |operand_inst| {
switch (sema.air_instructions.items(.tag)[operand_inst]) {
.wrap_errunion_payload => return Air.Inst.Ref.bool_true,
@ -27922,6 +28059,18 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
};
return sema.failWithOwnedErrorMsg(msg);
}
if (field_ty.zigTypeTag() == .NoReturn) {
const msg = msg: {
const tree = try sema.getAstTree(&block_scope);
const field_src = enumFieldSrcLoc(decl, tree.*, 0, i);
const msg = try sema.errMsg(&block_scope, field_src, "struct fields cannot be 'noreturn'", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
}
if (struct_obj.layout == .Extern and !sema.validateExternType(field.ty, .other)) {
const msg = msg: {
const tree = try sema.getAstTree(&block_scope);
@ -28028,10 +28177,6 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..body_len];
if (fields_len == 0) {
assert(body.len == 0);
return;
}
extra_index += body.len;
const decl = mod.declPtr(decl_index);
@ -28119,6 +28264,10 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields;
}
if (fields_len == 0) {
return;
}
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
@ -28654,7 +28803,9 @@ pub fn typeHasOnePossibleValue(
const union_obj = resolved_ty.cast(Type.Payload.Union).?.data;
const tag_val = (try sema.typeHasOnePossibleValue(block, src, union_obj.tag_ty)) orelse
return null;
const only_field = union_obj.fields.values()[0];
const fields = union_obj.fields.values();
if (fields.len == 0) return Value.initTag(.empty_struct_value);
const only_field = fields[0];
if (only_field.ty.eql(resolved_ty, sema.mod)) {
const msg = try Module.ErrorMsg.create(
sema.gpa,
@ -28733,6 +28884,16 @@ fn enumFieldSrcLoc(
.container_decl_arg_trailing,
=> tree.containerDeclArg(enum_node),
.tagged_union,
.tagged_union_trailing,
=> tree.taggedUnion(enum_node),
.tagged_union_two,
.tagged_union_two_trailing,
=> tree.taggedUnionTwo(&buffer, enum_node),
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
=> tree.taggedUnionEnumTag(enum_node),
// Container was constructed with `@Type`.
else => return LazySrcLoc.nodeOffset(0),
};
@ -29383,7 +29544,9 @@ fn unionFieldAlignment(
src: LazySrcLoc,
field: Module.Union.Field,
) !u32 {
if (field.abi_align == 0) {
if (field.ty.zigTypeTag() == .NoReturn) {
return @as(u32, 0);
} else if (field.abi_align == 0) {
return sema.typeAbiAlignment(block, src, field.ty);
} else {
return field.abi_align;

View File

@ -1443,7 +1443,7 @@ const Writer = struct {
try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag);
if (decls_len == 0) {
try stream.writeAll("{}, ");
try stream.writeAll("{}");
} else {
const prev_parent_decl_node = self.parent_decl_node;
if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
@ -1454,16 +1454,21 @@ const Writer = struct {
extra_index = try self.writeDecls(stream, decls_len, extra_index);
self.indent -= 2;
try stream.writeByteNTimes(' ', self.indent);
try stream.writeAll("}, ");
try stream.writeAll("}");
}
assert(fields_len != 0);
if (tag_type_ref != .none) {
try self.writeInstRef(stream, tag_type_ref);
try stream.writeAll(", ");
try self.writeInstRef(stream, tag_type_ref);
}
if (fields_len == 0) {
try stream.writeAll("})");
try self.writeSrcNode(stream, src_node);
return;
}
try stream.writeAll(", ");
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body.len;

View File

@ -2488,7 +2488,7 @@ pub const Type = extern union {
},
.union_safety_tagged, .union_tagged => {
const union_obj = ty.cast(Payload.Union).?.data;
if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
if (union_obj.fields.count() > 0 and try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
return true;
}
if (sema_kit) |sk| {
@ -3113,6 +3113,9 @@ pub const Type = extern union {
.sema_kit => unreachable, // handled above
.lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) },
};
if (union_obj.fields.count() == 0) {
return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) };
}
var max_align: u32 = 0;
if (have_tag) max_align = union_obj.tag_ty.abiAlignment(target);

View File

@ -167,6 +167,7 @@ test {
if (builtin.zig_backend != .stage1) {
_ = @import("behavior/decltest.zig");
_ = @import("behavior/packed_struct_explicit_backing_int.zig");
_ = @import("behavior/empty_union.zig");
}
if (builtin.os.tag != .wasi) {

View File

@ -0,0 +1,54 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
test "switch on empty enum" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const E = enum {};
var e: E = undefined;
switch (e) {}
}
test "switch on empty enum with a specified tag type" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const E = enum(u8) {};
var e: E = undefined;
switch (e) {}
}
test "switch on empty auto numbered tagged union" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const U = union(enum(u8)) {};
var u: U = undefined;
switch (u) {}
}
test "switch on empty tagged union" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const E = enum {};
const U = union(E) {};
var u: U = undefined;
switch (u) {}
}
test "empty union" {
const U = union {};
try expect(@sizeOf(U) == 0);
try expect(@alignOf(U) == 0);
}
test "empty extern union" {
const U = extern union {};
try expect(@sizeOf(U) == 0);
try expect(@alignOf(U) == 1);
}

View File

@ -725,7 +725,7 @@ test "simple else prong allowed even when all errors handled" {
try expect(value == 255);
}
test {
test "pointer to error union payload" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
@ -736,3 +736,63 @@ test {
const payload_ptr = &(err_union catch unreachable);
try expect(payload_ptr.* == 15);
}
const NoReturn = struct {
var a: u32 = undefined;
fn someData() bool {
a -= 1;
return a == 0;
}
fn loop() !noreturn {
while (true) {
if (someData())
return error.GenericFailure;
}
}
fn testTry() anyerror {
try loop();
}
fn testCatch() anyerror {
loop() catch return error.OtherFailure;
@compileError("bad");
}
};
test "error union of noreturn used with if" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
NoReturn.a = 64;
if (NoReturn.loop()) {
@compileError("bad");
} else |err| {
try expect(err == error.GenericFailure);
}
}
test "error union of noreturn used with try" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
NoReturn.a = 64;
const err = NoReturn.testTry();
try expect(err == error.GenericFailure);
}
test "error union of noreturn used with catch" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
NoReturn.a = 64;
const err = NoReturn.testCatch();
try expect(err == error.OtherFailure);
}

View File

@ -369,3 +369,39 @@ test "optional pointer to zero bit error union payload" {
some.foo();
} else |_| {}
}
const NoReturn = struct {
var a: u32 = undefined;
fn someData() bool {
a -= 1;
return a == 0;
}
fn loop() ?noreturn {
while (true) {
if (someData()) return null;
}
}
fn testOrelse() u32 {
loop() orelse return 123;
@compileError("bad");
}
};
test "optional of noreturn used with if" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
NoReturn.a = 64;
if (NoReturn.loop()) |_| {
@compileError("bad");
} else {
try expect(true);
}
}
test "optional of noreturn used with orelse" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
NoReturn.a = 64;
const val = NoReturn.testOrelse();
try expect(val == 123);
}

View File

@ -1256,3 +1256,48 @@ test "return an extern union from C calling convention" {
});
try expect(u.d == 4.0);
}
test "noreturn field in union" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
const U = union(enum) {
a: u32,
b: noreturn,
c: noreturn,
};
var a = U{ .a = 1 };
var count: u32 = 0;
if (a == .b) @compileError("bad");
switch (a) {
.a => count += 1,
.b => |val| {
_ = val;
@compileError("bad");
},
.c => @compileError("bad"),
}
switch (a) {
.a => count += 1,
.b, .c => @compileError("bad"),
}
switch (a) {
.a, .b, .c => {
count += 1;
try expect(a == .a);
},
}
switch (a) {
.a => count += 1,
else => @compileError("bad"),
}
switch (a) {
else => {
count += 1;
try expect(a == .a);
},
}
try expect(count == 5);
}

View File

@ -1,7 +0,0 @@
const Foo = enum {};
// error
// backend=stage2
// target=native
//
// :1:13: error: enum declarations must have at least one tag

View File

@ -0,0 +1,13 @@
comptime {
_ = anyerror!anyopaque;
}
comptime {
_ = anyerror!anyerror;
}
// error
// backend=stage2
// target=native
//
// :2:18: error: error union with payload of opaque type 'anyopaque' not allowed
// :5:18: error: error union with payload of error set type 'anyerror' not allowed

View File

@ -0,0 +1,13 @@
comptime {
_ = ?anyopaque;
}
comptime {
_ = ?@TypeOf(null);
}
// error
// backend=stage2
// target=native
//
// :2:10: error: opaque type 'anyopaque' cannot be optional
// :5:10: error: type '@TypeOf(null)' cannot be optional

View File

@ -0,0 +1,12 @@
const S = struct {
s: noreturn,
};
comptime {
_ = @typeInfo(S);
}
// error
// backend=stage2
// target=native
//
// :2:5: error: struct fields cannot be 'noreturn'

View File

@ -1,18 +0,0 @@
const Tag = @Type(.{
.Enum = .{
.layout = .Auto,
.tag_type = u1,
.fields = &.{},
.decls = &.{},
.is_exhaustive = true,
},
});
export fn entry() void {
_ = @intToEnum(Tag, 0);
}
// error
// backend=stage2
// target=native
//
// :1:13: error: enums must have at least one field

View File

@ -1,17 +0,0 @@
const Untagged = @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = null,
.fields = &.{},
.decls = &.{},
},
});
export fn entry() void {
_ = Untagged{};
}
// error
// backend=stage2
// target=native
//
// :1:18: error: unions must have at least one field

View File

@ -18,6 +18,4 @@ fn foo(l: Letter) void {
//
// :11:20: error: runtime coercion from enum 'tmp.Letter' to union 'tmp.Value' which has non-void fields
// :3:5: note: field 'A' has type 'i32'
// :4:5: note: field 'B' has type 'void'
// :5:5: note: field 'C' has type 'void'
// :2:15: note: union declared here

View File

@ -1,7 +0,0 @@
const Foo = union {};
// error
// backend=stage2
// target=native
//
// :1:13: error: union declarations must have at least one tag

View File

@ -0,0 +1,43 @@
pub export fn entry1() void {
const U = union(enum) {
a: u32,
b: noreturn,
fn foo(_: @This()) void {}
fn bar() noreturn {
unreachable;
}
};
var a = U{ .b = undefined };
_ = a;
}
pub export fn entry2() void {
const U = union(enum) {
a: noreturn,
};
var u: U = undefined;
u = .a;
}
pub export fn entry3() void {
const U = union(enum) {
a: noreturn,
b: void,
};
var e = @typeInfo(U).Union.tag_type.?.a;
var u: U = undefined;
u = e;
}
// error
// backend=stage2
// target=native
//
// :11:21: error: cannot initialize 'noreturn' field of union
// :4:9: note: field 'b' declared here
// :2:15: note: union declared here
// :19:10: error: cannot initialize 'noreturn' field of union
// :16:9: note: field 'a' declared here
// :15:15: note: union declared here
// :28:9: error: runtime coercion from enum '@typeInfo(tmp.entry3.U).Union.tag_type.?' to union 'tmp.entry3.U' which has a 'noreturn' field
// :23:9: note: 'noreturn' field here
// :22:15: note: union declared here

View File

@ -1,7 +0,0 @@
const Foo = union {};
// error
// backend=stage2
// target=native
//
// :1:13: error: union declarations must have at least one tag

View File

@ -1,11 +0,0 @@
const MenuEffect = enum {};
fn func(effect: MenuEffect) void { _ = effect; }
export fn entry() void {
func(MenuEffect.ThisDoesNotExist);
}
// error
// backend=stage2
// target=native
//
// :1:20: error: enum declarations must have at least one tag

View File

@ -704,15 +704,6 @@ pub fn addCases(ctx: *TestContext) !void {
":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
});
case.addError(
\\const E1 = enum {};
\\export fn foo() void {
\\ _ = E1.a;
\\}
, &.{
":1:12: error: enum declarations must have at least one tag",
});
case.addError(
\\const E1 = enum { a, b, _ };
\\export fn foo() void {