Skip to content

Stage2: improve behavior of noreturn #12462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions src/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
399 changes: 281 additions & 118 deletions src/Sema.zig

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions src/print_zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -1454,15 +1454,20 @@ 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;
Expand Down
5 changes: 4 additions & 1 deletion src/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions test/behavior.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
54 changes: 54 additions & 0 deletions test/behavior/empty_union.zig
Original file line number Diff line number Diff line change
@@ -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);
}
62 changes: 61 additions & 1 deletion test/behavior/error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
36 changes: 36 additions & 0 deletions test/behavior/optional.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
45 changes: 45 additions & 0 deletions test/behavior/union.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
7 changes: 0 additions & 7 deletions test/cases/compile_errors/enum_with_0_fields.zig

This file was deleted.

13 changes: 13 additions & 0 deletions test/cases/compile_errors/invalid_error_union_payload_type.zig
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions test/cases/compile_errors/invalid_optional_payload_type.zig
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions test/cases/compile_errors/noreturn_struct_field.zig
Original file line number Diff line number Diff line change
@@ -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'

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading