Skip to content

Commit b038dba

Browse files
authored
Merge pull request #12462 from Vexu/stage2-noreturn
Stage2: improve behavior of noreturn
2 parents 4f2143b + 2330495 commit b038dba

21 files changed

+573
-212
lines changed

src/AstGen.zig

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4557,9 +4557,6 @@ fn unionDeclInner(
45574557
wip_members.appendToField(@enumToInt(tag_value));
45584558
}
45594559
}
4560-
if (field_count == 0) {
4561-
return astgen.failNode(node, "union declarations must have at least one tag", .{});
4562-
}
45634560

45644561
if (!block_scope.isEmpty()) {
45654562
_ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
@@ -4715,12 +4712,6 @@ fn containerDecl(
47154712
.nonexhaustive_node = nonexhaustive_node,
47164713
};
47174714
};
4718-
if (counts.total_fields == 0 and counts.nonexhaustive_node == 0) {
4719-
// One can construct an enum with no tags, and it functions the same as `noreturn`. But
4720-
// this is only useful for generic code; when explicitly using `enum {}` syntax, there
4721-
// must be at least one tag.
4722-
try astgen.appendErrorNode(node, "enum declarations must have at least one tag", .{});
4723-
}
47244715
if (counts.nonexhaustive_node != 0 and container_decl.ast.arg == 0) {
47254716
try astgen.appendErrorNodeNotes(
47264717
node,

src/Sema.zig

Lines changed: 281 additions & 118 deletions
Large diffs are not rendered by default.

src/print_zir.zig

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,7 @@ const Writer = struct {
14431443
try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag);
14441444

14451445
if (decls_len == 0) {
1446-
try stream.writeAll("{}, ");
1446+
try stream.writeAll("{}");
14471447
} else {
14481448
const prev_parent_decl_node = self.parent_decl_node;
14491449
if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off);
@@ -1454,15 +1454,20 @@ const Writer = struct {
14541454
extra_index = try self.writeDecls(stream, decls_len, extra_index);
14551455
self.indent -= 2;
14561456
try stream.writeByteNTimes(' ', self.indent);
1457-
try stream.writeAll("}, ");
1457+
try stream.writeAll("}");
14581458
}
14591459

1460-
assert(fields_len != 0);
1461-
14621460
if (tag_type_ref != .none) {
1463-
try self.writeInstRef(stream, tag_type_ref);
14641461
try stream.writeAll(", ");
1462+
try self.writeInstRef(stream, tag_type_ref);
1463+
}
1464+
1465+
if (fields_len == 0) {
1466+
try stream.writeAll("})");
1467+
try self.writeSrcNode(stream, src_node);
1468+
return;
14651469
}
1470+
try stream.writeAll(", ");
14661471

14671472
const body = self.code.extra[extra_index..][0..body_len];
14681473
extra_index += body.len;

src/type.zig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2488,7 +2488,7 @@ pub const Type = extern union {
24882488
},
24892489
.union_safety_tagged, .union_tagged => {
24902490
const union_obj = ty.cast(Payload.Union).?.data;
2491-
if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
2491+
if (union_obj.fields.count() > 0 and try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) {
24922492
return true;
24932493
}
24942494
if (sema_kit) |sk| {
@@ -3113,6 +3113,9 @@ pub const Type = extern union {
31133113
.sema_kit => unreachable, // handled above
31143114
.lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) },
31153115
};
3116+
if (union_obj.fields.count() == 0) {
3117+
return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) };
3118+
}
31163119

31173120
var max_align: u32 = 0;
31183121
if (have_tag) max_align = union_obj.tag_ty.abiAlignment(target);

