diff options
34 files changed, 1092 insertions, 256 deletions
@@ -124,6 +124,7 @@ pub fn build(b: *Builder) !void { const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse false; const force_gpa = b.option(bool, "force-gpa", "Force the compiler to use GeneralPurposeAllocator") orelse false; const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse enable_llvm; + const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false; const strip = b.option(bool, "strip", "Omit debug information") orelse false; const use_zig0 = b.option(bool, "zig0", "Bootstrap using zig0") orelse false; const value_tracing = b.option(bool, "value-tracing", "Enable extra state tracking to help troubleshoot bugs in the compiler (using the std.debug.Trace API)") orelse false; @@ -143,6 +144,7 @@ pub fn build(b: *Builder) !void { const exe = b.addExecutable("zig", main_file); exe.stack_size = stack_size; exe.strip = strip; + exe.sanitize_thread = sanitize_thread; exe.build_id = b.option(bool, "build-id", "Include a build id note") orelse false; exe.install(); exe.setBuildMode(mode); diff --git a/doc/langref.html.in b/doc/langref.html.in index ef9e8402c1..7cc26d662a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4255,6 +4255,134 @@ test "enum literals with switch" { } {#code_end#} {#header_close#} + + {#header_open|Inline switch#} + <p> + Switch prongs can be marked as {#syntax#}inline{#endsyntax#} to generate + the prong's body for each possible value it could have: + </p> + {#code_begin|test|test_inline_switch#} +const std = @import("std"); +const expect = std.testing.expect; +const expectError = std.testing.expectError; + +fn isFieldOptional(comptime T: type, field_index: usize) !bool { + const fields = @typeInfo(T).Struct.fields; + return switch (field_index) { + // This prong is analyzed `fields.len - 1` times with `idx` being an + // unique comptime known value each time. + inline 0...fields.len - 1 => |idx| @typeInfo(fields[idx].field_type) == .Optional, + else => return error.IndexOutOfBounds, + }; +} + +const Struct1 = struct { a: u32, b: ?u32 }; + +test "using @typeInfo with runtime values" { + var index: usize = 0; + try expect(!try isFieldOptional(Struct1, index)); + index += 1; + try expect(try isFieldOptional(Struct1, index)); + index += 1; + try expectError(error.IndexOutOfBounds, isFieldOptional(Struct1, index)); +} + +// Calls to `isFieldOptional` on `Struct1` get unrolled to an equivalent +// of this function: +fn isFieldOptionalUnrolled(field_index: usize) !bool { + return switch (field_index) { + 0 => false, + 1 => true, + else => return error.IndexOutOfBounds, + }; +} + {#code_end#} + <p> + {#syntax#}inline else{#endsyntax#} prongs can be used as a type safe + alternative to {#syntax#}inline for{#endsyntax#} loops: + </p> + {#code_begin|test|test_inline_else#} +const std = @import("std"); +const expect = std.testing.expect; + +const SliceTypeA = extern struct { + len: usize, + ptr: [*]u32, +}; +const SliceTypeB = extern struct { + ptr: [*]SliceTypeA, + len: usize, +}; +const AnySlice = union(enum) { + a: SliceTypeA, + b: SliceTypeB, + c: []const u8, + d: []AnySlice, +}; + +fn withFor(any: AnySlice) usize { + const Tag = @typeInfo(AnySlice).Union.tag_type.?; + inline for (@typeInfo(Tag).Enum.fields) |field| { + // With `inline for` the function gets generated as + // a series of `if` statements relying on the optimizer + // to convert it to a switch. + if (field.value == @enumToInt(any)) { + return @field(any, field.name).len; + } + } + // When using `inline for` the compiler doesn't know that every + // possible case has been handled requiring an explicit `unreachable`. + unreachable; +} + +fn withSwitch(any: AnySlice) usize { + return switch (any) { + // With `inline else` the function is explicitly generated + // as the desired switch and the compiler can check that + // every possible case is handled. + inline else => |slice| slice.len, + }; +} + +test "inline for and inline else similarity" { + var any = AnySlice{ .c = "hello" }; + try expect(withFor(any) == 5); + try expect(withSwitch(any) == 5); +} + {#code_end#} + <p> + When using an inline prong switching on an union an additional + capture can be used to obtain the union's enum tag value. + </p> + {#code_begin|test|test_inline_switch_union_tag#} +const std = @import("std"); +const expect = std.testing.expect; + +const U = union(enum) { + a: u32, + b: f32, +}; + +fn getNum(u: U) u32 { + switch (u) { + // Here `num` is a runtime known value that is either + // `u.a` or `u.b` and `tag` is `u`'s comptime known tag value. + inline else => |num, tag| { + if (tag == .b) { + return @floatToInt(u32, num); + } + return num; + } + } +} + +test "test" { + var u = U{ .b = 42 }; + try expect(getNum(u) == 42); +} + {#code_end#} + {#see_also|inline while|inline for#} + {#header_close#} {#header_close#} {#header_open|while#} @@ -10768,7 +10896,7 @@ test "string literal to constant slice" { </p> {#code_begin|syntax#} const builtin = @import("builtin"); -const separator = if (builtin.os.tag == builtin.Os.windows) '\\' else '/'; +const separator = if (builtin.os.tag == .windows) '\\' else '/'; {#code_end#} <p> Example of what is imported with {#syntax#}@import("builtin"){#endsyntax#}: diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index 031a9fab5d..a502c5fa3f 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -773,9 +773,9 @@ pub fn ArrayHashMapUnmanaged( } } + try self.entries.ensureTotalCapacity(allocator, new_capacity); const new_bit_index = try IndexHeader.findBitIndex(new_capacity); const new_header = try IndexHeader.alloc(allocator, new_bit_index); - try self.entries.ensureTotalCapacity(allocator, new_capacity); if (self.index_header) |old_header| old_header.free(allocator); self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); @@ -2042,6 +2042,19 @@ test "ensure capacity" { try testing.expect(initial_capacity == map.capacity()); } +test "ensure capacity leak" { + try testing.checkAllAllocationFailures(std.testing.allocator, struct { + pub fn f(allocator: Allocator) !void { + var map = AutoArrayHashMap(i32, i32).init(allocator); + defer map.deinit(); + + var i: i32 = 0; + // put more than `linear_scan_max` in so index_header gets allocated. + while (i <= 20) : (i += 1) try map.put(i, i); + } + }.f, .{}); +} + test "big map" { var map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); defer map.deinit(); diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index f1604bb86c..322c74b11b 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -122,7 +122,7 @@ pub const ChildProcess = struct { } pub fn setUserName(self: *ChildProcess, name: []const u8) !void { - const user_info = try os.getUserInfo(name); + const user_info = try std.process.getUserInfo(name); self.uid = user_info.uid; self.gid = user_info.gid; } diff --git a/lib/std/math.zig b/lib/std/math.zig index a69a6f428c..18c8f555d4 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -285,10 +285,10 @@ pub inline fn tan(value: anytype) @TypeOf(value) { return @tan(value); } -// Convert an angle in radians to degrees. T must be a float type. +/// Converts an angle in radians to degrees. T must be a float type. pub fn radiansToDegrees(comptime T: type, angle_in_radians: T) T { - if (@typeInfo(T) != .Float) - @compileError("T must be a float type."); + if (@typeInfo(T) != .Float and @typeInfo(T) != .ComptimeFloat) + @compileError("T must be a float type"); return angle_in_radians * 180.0 / pi; } @@ -300,10 +300,10 @@ test "radiansToDegrees" { try std.testing.expectApproxEqAbs(@as(f32, 360), radiansToDegrees(f32, 2.0 * pi), 1e-6); } -// Convert an angle in degrees to radians. T must be a float type. +/// Converts an angle in degrees to radians. T must be a float type. pub fn degreesToRadians(comptime T: type, angle_in_degrees: T) T { - if (@typeInfo(T) != .Float) - @compileError("T must be a float type."); + if (@typeInfo(T) != .Float and @typeInfo(T) != .ComptimeFloat) + @compileError("T must be a float type"); return angle_in_degrees * pi / 180.0; } diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index ec2c577de1..db6b4c930c 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -76,6 +76,7 @@ pub extern "wasi_snapshot_preview1" fn random_get(buf: [*]u8, buf_len: usize) er pub extern "wasi_snapshot_preview1" fn sched_yield() errno_t; +pub extern "wasi_snapshot_preview1" fn sock_accept(sock: fd_t, flags: fdflags_t, result_fd: *fd_t) errno_t; pub extern "wasi_snapshot_preview1" fn sock_recv(sock: fd_t, ri_data: *const iovec_t, ri_data_len: usize, ri_flags: riflags_t, ro_datalen: *usize, ro_flags: *roflags_t) errno_t; pub extern "wasi_snapshot_preview1" fn sock_send(sock: fd_t, si_data: *const ciovec_t, si_data_len: usize, si_flags: siflags_t, so_datalen: *usize) errno_t; pub extern "wasi_snapshot_preview1" fn sock_shutdown(sock: fd_t, how: sdflags_t) errno_t; @@ -434,6 +435,7 @@ pub const RIGHT = struct { pub const PATH_UNLINK_FILE: rights_t = 0x0000000004000000; pub const POLL_FD_READWRITE: rights_t = 0x0000000008000000; pub const SOCK_SHUTDOWN: rights_t = 0x0000000010000000; + pub const SOCK_ACCEPT: rights_t = 0x0000000020000000; pub const ALL: rights_t = FD_DATASYNC | FD_READ | FD_SEEK | @@ -462,7 +464,8 @@ pub const RIGHT = struct { PATH_REMOVE_DIRECTORY | PATH_UNLINK_FILE | POLL_FD_READWRITE | - SOCK_SHUTDOWN; + SOCK_SHUTDOWN | + SOCK_ACCEPT; }; pub const sdflags_t = u8; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c20457943b..f6d148a317 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3695,4 +3695,4 @@ pub const CTRL_CLOSE_EVENT: DWORD = 2; pub const CTRL_LOGOFF_EVENT: DWORD = 5; pub const CTRL_SHUTDOWN_EVENT: DWORD = 6; -pub const HANDLER_ROUTINE = std.meta.FnPtr(fn (dwCtrlType: DWORD) callconv(.C) BOOL); +pub const HANDLER_ROUTINE = std.meta.FnPtr(fn (dwCtrlType: DWORD) callconv(WINAPI) BOOL); diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index bfa73dc9ac..62a567387f 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -643,11 +643,23 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex { n = datas[n].lhs; } }, + .switch_case_inline_one => { + if (datas[n].lhs == 0) { + return main_tokens[n] - 2 - end_offset; // else token + } else { + return firstToken(tree, datas[n].lhs) - 1; + } + }, .switch_case => { const extra = tree.extraData(datas[n].lhs, Node.SubRange); assert(extra.end - extra.start > 0); n = tree.extra_data[extra.start]; }, + .switch_case_inline => { + const extra = tree.extraData(datas[n].lhs, Node.SubRange); + assert(extra.end - extra.start > 0); + return firstToken(tree, tree.extra_data[extra.start]) - 1; + }, .asm_output, .asm_input => { assert(token_tags[main_tokens[n] - 1] == .l_bracket); @@ -763,7 +775,9 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { .ptr_type_bit_range, .array_type, .switch_case_one, + .switch_case_inline_one, .switch_case, + .switch_case_inline, .switch_range, => n = datas[n].rhs, @@ -1755,7 +1769,7 @@ pub fn switchCaseOne(tree: Ast, node: Node.Index) full.SwitchCase { .values = if (data.lhs == 0) values[0..0] else values[0..1], .arrow_token = tree.nodes.items(.main_token)[node], .target_expr = data.rhs, - }); + }, node); } pub fn switchCase(tree: Ast, node: Node.Index) full.SwitchCase { @@ -1765,7 +1779,7 @@ pub fn switchCase(tree: Ast, node: Node.Index) full.SwitchCase { .values = tree.extra_data[extra.start..extra.end], .arrow_token = tree.nodes.items(.main_token)[node], .target_expr = data.rhs, - }); + }, node); } pub fn asmSimple(tree: Ast, node: Node.Index) full.Asm { @@ -2038,15 +2052,21 @@ fn fullContainerDecl(tree: Ast, info: full.ContainerDecl.Components) full.Contai return result; } -fn fullSwitchCase(tree: Ast, info: full.SwitchCase.Components) full.SwitchCase { +fn fullSwitchCase(tree: Ast, info: full.SwitchCase.Components, node: Node.Index) full.SwitchCase { const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); var result: full.SwitchCase = .{ .ast = info, .payload_token = null, + .inline_token = null, }; if (token_tags[info.arrow_token + 1] == .pipe) { result.payload_token = info.arrow_token + 2; } + switch (node_tags[node]) { + .switch_case_inline, .switch_case_inline_one => result.inline_token = firstToken(tree, node), + else => {}, + } return result; } @@ -2454,6 +2474,7 @@ pub const full = struct { }; pub const SwitchCase = struct { + inline_token: ?TokenIndex, /// Points to the first token after the `|`. Will either be an identifier or /// a `*` (with an identifier immediately after it). payload_token: ?TokenIndex, @@ -2847,9 +2868,13 @@ pub const Node = struct { /// `lhs => rhs`. If lhs is omitted it means `else`. /// main_token is the `=>` switch_case_one, + /// Same ast `switch_case_one` but the case is inline + switch_case_inline_one, /// `a, b, c => rhs`. `SubRange[lhs]`. /// main_token is the `=>` switch_case, + /// Same ast `switch_case` but the case is inline + switch_case_inline, /// `lhs...rhs`. switch_range, /// `while (lhs) rhs`. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 43b6eda8e0..db56cef21e 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3100,7 +3100,7 @@ const Parser = struct { return identifier; } - /// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr + /// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr /// SwitchCase /// <- SwitchItem (COMMA SwitchItem)* COMMA? /// / KEYWORD_else @@ -3108,6 +3108,8 @@ const Parser = struct { const scratch_top = p.scratch.items.len; defer p.scratch.shrinkRetainingCapacity(scratch_top); + const is_inline = p.eatToken(.keyword_inline) != null; + if (p.eatToken(.keyword_else) == null) { while (true) { const item = try p.parseSwitchItem(); @@ -3115,15 +3117,18 @@ const Parser = struct { try p.scratch.append(p.gpa, item); if (p.eatToken(.comma) == null) break; } - if (scratch_top == p.scratch.items.len) return null_node; + if (scratch_top == p.scratch.items.len) { + if (is_inline) p.tok_i -= 1; + return null_node; + } } const arrow_token = try p.expectToken(.equal_angle_bracket_right); - _ = try p.parsePtrPayload(); + _ = try p.parsePtrIndexPayload(); const items = p.scratch.items[scratch_top..]; switch (items.len) { 0 => return p.addNode(.{ - .tag = .switch_case_one, + .tag = if (is_inline) .switch_case_inline_one else .switch_case_one, .main_token = arrow_token, .data = .{ .lhs = 0, @@ -3131,7 +3136,7 @@ const Parser = struct { }, }), 1 => return p.addNode(.{ - .tag = .switch_case_one, + .tag = if (is_inline) .switch_case_inline_one else .switch_case_one, .main_token = arrow_token, .data = .{ .lhs = items[0], @@ -3139,7 +3144,7 @@ const Parser = struct { }, }), else => return p.addNode(.{ - .tag = .switch_case, + .tag = if (is_inline) .switch_case_inline else .switch_case, .main_token = arrow_token, .data = .{ .lhs = try p.addExtra(try p.listToSpan(items)), diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 0e1817ffab..4e155df6d8 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3276,6 +3276,8 @@ test "zig fmt: switch" { \\ switch (u) { \\ Union.Int => |int| {}, \\ Union.Float => |*float| unreachable, + \\ 1 => |a, b| unreachable, + \\ 2 => |*a, b| unreachable, \\ } \\} \\ diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 6ef0cfcd6f..ab009f8390 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -685,8 +685,8 @@ fn renderExpression(gpa: Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, return renderToken(ais, tree, tree.lastToken(node), space); // rbrace }, - .switch_case_one => return renderSwitchCase(gpa, ais, tree, tree.switchCaseOne(node), space), - .switch_case => return renderSwitchCase(gpa, ais, tree, tree.switchCase(node), space), + .switch_case_one, .switch_case_inline_one => return renderSwitchCase(gpa, ais, tree, tree.switchCaseOne(node), space), + .switch_case, .switch_case_inline => return renderSwitchCase(gpa, ais, tree, tree.switchCase(node), space), .while_simple => return renderWhile(gpa, ais, tree, tree.whileSimple(node), space), .while_cont => return renderWhile(gpa, ais, tree, tree.whileCont(node), space), @@ -1509,6 +1509,11 @@ fn renderSwitchCase( break :blk hasComment(tree, tree.firstToken(switch_case.ast.values[0]), switch_case.ast.arrow_token); }; + // render inline keyword + if (switch_case.inline_token) |some| { + try renderToken(ais, tree, some, .space); + } + // Render everything before the arrow if (switch_case.ast.values.len == 0) { try renderToken(ais, tree, switch_case.ast.arrow_token - 1, .space); // else keyword @@ -1536,13 +1541,17 @@ fn renderSwitchCase( if (switch_case.payload_token) |payload_token| { try renderToken(ais, tree, payload_token - 1, .none); // pipe + const ident = payload_token + @boolToInt(token_tags[payload_token] == .asterisk); if (token_tags[payload_token] == .asterisk) { try renderToken(ais, tree, payload_token, .none); // asterisk - try renderToken(ais, tree, payload_token + 1, .none); // identifier - try renderToken(ais, tree, payload_token + 2, pre_target_space); // pipe + } + try renderToken(ais, tree, ident, .none); // identifier + if (token_tags[ident + 1] == .comma) { + try renderToken(ais, tree, ident + 1, .space); // , + try renderToken(ais, tree, ident + 2, .none); // identifier + try renderToken(ais, tree, ident + 3, pre_target_space); // pipe } else { - try renderToken(ais, tree, payload_token, .none); // identifier - try renderToken(ais, tree, payload_token + 1, pre_target_space); // pipe + try renderToken(ais, tree, ident + 1, pre_target_space); // pipe } } diff --git a/src/AstGen.zig b/src/AstGen.zig index 422827c673..c12778929c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -386,7 +386,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins .simple_var_decl => unreachable, .aligned_var_decl => unreachable, .switch_case => unreachable, + .switch_case_inline => unreachable, .switch_case_one => unreachable, + .switch_case_inline_one => unreachable, .container_field_init => unreachable, .container_field_align => unreachable, .container_field => unreachable, @@ -600,7 +602,9 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .@"errdefer" => unreachable, // Handled in `blockExpr`. .switch_case => unreachable, // Handled in `switchExpr`. + .switch_case_inline => unreachable, // Handled in `switchExpr`. .switch_case_one => unreachable, // Handled in `switchExpr`. + .switch_case_inline_one => unreachable, // Handled in `switchExpr`. .switch_range => unreachable, // Handled in `switchExpr`. .asm_output => unreachable, // Handled in `asmExpr`. @@ -2369,6 +2373,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .switch_capture_ref, .switch_capture_multi, .switch_capture_multi_ref, + .switch_capture_tag, .struct_init_empty, .struct_init, .struct_init_ref, @@ -6213,14 +6218,15 @@ fn switchExpr( var any_payload_is_ref = false; var scalar_cases_len: u32 = 0; var multi_cases_len: u32 = 0; + var inline_cases_len: u32 = 0; var special_prong: Zir.SpecialProng = .none; var special_node: Ast.Node.Index = 0; var else_src: ?Ast.TokenIndex = null; var underscore_src: ?Ast.TokenIndex = null; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; if (case.payload_token) |payload_token| { @@ -6304,6 +6310,9 @@ fn switchExpr( }, ); } + if (case.inline_token != null) { + return astgen.failTok(case_src, "cannot inline '_' prong", .{}); + } special_node = case_node; special_prong = .under; underscore_src = case_src; @@ -6315,6 +6324,9 @@ fn switchExpr( } else { multi_cases_len += 1; } + if (case.inline_token != null) { + inline_cases_len += 1; + } } const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none; @@ -6354,8 +6366,8 @@ fn switchExpr( var scalar_case_index: u32 = 0; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; @@ -6364,8 +6376,12 @@ fn switchExpr( var dbg_var_name: ?u32 = null; var dbg_var_inst: Zir.Inst.Ref = undefined; + var dbg_var_tag_name: ?u32 = null; + var dbg_var_tag_inst: Zir.Inst.Ref = undefined; var capture_inst: Zir.Inst.Index = 0; + var tag_inst: Zir.Inst.Index = 0; var capture_val_scope: Scope.LocalVal = undefined; + var tag_scope: Scope.LocalVal = undefined; const sub_scope = blk: { const payload_token = case.payload_token orelse break :blk &case_scope.base; const ident = if (token_tags[payload_token] == .asterisk) @@ -6373,59 +6389,96 @@ fn switchExpr( else payload_token; const is_ptr = ident != payload_token; - if (mem.eql(u8, tree.tokenSlice(ident), "_")) { + const ident_slice = tree.tokenSlice(ident); + var payload_sub_scope: *Scope = undefined; + if (mem.eql(u8, ident_slice, "_")) { if (is_ptr) { return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); } - break :blk &case_scope.base; - } - if (case_node == special_node) { - const capture_tag: Zir.Inst.Tag = if (is_ptr) - .switch_capture_ref - else - .switch_capture; - capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len); - try astgen.instructions.append(gpa, .{ - .tag = capture_tag, - .data = .{ - .switch_capture = .{ - .switch_inst = switch_block, - // Max int communicates that this is the else/underscore prong. - .prong_index = std.math.maxInt(u32), - }, - }, - }); + payload_sub_scope = &case_scope.base; } else { - const is_multi_case_bits: u2 = @boolToInt(is_multi_case); - const is_ptr_bits: u2 = @boolToInt(is_ptr); - const capture_tag: Zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) { - 0b00 => .switch_capture, - 0b01 => .switch_capture_ref, - 0b10 => .switch_capture_multi, - 0b11 => .switch_capture_multi_ref, + if (case_node == special_node) { + const capture_tag: Zir.Inst.Tag = if (is_ptr) + .switch_capture_ref + else + .switch_capture; + capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len); + try astgen.instructions.append(gpa, .{ + .tag = capture_tag, + .data = .{ + .switch_capture = .{ + .switch_inst = switch_block, + // Max int communicates that this is the else/underscore prong. + .prong_index = std.math.maxInt(u32), + }, + }, + }); + } else { + const is_multi_case_bits: u2 = @boolToInt(is_multi_case); + const is_ptr_bits: u2 = @boolToInt(is_ptr); + const capture_tag: Zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) { + 0b00 => .switch_capture, + 0b01 => .switch_capture_ref, + 0b10 => .switch_capture_multi, + 0b11 => .switch_capture_multi_ref, + }; + const capture_index = if (is_multi_case) multi_case_index else scalar_case_index; + capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len); + try astgen.instructions.append(gpa, .{ + .tag = capture_tag, + .data = .{ .switch_capture = .{ + .switch_inst = switch_block, + .prong_index = capture_index, + } }, + }); + } + const capture_name = try astgen.identAsString(ident); + try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice); + capture_val_scope = .{ + .parent = &case_scope.base, + .gen_zir = &case_scope, + .name = capture_name, + .inst = indexToRef(capture_inst), + .token_src = payload_token, + .id_cat = .@"capture", }; - const capture_index = if (is_multi_case) multi_case_index else scalar_case_index; - capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len); - try astgen.instructions.append(gpa, .{ - .tag = capture_tag, - .data = .{ .switch_capture = .{ - .switch_inst = switch_block, - .prong_index = capture_index, - } }, - }); + dbg_var_name = capture_name; + dbg_var_inst = indexToRef(capture_inst); + payload_sub_scope = &capture_val_scope.base; + } + + const tag_token = if (token_tags[ident + 1] == .comma) + ident + 2 + else + break :blk payload_sub_scope; + const tag_slice = tree.tokenSlice(tag_token); + if (mem.eql(u8, tag_slice, "_")) { + return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{}); + } else if (case.inline_token == null) { + return astgen.failTok(tag_token, "tag capture on non-inline prong", .{}); } - const capture_name = try astgen.identAsString(ident); - capture_val_scope = .{ - .parent = &case_scope.base, + const tag_name = try astgen.identAsString(tag_token); + try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice); + tag_inst = @intCast(Zir.Inst.Index, astgen.instructions.len); + try astgen.instructions.append(gpa, .{ + .tag = .switch_capture_tag, + .data = .{ .un_tok = .{ + .operand = cond, + .src_tok = case_scope.tokenIndexToRelative(tag_token), + } }, + }); + + tag_scope = .{ + .parent = payload_sub_scope, .gen_zir = &case_scope, - .name = capture_name, - .inst = indexToRef(capture_inst), - .token_src = payload_token, - .id_cat = .@"capture", + .name = tag_name, + .inst = indexToRef(tag_inst), + .token_src = tag_token, + .id_cat = .@"switch tag capture", }; - dbg_var_name = capture_name; - dbg_var_inst = indexToRef(capture_inst); - break :blk &capture_val_scope.base; + dbg_var_tag_name = tag_name; + dbg_var_tag_inst = indexToRef(tag_inst); + break :blk &tag_scope.base; }; const header_index = @intCast(u32, payloads.items.len); @@ -6480,10 +6533,14 @@ fn switchExpr( defer case_scope.unstack(); if (capture_inst != 0) try case_scope.instructions.append(gpa, capture_inst); + if (tag_inst != 0) try case_scope.instructions.append(gpa, tag_inst); try case_scope.addDbgBlockBegin(); if (dbg_var_name) |some| { try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst); } + if (dbg_var_tag_name) |some| { + try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_tag_inst); + } const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); try checkUsed(parent_gz, &case_scope.base, sub_scope); try case_scope.addDbgBlockEnd(); @@ -6495,7 +6552,8 @@ fn switchExpr( const case_slice = case_scope.instructionsSlice(); const body_len = astgen.countBodyLenAfterFixups(case_slice); try payloads.ensureUnusedCapacity(gpa, body_len); - payloads.items[body_len_index] = body_len; + const inline_bit = @as(u32, @boolToInt(case.inline_token != null)) << 31; + payloads.items[body_len_index] = body_len | inline_bit; appendBodyWithFixupsArrayList(astgen, payloads, case_slice); } } @@ -6509,7 +6567,6 @@ fn switchExpr( const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ .operand = cond, .bits = Zir.Inst.SwitchBlock.Bits{ - .is_ref = any_payload_is_ref, .has_multi_cases = multi_cases_len != 0, .has_else = special_prong == .@"else", .has_under = special_prong == .under, @@ -6543,7 +6600,7 @@ fn switchExpr( end_index += 3 + items_len + 2 * ranges_len; } - const body_len = payloads.items[body_len_index]; + const body_len = @truncate(u31, payloads.items[body_len_index]); end_index += body_len; switch (strat.tag) { @@ -8433,7 +8490,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index, have_ .@"usingnamespace", .test_decl, .switch_case, + .switch_case_inline, .switch_case_one, + .switch_case_inline_one, .container_field_init, .container_field_align, .container_field, @@ -8665,7 +8724,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev .@"usingnamespace", .test_decl, .switch_case, + .switch_case_inline, .switch_case_one, + .switch_case_inline_one, .container_field_init, .container_field_align, .container_field, @@ -8876,7 +8937,9 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In .@"usingnamespace", .test_decl, .switch_case, + .switch_case_inline, .switch_case_one, + .switch_case_inline_one, .container_field_init, .container_field_align, .container_field, @@ -9118,7 +9181,9 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool { .@"usingnamespace", .test_decl, .switch_case, + .switch_case_inline, .switch_case_one, + .switch_case_inline_one, .container_field_init, .container_field_align, .container_field, @@ -10051,6 +10116,7 @@ const Scope = struct { @"local constant", @"local variable", @"loop index capture", + @"switch tag capture", @"capture", }; diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 9fe56a8ec2..496e5afa61 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -853,7 +853,7 @@ fn walkInstruction( var path = str_tok.get(file.zir); const maybe_other_package: ?*Package = blk: { - if (self.module.main_pkg_in_std and std.mem.eql(u8, path, "std")) { + if (self.module.main_pkg_is_std and std.mem.eql(u8, path, "std")) { path = "root"; break :blk self.module.main_pkg; } else { diff --git a/src/Compilation.zig b/src/Compilation.zig index f0d100cb9a..7c4c369a6b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1588,10 +1588,10 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { try main_pkg.add(gpa, "root", root_pkg); try main_pkg.addAndAdopt(gpa, "std", std_pkg); - const main_pkg_in_std = m: { + const main_pkg_is_std = m: { const std_path = try std.fs.path.resolve(arena, &[_][]const u8{ std_pkg.root_src_directory.path orelse ".", - std.fs.path.dirname(std_pkg.root_src_path) orelse ".", + std_pkg.root_src_path, }); defer arena.free(std_path); const main_path = try std.fs.path.resolve(arena, &[_][]const u8{ @@ -1599,7 +1599,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { main_pkg.root_src_path, }); defer arena.free(main_path); - break :m mem.startsWith(u8, main_path, std_path); + break :m mem.eql(u8, main_path, std_path); }; // Pre-open the directory handles for cached ZIR code so that it does not need @@ -1638,7 +1638,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .gpa = gpa, .comp = comp, .main_pkg = main_pkg, - .main_pkg_in_std = main_pkg_in_std, + .main_pkg_is_std = main_pkg_is_std, .root_pkg = root_pkg, .zig_cache_artifact_directory = zig_cache_artifact_directory, .global_zir_cache = global_zir_cache, @@ -4207,14 +4207,6 @@ pub fn addCCArgs( else => {}, } - if (!comp.bin_file.options.strip) { - switch (target.ofmt) { - .coff => try argv.append("-gcodeview"), - .elf, .macho => try argv.append("-gdwarf-4"), - else => try argv.append("-g"), - } - } - if (target.cpu.arch.isThumb()) { try argv.append("-mthumb"); } @@ -4356,6 +4348,14 @@ pub fn addCCArgs( }, } + if (!comp.bin_file.options.strip) { + switch (target.ofmt) { + .coff => try argv.append("-gcodeview"), + .elf, .macho => try argv.append("-gdwarf-4"), + else => try argv.append("-g"), + } + } + if (target_util.llvmMachineAbi(target)) |mabi| { try argv.append(try std.fmt.allocPrint(arena, "-mabi={s}", .{mabi})); } diff --git a/src/Module.zig b/src/Module.zig index 6056c385e3..81a9ec220b 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -142,7 +142,7 @@ job_queued_update_builtin_zig: bool = true, /// This makes it so that we can run `zig test` on the standard library. /// Otherwise, the logic for scanning test decls skips all of them because /// `main_pkg != std_pkg`. -main_pkg_in_std: bool, +main_pkg_is_std: bool, compile_log_text: ArrayListUnmanaged(u8) = .{}, @@ -2445,8 +2445,8 @@ pub const SrcLoc = struct { const case_nodes = tree.extra_data[extra.start..extra.end]; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; const is_special = (case.ast.values.len == 0) or @@ -2469,8 +2469,8 @@ pub const SrcLoc = struct { const case_nodes = tree.extra_data[extra.start..extra.end]; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; const is_special = (case.ast.values.len == 0) or @@ -2491,8 +2491,8 @@ pub const SrcLoc = struct { const case_node = src_loc.declRelativeToNodeIndex(node_off); const node_tags = tree.nodes.items(.tag); const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; const start_tok = case.payload_token.?; @@ -4430,6 +4430,8 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { new_decl.has_linksection_or_addrspace = false; new_decl.ty = ty_ty; new_decl.val = struct_val; + new_decl.@"align" = 0; + new_decl.@"linksection" = null; new_decl.has_tv = true; new_decl.owns_tv = true; new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive. @@ -5172,7 +5174,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err // the test name filter. if (!comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) { - if (!mod.main_pkg_in_std) break :blk false; + if (!mod.main_pkg_is_std) break :blk false; const std_pkg = mod.main_pkg.table.get("std").?; if (std_pkg != decl_pkg) break :blk false; } @@ -5183,7 +5185,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err if (!is_named_test) break :blk false; if (!comp.bin_file.options.is_test) break :blk false; if (decl_pkg != mod.main_pkg) { - if (!mod.main_pkg_in_std) break :blk false; + if (!mod.main_pkg_is_std) break :blk false; const std_pkg = mod.main_pkg.table.get("std").?; if (std_pkg != decl_pkg) break :blk false; } @@ -5938,8 +5940,8 @@ pub const SwitchProngSrc = union(enum) { var scalar_i: u32 = 0; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), + .switch_case_one, .switch_case_inline_one => tree.switchCaseOne(case_node), + .switch_case, .switch_case_inline => tree.switchCase(case_node), else => unreachable, }; if (case.ast.values.len == 0) diff --git a/src/Sema.zig b/src/Sema.zig index aed09d6201..c185752dc0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -162,6 +162,9 @@ pub const Block = struct { /// type of `err` in `else => |err|` switch_else_err_ty: ?Type = null, + /// Value for switch_capture in an inline case + inline_case_capture: Air.Inst.Ref = .none, + const Param = struct { /// `noreturn` means `anytype`. ty: Type, @@ -603,6 +606,21 @@ fn resolveBody( return try sema.resolveInst(break_data.operand); } +fn analyzeBodyRuntimeBreak(sema: *Sema, block: *Block, body: []const Zir.Inst.Index) !void { + _ = sema.analyzeBodyInner(block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; +} + pub fn analyzeBody( sema: *Sema, block: *Block, @@ -796,6 +814,7 @@ fn analyzeBodyInner( .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true), .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false), .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true), + .switch_capture_tag => try sema.zirSwitchCaptureTag(block, inst), .type_info => try sema.zirTypeInfo(block, inst), .size_of => try sema.zirSizeOf(block, inst), .bit_size_of => try sema.zirBitSizeOf(block, inst), @@ -8107,6 +8126,13 @@ fn funcCommon( for (comptime_params) |ct| is_generic = is_generic or ct; is_generic = is_generic or ret_ty_requires_comptime; + if (!is_generic and sema.wantErrorReturnTracing(return_type)) { + // Make sure that StackTrace's fields are resolved so that the backend can + // lower this fn type. + const unresolved_stack_trace_ty = try sema.getBuiltinType(block, ret_ty_src, "StackTrace"); + _ = try sema.resolveTypeFields(block, ret_ty_src, unresolved_stack_trace_ty); + } + break :fn_ty try Type.Tag.function.create(sema.arena, .{ .param_types = param_types, .comptime_params = comptime_params.ptr, @@ -9023,13 +9049,38 @@ fn zirSwitchCapture( const switch_info = zir_datas[capture_info.switch_inst].pl_node; const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index); const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node }; - const operand_is_ref = switch_extra.data.bits.is_ref; const cond_inst = Zir.refToIndex(switch_extra.data.operand).?; - const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node; + const cond_info = zir_datas[cond_inst].un_node; + const cond_tag = sema.code.instructions.items(.tag)[cond_inst]; + const operand_is_ref = cond_tag == .switch_cond_ref; const operand_ptr = try sema.resolveInst(cond_info.operand); const operand_ptr_ty = sema.typeOf(operand_ptr); const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty; + if (block.inline_case_capture != .none) { + const item_val = sema.resolveConstValue(block, .unneeded, block.inline_case_capture, undefined) catch unreachable; + if (operand_ty.zigTypeTag() == .Union) { + const field_index = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, sema.mod).?); + const union_obj = operand_ty.cast(Type.Payload.Union).?.data; + const field_ty = union_obj.fields.values()[field_index].ty; + if (is_ref) { + const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = field_ty, + .mutable = operand_ptr_ty.ptrIsMutable(), + .@"volatile" = operand_ptr_ty.isVolatilePtr(), + .@"addrspace" = operand_ptr_ty.ptrAddressSpace(), + }); + return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty); + } else { + return block.addStructFieldVal(operand_ptr, field_index, field_ty); + } + } else if (is_ref) { + return sema.addConstantMaybeRef(block, operand_src, operand_ty, item_val, true); + } else { + return block.inline_case_capture; + } + } + const operand = if (operand_is_ref) try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) else @@ -9038,7 +9089,6 @@ fn zirSwitchCapture( if (capture_info.prong_index == std.math.maxInt(@TypeOf(capture_info.prong_index))) { // It is the else/`_` prong. if (is_ref) { - assert(operand_is_ref); return operand_ptr; } @@ -9098,8 +9148,6 @@ fn zirSwitchCapture( } if (is_ref) { - assert(operand_is_ref); - const field_ty_ptr = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = first_field.ty, .@"addrspace" = .generic, @@ -9160,7 +9208,6 @@ fn zirSwitchCapture( // In this case the capture value is just the passed-through value of the // switch condition. if (is_ref) { - assert(operand_is_ref); return operand_ptr; } else { return operand; @@ -9169,6 +9216,33 @@ fn zirSwitchCapture( } } +fn zirSwitchCaptureTag(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const zir_datas = sema.code.instructions.items(.data); + const inst_data = zir_datas[inst].un_tok; + const src = inst_data.src(); + + const switch_tag = sema.code.instructions.items(.tag)[Zir.refToIndex(inst_data.operand).?]; + const is_ref = switch_tag == .switch_cond_ref; + const cond_data = zir_datas[Zir.refToIndex(inst_data.operand).?].un_node; + const operand_ptr = try sema.resolveInst(cond_data.operand); + const operand_ptr_ty = sema.typeOf(operand_ptr); + const operand_ty = if (is_ref) operand_ptr_ty.childType() else operand_ptr_ty; + + if (operand_ty.zigTypeTag() != .Union) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "cannot capture tag of non-union type '{}'", .{ + operand_ty.fmt(sema.mod), + }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, operand_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + } + + return block.inline_case_capture; +} + fn zirSwitchCond( sema: *Sema, block: *Block, @@ -9266,14 +9340,15 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } else 0; const special_prong = extra.data.bits.specialProng(); - const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) { - .none => .{ .body = &.{}, .end = header_extra_index }, + const special: struct { body: []const Zir.Inst.Index, end: usize, is_inline: bool } = switch (special_prong) { + .none => .{ .body = &.{}, .end = header_extra_index, .is_inline = false }, .under, .@"else" => blk: { - const body_len = sema.code.extra[header_extra_index]; + const body_len = @truncate(u31, sema.code.extra[header_extra_index]); const extra_body_start = header_extra_index + 1; break :blk .{ .body = sema.code.extra[extra_body_start..][0..body_len], .end = extra_body_start + body_len, + .is_inline = sema.code.extra[header_extra_index] >> 31 != 0, }; }, }; @@ -9285,8 +9360,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError 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); + + // Duplicate checking variables later also used for `inline else`. + var seen_enum_fields: []?Module.SwitchProngSrc = &.{}; + var seen_errors = SwitchErrorSet.init(gpa); + var range_set = RangeSet.init(gpa, sema.mod); + var true_count: u8 = 0; + var false_count: u8 = 0; + + defer { + range_set.deinit(); + gpa.free(seen_enum_fields); + seen_errors.deinit(); + } var empty_enum = false; @@ -9323,15 +9409,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError switch (operand_ty.zigTypeTag()) { .Union => unreachable, // handled in zirSwitchCond .Enum => { - var seen_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); - 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. - var range_set = RangeSet.init(gpa, sema.mod); - defer range_set.deinit(); + seen_enum_fields = try gpa.alloc(?Module.SwitchProngSrc, operand_ty.enumFieldCount()); + empty_enum = seen_enum_fields.len == 0 and !operand_ty.isNonexhaustiveEnum(); + mem.set(?Module.SwitchProngSrc, seen_enum_fields, null); + // `range_set` is used for non-exhaustive enum values that do not correspond to any tags. var extra_index: usize = special.end; { @@ -9339,13 +9420,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; extra_index += body_len; try sema.validateSwitchItemEnum( block, - seen_fields, + seen_enum_fields, &range_set, item_ref, src_node_offset, @@ -9360,7 +9441,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len + body_len; @@ -9368,7 +9449,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError for (items) |item_ref, item_i| { try sema.validateSwitchItemEnum( block, - seen_fields, + seen_enum_fields, &range_set, item_ref, src_node_offset, @@ -9379,7 +9460,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset); } } - const all_tags_handled = for (seen_fields) |seen_src| { + const all_tags_handled = for (seen_enum_fields) |seen_src| { if (seen_src == null) break false; } else true; @@ -9399,7 +9480,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .{}, ); errdefer msg.destroy(sema.gpa); - for (seen_fields) |seen_src, i| { + for (seen_enum_fields) |seen_src, i| { if (seen_src != null) continue; const field_name = operand_ty.enumFieldName(i); @@ -9430,16 +9511,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } }, .ErrorSet => { - var seen_errors = SwitchErrorSet.init(gpa); - defer seen_errors.deinit(); - var extra_index: usize = special.end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; extra_index += body_len; @@ -9459,7 +9537,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len + body_len; @@ -9572,16 +9650,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } }, .Int, .ComptimeInt => { - var range_set = RangeSet.init(gpa, sema.mod); - defer range_set.deinit(); - var extra_index: usize = special.end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; extra_index += body_len; @@ -9602,7 +9677,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len; @@ -9670,16 +9745,13 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } }, .Bool => { - var true_count: u8 = 0; - var false_count: u8 = 0; - var extra_index: usize = special.end; { var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; extra_index += body_len; @@ -9700,7 +9772,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len + body_len; @@ -9764,7 +9836,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; extra_index += body_len; @@ -9784,7 +9856,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len + body_len; @@ -9864,7 +9936,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; @@ -9885,7 +9957,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len; @@ -9926,7 +9998,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); } - if (scalar_cases_len + multi_cases_len == 0) { + if (scalar_cases_len + multi_cases_len == 0 and !special.is_inline) { if (empty_enum) { return Air.Inst.Ref.void_value; } @@ -9958,7 +10030,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); + const is_inline = sema.code.extra[extra_index] >> 31 != 0; extra_index += 1; const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; @@ -9968,8 +10041,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; + case_block.inline_case_capture = .none; const item = try sema.resolveInst(item_ref); + if (is_inline) case_block.inline_case_capture = item; // `item` is already guaranteed to be constant known. const analyze_body = if (union_originally) blk: { @@ -9981,18 +10056,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else if (analyze_body) { - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + try sema.analyzeBodyRuntimeBreak(&case_block, body); } else { _ = try case_block.addNoOp(.unreach); } @@ -10014,19 +10078,115 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError defer gpa.free(prev_then_body); var cases_len = scalar_cases_len; - var multi_i: usize = 0; + var multi_i: u32 = 0; while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = sema.code.extra[extra_index]; extra_index += 1; const ranges_len = sema.code.extra[extra_index]; extra_index += 1; - const body_len = sema.code.extra[extra_index]; + const body_len = @truncate(u31, sema.code.extra[extra_index]); + const is_inline = sema.code.extra[extra_index] >> 31 != 0; extra_index += 1; const items = sema.code.refSlice(extra_index, items_len); extra_index += items_len; case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = child_block.wip_capture_scope; + case_block.inline_case_capture = .none; + + // Generate all possible cases as scalar prongs. + if (is_inline) { + const body_start = extra_index + 2 * ranges_len; + const body = sema.code.extra[body_start..][0..body_len]; + var emit_bb = false; + + var range_i: u32 = 0; + while (range_i < ranges_len) : (range_i += 1) { + const first_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + + const item_first_ref = try sema.resolveInst(first_ref); + var item = sema.resolveConstValue(block, .unneeded, item_first_ref, undefined) catch unreachable; + const item_last_ref = try sema.resolveInst(last_ref); + const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable; + + while (item.compare(.lte, item_last, operand_ty, sema.mod)) : ({ + // Previous validation has resolved any possible lazy values. + item = try sema.intAddScalar(block, .unneeded, item, Value.one); + }) { + cases_len += 1; + + const item_ref = try sema.addConstant(operand_ty, item); + case_block.inline_case_capture = item_ref; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + if (emit_bb) sema.emitBackwardBranch(block, .unneeded) catch |err| switch (err) { + error.NeededSourceLocation => { + const case_src = Module.SwitchProngSrc{ .range = .{ .prong = multi_i, .item = range_i } }; + const decl = sema.mod.declPtr(case_block.src_decl); + try sema.emitBackwardBranch(block, case_src.resolve(sema.gpa, decl, src_node_offset, .none)); + return error.AnalysisFail; + }, + else => return err, + }; + emit_bb = true; + + try sema.analyzeBodyRuntimeBreak(&case_block, body); + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + } + + for (items) |item_ref, item_i| { + cases_len += 1; + + const item = try sema.resolveInst(item_ref); + case_block.inline_case_capture = item; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + 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 (emit_bb) sema.emitBackwardBranch(block, .unneeded) catch |err| switch (err) { + error.NeededSourceLocation => { + const case_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_i, .item = @intCast(u32, item_i) } }; + const decl = sema.mod.declPtr(case_block.src_decl); + try sema.emitBackwardBranch(block, case_src.resolve(sema.gpa, decl, src_node_offset, .none)); + return error.AnalysisFail; + }, + else => return err, + }; + emit_bb = true; + + if (analyze_body) { + try sema.analyzeBodyRuntimeBreak(&case_block, body); + } else { + _ = try case_block.addNoOp(.unreach); + } + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + + extra_index += body_len; + continue; + } var any_ok: Air.Inst.Ref = .none; @@ -10051,18 +10211,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else if (analyze_body) { - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + try sema.analyzeBodyRuntimeBreak(&case_block, body); } else { _ = try case_block.addNoOp(.unreach); } @@ -10143,18 +10292,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError if (err_set and try sema.maybeErrorUnwrap(&case_block, body, operand)) { // nothing to do here } else { - _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + try sema.analyzeBodyRuntimeBreak(&case_block, body); } try wip_captures.finalize(); @@ -10185,14 +10323,150 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var final_else_body: []const Air.Inst.Index = &.{}; if (special.body.len != 0 or !is_first or case_block.wantSafety()) { + var emit_bb = false; + if (special.is_inline) switch (operand_ty.zigTypeTag()) { + .Enum => { + if (operand_ty.isNonexhaustiveEnum() and !union_originally) { + return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{ + operand_ty.fmt(sema.mod), + }); + } + for (seen_enum_fields) |f, i| { + if (f != null) continue; + cases_len += 1; + + const item_val = try Value.Tag.enum_field_index.create(sema.arena, @intCast(u32, i)); + const item_ref = try sema.addConstant(operand_ty, item_val); + case_block.inline_case_capture = item_ref; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + const analyze_body = if (union_originally) blk: { + const field_ty = maybe_union_ty.unionFieldType(item_val, sema.mod); + break :blk field_ty.zigTypeTag() != .NoReturn; + } else true; + + if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + emit_bb = true; + + if (analyze_body) { + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + } else { + _ = try case_block.addNoOp(.unreach); + } + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + }, + .ErrorSet => { + if (operand_ty.isAnyError()) { + return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{ + operand_ty.fmt(sema.mod), + }); + } + for (operand_ty.errorSetNames()) |error_name| { + if (seen_errors.contains(error_name)) continue; + cases_len += 1; + + const item_val = try Value.Tag.@"error".create(sema.arena, .{ .name = error_name }); + const item_ref = try sema.addConstant(operand_ty, item_val); + case_block.inline_case_capture = item_ref; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + emit_bb = true; + + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + }, + .Int => { + var it = try RangeSetUnhandledIterator.init(sema, block, special_prong_src, operand_ty, range_set); + while (try it.next()) |cur| { + cases_len += 1; + + const item_ref = try sema.addConstant(operand_ty, cur); + case_block.inline_case_capture = item_ref; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + emit_bb = true; + + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + }, + .Bool => { + if (true_count == 0) { + cases_len += 1; + case_block.inline_case_capture = Air.Inst.Ref.bool_true; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + emit_bb = true; + + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + if (false_count == 0) { + cases_len += 1; + case_block.inline_case_capture = Air.Inst.Ref.bool_false; + + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; + + if (emit_bb) try sema.emitBackwardBranch(block, special_prong_src); + emit_bb = true; + + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); + + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); + cases_extra.appendAssumeCapacity(1); // items_len + cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); + cases_extra.appendAssumeCapacity(@enumToInt(case_block.inline_case_capture)); + cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); + } + }, + else => return sema.fail(block, special_prong_src, "cannot enumerate values of type '{}' for 'inline else'", .{ + operand_ty.fmt(sema.mod), + }), + }; + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); defer wip_captures.deinit(); case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; + case_block.inline_case_capture = .none; - const analyze_body = if (union_originally) - for (seen_union_fields) |seen_field, index| { + const analyze_body = if (union_originally and !special.is_inline) + for (seen_enum_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; @@ -10204,19 +10478,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError try sema.maybeErrorUnwrap(&case_block, special.body, operand)) { // nothing to do here - } else 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); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&case_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + } else if (special.body.len != 0 and analyze_body and !special.is_inline) { + try sema.analyzeBodyRuntimeBreak(&case_block, special.body); } else { // We still need a terminator in this block, but we have proven // that it is unreachable. @@ -10262,6 +10525,55 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return sema.analyzeBlockBody(block, src, &child_block, merges); } +const RangeSetUnhandledIterator = struct { + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ty: Type, + cur: Value, + max: Value, + ranges: []const RangeSet.Range, + range_i: usize = 0, + first: bool = true, + + fn init(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator { + const target = sema.mod.getTarget(); + const min = try ty.minInt(sema.arena, target); + const max = try ty.maxInt(sema.arena, target); + + return RangeSetUnhandledIterator{ + .sema = sema, + .block = block, + .src = src, + .ty = ty, + .cur = min, + .max = max, + .ranges = range_set.ranges.items, + }; + } + + fn next(it: *RangeSetUnhandledIterator) !?Value { + while (it.range_i < it.ranges.len) : (it.range_i += 1) { + if (!it.first) { + it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty); + } + it.first = false; + if (it.cur.compare(.lt, it.ranges[it.range_i].first, it.ty, it.sema.mod)) { + return it.cur; + } + it.cur = it.ranges[it.range_i].last; + } + if (!it.first) { + it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty); + } + it.first = false; + if (it.cur.compare(.lte, it.max, it.ty, it.sema.mod)) { + return it.cur; + } + return null; + } +}; + fn resolveSwitchItemVal( sema: *Sema, block: *Block, @@ -15344,18 +15656,7 @@ fn zirCondbr( sub_block.runtime_index.increment(); defer sub_block.instructions.deinit(gpa); - _ = sema.analyzeBodyInner(&sub_block, then_body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&sub_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + try sema.analyzeBodyRuntimeBreak(&sub_block, then_body); const true_instructions = sub_block.instructions.toOwnedSlice(gpa); defer gpa.free(true_instructions); @@ -15374,18 +15675,7 @@ fn zirCondbr( if (err_cond != null and try sema.maybeErrorUnwrap(&sub_block, else_body, err_cond.?)) { // nothing to do } else { - _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) { - error.ComptimeBreak => { - const zir_datas = sema.code.instructions.items(.data); - const break_data = zir_datas[sema.comptime_break_inst].@"break"; - try sema.addRuntimeBreak(&sub_block, .{ - .block_inst = break_data.block_inst, - .operand = break_data.operand, - .inst = sema.comptime_break_inst, - }); - }, - else => |e| return e, - }; + try sema.analyzeBodyRuntimeBreak(&sub_block, else_body); } try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len + true_instructions.len + sub_block.instructions.items.len); @@ -15631,7 +15921,7 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir return sema.analyzeRet(block, operand, src); } - if (sema.wantErrorReturnTracing()) { + if (sema.wantErrorReturnTracing(sema.fn_ret_ty)) { const is_non_err = try sema.analyzePtrIsNonErr(block, src, ret_ptr); return retWithErrTracing(sema, block, src, is_non_err, .ret_load, ret_ptr); } @@ -15698,11 +15988,11 @@ fn retWithErrTracing( return always_noreturn; } -fn wantErrorReturnTracing(sema: *Sema) bool { +fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool { // TODO implement this feature in all the backends and then delete this check. const backend_supports_error_return_tracing = sema.mod.comp.bin_file.options.use_llvm; - return sema.fn_ret_ty.isError() and + return fn_ret_ty.isError() and sema.mod.comp.bin_file.options.error_return_tracing and backend_supports_error_return_tracing; } @@ -15754,7 +16044,7 @@ fn analyzeRet( try sema.resolveTypeLayout(block, src, sema.fn_ret_ty); - if (sema.wantErrorReturnTracing()) { + if (sema.wantErrorReturnTracing(sema.fn_ret_ty)) { // Avoid adding a frame to the error return trace in case the value is comptime-known // to be not an error. const is_non_err = try sema.analyzeIsNonErr(block, src, operand); @@ -17682,6 +17972,10 @@ fn reifyStruct( } const abi_align = @intCast(u29, (try alignment_val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?); + if (layout == .Packed and abi_align != 0) { + return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{}); + } + const field_name = try name_val.toAllocatedBytes( Type.initTag(.const_slice_u8), new_decl_arena_allocator, @@ -24308,7 +24602,7 @@ fn coerceInMemoryAllowedFns( if (dest_info.param_types.len != src_info.param_types.len) { return InMemoryCoercionResult{ .fn_param_count = .{ - .actual = dest_info.param_types.len, + .actual = src_info.param_types.len, .wanted = dest_info.param_types.len, } }; } diff --git a/src/Zir.zig b/src/Zir.zig index 5b1aefea64..add8bad801 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -683,6 +683,9 @@ pub const Inst = struct { /// Result is a pointer to the value. /// Uses the `switch_capture` field. switch_capture_multi_ref, + /// Produces the capture value for an inline switch prong tag capture. + /// Uses the `un_tok` field. + switch_capture_tag, /// Given a /// *A returns *A /// *E!A returns *A @@ -1128,6 +1131,7 @@ pub const Inst = struct { .switch_capture_ref, .switch_capture_multi, .switch_capture_multi_ref, + .switch_capture_tag, .switch_block, .switch_cond, .switch_cond_ref, @@ -1422,6 +1426,7 @@ pub const Inst = struct { .switch_capture_ref, .switch_capture_multi, .switch_capture_multi_ref, + .switch_capture_tag, .switch_block, .switch_cond, .switch_cond_ref, @@ -1681,6 +1686,7 @@ pub const Inst = struct { .switch_capture_ref = .switch_capture, .switch_capture_multi = .switch_capture, .switch_capture_multi_ref = .switch_capture, + .switch_capture_tag = .un_tok, .array_base_ptr = .un_node, .field_base_ptr = .un_node, .validate_array_init_ty = .pl_node, @@ -2952,12 +2958,9 @@ pub const Inst = struct { has_else: bool, /// If true, there is an underscore prong. This is mutually exclusive with `has_else`. has_under: bool, - /// If true, the `operand` is a pointer to the value being switched on. - /// TODO this flag is redundant with the tag of operand and can be removed. - is_ref: bool, scalar_cases_len: ScalarCasesLen, - pub const ScalarCasesLen = u28; + pub const ScalarCasesLen = u29; pub fn specialProng(bits: Bits) SpecialProng { const has_else: u2 = @boolToInt(bits.has_else); @@ -2993,7 +2996,7 @@ pub const Inst = struct { } if (self.bits.specialProng() != .none) { - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; extra_index += body.len; @@ -3003,7 +3006,7 @@ pub const Inst = struct { while (true) : (scalar_i += 1) { const item = @intToEnum(Ref, zir.extra[extra_index]); extra_index += 1; - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; extra_index += body.len; @@ -3032,7 +3035,7 @@ pub const Inst = struct { var extra_index: usize = extra_end + 1; if (self.bits.specialProng() != .none) { - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; extra_index += body.len; @@ -3041,7 +3044,7 @@ pub const Inst = struct { var scalar_i: usize = 0; while (scalar_i < self.bits.scalar_cases_len) : (scalar_i += 1) { extra_index += 1; - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; extra_index += body_len; } @@ -3049,7 +3052,7 @@ pub const Inst = struct { while (true) : (multi_i += 1) { const items_len = zir.extra[extra_index]; extra_index += 2; - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const items = zir.refSlice(extra_index, items_len); extra_index += items_len; @@ -3861,7 +3864,7 @@ fn findDeclsSwitch( const special_prong = extra.data.bits.specialProng(); if (special_prong != .none) { - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; extra_index += body.len; @@ -3874,7 +3877,7 @@ fn findDeclsSwitch( var scalar_i: usize = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { extra_index += 1; - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; extra_index += body_len; @@ -3889,7 +3892,7 @@ fn findDeclsSwitch( extra_index += 1; const ranges_len = zir.extra[extra_index]; extra_index += 1; - const body_len = zir.extra[extra_index]; + const body_len = @truncate(u31, zir.extra[extra_index]); extra_index += 1; const items = zir.refSlice(extra_index, items_len); extra_index += items_len; diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 0cdc7a4c5f..c1c00d8303 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -2159,7 +2159,7 @@ const RegisterOrMemory = union(enum) { /// Returns size in bits. fn size(reg_or_mem: RegisterOrMemory) u64 { return switch (reg_or_mem) { - .register => |reg| reg.size(), + .register => |register| register.size(), .memory => |memory| memory.size(), }; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 1b793265da..a24c14c9c6 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2332,10 +2332,13 @@ pub const Object = struct { // buffer is only used for int_type, `builtin` is a struct. const builtin_ty = mod.declPtr(builtin_decl).val.toType(undefined); const builtin_namespace = builtin_ty.getNamespace().?; - const stack_trace_decl = builtin_namespace.decls + const stack_trace_decl_index = builtin_namespace.decls .getKeyAdapted(stack_trace_str, Module.DeclAdapter{ .mod = mod }).?; + const stack_trace_decl = mod.declPtr(stack_trace_decl_index); - return mod.declPtr(stack_trace_decl).val.toType(undefined); + // Sema should have ensured that StackTrace was analyzed. + assert(stack_trace_decl.has_tv); + return stack_trace_decl.val.toType(undefined); } }; @@ -10277,6 +10280,7 @@ fn ccAbiPromoteInt( else => {}, } const int_info = switch (ty.zigTypeTag()) { + .Bool => Type.@"u1".intInfo(target), .Int, .Enum, .ErrorSet => ty.intInfo(target), else => return null, }; diff --git a/src/print_zir.zig b/src/print_zir.zig index b273365596..d383664c16 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -237,6 +237,7 @@ const Writer = struct { .ret_tok, .ensure_err_payload_void, .closure_capture, + .switch_capture_tag, => try self.writeUnTok(stream, inst), .bool_br_and, @@ -1857,7 +1858,6 @@ const Writer = struct { } else 0; try self.writeInstRef(stream, extra.data.operand); - try self.writeFlag(stream, ", ref", extra.data.bits.is_ref); self.indent += 2; @@ -1869,14 +1869,15 @@ const Writer = struct { else => break :else_prong, }; - const body_len = self.code.extra[extra_index]; + const body_len = @truncate(u31, self.code.extra[extra_index]); + const inline_text = if (self.code.extra[extra_index] >> 31 != 0) "inline " else ""; extra_index += 1; const body = self.code.extra[extra_index..][0..body_len]; extra_index += body.len; try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); - try stream.print("{s} => ", .{prong_name}); + try stream.print("{s}{s} => ", .{ inline_text, prong_name }); try self.writeBracedBody(stream, body); } @@ -1886,13 +1887,15 @@ const Writer = struct { while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); extra_index += 1; - const body_len = self.code.extra[extra_index]; + const body_len = @truncate(u31, self.code.extra[extra_index]); + const is_inline = self.code.extra[extra_index] >> 31 != 0; extra_index += 1; const body = self.code.extra[extra_index..][0..body_len]; extra_index += body_len; try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); + if (is_inline) try stream.writeAll("inline "); try self.writeInstRef(stream, item_ref); try stream.writeAll(" => "); try self.writeBracedBody(stream, body); @@ -1905,13 +1908,15 @@ const Writer = struct { extra_index += 1; const ranges_len = self.code.extra[extra_index]; extra_index += 1; - const body_len = self.code.extra[extra_index]; + const body_len = @truncate(u31, self.code.extra[extra_index]); + const is_inline = self.code.extra[extra_index] >> 31 != 0; extra_index += 1; const items = self.code.refSlice(extra_index, items_len); extra_index += items_len; try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); + if (is_inline) try stream.writeAll("inline "); for (items) |item_ref, item_i| { if (item_i != 0) try stream.writeAll(", "); diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 5f216fe388..d4a2abece9 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1039,6 +1039,7 @@ struct AstNodeSwitchProng { AstNode *expr; bool var_is_ptr; bool any_items_are_range; + bool is_inline; }; struct AstNodeSwitchRange { diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index 54d9c969a5..9eea2e650e 100644 --- a/src/stage1/astgen.cpp +++ b/src/stage1/astgen.cpp @@ -6987,6 +6987,12 @@ static bool astgen_switch_prong_expr(Stage1AstGen *ag, Scope *scope, AstNode *sw assert(switch_node->type == NodeTypeSwitchExpr); assert(prong_node->type == NodeTypeSwitchProng); + if (prong_node->data.switch_prong.is_inline) { + exec_add_error_node(ag->codegen, ag->exec, prong_node, + buf_sprintf("inline switch cases not supported by stage1")); + return ag->codegen->invalid_inst_src; + } + AstNode *expr_node = prong_node->data.switch_prong.expr; AstNode *var_symbol_node = prong_node->data.switch_prong.var_symbol; Scope *child_scope; diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index bd778484cb..ec02e6fa8b 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2306,17 +2306,17 @@ static Optional<PtrIndexPayload> ast_parse_ptr_index_payload(ParseContext *pc) { return Optional<PtrIndexPayload>::some(res); } -// SwitchProng <- SwitchCase EQUALRARROW PtrPayload? AssignExpr +// SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? AssignExpr static AstNode *ast_parse_switch_prong(ParseContext *pc) { AstNode *res = ast_parse_switch_case(pc); if (res == nullptr) return nullptr; expect_token(pc, TokenIdFatArrow); - Optional<PtrPayload> opt_payload = ast_parse_ptr_payload(pc); + Optional<PtrIndexPayload> opt_payload = ast_parse_ptr_index_payload(pc); AstNode *expr = ast_expect(pc, ast_parse_assign_expr); - PtrPayload payload; + PtrIndexPayload payload; assert(res->type == NodeTypeSwitchProng); res->data.switch_prong.expr = expr; if (opt_payload.unwrap(&payload)) { @@ -2331,9 +2331,11 @@ static AstNode *ast_parse_switch_prong(ParseContext *pc) { // <- SwitchItem (COMMA SwitchItem)* COMMA? // / KEYWORD_else static AstNode *ast_parse_switch_case(ParseContext *pc) { + bool is_inline = eat_token_if(pc, TokenIdKeywordInline) != 0; AstNode *first = ast_parse_switch_item(pc); if (first != nullptr) { AstNode *res = ast_create_node_copy_line_info(pc, NodeTypeSwitchProng, first); + res->data.switch_prong.is_inline = is_inline; res->data.switch_prong.items.append(first); res->data.switch_prong.any_items_are_range = first->type == NodeTypeSwitchRange; @@ -2350,9 +2352,13 @@ static AstNode *ast_parse_switch_case(ParseContext *pc) { } TokenIndex else_token = eat_token_if(pc, TokenIdKeywordElse); - if (else_token != 0) - return ast_create_node(pc, NodeTypeSwitchProng, else_token); + if (else_token != 0) { + AstNode *res = ast_create_node(pc, NodeTypeSwitchProng, else_token); + res->data.switch_prong.is_inline = is_inline; + return res; + } + if (is_inline) pc->current_token -= 1; return nullptr; } diff --git a/test/behavior.zig b/test/behavior.zig index ed7910cfbc..8f4657e634 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -182,6 +182,7 @@ test { _ = @import("behavior/decltest.zig"); _ = @import("behavior/packed_struct_explicit_backing_int.zig"); _ = @import("behavior/empty_union.zig"); + _ = @import("behavior/inline_switch.zig"); } if (builtin.os.tag != .wasi) { diff --git a/test/behavior/inline_switch.zig b/test/behavior/inline_switch.zig new file mode 100644 index 0000000000..ecc7bba280 --- /dev/null +++ b/test/behavior/inline_switch.zig @@ -0,0 +1,131 @@ +const std = @import("std"); +const expect = std.testing.expect; +const builtin = @import("builtin"); + +test "inline scalar prongs" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var x: usize = 0; + switch (x) { + 10 => |*item| try expect(@TypeOf(item) == *usize), + inline 11 => |*item| { + try expect(@TypeOf(item) == *const usize); + try expect(item.* == 11); + }, + else => {}, + } +} + +test "inline prong ranges" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var x: usize = 0; + switch (x) { + inline 0...20, 24 => |item| { + if (item > 25) @compileError("bad"); + }, + else => {}, + } +} + +const E = enum { a, b, c, d }; +test "inline switch enums" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var x: E = .a; + switch (x) { + inline .a, .b => |aorb| if (aorb != .a and aorb != .b) @compileError("bad"), + inline .c, .d => |cord| if (cord != .c and cord != .d) @compileError("bad"), + } +} + +const U = union(E) { a: void, b: u2, c: u3, d: u4 }; +test "inline switch unions" { + 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 + + var x: U = .a; + switch (x) { + inline .a, .b => |aorb, tag| { + if (tag == .a) { + try expect(@TypeOf(aorb) == void); + } else { + try expect(tag == .b); + try expect(@TypeOf(aorb) == u2); + } + }, + inline .c, .d => |cord, tag| { + if (tag == .c) { + try expect(@TypeOf(cord) == u3); + } else { + try expect(tag == .d); + try expect(@TypeOf(cord) == u4); + } + }, + } +} + +test "inline else bool" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var a = true; + switch (a) { + true => {}, + inline else => |val| if (val != false) @compileError("bad"), + } +} + +test "inline else error" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + const Err = error{ a, b, c }; + var a = Err.a; + switch (a) { + error.a => {}, + inline else => |val| comptime if (val == error.a) @compileError("bad"), + } +} + +test "inline else enum" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + + const E2 = enum(u8) { a = 2, b = 3, c = 4, d = 5 }; + var a: E2 = .a; + switch (a) { + .a, .b => {}, + inline else => |val| comptime if (@enumToInt(val) < 4) @compileError("bad"), + } +} + +test "inline else int with gaps" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var a: u8 = 0; + switch (a) { + 1...125, 128...254 => {}, + inline else => |val| { + if (val != 0 and + val != 126 and + val != 127 and + val != 255) + @compileError("bad"); + }, + } +} + +test "inline else int all values" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + + var a: u2 = 0; + switch (a) { + inline else => |val| { + if (val != 0 and + val != 1 and + val != 2 and + val != 3) + @compileError("bad"); + }, + } +} diff --git a/test/cases/compile_errors/function_type_coercion.zig b/test/cases/compile_errors/function_type_coercion.zig new file mode 100644 index 0000000000..552995304d --- /dev/null +++ b/test/cases/compile_errors/function_type_coercion.zig @@ -0,0 +1,21 @@ +fn f(_: i32) void {} +export fn wrong_param_count() void { + _ = @as(fn () void, f); +} +export fn wrong_param_type() void { + _ = @as(fn (f32) void, f); +} +export fn wrong_return_type() void { + _ = @as(fn () i32, f); +} + +// error +// backend=stage2,llvm +// target=native +// +// :3:25: error: expected type 'fn() void', found 'fn(i32) void' +// :3:25: note: function with 1 parameters cannot cast into a function with 0 parameters +// :6:28: error: expected type 'fn(f32) void', found 'fn(i32) void' +// :6:28: note: parameter 0 'i32' cannot cast into 'f32' +// :9:24: error: expected type 'fn() i32', found 'fn(i32) void' +// :9:24: note: return type 'void' cannot cast into return type 'i32' diff --git a/test/cases/compile_errors/inline_underscore_prong.zig b/test/cases/compile_errors/inline_underscore_prong.zig new file mode 100644 index 0000000000..12e20e65bc --- /dev/null +++ b/test/cases/compile_errors/inline_underscore_prong.zig @@ -0,0 +1,15 @@ +const E = enum(u8) { a, b, c, d, _ }; +pub export fn entry() void { + var x: E = .a; + switch (x) { + inline .a, .b => |aorb| @compileLog(aorb), + .c, .d => |cord| @compileLog(cord), + inline _ => {}, + } +} + +// error +// backend=stage2 +// target=native +// +// :7:16: error: cannot inline '_' prong diff --git a/test/cases/compile_errors/invalid_inline_else_type.zig b/test/cases/compile_errors/invalid_inline_else_type.zig new file mode 100644 index 0000000000..2d52fca43e --- /dev/null +++ b/test/cases/compile_errors/invalid_inline_else_type.zig @@ -0,0 +1,27 @@ +pub export fn entry1() void { + var a: anyerror = undefined; + switch (a) { + inline else => {}, + } +} +const E = enum(u8) { a, _ }; +pub export fn entry2() void { + var a: E = undefined; + switch (a) { + inline else => {}, + } +} +pub export fn entry3() void { + var a: *u32 = undefined; + switch (a) { + inline else => {}, + } +} + +// error +// backend=stage2 +// target=native +// +// :4:21: error: cannot enumerate values of type 'anyerror' for 'inline else' +// :11:21: error: cannot enumerate values of type 'tmp.E' for 'inline else' +// :17:21: error: cannot enumerate values of type '*u32' for 'inline else' diff --git a/test/cases/compile_errors/invalid_tag_capture.zig b/test/cases/compile_errors/invalid_tag_capture.zig new file mode 100644 index 0000000000..2cb9135792 --- /dev/null +++ b/test/cases/compile_errors/invalid_tag_capture.zig @@ -0,0 +1,15 @@ +const E = enum { a, b, c, d }; +pub export fn entry() void { + var x: E = .a; + switch (x) { + inline .a, .b => |aorb, d| @compileLog(aorb, d), + inline .c, .d => |*cord| @compileLog(cord), + } +} + +// error +// backend=stage2 +// target=native +// +// :5:33: error: cannot capture tag of non-union type 'tmp.E' +// :1:11: note: enum declared here diff --git a/test/cases/compile_errors/packed_struct_field_alignment_unavailable_for_reify_type.zig b/test/cases/compile_errors/packed_struct_field_alignment_unavailable_for_reify_type.zig new file mode 100644 index 0000000000..8c17e9100b --- /dev/null +++ b/test/cases/compile_errors/packed_struct_field_alignment_unavailable_for_reify_type.zig @@ -0,0 +1,11 @@ +export fn entry() void { + _ = @Type(.{ .Struct = .{ .layout = .Packed, .fields = &.{ + .{ .name = "one", .field_type = u4, .default_value = null, .is_comptime = false, .alignment = 2 }, + }, .decls = &.{}, .is_tuple = false } }); +} + +// error +// backend=stage2 +// target=native +// +// :2:9: error: alignment in a packed struct field must be set to 0 diff --git a/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig b/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig new file mode 100644 index 0000000000..b525aa4db3 --- /dev/null +++ b/test/cases/compile_errors/tag_capture_on_non_inline_prong.zig @@ -0,0 +1,14 @@ +const E = enum { a, b, c, d }; +pub export fn entry() void { + var x: E = .a; + switch (x) { + .a, .b => |aorb, d| @compileLog(aorb, d), + inline .c, .d => |*cord| @compileLog(cord), + } +} + +// error +// backend=stage2 +// target=native +// +// :5:26: error: tag capture on non-inline prong diff --git a/test/standalone.zig b/test/standalone.zig index aa6c0f0a14..d3dedb59e6 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -97,4 +97,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void { // Disabled due to tripping LLVM 13 assertion: // https://github.com/ziglang/zig/issues/12015 //cases.add("tools/update_spirv_features.zig"); + + cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true }); } diff --git a/test/standalone/issue_13030/build.zig b/test/standalone/issue_13030/build.zig new file mode 100644 index 0000000000..8c05e47cf6 --- /dev/null +++ b/test/standalone/issue_13030/build.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Builder = std.build.Builder; +const CrossTarget = std.zig.CrossTarget; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const target = b.standardTargetOptions(.{}); + + const obj = b.addObject("main", "main.zig"); + obj.setBuildMode(mode); + + obj.setTarget(target); + b.default_step.dependOn(&obj.step); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(&obj.step); +} diff --git a/test/standalone/issue_13030/main.zig b/test/standalone/issue_13030/main.zig new file mode 100644 index 0000000000..5e4c976db3 --- /dev/null +++ b/test/standalone/issue_13030/main.zig @@ -0,0 +1,7 @@ +fn b(comptime T: type) ?*const fn () error{}!T { + return null; +} + +export fn entry() void { + _ = b(void); +} |