test/behavior.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ test {
167167
if (builtin.zig_backend != .stage1) {
168168
_ = @import("behavior/decltest.zig");
169169
_ = @import("behavior/packed_struct_explicit_backing_int.zig");
170+
_ = @import("behavior/empty_union.zig");
170171
}
171172

172173
if (builtin.os.tag != .wasi) {

test/behavior/empty_union.zig

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
const expect = std.testing.expect;
4+
5+
test "switch on empty enum" {
6+
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
7+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
8+
9+
const E = enum {};
10+
var e: E = undefined;
11+
switch (e) {}
12+
}
13+
14+
test "switch on empty enum with a specified tag type" {
15+
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
16+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
17+
18+
const E = enum(u8) {};
19+
var e: E = undefined;
20+
switch (e) {}
21+
}
22+
23+
test "switch on empty auto numbered tagged union" {
24+
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
25+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
26+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
27+
28+
const U = union(enum(u8)) {};
29+
var u: U = undefined;
30+
switch (u) {}
31+
}
32+
33+
test "switch on empty tagged union" {
34+
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
35+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
36+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
37+
38+
const E = enum {};
39+
const U = union(E) {};
40+
var u: U = undefined;
41+
switch (u) {}
42+
}
43+
44+
test "empty union" {
45+
const U = union {};
46+
try expect(@sizeOf(U) == 0);
47+
try expect(@alignOf(U) == 0);
48+
}
49+
50+
test "empty extern union" {
51+
const U = extern union {};
52+
try expect(@sizeOf(U) == 0);
53+
try expect(@alignOf(U) == 1);
54+
}

test/behavior/error.zig

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ test "simple else prong allowed even when all errors handled" {
725725
try expect(value == 255);
726726
}
727727

728-
test {
728+
test "pointer to error union payload" {
729729
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
730730
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
731731
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
@@ -736,3 +736,63 @@ test {
736736
const payload_ptr = &(err_union catch unreachable);
737737
try expect(payload_ptr.* == 15);
738738
}
739+
740+
const NoReturn = struct {
741+
var a: u32 = undefined;
742+
fn someData() bool {
743+
a -= 1;
744+
return a == 0;
745+
}
746+
fn loop() !noreturn {
747+
while (true) {
748+
if (someData())
749+
return error.GenericFailure;
750+
}
751+
}
752+
fn testTry() anyerror {
753+
try loop();
754+
}
755+
fn testCatch() anyerror {
756+
loop() catch return error.OtherFailure;
757+
@compileError("bad");
758+
}
759+
};
760+
761+
test "error union of noreturn used with if" {
762+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
763+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
764+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
765+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
766+
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
767+
768+
NoReturn.a = 64;
769+
if (NoReturn.loop()) {
770+
@compileError("bad");
771+
} else |err| {
772+
try expect(err == error.GenericFailure);
773+
}
774+
}
775+
776+
test "error union of noreturn used with try" {
777+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
778+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
779+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
780+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
781+
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
782+
783+
NoReturn.a = 64;
784+
const err = NoReturn.testTry();
785+
try expect(err == error.GenericFailure);
786+
}
787+
788+
test "error union of noreturn used with catch" {
789+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
790+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
791+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
792+
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
793+
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
794+
795+
NoReturn.a = 64;
796+
const err = NoReturn.testCatch();
797+
try expect(err == error.OtherFailure);
798+
}

test/behavior/optional.zig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,39 @@ test "optional pointer to zero bit error union payload" {
369369
some.foo();
370370
} else |_| {}
371371
}
372+
373+
const NoReturn = struct {
374+
var a: u32 = undefined;
375+
fn someData() bool {
376+
a -= 1;
377+
return a == 0;
378+
}
379+
fn loop() ?noreturn {
380+
while (true) {
381+
if (someData()) return null;
382+
}
383+
}
384+
fn testOrelse() u32 {
385+
loop() orelse return 123;
386+
@compileError("bad");
387+
}
388+
};
389+
390+
test "optional of noreturn used with if" {
391+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
392+
393+
NoReturn.a = 64;
394+
if (NoReturn.loop()) |_| {
395+
@compileError("bad");
396+
} else {
397+
try expect(true);
398+
}
399+
}
400+
401+
test "optional of noreturn used with orelse" {
402+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
403+
404+
NoReturn.a = 64;
405+
const val = NoReturn.testOrelse();
406+
try expect(val == 123);
407+
}

test/behavior/union.zig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,3 +1256,48 @@ test "return an extern union from C calling convention" {
12561256
});
12571257
try expect(u.d == 4.0);
12581258
}
1259+
1260+
test "noreturn field in union" {
1261+
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
1262+
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
1263+
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
1264+
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
1265+
1266+
const U = union(enum) {
1267+
a: u32,
1268+
b: noreturn,
1269+
c: noreturn,
1270+
};
1271+
var a = U{ .a = 1 };
1272+
var count: u32 = 0;
1273+
if (a == .b) @compileError("bad");
1274+
switch (a) {
1275+
.a => count += 1,
1276+
.b => |val| {
1277+
_ = val;
1278+
@compileError("bad");
1279+
},
1280+
.c => @compileError("bad"),
1281+
}
1282+
switch (a) {
1283+
.a => count += 1,
1284+
.b, .c => @compileError("bad"),
1285+
}
1286+
switch (a) {
1287+
.a, .b, .c => {
1288+
count += 1;
1289+
try expect(a == .a);
1290+
},
1291+
}
1292+
switch (a) {
1293+
.a => count += 1,
1294+
else => @compileError("bad"),
1295+
}
1296+
switch (a) {
1297+
else => {
1298+
count += 1;
1299+
try expect(a == .a);
1300+
},
1301+
}
1302+
try expect(count == 5);
1303+
}

test/cases/compile_errors/enum_with_0_fields.zig

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)