authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 19:44:24+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-11 19:44:24+01:00
log76dd39d5316d302de6aa9bcab45ccb3f5a123bcb
tree19eb970a346b362eac55f7e73eb7ee3bfbf90cd7
parentc4345991340af5ff2e0155a9832f4eed9ec677fc
parent01546e68cd0d82ef78498a10649e6bc2937680da

Merge pull request 'frontend: rewrite `switch` logic' (#30776) from justusk/zig:have-you-tried-switching-it-off-and-on-again into master

Resolves: https://codeberg.org/ziglang/zig/issues/30660 Resolves: https://codeberg.org/ziglang/zig/issues/30606 Resolves: https://codeberg.org/ziglang/zig/issues/30157 Resolves: https://codeberg.org/ziglang/zig/issues/30154 Resolves: https://codeberg.org/ziglang/zig/issues/30153 Resolves: https://github.com/ziglang/zig/issues/25644 Resolves: https://github.com/ziglang/zig/issues/25632 Resolves: https://github.com/ziglang/zig/issues/24789 Resolves: https://github.com/ziglang/zig/issues/24152 Resolves: https://github.com/ziglang/zig/issues/24128 Resolves: https://github.com/ziglang/zig/issues/24126 Resolves: https://github.com/ziglang/zig/issues/23973 Resolves: https://github.com/ziglang/zig/issues/23156 Resolves: https://github.com/ziglang/zig/issues/23123 Resolves: https://github.com/ziglang/zig/issues/22138 Resolves: https://github.com/ziglang/zig/issues/21772 Resolves: https://github.com/ziglang/zig/issues/18087 Resolves: https://github.com/ziglang/zig/issues/15237 Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30776 Reviewed-by: Matthew Lugg <mlugg@mlugg.co.uk>

30 files changed, 5197 insertions(+), 5010 deletions(-)

doc/langref.html.in+13-4
...@@ -2461,7 +2461,8 @@ or...@@ -2461,7 +2461,8 @@ or
2461 {#header_open|Tagged union#}2461 {#header_open|Tagged union#}
2462 <p>Unions can be declared with an enum tag type.2462 <p>Unions can be declared with an enum tag type.
2463 This turns the union into a <em>tagged</em> union, which makes it eligible2463 This turns the union into a <em>tagged</em> union, which makes it eligible
2464 to use with {#link|switch#} expressions.2464 to use with {#link|switch#} expressions. When switching on tagged unions,
2465 the tag value can be obtained using an additional capture.
2465 Tagged unions coerce to their tag type: {#link|Type Coercion: Unions and Enums#}.2466 Tagged unions coerce to their tag type: {#link|Type Coercion: Unions and Enums#}.
2466 </p>2467 </p>
2467 {#code|test_tagged_union.zig#}2468 {#code|test_tagged_union.zig#}
...@@ -2594,6 +2595,13 @@ or...@@ -2594,6 +2595,13 @@ or
25942595
2595 {#header_close#}2596 {#header_close#}
25962597
2598 {#header_open|Switching on Errors#}
2599 <p>
2600 When switching on errors, some special cases are allowed to simplify generic programming patterns:
2601 </p>
2602 {#code|test_switch_on_errors.zig#}
2603 {#header_close#}
2604
2597 {#header_open|Labeled switch#}2605 {#header_open|Labeled switch#}
2598 <p>2606 <p>
2599 When a switch statement is labeled, it can be referenced from a2607 When a switch statement is labeled, it can be referenced from a
...@@ -2659,12 +2667,13 @@ or...@@ -2659,12 +2667,13 @@ or
2659 {#code|test_inline_else.zig#}2667 {#code|test_inline_else.zig#}
26602668
2661 <p>2669 <p>
2662 When using an inline prong switching on an union an additional2670 When using an inline prong switching on an union an additional capture
2663 capture can be used to obtain the union's enum tag value.2671 can be used to obtain the union's enum tag value at comptime, even though
2672 its payload might only be known at runtime.
2664 </p>2673 </p>
2665 {#code|test_inline_switch_union_tag.zig#}2674 {#code|test_inline_switch_union_tag.zig#}
26662675
2667 {#see_also|inline while|inline for#}2676 {#see_also|inline while|inline for|Tagged union#}
2668 {#header_close#}2677 {#header_close#}
2669 {#header_close#}2678 {#header_close#}
26702679
doc/langref/test_switch_on_errors.zig created+55
...@@ -0,0 +1,55 @@
1const FileOpenError0 = error{
2 AccessDenied,
3 OutOfMemory,
4 FileNotFound,
5};
6
7fn openFile0() FileOpenError0 {
8 return error.OutOfMemory;
9}
10
11test "unreachable else prong" {
12 switch (openFile0()) {
13 error.AccessDenied, error.FileNotFound => |e| return e,
14 error.OutOfMemory => {},
15 // 'openFile0' cannot return any more errors, so an 'else' prong would be
16 // statically known to be unreachable. Nonetheless, in this case, adding
17 // one does not raise an "unreachable else prong" compile error:
18 else => unreachable,
19 }
20
21 // Allowed unreachable else prongs are:
22 // `else => unreachable,`
23 // `else => return,`
24 // `else => |e| return e,` (where `e` is any identifier)
25}
26
27const FileOpenError1 = error{
28 AccessDenied,
29 SystemResources,
30 FileNotFound,
31};
32
33fn openFile1() FileOpenError1 {
34 return error.SystemResources;
35}
36
37fn openFileGeneric(comptime kind: u1) switch (kind) {
38 0 => FileOpenError0,
39 1 => FileOpenError1,
40} {
41 return switch (kind) {
42 0 => openFile0(),
43 1 => openFile1(),
44 };
45}
46
47test "comptime unreachable errors not in error set" {
48 switch (openFileGeneric(1)) {
49 error.AccessDenied, error.FileNotFound => |e| return e,
50 error.OutOfMemory => comptime unreachable, // not in `FileOpenError1`!
51 error.SystemResources => {},
52 }
53}
54
55// test
doc/langref/test_tagged_union.zig+8
...@@ -18,6 +18,14 @@ test "switch on tagged union" {...@@ -18,6 +18,14 @@ test "switch on tagged union" {
18 .ok => |value| try expect(value == 42),18 .ok => |value| try expect(value == 42),
19 .not_ok => unreachable,19 .not_ok => unreachable,
20 }20 }
21
22 switch (c) {
23 .ok => |_, tag| {
24 // Because we're in the '.ok' prong, 'tag' is compile-time known to be '.ok':
25 comptime std.debug.assert(tag == .ok);
26 },
27 .not_ok => unreachable,
28 }
21}29}
2230
23test "get tag type" {31test "get tag type" {
lib/std/tar/Writer.zig+2-2
...@@ -114,7 +114,7 @@ fn writeHeader(...@@ -114,7 +114,7 @@ fn writeHeader(
114 if (typeflag == .symbolic_link)114 if (typeflag == .symbolic_link)
115 header.setLinkname(link_name) catch |err| switch (err) {115 header.setLinkname(link_name) catch |err| switch (err) {
116 error.NameTooLong => try w.writeExtendedHeader(.gnu_long_link, &.{link_name}),116 error.NameTooLong => try w.writeExtendedHeader(.gnu_long_link, &.{link_name}),
117 else => return err,117 else => |e| return e,
118 };118 };
119 try header.write(w.underlying_writer);119 try header.write(w.underlying_writer);
120}120}
...@@ -131,7 +131,7 @@ fn setPath(w: *Writer, header: *Header, sub_path: []const u8) Error!void {...@@ -131,7 +131,7 @@ fn setPath(w: *Writer, header: *Header, sub_path: []const u8) Error!void {
131 &.{ w.prefix, "/", sub_path };131 &.{ w.prefix, "/", sub_path };
132 try w.writeExtendedHeader(.gnu_long_name, buffers);132 try w.writeExtendedHeader(.gnu_long_name, buffers);
133 },133 },
134 else => return err,134 else => |e| return e,
135 };135 };
136}136}
137137
lib/std/testing.zig+2-2
...@@ -813,7 +813,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe...@@ -813,7 +813,7 @@ fn expectEqualDeepInner(comptime T: type, expected: T, actual: T) error{TestExpe
813 }813 }
814 },814 },
815815
816 .array => |_| {816 .array => {
817 if (expected.len != actual.len) {817 if (expected.len != actual.len) {
818 print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });818 print("Array len not the same, expected {d}, found {d}\n", .{ expected.len, actual.len });
819 return error.TestExpectedEqual;819 return error.TestExpectedEqual;
...@@ -1187,7 +1187,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime...@@ -1187,7 +1187,7 @@ pub fn checkAllAllocationFailures(backing_allocator: std.mem.Allocator, comptime
1187 return error.MemoryLeakDetected;1187 return error.MemoryLeakDetected;
1188 }1188 }
1189 },1189 },
1190 else => return err,1190 else => |e| return e,
1191 }1191 }
1192 }1192 }
1193}1193}
lib/std/zig/AstGen.zig+1105-1124
...@@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {...@@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
115 Zir.Inst.Call.Flags,115 Zir.Inst.Call.Flags,
116 Zir.Inst.BuiltinCall.Flags,116 Zir.Inst.BuiltinCall.Flags,
117 Zir.Inst.SwitchBlock.Bits,117 Zir.Inst.SwitchBlock.Bits,
118 Zir.Inst.SwitchBlockErrUnion.Bits,
119 Zir.Inst.FuncFancy.Bits,118 Zir.Inst.FuncFancy.Bits,
120 Zir.Inst.Param.Type,119 Zir.Inst.Param.Type,
121 Zir.Inst.Func.RetTy,120 Zir.Inst.Func.RetTy,
...@@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
858 no_switch_on_err: {857 no_switch_on_err: {
859 const error_token = if_full.error_token orelse break :no_switch_on_err;858 const error_token = if_full.error_token orelse break :no_switch_on_err;
860 const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err;859 const else_node = if_full.ast.else_expr.unwrap() orelse break :no_switch_on_err;
861 const full_switch = tree.fullSwitch(else_node) orelse break :no_switch_on_err;860 const switch_full = tree.fullSwitch(else_node) orelse break :no_switch_on_err;
862 if (full_switch.label_token != null) break :no_switch_on_err;861 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `ifExpr`
863 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;862 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
864 if (!mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;863 if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
865 return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");864 return switchExpr(gz, scope, ri.br(), node, switch_full, .{ .@"if" = if_full });
866 }865 }
867 return ifExpr(gz, scope, ri.br(), node, if_full);866 return ifExpr(gz, scope, ri.br(), node, if_full);
868 },867 },
...@@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1024,11 +1023,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1024 null;1023 null;
1025 no_switch_on_err: {1024 no_switch_on_err: {
1026 const capture_token = payload_token orelse break :no_switch_on_err;1025 const capture_token = payload_token orelse break :no_switch_on_err;
1027 const full_switch = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err;1026 const switch_full = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err;
1028 if (full_switch.label_token != null) break :no_switch_on_err;1027 if (switch_full.label_token != null) break :no_switch_on_err; // handled in `orelseCatchExpr`
1029 if (tree.nodeTag(full_switch.ast.condition) != .identifier) break :no_switch_on_err;1028 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
1030 if (!mem.eql(u8, tree.tokenSlice(capture_token), tree.tokenSlice(tree.nodeMainToken(full_switch.ast.condition)))) break :no_switch_on_err;1029 if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
1031 return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");1030 return switchExpr(gz, scope, ri.br(), node, switch_full, .@"catch");
1032 }1031 }
1033 switch (ri.rl) {1032 switch (ri.rl) {
1034 .ref, .ref_coerced_ty => return orelseCatchExpr(1033 .ref, .ref_coerced_ty => return orelseCatchExpr(
...@@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE...@@ -1108,7 +1107,7 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
1108 .error_set_decl => return errorSetDecl(gz, ri, node),1107 .error_set_decl => return errorSetDecl(gz, ri, node),
1109 .array_access => return arrayAccess(gz, scope, ri, node),1108 .array_access => return arrayAccess(gz, scope, ri, node),
1110 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),1109 .@"comptime" => return comptimeExprAst(gz, scope, ri, node),
1111 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?),1110 .@"switch", .switch_comma => return switchExpr(gz, scope, ri.br(), node, tree.fullSwitch(node).?, .none),
11121111
1113 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),1112 .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node),
1114 .@"suspend" => return suspendExpr(gz, scope, node),1113 .@"suspend" => return suspendExpr(gz, scope, node),
...@@ -2161,93 +2160,90 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn...@@ -2161,93 +2160,90 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index) Inn
2161 const opt_break_label, const opt_rhs = tree.nodeData(node).opt_token_and_opt_node;2160 const opt_break_label, const opt_rhs = tree.nodeData(node).opt_token_and_opt_node;
21622161
2163 // Look for the label in the scope.2162 // Look for the label in the scope.
2164 var scope = parent_scope;2163 find_scope: switch (parent_scope.unwrap()) {
2165 while (true) {2164 .gen_zir => |gen_zir| {
2166 switch (scope.tag) {2165 const scope = &gen_zir.base;
2167 .gen_zir => {
2168 const block_gz = scope.cast(GenZir).?;
21692166
2170 if (block_gz.cur_defer_node.unwrap()) |cur_defer_node| {2167 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2171 // We are breaking out of a `defer` block.2168 // We are breaking out of a `defer` block.
2172 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{2169 return astgen.failNodeNotes(node, "cannot break out of defer expression", .{}, &.{
2173 try astgen.errNoteNode(2170 try astgen.errNoteNode(
2174 cur_defer_node,2171 cur_defer_node,
2175 "defer expression here",2172 "defer expression here",
2176 .{},2173 .{},
2177 ),2174 ),
2178 });2175 });
2179 }2176 }
21802177
2181 const block_inst = blk: {2178 if (opt_break_label.unwrap()) |break_label| labeled: {
2182 if (opt_break_label.unwrap()) |break_label| {2179 if (gen_zir.label) |*label| {
2183 if (block_gz.label) |*label| {2180 if (try astgen.tokenIdentEql(label.token, break_label)) {
2184 if (try astgen.tokenIdentEql(label.token, break_label)) {2181 label.used = true;
2185 label.used = true;2182 break :labeled;
2186 break :blk label.block_inst;
2187 }
2188 }
2189 } else if (block_gz.break_block.unwrap()) |i| {
2190 break :blk i;
2191 }2183 }
2192 // If not the target, start over with the parent2184 }
2193 scope = block_gz.parent;2185 // gz without or with different label, continue to parent scopes.
2194 continue;2186 continue :find_scope gen_zir.parent.unwrap();
2195 };2187 } else if (!gen_zir.allow_unlabeled_control_flow) {
2196 // If we made it here, this block is the target of the break expr2188 // This `break` is unlabeled and the gz we've found doesn't allow
21972189 // unlabeled control flow. Continue to parent scopes.
2198 const break_tag: Zir.Inst.Tag = if (block_gz.is_inline)2190 continue :find_scope gen_zir.parent.unwrap();
2199 .break_inline2191 }
2200 else
2201 .@"break";
2202
2203 const rhs = opt_rhs.unwrap() orelse {
2204 _ = try rvalue(parent_gz, block_gz.break_result_info, .void_value, node);
2205
2206 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2207
2208 // As our last action before the break, "pop" the error trace if needed
2209 if (!block_gz.is_comptime)
2210 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block_inst }, .always, node);
22112192
2212 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2193 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2213 return Zir.Inst.Ref.unreachable_value;2194 .break_inline
2214 };2195 else
2196 .@"break";
22152197
2216 const operand = try reachableExpr(parent_gz, parent_scope, block_gz.break_result_info, rhs, node);2198 if (opt_rhs.unwrap()) |rhs| {
2199 // We have a `break` operand.
2200 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.break_result_info, rhs, node);
22172201
2218 try genDefers(parent_gz, scope, parent_scope, .normal_only);2202 try genDefers(parent_gz, scope, parent_scope, .normal_only);
22192203
2220 // As our last action before the break, "pop" the error trace if needed2204 // As our last action before the break, "pop" the error trace if needed
2221 if (!block_gz.is_comptime)2205 if (!gen_zir.is_comptime) {
2222 try restoreErrRetIndex(parent_gz, .{ .block = block_inst }, block_gz.break_result_info, rhs, operand);2206 try restoreErrRetIndex(parent_gz, .{ .block = gen_zir.break_target }, gen_zir.break_result_info, rhs, operand);
22232207 }
2224 switch (block_gz.break_result_info.rl) {2208 switch (gen_zir.break_result_info.rl) {
2225 .ptr => {2209 .ptr => {
2226 // In this case we don't have any mechanism to intercept it;2210 // In this case we don't have any mechanism to intercept it;
2227 // we assume the result location is written, and we break with void.2211 // we assume the result location is written, and we break with void.
2228 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2212 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2229 },2213 },
2230 .discard => {2214 .discard => {
2231 _ = try parent_gz.addBreak(break_tag, block_inst, .void_value);2215 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2232 },2216 },
2233 else => {2217 else => {
2234 _ = try parent_gz.addBreakWithSrcNode(break_tag, block_inst, operand, rhs);2218 _ = try parent_gz.addBreakWithSrcNode(break_tag, gen_zir.break_target, operand, rhs);
2235 },2219 },
2236 }2220 }
2237 return Zir.Inst.Ref.unreachable_value;2221 return .unreachable_value;
2238 },2222 } else {
2239 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2223 _ = try rvalue(parent_gz, gen_zir.break_result_info, .void_value, node);
2240 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2224
2241 .namespace => break,2225 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2242 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,2226
2243 .top => unreachable,2227 // As our last action before the break, "pop" the error trace if needed
2244 }2228 if (!gen_zir.is_comptime)
2245 }2229 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = gen_zir.break_target }, .always, node);
2246 if (opt_break_label.unwrap()) |break_label| {2230
2247 const label_name = try astgen.identifierTokenString(break_label);2231 _ = try parent_gz.addBreak(break_tag, gen_zir.break_target, .void_value);
2248 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});2232 return .unreachable_value;
2249 } else {2233 }
2250 return astgen.failNode(node, "break expression outside loop", .{});2234 },
2235 .local_val => |local_val| continue :find_scope local_val.parent.unwrap(),
2236 .local_ptr => |local_ptr| continue :find_scope local_ptr.parent.unwrap(),
2237 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
2238 .namespace => {
2239 if (opt_break_label.unwrap()) |break_label| {
2240 const label_name = try astgen.identifierTokenString(break_label);
2241 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2242 } else {
2243 return astgen.failNode(node, "break expression outside loop", .{});
2244 }
2245 },
2246 .top => unreachable,
2251 }2247 }
2252}2248}
22532249
...@@ -2261,101 +2257,104 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)...@@ -2261,101 +2257,104 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: Ast.Node.Index)
2261 }2257 }
22622258
2263 // Look for the label in the scope.2259 // Look for the label in the scope.
2264 var scope = parent_scope;2260 find_scope: switch (parent_scope.unwrap()) {
2265 while (true) {2261 .gen_zir => |gen_zir| {
2266 switch (scope.tag) {2262 const scope = &gen_zir.base;
2267 .gen_zir => {
2268 const gen_zir = scope.cast(GenZir).?;
22692263
2270 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {2264 if (gen_zir.cur_defer_node.unwrap()) |cur_defer_node| {
2271 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{2265 return astgen.failNodeNotes(node, "cannot continue out of defer expression", .{}, &.{
2272 try astgen.errNoteNode(2266 try astgen.errNoteNode(
2273 cur_defer_node,2267 cur_defer_node,
2274 "defer expression here",2268 "defer expression here",
2275 .{},2269 .{},
2276 ),2270 ),
2277 });2271 });
2278 }2272 }
2279 const continue_block = gen_zir.continue_block.unwrap() orelse {2273
2280 scope = gen_zir.parent;2274 if (opt_break_label.unwrap()) |break_label| labeled: {
2281 continue;2275 if (gen_zir.label) |*label| {
2282 };2276 if (try astgen.tokenIdentEql(label.token, break_label)) {
2283 if (opt_break_label.unwrap()) |break_label| blk: {2277 switch (gen_zir.continue_target) {
2284 if (gen_zir.label) |*label| {2278 .none => {
2285 if (try astgen.tokenIdentEql(label.token, break_label)) {2279 return astgen.failNode(node, "continue outside of loop or labeled switch expression", .{});
2286 const maybe_switch_tag = astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)];2280 },
2287 if (opt_rhs != .none) switch (maybe_switch_tag) {2281 .@"break" => if (opt_rhs != .none) {
2288 .switch_block, .switch_block_ref => {},2282 return astgen.failNode(node, "cannot continue loop with operand", .{});
2289 else => return astgen.failNode(node, "cannot continue loop with operand", .{}),2283 },
2290 } else switch (maybe_switch_tag) {2284 .switch_continue => if (opt_rhs == .none) {
2291 .switch_block, .switch_block_ref => return astgen.failNode(node, "cannot continue switch without operand", .{}),2285 return astgen.failNode(node, "cannot continue switch without operand", .{});
2292 else => {},2286 },
2293 }
2294
2295 label.used = true;
2296 label.used_for_continue = true;
2297 break :blk;
2298 }2287 }
2299 }2288 label.used = true;
2300 // found continue but either it has a different label, or no label2289 label.used_for_continue = true;
2301 scope = gen_zir.parent;2290 break :labeled;
2302 continue;
2303 } else if (gen_zir.label) |label| {
2304 // This `continue` is unlabeled. If the gz we've found corresponds to a labeled
2305 // `switch`, ignore it and continue to parent scopes.
2306 switch (astgen.instructions.items(.tag)[@intFromEnum(label.block_inst)]) {
2307 .switch_block, .switch_block_ref => {
2308 scope = gen_zir.parent;
2309 continue;
2310 },
2311 else => {},
2312 }2291 }
2313 }2292 }
2293 // gz without or with different label, continue to parent scopes.
2294 continue :find_scope gen_zir.parent.unwrap();
2295 } else if (gen_zir.allow_unlabeled_control_flow) {
2296 // This `continue` is unlabeled. If the gz we've found doesn't
2297 // provide a `continue` target or corresponds to a labeled
2298 // `switch`, ignore it and continue to parent scopes.
2299 switch (gen_zir.continue_target) {
2300 .none, .switch_continue => {
2301 continue :find_scope gen_zir.parent.unwrap();
2302 },
2303 .@"break" => {},
2304 }
2305 } else {
2306 // We don't have a break label and the gz we found doesn't allow
2307 // unlabeled control flow, so we continue to its parent scopes.
2308 continue :find_scope gen_zir.parent.unwrap();
2309 }
23142310
2315 if (opt_rhs.unwrap()) |rhs| {2311 switch (gen_zir.continue_target) {
2316 // We need to figure out the result info to use.2312 .none => unreachable, // should have failed or continued to parent scopes by now
2317 // The type should match2313 .@"break" => |block| {
2318 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
2319
2320 try genDefers(parent_gz, scope, parent_scope, .normal_only);2314 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23212315
2322 // As our last action before the continue, "pop" the error trace if needed2316 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)
2323 if (!gen_zir.is_comptime)2317 .break_inline
2324 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);2318 else
23252319 .@"break";
2326 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, continue_block, operand, rhs);2320 if (break_tag == .break_inline) {
2327 return Zir.Inst.Ref.unreachable_value;2321 _ = try parent_gz.addUnNode(.check_comptime_control_flow, block.toRef(), node);
2328 }2322 }
2329
2330 try genDefers(parent_gz, scope, parent_scope, .normal_only);
23312323
2332 const break_tag: Zir.Inst.Tag = if (gen_zir.is_inline)2324 // As our last action before the continue, "pop" the error trace if needed
2333 .break_inline2325 if (!gen_zir.is_comptime) {
2334 else2326 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = block }, .always, node);
2335 .@"break";2327 }
2336 if (break_tag == .break_inline) {2328 _ = try parent_gz.addBreak(break_tag, block, .void_value);
2337 _ = try parent_gz.addUnNode(.check_comptime_control_flow, continue_block.toRef(), node);2329 return .unreachable_value;
2338 }2330 },
2331 .switch_continue => |switch_block| {
2332 const rhs = opt_rhs.unwrap().?; // checked above
2333 const operand = try reachableExpr(parent_gz, parent_scope, gen_zir.continue_result_info, rhs, node);
23392334
2340 // As our last action before the continue, "pop" the error trace if needed2335 try genDefers(parent_gz, scope, parent_scope, .normal_only);
2341 if (!gen_zir.is_comptime)
2342 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = continue_block }, .always, node);
23432336
2344 _ = try parent_gz.addBreak(break_tag, continue_block, .void_value);2337 // As our last action before the continue, "pop" the error trace if needed
2345 return Zir.Inst.Ref.unreachable_value;2338 if (!gen_zir.is_comptime) {
2346 },2339 _ = try parent_gz.addRestoreErrRetIndex(.{ .block = switch_block }, .always, node);
2347 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2340 }
2348 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2341 _ = try parent_gz.addBreakWithSrcNode(.switch_continue, switch_block, operand, rhs);
2349 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,2342 return .unreachable_value;
2350 .namespace => break,2343 },
2351 .top => unreachable,2344 }
2352 }2345 },
2353 }2346 .local_val => |local_val| continue :find_scope local_val.parent.unwrap(),
2354 if (opt_break_label.unwrap()) |break_label| {2347 .local_ptr => |local_ptr| continue :find_scope local_ptr.parent.unwrap(),
2355 const label_name = try astgen.identifierTokenString(break_label);2348 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
2356 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});2349 .namespace => {
2357 } else {2350 if (opt_break_label.unwrap()) |break_label| {
2358 return astgen.failNode(node, "continue expression outside loop", .{});2351 const label_name = try astgen.identifierTokenString(break_label);
2352 return astgen.failTok(break_label, "label not found: '{s}'", .{label_name});
2353 } else {
2354 return astgen.failNode(node, "continue expression outside loop", .{});
2355 }
2356 },
2357 .top => unreachable,
2359 }2358 }
2360}2359}
23612360
...@@ -2441,33 +2440,29 @@ fn blockExpr(...@@ -2441,33 +2440,29 @@ fn blockExpr(
24412440
2442fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.TokenIndex) !void {2441fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.TokenIndex) !void {
2443 // Look for the label in the scope.2442 // Look for the label in the scope.
2444 var scope = parent_scope;2443 find_scope: switch (parent_scope.unwrap()) {
2445 while (true) {2444 .gen_zir => |gen_zir| {
2446 switch (scope.tag) {2445 if (gen_zir.label) |prev_label| {
2447 .gen_zir => {2446 if (try astgen.tokenIdentEql(label, prev_label.token)) {
2448 const gen_zir = scope.cast(GenZir).?;2447 const label_name = try astgen.identifierTokenString(label);
2449 if (gen_zir.label) |prev_label| {2448 return astgen.failTokNotes(label, "redefinition of label '{s}'", .{
2450 if (try astgen.tokenIdentEql(label, prev_label.token)) {2449 label_name,
2451 const label_name = try astgen.identifierTokenString(label);2450 }, &[_]u32{
2452 return astgen.failTokNotes(label, "redefinition of label '{s}'", .{2451 try astgen.errNoteTok(
2453 label_name,2452 prev_label.token,
2454 }, &[_]u32{2453 "previous definition here",
2455 try astgen.errNoteTok(2454 .{},
2456 prev_label.token,2455 ),
2457 "previous definition here",2456 });
2458 .{},
2459 ),
2460 });
2461 }
2462 }2457 }
2463 scope = gen_zir.parent;2458 }
2464 },2459 continue :find_scope gen_zir.parent.unwrap();
2465 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2460 },
2466 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2461 .local_val => |local_val| continue :find_scope local_val.parent.unwrap(),
2467 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,2462 .local_ptr => |local_ptr| continue :find_scope local_ptr.parent.unwrap(),
2468 .namespace => break,2463 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
2469 .top => unreachable,2464 .namespace => break :find_scope,
2470 }2465 .top => unreachable,
2471 }2466 }
2472}2467}
24732468
...@@ -2509,10 +2504,9 @@ fn labeledBlockExpr(...@@ -2509,10 +2504,9 @@ fn labeledBlockExpr(
2509 try gz.instructions.append(astgen.gpa, block_inst);2504 try gz.instructions.append(astgen.gpa, block_inst);
2510 var block_scope = gz.makeSubBlock(parent_scope);2505 var block_scope = gz.makeSubBlock(parent_scope);
2511 block_scope.is_inline = force_comptime;2506 block_scope.is_inline = force_comptime;
2512 block_scope.label = GenZir.Label{2507 block_scope.label = .{ .token = label_token };
2513 .token = label_token,2508 block_scope.break_target = block_inst;
2514 .block_inst = block_inst,2509 block_scope.continue_target = .none;
2515 };
2516 block_scope.setBreakResultInfo(block_ri);2510 block_scope.setBreakResultInfo(block_ri);
2517 if (force_comptime) block_scope.is_comptime = true;2511 if (force_comptime) block_scope.is_comptime = true;
2518 defer block_scope.unstack();2512 defer block_scope.unstack();
...@@ -2983,18 +2977,16 @@ fn countDefers(outer_scope: *Scope, inner_scope: *Scope) struct {...@@ -2983,18 +2977,16 @@ fn countDefers(outer_scope: *Scope, inner_scope: *Scope) struct {
2983 var need_err_code = false;2977 var need_err_code = false;
2984 var scope = inner_scope;2978 var scope = inner_scope;
2985 while (scope != outer_scope) {2979 while (scope != outer_scope) {
2986 switch (scope.tag) {2980 switch (scope.unwrap()) {
2987 .gen_zir => scope = scope.cast(GenZir).?.parent,2981 .gen_zir => |gen_zir| scope = gen_zir.parent,
2988 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,2982 .local_val => |local_val| scope = local_val.parent,
2989 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,2983 .local_ptr => |local_ptr| scope = local_ptr.parent,
2990 .defer_normal => {2984 .defer_normal => |defer_scope| {
2991 const defer_scope = scope.cast(Scope.Defer).?;
2992 scope = defer_scope.parent;2985 scope = defer_scope.parent;
29932986
2994 have_normal = true;2987 have_normal = true;
2995 },2988 },
2996 .defer_error => {2989 .defer_error => |defer_scope| {
2997 const defer_scope = scope.cast(Scope.Defer).?;
2998 scope = defer_scope.parent;2990 scope = defer_scope.parent;
29992991
3000 have_err = true;2992 have_err = true;
...@@ -3030,17 +3022,15 @@ fn genDefers(...@@ -3030,17 +3022,15 @@ fn genDefers(
30303022
3031 var scope = inner_scope;3023 var scope = inner_scope;
3032 while (scope != outer_scope) {3024 while (scope != outer_scope) {
3033 switch (scope.tag) {3025 switch (scope.unwrap()) {
3034 .gen_zir => scope = scope.cast(GenZir).?.parent,3026 .gen_zir => |gen_zir| scope = gen_zir.parent,
3035 .local_val => scope = scope.cast(Scope.LocalVal).?.parent,3027 .local_val => |local_val| scope = local_val.parent,
3036 .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent,3028 .local_ptr => |local_ptr| scope = local_ptr.parent,
3037 .defer_normal => {3029 .defer_normal => |defer_scope| {
3038 const defer_scope = scope.cast(Scope.Defer).?;
3039 scope = defer_scope.parent;3030 scope = defer_scope.parent;
3040 try gz.addDefer(defer_scope.index, defer_scope.len);3031 try gz.addDefer(defer_scope.index, defer_scope.len);
3041 },3032 },
3042 .defer_error => {3033 .defer_error => |defer_scope| {
3043 const defer_scope = scope.cast(Scope.Defer).?;
3044 scope = defer_scope.parent;3034 scope = defer_scope.parent;
3045 switch (which_ones) {3035 switch (which_ones) {
3046 .both_sans_err => {3036 .both_sans_err => {
...@@ -3083,10 +3073,9 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v...@@ -3083,10 +3073,9 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v
30833073
3084 var scope = inner_scope;3074 var scope = inner_scope;
3085 while (scope != outer_scope) {3075 while (scope != outer_scope) {
3086 switch (scope.tag) {3076 switch (scope.unwrap()) {
3087 .gen_zir => scope = scope.cast(GenZir).?.parent,3077 .gen_zir => |gen_zir| scope = gen_zir.parent,
3088 .local_val => {3078 .local_val => |s| {
3089 const s = scope.cast(Scope.LocalVal).?;
3090 if (s.used == .none and s.discarded == .none) {3079 if (s.used == .none and s.discarded == .none) {
3091 try astgen.appendErrorTok(s.token_src, "unused {s}", .{@tagName(s.id_cat)});3080 try astgen.appendErrorTok(s.token_src, "unused {s}", .{@tagName(s.id_cat)});
3092 } else if (s.used != .none and s.discarded != .none) {3081 } else if (s.used != .none and s.discarded != .none) {
...@@ -3096,8 +3085,7 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v...@@ -3096,8 +3085,7 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v
3096 }3085 }
3097 scope = s.parent;3086 scope = s.parent;
3098 },3087 },
3099 .local_ptr => {3088 .local_ptr => |s| {
3100 const s = scope.cast(Scope.LocalPtr).?;
3101 if (s.used == .none and s.discarded == .none) {3089 if (s.used == .none and s.discarded == .none) {
3102 try astgen.appendErrorTok(s.token_src, "unused {s}", .{@tagName(s.id_cat)});3090 try astgen.appendErrorTok(s.token_src, "unused {s}", .{@tagName(s.id_cat)});
3103 } else {3091 } else {
...@@ -3112,10 +3100,9 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v...@@ -3112,10 +3100,9 @@ fn checkUsed(gz: *GenZir, outer_scope: *Scope, inner_scope: *Scope) InnerError!v
3112 });3100 });
3113 }3101 }
3114 }3102 }
3115
3116 scope = s.parent;3103 scope = s.parent;
3117 },3104 },
3118 .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent,3105 .defer_normal, .defer_error => |defer_scope| scope = defer_scope.parent,
3119 .namespace => unreachable,3106 .namespace => unreachable,
3120 .top => unreachable,3107 .top => unreachable,
3121 }3108 }
...@@ -3146,14 +3133,7 @@ fn deferStmt(...@@ -3146,14 +3133,7 @@ fn deferStmt(
3146 }3133 }
3147 const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);3134 const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len);
3148 opt_remapped_err_code = remapped_err_code.toOptional();3135 opt_remapped_err_code = remapped_err_code.toOptional();
3149 try gz.astgen.instructions.append(gz.astgen.gpa, .{3136 _ = try gz.astgen.appendPlaceholder();
3150 .tag = .extended,
3151 .data = .{ .extended = .{
3152 .opcode = .value_placeholder,
3153 .small = undefined,
3154 .operand = undefined,
3155 } },
3156 });
3157 const remapped_err_code_ref = remapped_err_code.toRef();3137 const remapped_err_code_ref = remapped_err_code.toRef();
3158 local_val_scope = .{3138 local_val_scope = .{
3159 .parent = &defer_gen.base,3139 .parent = &defer_gen.base,
...@@ -4781,13 +4761,11 @@ fn testDecl(...@@ -4781,13 +4761,11 @@ fn testDecl(
47814761
4782 // Local variables, including function parameters.4762 // Local variables, including function parameters.
4783 const name_str_index = try astgen.identAsString(test_name_token);4763 const name_str_index = try astgen.identAsString(test_name_token);
4784 var s = scope;
4785 var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already4764 var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
4786 var num_namespaces_out: u32 = 0;4765 var num_namespaces_out: u32 = 0;
4787 var capturing_namespace: ?*Scope.Namespace = null;4766 var capturing_namespace: ?*Scope.Namespace = null;
4788 while (true) switch (s.tag) {4767 find_scope: switch (scope.unwrap()) {
4789 .local_val => {4768 .local_val => |local_val| {
4790 const local_val = s.cast(Scope.LocalVal).?;
4791 if (local_val.name == name_str_index) {4769 if (local_val.name == name_str_index) {
4792 local_val.used = .fromToken(test_name_token);4770 local_val.used = .fromToken(test_name_token);
4793 return astgen.failTokNotes(test_name_token, "cannot test a {s}", .{4771 return astgen.failTokNotes(test_name_token, "cannot test a {s}", .{
...@@ -4798,10 +4776,9 @@ fn testDecl(...@@ -4798,10 +4776,9 @@ fn testDecl(
4798 }),4776 }),
4799 });4777 });
4800 }4778 }
4801 s = local_val.parent;4779 continue :find_scope local_val.parent.unwrap();
4802 },4780 },
4803 .local_ptr => {4781 .local_ptr => |local_ptr| {
4804 const local_ptr = s.cast(Scope.LocalPtr).?;
4805 if (local_ptr.name == name_str_index) {4782 if (local_ptr.name == name_str_index) {
4806 local_ptr.used = .fromToken(test_name_token);4783 local_ptr.used = .fromToken(test_name_token);
4807 return astgen.failTokNotes(test_name_token, "cannot test a {s}", .{4784 return astgen.failTokNotes(test_name_token, "cannot test a {s}", .{
...@@ -4812,12 +4789,11 @@ fn testDecl(...@@ -4812,12 +4789,11 @@ fn testDecl(
4812 }),4789 }),
4813 });4790 });
4814 }4791 }
4815 s = local_ptr.parent;4792 continue :find_scope local_ptr.parent.unwrap();
4816 },4793 },
4817 .gen_zir => s = s.cast(GenZir).?.parent,4794 .gen_zir => |gen_zir| continue :find_scope gen_zir.parent.unwrap(),
4818 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,4795 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
4819 .namespace => {4796 .namespace => |ns| {
4820 const ns = s.cast(Scope.Namespace).?;
4821 if (ns.decls.get(name_str_index)) |i| {4797 if (ns.decls.get(name_str_index)) |i| {
4822 if (found_already) |f| {4798 if (found_already) |f| {
4823 return astgen.failTokNotes(test_name_token, "ambiguous reference", .{}, &.{4799 return astgen.failTokNotes(test_name_token, "ambiguous reference", .{}, &.{
...@@ -4830,10 +4806,10 @@ fn testDecl(...@@ -4830,10 +4806,10 @@ fn testDecl(
4830 }4806 }
4831 num_namespaces_out += 1;4807 num_namespaces_out += 1;
4832 capturing_namespace = ns;4808 capturing_namespace = ns;
4833 s = ns.parent;4809 continue :find_scope ns.parent.unwrap();
4834 },4810 },
4835 .top => break,4811 .top => break :find_scope,
4836 };4812 }
4837 if (found_already == null) {4813 if (found_already == null) {
4838 const ident_name = try astgen.identifierTokenString(test_name_token);4814 const ident_name = try astgen.identifierTokenString(test_name_token);
4839 return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name});4815 return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name});
...@@ -6131,7 +6107,30 @@ fn orelseCatchExpr(...@@ -6131,7 +6107,30 @@ fn orelseCatchExpr(
6131 break :blk &err_val_scope.base;6107 break :blk &err_val_scope.base;
6132 };6108 };
61336109
6134 const else_result = try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint);6110 const else_result = else_result: {
6111 if (tree.fullSwitch(rhs)) |switch_full| no_switch_on_err: {
6112 if (tree.nodeTag(node) != .@"catch") break :no_switch_on_err;
6113 const catch_token = tree.nodeMainToken(node);
6114 const capture_token = if (tree.tokenTag(catch_token + 1) == .pipe) token: {
6115 break :token catch_token + 2;
6116 } else break :no_switch_on_err;
6117 if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"`
6118 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
6119 if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
6120 break :else_result try switchExpr(
6121 &else_scope,
6122 else_sub_scope,
6123 block_scope.break_result_info,
6124 rhs,
6125 switch_full,
6126 .{ .peer_break_target = .{
6127 .block_inst = block,
6128 .block_ri = block_ri,
6129 } },
6130 );
6131 }
6132 break :else_result try fullBodyExpr(&else_scope, else_sub_scope, block_scope.break_result_info, rhs, .allow_branch_hint);
6133 };
6135 if (!else_scope.endsWithNoReturn()) {6134 if (!else_scope.endsWithNoReturn()) {
6136 // As our last action before the break, "pop" the error trace if needed6135 // As our last action before the break, "pop" the error trace if needed
6137 if (do_err_trace)6136 if (do_err_trace)
...@@ -6484,7 +6483,26 @@ fn ifExpr(...@@ -6484,7 +6483,26 @@ fn ifExpr(
6484 break :s &else_scope.base;6483 break :s &else_scope.base;
6485 }6484 }
6486 };6485 };
6487 const else_result = try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint);6486 const else_result = else_result: {
6487 if (tree.fullSwitch(else_node)) |switch_full| no_switch_on_err: {
6488 const error_token = if_full.error_token orelse break :no_switch_on_err;
6489 if (switch_full.label_token == null) break :no_switch_on_err; // must use `switchExpr` with `non_err = .@"if"`
6490 if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err;
6491 if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err;
6492 break :else_result try switchExpr(
6493 &else_scope,
6494 sub_scope,
6495 block_scope.break_result_info,
6496 else_node,
6497 switch_full,
6498 .{ .peer_break_target = .{
6499 .block_inst = block,
6500 .block_ri = block_ri,
6501 } },
6502 );
6503 }
6504 break :else_result try fullBodyExpr(&else_scope, sub_scope, block_scope.break_result_info, else_node, .allow_branch_hint);
6505 };
6488 if (!else_scope.endsWithNoReturn()) {6506 if (!else_scope.endsWithNoReturn()) {
6489 // As our last action before the break, "pop" the error trace if needed6507 // As our last action before the break, "pop" the error trace if needed
6490 if (do_err_trace)6508 if (do_err_trace)
...@@ -6574,7 +6592,6 @@ fn whileExpr(...@@ -6574,7 +6592,6 @@ fn whileExpr(
65746592
6575 var loop_scope = parent_gz.makeSubBlock(scope);6593 var loop_scope = parent_gz.makeSubBlock(scope);
6576 loop_scope.is_inline = is_inline;6594 loop_scope.is_inline = is_inline;
6577 loop_scope.setBreakResultInfo(block_ri);
6578 defer loop_scope.unstack();6595 defer loop_scope.unstack();
65796596
6580 var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);6597 var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
...@@ -6707,14 +6724,13 @@ fn whileExpr(...@@ -6707,14 +6724,13 @@ fn whileExpr(
6707 _ = try loop_scope.addNode(repeat_tag, node);6724 _ = try loop_scope.addNode(repeat_tag, node);
67086725
6709 try loop_scope.setBlockBody(loop_block);6726 try loop_scope.setBlockBody(loop_block);
6710 loop_scope.break_block = loop_block.toOptional();
6711 loop_scope.continue_block = continue_block.toOptional();
6712 if (while_full.label_token) |label_token| {6727 if (while_full.label_token) |label_token| {
6713 loop_scope.label = .{6728 loop_scope.label = .{ .token = label_token };
6714 .token = label_token,
6715 .block_inst = loop_block,
6716 };
6717 }6729 }
6730 loop_scope.allow_unlabeled_control_flow = true;
6731 loop_scope.break_target = loop_block;
6732 loop_scope.continue_target = .{ .@"break" = continue_block };
6733 loop_scope.setBreakResultInfo(block_ri);
67186734
6719 // done adding instructions to loop_scope, can now stack then_scope6735 // done adding instructions to loop_scope, can now stack then_scope
6720 then_scope.instructions_top = then_scope.instructions.items.len;6736 then_scope.instructions_top = then_scope.instructions.items.len;
...@@ -6787,10 +6803,11 @@ fn whileExpr(...@@ -6787,10 +6803,11 @@ fn whileExpr(
6787 break :s &else_scope.base;6803 break :s &else_scope.base;
6788 }6804 }
6789 };6805 };
6790 // Remove the continue block and break block so that `continue` and `break`6806 // Disallow unlabeled control flow to this scope so that bare `continue`
6791 // control flow apply to outer loops; not this one.6807 // and `break` control flow apply to outer loops; not this one.
6792 loop_scope.continue_block = .none;6808 // Also disallow `continue` targeting the loop label.
6793 loop_scope.break_block = .none;6809 loop_scope.allow_unlabeled_control_flow = false;
6810 loop_scope.continue_target = .none;
6794 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);6811 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
6795 if (is_statement) {6812 if (is_statement) {
6796 _ = try addEnsureResult(&else_scope, else_result, else_node);6813 _ = try addEnsureResult(&else_scope, else_result, else_node);
...@@ -6979,14 +6996,12 @@ fn forExpr(...@@ -6979,14 +6996,12 @@ fn forExpr(
6979 const cond_block = try loop_scope.makeBlockInst(block_tag, node);6996 const cond_block = try loop_scope.makeBlockInst(block_tag, node);
6980 try cond_scope.setBlockBody(cond_block);6997 try cond_scope.setBlockBody(cond_block);
69816998
6982 loop_scope.break_block = loop_block.toOptional();
6983 loop_scope.continue_block = cond_block.toOptional();
6984 if (for_full.label_token) |label_token| {6999 if (for_full.label_token) |label_token| {
6985 loop_scope.label = .{7000 loop_scope.label = .{ .token = label_token };
6986 .token = label_token,
6987 .block_inst = loop_block,
6988 };
6989 }7001 }
7002 loop_scope.allow_unlabeled_control_flow = true;
7003 loop_scope.break_target = loop_block;
7004 loop_scope.continue_target = .{ .@"break" = cond_block };
69907005
6991 const then_node = for_full.ast.then_expr;7006 const then_node = for_full.ast.then_expr;
6992 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);7007 var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
...@@ -7077,10 +7092,11 @@ fn forExpr(...@@ -7077,10 +7092,11 @@ fn forExpr(
70777092
7078 if (for_full.ast.else_expr.unwrap()) |else_node| {7093 if (for_full.ast.else_expr.unwrap()) |else_node| {
7079 const sub_scope = &else_scope.base;7094 const sub_scope = &else_scope.base;
7080 // Remove the continue block and break block so that `continue` and `break`7095 // Disallow unlabeled control flow to this scope so that bare `continue`
7081 // control flow apply to outer loops; not this one.7096 // and `break` control flow apply to outer loops; not this one.
7082 loop_scope.continue_block = .none;7097 // Also disallow `continue` targeting the loop label.
7083 loop_scope.break_block = .none;7098 loop_scope.allow_unlabeled_control_flow = false;
7099 loop_scope.continue_target = .none;
7084 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);7100 const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
7085 if (is_statement) {7101 if (is_statement) {
7086 _ = try addEnsureResult(&else_scope, else_result, else_node);7102 _ = try addEnsureResult(&else_scope, else_result, else_node);
...@@ -7133,503 +7149,34 @@ fn forExpr(...@@ -7133,503 +7149,34 @@ fn forExpr(
7133 return result;7149 return result;
7134}7150}
71357151
7136fn switchExprErrUnion(7152const SwitchNonErr = union(enum) {
7137 parent_gz: *GenZir,7153 /// A regular switch expression.
7138 scope: *Scope,7154 /// Emits `switch_block[_ref]`.
7139 ri: ResultInfo,7155 none,
7140 catch_or_if_node: Ast.Node.Index,7156 /// `eu catch |err| switch (err) { ... }`
7141 node_ty: enum { @"catch", @"if" },7157 ///
7142) InnerError!Zir.Inst.Ref {7158 /// `switch` must not be labeled.
7143 const astgen = parent_gz.astgen;7159 /// Emits `switch_block_err_union`.
7144 const gpa = astgen.gpa;7160 @"catch",
7145 const tree = astgen.tree;7161 /// `if (eu) |payload| { ... } else |err| switch (err) { ... }`
71467162 ///
7147 const if_full = switch (node_ty) {7163 /// `switch` must not be labeled.
7148 .@"catch" => undefined,7164 /// Emits `switch_block_err_union`.
7149 .@"if" => tree.fullIf(catch_or_if_node).?,7165 @"if": Ast.full.If,
7150 };7166 /// `eu catch |err| label: switch (err) { ... }`
71517167 /// `if (eu) |payload| { ... } else |err| label: switch (err) { ... }`
7152 const switch_node, const operand_node, const error_payload = switch (node_ty) {7168 ///
7153 .@"catch" => .{7169 /// `switch` must be labeled.
7154 tree.nodeData(catch_or_if_node).node_and_node[1],7170 /// Emits a `condbr` on the non-error body and a regular switch, though the
7155 tree.nodeData(catch_or_if_node).node_and_node[0],7171 /// non-error prong and all `break`s from switch prongs are peers.
7156 tree.nodeMainToken(catch_or_if_node) + 2,7172 /// Exists to avoid a rather complex special case of `switch_block_err_union`.
7157 },7173 peer_break_target: struct {
7158 .@"if" => .{7174 /// Refers to the enclosing block of the entire switch-on-err expression.
7159 if_full.ast.else_expr.unwrap().?,7175 block_inst: Zir.Inst.Index,
7160 if_full.ast.cond_expr,7176 /// Belongs to `block_inst`.
7161 if_full.error_token.?,7177 block_ri: ResultInfo,
7162 },7178 },
7163 };7179};
7164 const switch_full = tree.fullSwitch(switch_node).?;
7165
7166 const do_err_trace = astgen.fn_block != null;
7167 const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node);
7168 const block_ri: ResultInfo = if (need_rl) ri else .{
7169 .rl = switch (ri.rl) {
7170 .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? },
7171 .inferred_ptr => .none,
7172 else => ri.rl,
7173 },
7174 .ctx = ri.ctx,
7175 };
7176
7177 const payload_is_ref = switch (node_ty) {
7178 .@"if" => if_full.payload_token != null and tree.tokenTag(if_full.payload_token.?) == .asterisk,
7179 .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty,
7180 };
7181
7182 // We need to call `rvalue` to write through to the pointer only if we had a
7183 // result pointer and aren't forwarding it.
7184 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7185 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
7186 var scalar_cases_len: u32 = 0;
7187 var multi_cases_len: u32 = 0;
7188 var inline_cases_len: u32 = 0;
7189 var has_else = false;
7190 var else_node: Ast.Node.OptionalIndex = .none;
7191 var else_src: ?Ast.TokenIndex = null;
7192 for (switch_full.ast.cases) |case_node| {
7193 const case = tree.fullSwitchCase(case_node).?;
7194
7195 if (case.ast.values.len == 0) {
7196 const case_src = case.ast.arrow_token - 1;
7197 if (else_src) |src| {
7198 return astgen.failTokNotes(
7199 case_src,
7200 "multiple else prongs in switch expression",
7201 .{},
7202 &[_]u32{
7203 try astgen.errNoteTok(
7204 src,
7205 "previous else prong here",
7206 .{},
7207 ),
7208 },
7209 );
7210 }
7211 has_else = true;
7212 else_node = case_node.toOptional();
7213 else_src = case_src;
7214 continue;
7215 } else if (case.ast.values.len == 1 and
7216 tree.nodeTag(case.ast.values[0]) == .identifier and
7217 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_"))
7218 {
7219 const case_src = case.ast.arrow_token - 1;
7220 return astgen.failTokNotes(
7221 case_src,
7222 "'_' prong is not allowed when switching on errors",
7223 .{},
7224 &[_]u32{
7225 try astgen.errNoteTok(
7226 case_src,
7227 "consider using 'else'",
7228 .{},
7229 ),
7230 },
7231 );
7232 }
7233
7234 for (case.ast.values) |val| {
7235 if (tree.nodeTag(val) == .string_literal)
7236 return astgen.failNode(val, "cannot switch on strings", .{});
7237 }
7238
7239 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {
7240 scalar_cases_len += 1;
7241 } else {
7242 multi_cases_len += 1;
7243 }
7244 if (case.inline_token != null) {
7245 inline_cases_len += 1;
7246 }
7247 }
7248
7249 const operand_ri: ResultInfo = .{
7250 .rl = if (payload_is_ref) .ref else .none,
7251 .ctx = .error_handling_expr,
7252 };
7253
7254 astgen.advanceSourceCursorToNode(operand_node);
7255 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
7256
7257 const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node);
7258 const item_ri: ResultInfo = .{ .rl = .none };
7259
7260 // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except
7261 // the first cases_nodes.len slots are a table that indexes payloads later in the array,
7262 // with the non-error and else case indices coming first, then scalar_cases_len indexes, then
7263 // multi_cases_len indexes
7264 const payloads = &astgen.scratch;
7265 const scratch_top = astgen.scratch.items.len;
7266 const case_table_start = scratch_top;
7267 const scalar_case_table = case_table_start + 1 + @intFromBool(has_else);
7268 const multi_case_table = scalar_case_table + scalar_cases_len;
7269 const case_table_end = multi_case_table + multi_cases_len;
7270
7271 try astgen.scratch.resize(gpa, case_table_end);
7272 defer astgen.scratch.items.len = scratch_top;
7273
7274 var block_scope = parent_gz.makeSubBlock(scope);
7275 // block_scope not used for collecting instructions
7276 block_scope.instructions_top = GenZir.unstacked_top;
7277 block_scope.setBreakResultInfo(block_ri);
7278
7279 // Sema expects a dbg_stmt immediately before switch_block_err_union
7280 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7281 // This gets added to the parent block later, after the item expressions.
7282 const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node);
7283
7284 // We re-use this same scope for all cases, including the special prong, if any.
7285 var case_scope = parent_gz.makeSubBlock(&block_scope.base);
7286 case_scope.instructions_top = GenZir.unstacked_top;
7287
7288 {
7289 const body_len_index: u32 = @intCast(payloads.items.len);
7290 payloads.items[case_table_start] = body_len_index;
7291 try payloads.resize(gpa, body_len_index + 1); // body_len
7292
7293 case_scope.instructions_top = parent_gz.instructions.items.len;
7294 defer case_scope.unstack();
7295
7296 const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref)
7297 .err_union_payload_unsafe_ptr
7298 else
7299 .err_union_payload_unsafe;
7300
7301 const unwrapped_payload = try case_scope.addUnNode(
7302 unwrap_payload_tag,
7303 raw_operand,
7304 catch_or_if_node,
7305 );
7306
7307 switch (node_ty) {
7308 .@"catch" => {
7309 const case_result = switch (ri.rl) {
7310 .ref, .ref_coerced_ty => unwrapped_payload,
7311 else => try rvalue(
7312 &case_scope,
7313 block_scope.break_result_info,
7314 unwrapped_payload,
7315 catch_or_if_node,
7316 ),
7317 };
7318 _ = try case_scope.addBreakWithSrcNode(
7319 .@"break",
7320 switch_block,
7321 case_result,
7322 catch_or_if_node,
7323 );
7324 },
7325 .@"if" => {
7326 var payload_val_scope: Scope.LocalVal = undefined;
7327
7328 const then_node = if_full.ast.then_expr;
7329 const then_sub_scope = s: {
7330 assert(if_full.error_token != null);
7331 if (if_full.payload_token) |payload_token| {
7332 const token_name_index = payload_token + @intFromBool(payload_is_ref);
7333 const ident_name = try astgen.identAsString(token_name_index);
7334 const token_name_str = tree.tokenSlice(token_name_index);
7335 if (mem.eql(u8, "_", token_name_str))
7336 break :s &case_scope.base;
7337 try astgen.detectLocalShadowing(
7338 &case_scope.base,
7339 ident_name,
7340 token_name_index,
7341 token_name_str,
7342 .capture,
7343 );
7344 payload_val_scope = .{
7345 .parent = &case_scope.base,
7346 .gen_zir = &case_scope,
7347 .name = ident_name,
7348 .inst = unwrapped_payload,
7349 .token_src = token_name_index,
7350 .id_cat = .capture,
7351 };
7352 try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload);
7353 break :s &payload_val_scope.base;
7354 } else {
7355 _ = try case_scope.addUnNode(
7356 .ensure_err_union_payload_void,
7357 raw_operand,
7358 catch_or_if_node,
7359 );
7360 break :s &case_scope.base;
7361 }
7362 };
7363 const then_result = try expr(
7364 &case_scope,
7365 then_sub_scope,
7366 block_scope.break_result_info,
7367 then_node,
7368 );
7369 try checkUsed(parent_gz, &case_scope.base, then_sub_scope);
7370 if (!case_scope.endsWithNoReturn()) {
7371 _ = try case_scope.addBreakWithSrcNode(
7372 .@"break",
7373 switch_block,
7374 then_result,
7375 then_node,
7376 );
7377 }
7378 },
7379 }
7380
7381 const case_slice = case_scope.instructionsSlice();
7382 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, &.{switch_block});
7383 try payloads.ensureUnusedCapacity(gpa, body_len);
7384 const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) {
7385 .@"catch" => .none,
7386 .@"if" => if (if_full.payload_token == null)
7387 .none
7388 else if (payload_is_ref)
7389 .by_ref
7390 else
7391 .by_val,
7392 };
7393 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
7394 .body_len = @intCast(body_len),
7395 .capture = capture,
7396 .is_inline = false,
7397 .has_tag_capture = false,
7398 });
7399 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, &.{switch_block});
7400 }
7401
7402 const err_name = blk: {
7403 const err_str = tree.tokenSlice(error_payload);
7404 if (mem.eql(u8, err_str, "_")) {
7405 // This is fatal because we already know we're switching on the captured error.
7406 return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{});
7407 }
7408 const err_name = try astgen.identAsString(error_payload);
7409 try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture);
7410
7411 break :blk err_name;
7412 };
7413
7414 // allocate a shared dummy instruction for the error capture
7415 const err_inst = err_inst: {
7416 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
7417 try astgen.instructions.append(astgen.gpa, .{
7418 .tag = .extended,
7419 .data = .{ .extended = .{
7420 .opcode = .value_placeholder,
7421 .small = undefined,
7422 .operand = undefined,
7423 } },
7424 });
7425 break :err_inst inst;
7426 };
7427
7428 // In this pass we generate all the item and prong expressions for error cases.
7429 var multi_case_index: u32 = 0;
7430 var scalar_case_index: u32 = 0;
7431 var any_uses_err_capture = false;
7432 for (switch_full.ast.cases) |case_node| {
7433 const case = tree.fullSwitchCase(case_node).?;
7434
7435 const is_multi_case = case.ast.values.len > 1 or
7436 (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range);
7437
7438 var dbg_var_name: Zir.NullTerminatedString = .empty;
7439 var dbg_var_inst: Zir.Inst.Ref = undefined;
7440 var err_scope: Scope.LocalVal = undefined;
7441 var capture_scope: Scope.LocalVal = undefined;
7442
7443 const sub_scope = blk: {
7444 err_scope = .{
7445 .parent = &case_scope.base,
7446 .gen_zir = &case_scope,
7447 .name = err_name,
7448 .inst = err_inst.toRef(),
7449 .token_src = error_payload,
7450 .id_cat = .capture,
7451 };
7452
7453 const capture_token = case.payload_token orelse break :blk &err_scope.base;
7454 if (tree.tokenTag(capture_token) != .identifier) {
7455 return astgen.failTok(capture_token + 1, "error set cannot be captured by reference", .{});
7456 }
7457
7458 const capture_slice = tree.tokenSlice(capture_token);
7459 if (mem.eql(u8, capture_slice, "_")) {
7460 try astgen.appendErrorTok(capture_token, "discard of error capture; omit it instead", .{});
7461 }
7462 const tag_name = try astgen.identAsString(capture_token);
7463 try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture);
7464
7465 capture_scope = .{
7466 .parent = &case_scope.base,
7467 .gen_zir = &case_scope,
7468 .name = tag_name,
7469 .inst = switch_block.toRef(),
7470 .token_src = capture_token,
7471 .id_cat = .capture,
7472 };
7473 dbg_var_name = tag_name;
7474 dbg_var_inst = switch_block.toRef();
7475
7476 err_scope.parent = &capture_scope.base;
7477
7478 break :blk &err_scope.base;
7479 };
7480
7481 const header_index: u32 = @intCast(payloads.items.len);
7482 const body_len_index = if (is_multi_case) blk: {
7483 payloads.items[multi_case_table + multi_case_index] = header_index;
7484 multi_case_index += 1;
7485 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
7486
7487 // items
7488 var items_len: u32 = 0;
7489 for (case.ast.values) |item_node| {
7490 if (tree.nodeTag(item_node) == .switch_range) continue;
7491 items_len += 1;
7492
7493 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7494 try payloads.append(gpa, @intFromEnum(item_inst));
7495 }
7496
7497 // ranges
7498 var ranges_len: u32 = 0;
7499 for (case.ast.values) |range| {
7500 if (tree.nodeTag(range) != .switch_range) continue;
7501 ranges_len += 1;
7502
7503 const first_node, const last_node = tree.nodeData(range).node_and_node;
7504 const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item);
7505 const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item);
7506 try payloads.appendSlice(gpa, &[_]u32{
7507 @intFromEnum(first), @intFromEnum(last),
7508 });
7509 }
7510
7511 payloads.items[header_index] = items_len;
7512 payloads.items[header_index + 1] = ranges_len;
7513 break :blk header_index + 2;
7514 } else if (case_node.toOptional() == else_node) blk: {
7515 payloads.items[case_table_start + 1] = header_index;
7516 try payloads.resize(gpa, header_index + 1); // body_len
7517 break :blk header_index;
7518 } else blk: {
7519 payloads.items[scalar_case_table + scalar_case_index] = header_index;
7520 scalar_case_index += 1;
7521 try payloads.resize(gpa, header_index + 2); // item, body_len
7522 const item_node = case.ast.values[0];
7523 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7524 payloads.items[header_index] = @intFromEnum(item_inst);
7525 break :blk header_index + 1;
7526 };
7527
7528 {
7529 // temporarily stack case_scope on parent_gz
7530 case_scope.instructions_top = parent_gz.instructions.items.len;
7531 defer case_scope.unstack();
7532
7533 if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node))
7534 _ = try case_scope.addSaveErrRetIndex(.always);
7535
7536 if (dbg_var_name != .empty) {
7537 try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
7538 }
7539
7540 const target_expr_node = case.ast.target_expr;
7541 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
7542 // check capture_scope, not err_scope to avoid false positive unused error capture
7543 try checkUsed(parent_gz, &case_scope.base, err_scope.parent);
7544 const uses_err = err_scope.used != .none or err_scope.discarded != .none;
7545 if (uses_err) {
7546 try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef());
7547 any_uses_err_capture = true;
7548 }
7549
7550 if (!parent_gz.refIsNoReturn(case_result)) {
7551 if (do_err_trace)
7552 try restoreErrRetIndex(
7553 &case_scope,
7554 .{ .block = switch_block },
7555 block_scope.break_result_info,
7556 target_expr_node,
7557 case_result,
7558 );
7559
7560 _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
7561 }
7562
7563 const case_slice = case_scope.instructionsSlice();
7564 const extra_insts: []const Zir.Inst.Index = if (uses_err) &.{ switch_block, err_inst } else &.{switch_block};
7565 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts);
7566 try payloads.ensureUnusedCapacity(gpa, body_len);
7567 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
7568 .body_len = @intCast(body_len),
7569 .capture = if (case.payload_token != null) .by_val else .none,
7570 .is_inline = case.inline_token != null,
7571 .has_tag_capture = false,
7572 });
7573 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts);
7574 }
7575 }
7576 // Now that the item expressions are generated we can add this.
7577 try parent_gz.instructions.append(gpa, switch_block);
7578
7579 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).@"struct".fields.len +
7580 @intFromBool(multi_cases_len != 0) +
7581 payloads.items.len - case_table_end +
7582 (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len);
7583
7584 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{
7585 .operand = raw_operand,
7586 .bits = Zir.Inst.SwitchBlockErrUnion.Bits{
7587 .has_multi_cases = multi_cases_len != 0,
7588 .has_else = has_else,
7589 .scalar_cases_len = @intCast(scalar_cases_len),
7590 .any_uses_err_capture = any_uses_err_capture,
7591 .payload_is_ref = payload_is_ref,
7592 },
7593 .main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node),
7594 });
7595
7596 if (multi_cases_len != 0) {
7597 astgen.extra.appendAssumeCapacity(multi_cases_len);
7598 }
7599
7600 if (any_uses_err_capture) {
7601 astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst));
7602 }
7603
7604 const zir_datas = astgen.instructions.items(.data);
7605 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
7606
7607 for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {
7608 var body_len_index = start_index;
7609 var end_index = start_index;
7610 const table_index = case_table_start + i;
7611 if (table_index < scalar_case_table) {
7612 end_index += 1;
7613 } else if (table_index < multi_case_table) {
7614 body_len_index += 1;
7615 end_index += 2;
7616 } else {
7617 body_len_index += 2;
7618 const items_len = payloads.items[start_index];
7619 const ranges_len = payloads.items[start_index + 1];
7620 end_index += 3 + items_len + 2 * ranges_len;
7621 }
7622 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
7623 end_index += prong_info.body_len;
7624 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
7625 }
7626
7627 if (need_result_rvalue) {
7628 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
7629 } else {
7630 return switch_block.toRef();
7631 }
7632}
76337180
7634fn switchExpr(7181fn switchExpr(
7635 parent_gz: *GenZir,7182 parent_gz: *GenZir,
...@@ -7637,13 +7184,38 @@ fn switchExpr(...@@ -7637,13 +7184,38 @@ fn switchExpr(
7637 ri: ResultInfo,7184 ri: ResultInfo,
7638 node: Ast.Node.Index,7185 node: Ast.Node.Index,
7639 switch_full: Ast.full.Switch,7186 switch_full: Ast.full.Switch,
7187 non_err: SwitchNonErr,
7640) InnerError!Zir.Inst.Ref {7188) InnerError!Zir.Inst.Ref {
7641 const astgen = parent_gz.astgen;7189 const astgen = parent_gz.astgen;
7642 const gpa = astgen.gpa;7190 const gpa = astgen.gpa;
7643 const tree = astgen.tree;7191 const tree = astgen.tree;
7644 const operand_node = switch_full.ast.condition;7192
7193 const switch_node, const operand_node, const err_token = switch (non_err) {
7194 .none, .peer_break_target => .{
7195 node,
7196 switch_full.ast.condition,
7197 undefined,
7198 },
7199 .@"catch" => .{
7200 tree.nodeData(node).node_and_node[1],
7201 tree.nodeData(node).node_and_node[0],
7202 tree.nodeMainToken(node) + 2,
7203 },
7204 .@"if" => |if_full| .{
7205 if_full.ast.else_expr.unwrap().?,
7206 if_full.ast.cond_expr,
7207 if_full.error_token.?,
7208 },
7209 };
7645 const case_nodes = switch_full.ast.cases;7210 const case_nodes = switch_full.ast.cases;
76467211
7212 const is_err_switch = non_err != .none;
7213 const needs_non_err_handling = switch (non_err) {
7214 .none => false,
7215 .peer_break_target => false, // handled by parent expression
7216 .@"catch", .@"if" => true,
7217 };
7218
7647 const need_rl = astgen.nodes_need_rl.contains(node);7219 const need_rl = astgen.nodes_need_rl.contains(node);
7648 const block_ri: ResultInfo = if (need_rl) ri else .{7220 const block_ri: ResultInfo = if (need_rl) ri else .{
7649 .rl = switch (ri.rl) {7221 .rl = switch (ri.rl) {
...@@ -7653,226 +7225,562 @@ fn switchExpr(...@@ -7653,226 +7225,562 @@ fn switchExpr(
7653 },7225 },
7654 .ctx = ri.ctx,7226 .ctx = ri.ctx,
7655 };7227 };
7228
7656 // We need to call `rvalue` to write through to the pointer only if we had a7229 // We need to call `rvalue` to write through to the pointer only if we had a
7657 // result pointer and aren't forwarding it.7230 // result pointer and aren't forwarding it.
7658 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;7231 const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
7659 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);7232 const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
76607233
7234 const catch_or_if_node = if (needs_non_err_handling) node else undefined;
7235 const do_err_trace = needs_non_err_handling and astgen.fn_block != null;
7236 const non_err_is_ref: bool = switch (non_err) {
7237 .none, .peer_break_target => undefined,
7238 .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty,
7239 .@"if" => |if_full| if_full.payload_token != null and
7240 tree.tokenTag(if_full.payload_token.?) == .asterisk,
7241 };
7242
7661 if (switch_full.label_token) |label_token| {7243 if (switch_full.label_token) |label_token| {
7662 try astgen.checkLabelRedefinition(scope, label_token);7244 try astgen.checkLabelRedefinition(scope, label_token);
7663 }7245 }
76647246
7247 const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: {
7248 const err_str = tree.tokenSlice(err_token);
7249 if (mem.eql(u8, err_str, "_")) {
7250 // This is fatal because we already know we're switching on the captured error.
7251 return astgen.failTok(err_token, "discard of error capture; omit it instead", .{});
7252 }
7253 const err_name = try astgen.identAsString(err_token);
7254 try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture);
7255 break :blk err_name;
7256 } else undefined;
7257
7665 // We perform two passes over the AST. This first pass is to collect information7258 // We perform two passes over the AST. This first pass is to collect information
7666 // for the following variables, make note of the special prong AST node index,7259 // for the following variables, make note of the special prong AST node indices,
7667 // and bail out with a compile error if there are multiple special prongs present.7260 // and bail out with a compile error if there are incompatible special prongs present.
7668 var any_payload_is_ref = false;7261 var any_payload_is_ref = false;
7262 var any_has_payload_capture = false;
7669 var any_has_tag_capture = false;7263 var any_has_tag_capture = false;
7670 var any_non_inline_capture = false;7264 var any_maybe_runtime_capture = false;
7671 var scalar_cases_len: u32 = 0;7265 var scalar_cases_len: u32 = 0;
7672 var multi_cases_len: u32 = 0;7266 var multi_cases_len: u32 = 0;
7673 var inline_cases_len: u32 = 0;7267 var total_items_len: usize = 0;
7268 var total_ranges_len: usize = 0;
7674 var else_case_node: Ast.Node.OptionalIndex = .none;7269 var else_case_node: Ast.Node.OptionalIndex = .none;
7675 var else_src: ?Ast.TokenIndex = null;
7676 var underscore_case_node: Ast.Node.OptionalIndex = .none;
7677 var underscore_node: Ast.Node.OptionalIndex = .none;7270 var underscore_node: Ast.Node.OptionalIndex = .none;
7678 var underscore_src: ?Ast.TokenIndex = null;
7679 var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none;
7680 for (case_nodes) |case_node| {7271 for (case_nodes) |case_node| {
7681 const case = tree.fullSwitchCase(case_node).?;7272 const case = tree.fullSwitchCase(case_node).?;
7682 if (case.payload_token) |payload_token| {7273 if (case.payload_token) |payload_token| {
7683 const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: {7274 const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: {
7275 // Capturing errors by reference is never allowed, but as we will
7276 // check for this again later we will fail as late as possible.
7684 any_payload_is_ref = true;7277 any_payload_is_ref = true;
7685 break :blk payload_token + 1;7278 break :blk payload_token + 1;
7686 } else payload_token;7279 } else payload_token;
7280
7281 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7282 any_has_payload_capture = true;
7283
7284 // If we're capturing a union, its payload value cannot always be
7285 // comptime-known, even if its prong is inlined as inlining only
7286 // affects its enum tag.
7287 // This check isn't perfect, because for things like enums, the
7288 // entire capture *is* comptime-known for inline prongs! But such
7289 // knowledge requires semantic analysis.
7290 any_maybe_runtime_capture = true;
7291 }
7687 if (tree.tokenTag(ident + 1) == .comma) {7292 if (tree.tokenTag(ident + 1) == .comma) {
7688 any_has_tag_capture = true;7293 any_has_tag_capture = true;
7689 }
76907294
7691 // If the first capture is ignored, then there is no runtime-known7295 if (case.inline_token == null) {
7692 // capture, as the tag capture must be for an inline prong.7296 any_maybe_runtime_capture = true;
7693 // This check isn't perfect, because for things like enums, the7297 }
7694 // first prong *is* comptime-known for inline prongs! But such
7695 // knowledge requires semantic analysis.
7696 if (!mem.eql(u8, tree.tokenSlice(ident), "_")) {
7697 any_non_inline_capture = true;
7698 }7298 }
7699 }7299 }
77007300
7701 // Check for else prong.7301 // Check for else prong.
7702 if (case.ast.values.len == 0) {7302 if (case.ast.values.len == 0) {
7703 const case_src = case.ast.arrow_token - 1;7303 if (else_case_node.unwrap()) |prev_case_node| {
7704 if (else_src) |src| {7304 const prev_else_tok = tree.fullSwitchCase(prev_case_node).?.ast.arrow_token - 1;
7305 const else_tok = case.ast.arrow_token - 1;
7705 return astgen.failTokNotes(7306 return astgen.failTokNotes(
7706 case_src,7307 else_tok,
7707 "multiple else prongs in switch expression",7308 "multiple else prongs in switch expression",
7708 .{},7309 .{},
7709 &[_]u32{7310 &.{try astgen.errNoteTok(prev_else_tok, "previous else prong here", .{})},
7710 try astgen.errNoteTok(
7711 src,
7712 "previous else prong here",
7713 .{},
7714 ),
7715 },
7716 );7311 );
7717 }7312 }
7718 else_case_node = case_node.toOptional();7313 else_case_node = case_node.toOptional();
7719 else_src = case_src;
7720 continue;7314 continue;
7721 }7315 }
77227316
7723 // Check for '_' prong.7317 // Check for '_' prong and ranges.
7724 var case_has_underscore = false;7318 var case_has_ranges = false;
7725 for (case.ast.values) |val| {7319 for (case.ast.values) |val| {
7726 switch (tree.nodeTag(val)) {7320 switch (tree.nodeTag(val)) {
7727 .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) {7321 .switch_range => {
7728 const val_src = tree.nodeMainToken(val);7322 total_ranges_len += 1;
7729 if (underscore_src) |src| {7323 case_has_ranges = true;
7730 return astgen.failTokNotes(
7731 val_src,
7732 "multiple '_' prongs in switch expression",
7733 .{},
7734 &[_]u32{
7735 try astgen.errNoteTok(
7736 src,
7737 "previous '_' prong here",
7738 .{},
7739 ),
7740 },
7741 );
7742 }
7743 if (case.inline_token != null) {
7744 return astgen.failTok(val_src, "cannot inline '_' prong", .{});
7745 }
7746 underscore_case_node = case_node.toOptional();
7747 underscore_src = val_src;
7748 underscore_node = val.toOptional();
7749 underscore_additional_items = switch (case.ast.values.len) {
7750 0 => unreachable,
7751 1 => .none,
7752 2 => .one,
7753 else => .many,
7754 };
7755 case_has_underscore = true;
7756 },7324 },
7757 .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),7325 .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}),
7758 else => {},7326 else => |tag| {
7327 total_items_len += 1;
7328 if (tag == .identifier and
7329 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
7330 {
7331 if (is_err_switch) {
7332 const case_src = case.ast.arrow_token - 1;
7333 return astgen.failTokNotes(
7334 case_src,
7335 "'_' prong is not allowed when switching on errors",
7336 .{},
7337 &.{
7338 try astgen.errNoteTok(
7339 case_src,
7340 "consider using 'else'",
7341 .{},
7342 ),
7343 },
7344 );
7345 }
7346 if (underscore_node.unwrap()) |prev_src| {
7347 return astgen.failNodeNotes(
7348 val,
7349 "multiple '_' prongs in switch expression",
7350 .{},
7351 &.{try astgen.errNoteNode(prev_src, "previous '_' prong here", .{})},
7352 );
7353 }
7354 if (case.inline_token != null) {
7355 return astgen.failNode(val, "cannot inline '_' prong", .{});
7356 }
7357 underscore_node = val.toOptional();
7358 }
7359 },
7759 }7360 }
7760 }7361 }
7761 if (case_has_underscore) continue;
77627362
7763 if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) {7363 const case_len = case.ast.values.len;
7364 if (case_len == 1 and !case_has_ranges) {
7764 scalar_cases_len += 1;7365 scalar_cases_len += 1;
7765 } else {7366 } else if (case_len >= 1) {
7766 multi_cases_len += 1;7367 multi_cases_len += 1;
7767 }7368 }
7768 if (case.inline_token != null) {
7769 inline_cases_len += 1;
7770 }
7771 }7369 }
77727370
7773 const special_prongs: Zir.SpecialProngs = .init(7371 const has_else = else_case_node != .none;
7774 else_src != null,7372 const has_under = underscore_node != .none;
7775 underscore_src != null,7373 if (is_err_switch) assert(!has_under); // should have failed by now
7776 underscore_additional_items,7374 const any_ranges = total_ranges_len > 0;
7777 );
7778 const has_else = special_prongs.hasElse();
7779 const has_under = special_prongs.hasUnder();
7780
7781 const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none };
77827375
7783 astgen.advanceSourceCursorToNode(operand_node);7376 // This contains all of the body lengths (already in the correct order) and
7784 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };7377 // the bodies they belong to that go into the `extra` array later, except the
77857378 // first item_table_end slots are a table that indexes the item bodies (and
7786 const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node);7379 // also indirectly the prong bodies, as they are always trailing after their
7787 const item_ri: ResultInfo = .{ .rl = .none };7380 // item bodies).
7788
7789 // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type
7790 // to provide a result type.
7791 const raw_operand_ty_ref = if (switch_full.label_token != null) t: {
7792 break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node);
7793 } else undefined;
7794
7795 // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
7796 // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
7797 // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes
7798 const payloads = &astgen.scratch;7381 const payloads = &astgen.scratch;
7799 const scratch_top = astgen.scratch.items.len;7382 const scratch_top = astgen.scratch.items.len;
7800 const case_table_start = scratch_top;7383 var payloads_end = scratch_top;
7801 const else_case_index = if (has_else) case_table_start else undefined;7384
7802 const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined;7385 // Since range item body pairs are always contiguous we don't technically
7803 const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under);7386 // have to keep track of the position of the second body. However handling
7804 const multi_case_table = scalar_case_table + scalar_cases_len;7387 // all of the several indices and offsets is complicated enough as it is,
7805 const case_table_end = multi_case_table + multi_cases_len;7388 // so for the sake of keeping this function a little bit more simple we do
7806 try astgen.scratch.resize(gpa, case_table_end);7389 // it anyway.
7390
7391 const scalar_body_table = payloads_end;
7392 payloads_end += scalar_cases_len;
7393 const multi_item_body_table = payloads_end;
7394 payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len;
7395 const multi_prong_body_table = payloads_end;
7396 payloads_end += multi_cases_len;
7397 const body_table_end = payloads_end;
7398
7399 const scalar_prong_infos_start = payloads_end;
7400 payloads_end += scalar_cases_len;
7401 const multi_prong_infos_start = payloads_end;
7402 payloads_end += multi_cases_len;
7403 const multi_case_items_lens_start = payloads_end;
7404 payloads_end += multi_cases_len;
7405 const multi_case_ranges_lens_start = if (any_ranges) blk: {
7406 const multi_case_ranges_lens_start = payloads_end;
7407 payloads_end += multi_cases_len;
7408 break :blk multi_case_ranges_lens_start;
7409 } else undefined;
7410 const scalar_item_infos_start = payloads_end;
7411 payloads_end += scalar_cases_len;
7412 const multi_items_infos_start = payloads_end;
7413 payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len;
7414 const bodies_start = payloads_end;
7415
7416 try payloads.resize(gpa, bodies_start);
7807 defer astgen.scratch.items.len = scratch_top;7417 defer astgen.scratch.items.len = scratch_top;
78087418
7419 var non_err_prong_body_start: u32 = undefined;
7420 var else_prong_body_start: u32 = undefined;
7421 var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined;
7422 var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined;
7423
7809 var block_scope = parent_gz.makeSubBlock(scope);7424 var block_scope = parent_gz.makeSubBlock(scope);
7810 // block_scope not used for collecting instructions7425 // block_scope not used for collecting instructions
7811 block_scope.instructions_top = GenZir.unstacked_top;7426 block_scope.instructions_top = GenZir.unstacked_top;
7812 block_scope.setBreakResultInfo(block_ri);
78137427
7814 // Sema expects a dbg_stmt immediately before switch_block(_ref)7428 const operand_ri: ResultInfo = .{
7429 .rl = if (any_payload_is_ref or
7430 (needs_non_err_handling and non_err_is_ref)) .ref else .none,
7431 .ctx = if (do_err_trace) .error_handling_expr else .none,
7432 };
7433
7434 astgen.advanceSourceCursorToNode(operand_node);
7435 const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
7436
7437 const raw_operand: Zir.Inst.Ref = if (needs_non_err_handling)
7438 try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node)
7439 else
7440 try expr(parent_gz, scope, operand_ri, operand_node);
7441
7442 // Sema expects a dbg_stmt immediately before any kind of switch_block inst.
7815 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);7443 try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc);
7816 // This gets added to the parent block later, after the item expressions.7444 // This gets added to the parent block later, after the item expressions.
7817 const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;7445 const switch_tag: Zir.Inst.Tag = switch (non_err) {
7818 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);7446 .none, .peer_break_target => if (any_payload_is_ref) .switch_block_ref else .switch_block,
7447 .@"if", .@"catch" => .switch_block_err_union,
7448 };
7449 const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node);
7450
7451 // Set `break` target if applicable; `continue` target may differ!
7452 switch (non_err) {
7453 .none => {
7454 if (switch_full.label_token != null) {
7455 block_scope.break_target = switch_block;
7456 }
7457 block_scope.setBreakResultInfo(block_ri);
7458 },
7459 .@"catch", .@"if" => {
7460 assert(switch_full.label_token == null); // use `peer_break_target` code path instead!
7461 block_scope.setBreakResultInfo(block_ri);
7462 },
7463 .peer_break_target => |peer_break_target| {
7464
7465 // Special case; we have an error switch + label situation and we
7466 // want to generate this:
7467 // ```
7468 // %1 = block({
7469 // %2 = is_non_err(%operand)
7470 // %3 = condbr(%2, {
7471 // %4 = err_union_payload_unsafe(%operand)
7472 // %5 = break(%1, result) // targets enclosing `block`
7473 // }, {
7474 // %6 = err_union_code(%operand)
7475 // %7 = switch_block(%6,
7476 // { ... } => {
7477 // %8 = break(%1, result) // targets enclosing `block`
7478 // },
7479 // { ... } => {
7480 // %9 = switch_continue(%7, result) // targets `switch_block`
7481 // },
7482 // )
7483 // %10 = break(%1, @void_value)
7484 // })
7485 // })
7486 // ```
7487 // to ensure that the non-err case and the switch are only peers when
7488 // breaking from either, but not when continuing the switch. We use
7489 // this lowering to avoiding a rather complex special case in Sema.
7490
7491 assert(switch_full.label_token != null); // use `switch_block_err_union` code path instead!
7492 assert(.block == astgen.instructions.items(.tag)[@intFromEnum(peer_break_target.block_inst)]);
7493 block_scope.break_target = peer_break_target.block_inst;
7494 block_scope.setBreakResultInfo(peer_break_target.block_ri);
7495 },
7496 }
7497
7498 // We need a bunch of separate locations to store several capture values:
7499 // `... |err| switch (err) { else => |e| { ... } }` // `err` and `e`
7500 // `... => |payload, tag| { ... }` // `payload` and `tag`
7501 // and result types:
7502 // `foo => { ... }` // `foo` needs a result type
7503 // `... => continue :sw val` // `val` needs a result type
7504 // Some observations:
7505 // - If we just use the switch inst itself we don't need a placeholder!
7506 // - We can always tell for sure whether a capture exists. We also know
7507 // that its existence implies that it has to be used.
7508 // - We can't know whether there are any `continue`s before analyzing all
7509 // prong bodies. At that point we already need a result location. We do
7510 // know whether there even *could* be any though by looking for a label.
7511 // - Sema wants a result location in `zirSwitchContinue`. If that's the
7512 // switch inst itself, there's no need to look at the switch inst data.
7513 // Some conclusions:
7514 // - We should use the switch inst as the continue result location if needed.
7515 // - If we need more insts for captures and our switch inst is already used
7516 // for something else, we start creating placeholder insts.
7517
7518 // Prong items use the switch block instruction as their result type.
7519 // No other components of the switch statement are in scope while they are
7520 // being resolved, so this is never a problem.
7521 const item_ri: ResultInfo = .{ .rl = .{ .coerced_ty = switch_block.toRef() } };
7522
7523 var switch_block_inst_is_occupied: bool = false;
78197524
7820 if (switch_full.label_token) |label_token| {7525 if (switch_full.label_token) |label_token| {
7821 block_scope.continue_block = switch_block.toOptional();7526 block_scope.label = .{ .token = label_token };
7527 block_scope.continue_target = .{ .switch_continue = switch_block };
7822 block_scope.continue_result_info = .{7528 block_scope.continue_result_info = .{
7823 .rl = if (any_payload_is_ref)7529 .rl = if (any_payload_is_ref)
7824 .{ .ref_coerced_ty = raw_operand_ty_ref }7530 .{ .ref_coerced_ty = switch_block.toRef() }
7825 else7531 else
7826 .{ .coerced_ty = raw_operand_ty_ref },7532 .{ .coerced_ty = switch_block.toRef() },
7827 };7533 };
7534 switch_block_inst_is_occupied = true;
78287535
7829 block_scope.label = .{7536 // `break_target` and `break_result_info` already set above.
7830 .token = label_token,
7831 .block_inst = switch_block,
7832 };
7833 // `break` can target this via `label.block_inst`
7834 // `break_result_info` already set by `setBreakResultInfo`
7835 }7537 }
7538 if (needs_non_err_handling) {
7539 // `switch_block_err_union` uses the switch block inst as its err capture/
7540 // switch operand. This is always ok as its switch can never have a label.
7541 assert(!switch_block_inst_is_occupied);
7542 switch_block_inst_is_occupied = true;
7543 }
7544 // `... => |payload| { ... }`
7545 const payload_capture_inst, const payload_capture_inst_is_placeholder = inst: {
7546 if (!any_has_payload_capture) break :inst .{ undefined, false };
7547 if (!switch_block_inst_is_occupied) {
7548 switch_block_inst_is_occupied = true;
7549 break :inst .{ switch_block, false };
7550 }
7551 break :inst .{ try astgen.appendPlaceholder(), true };
7552 };
7553 // `... => |_, tag| { ... }`
7554 const tag_capture_inst, const tag_capture_inst_is_placeholder = inst: {
7555 if (!any_has_tag_capture) break :inst .{ undefined, false };
7556 if (!switch_block_inst_is_occupied) {
7557 switch_block_inst_is_occupied = true;
7558 break :inst .{ switch_block, false };
7559 }
7560 break :inst .{ try astgen.appendPlaceholder(), true };
7561 };
78367562
7837 // We re-use this same scope for all cases, including the special prong, if any.7563 var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined;
7838 var case_scope = parent_gz.makeSubBlock(&block_scope.base);7564 const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: {
7839 case_scope.instructions_top = GenZir.unstacked_top;7565 var extra_insts: std.ArrayList(Zir.Inst.Index) = .initBuffer(&prong_body_extra_insts_buf);
7566 if (switch_block_inst_is_occupied) extra_insts.appendAssumeCapacity(switch_block);
7567 if (payload_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(payload_capture_inst);
7568 if (tag_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(tag_capture_inst);
7569 break :extra_insts extra_insts.items;
7570 };
78407571
7841 // If any prong has an inline tag capture, allocate a shared dummy instruction for it7572 const switch_operand, const catch_or_if_operand = if (needs_non_err_handling)
7842 const tag_inst = if (any_has_tag_capture) tag_inst: {7573 .{ switch_block.toRef(), raw_operand }
7843 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);7574 else
7844 try astgen.instructions.append(astgen.gpa, .{7575 .{ raw_operand, undefined };
7845 .tag = .extended,7576
7846 .data = .{ .extended = .{7577 // We re-use this same scope for all case items and contents.
7847 .opcode = .value_placeholder,7578 var scratch_scope = parent_gz.makeSubBlock(&block_scope.base);
7848 .small = undefined,7579 scratch_scope.instructions_top = GenZir.unstacked_top;
7849 .operand = undefined,7580
7850 } },7581 // We have to take care of the non-error body first if there is one.
7851 });7582 non_err_body: {
7852 break :tag_inst inst;7583 if (!needs_non_err_handling) break :non_err_body;
7853 } else undefined;7584
7585 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7586 defer scratch_scope.unstack();
7587
7588 // It's always ok to use the switch block inst to refer to the error union
7589 // payload as the actual switch statement isn't even in scope yet.
7590 const non_err_payload_inst = switch_block;
7591 var non_err_capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
7592
7593 switch (non_err) {
7594 .none, .peer_break_target => unreachable,
7595 .@"catch" => {
7596 // We always effectively capture the error union payload; we use
7597 // it to `break` from the entire `switch_block_err_union`.
7598 non_err_capture = if (non_err_is_ref) .by_ref else .by_val;
7599
7600 const then_result = switch (ri.rl) {
7601 .ref, .ref_coerced_ty => non_err_payload_inst.toRef(),
7602 else => try rvalue(
7603 &scratch_scope,
7604 block_scope.break_result_info,
7605 non_err_payload_inst.toRef(),
7606 catch_or_if_node,
7607 ),
7608 };
7609 _ = try scratch_scope.addBreakWithSrcNode(
7610 .@"break",
7611 switch_block,
7612 then_result,
7613 catch_or_if_node,
7614 );
7615 },
7616 .@"if" => |if_full| {
7617 var payload_val_scope: Scope.LocalVal = undefined;
7618
7619 const then_node = if_full.ast.then_expr;
7620 const then_sub_scope: *Scope = scope: {
7621 if (if_full.payload_token) |payload_token| {
7622 const ident_token = payload_token + @intFromBool(non_err_is_ref);
7623 const ident_name = try astgen.identAsString(ident_token);
7624 const ident_name_str = tree.tokenSlice(ident_token);
7625 if (mem.eql(u8, "_", ident_name_str)) {
7626 break :scope &scratch_scope.base;
7627 }
7628 non_err_capture = if (non_err_is_ref) .by_ref else .by_val;
7629 try astgen.detectLocalShadowing(&scratch_scope.base, ident_name, ident_token, ident_name_str, .capture);
7630 payload_val_scope = .{
7631 .parent = &scratch_scope.base,
7632 .gen_zir = &scratch_scope,
7633 .name = ident_name,
7634 .inst = non_err_payload_inst.toRef(),
7635 .token_src = ident_token,
7636 .id_cat = .capture,
7637 };
7638 try scratch_scope.addDbgVar(.dbg_var_val, ident_name, non_err_payload_inst.toRef());
7639 break :scope &payload_val_scope.base;
7640 } else {
7641 _ = try scratch_scope.addUnNode(
7642 .ensure_err_union_payload_void,
7643 catch_or_if_operand,
7644 catch_or_if_node,
7645 );
7646 break :scope &scratch_scope.base;
7647 }
7648 };
7649 const then_result = try fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, .allow_branch_hint);
7650 try checkUsed(parent_gz, &scratch_scope.base, then_sub_scope);
7651 if (!scratch_scope.endsWithNoReturn()) {
7652 _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, then_result, then_node);
7653 }
7654 },
7655 }
7656 const body_slice = scratch_scope.instructionsSlice();
7657 const body_start: u32 = @intCast(payloads.items.len);
7658 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, &.{non_err_payload_inst});
7659 try payloads.ensureUnusedCapacity(gpa, body_len);
7660 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, &.{non_err_payload_inst});
7661
7662 non_err_prong_body_start = body_start;
7663 non_err_info = .{
7664 .body_len = @intCast(body_len),
7665 .capture = non_err_capture,
7666 .operand_is_ref = non_err_is_ref,
7667 };
7668 }
78547669
7855 // In this pass we generate all the item and prong expressions.7670 // In this pass we generate all the item and prong expressions.
7856 var multi_case_index: u32 = 0;7671 var multi_case_index: u32 = 0;
7857 var scalar_case_index: u32 = 0;7672 var scalar_case_index: u32 = 0;
7673 var multi_item_offset: usize = 0;
7858 for (case_nodes) |case_node| {7674 for (case_nodes) |case_node| {
7859 const case = tree.fullSwitchCase(case_node).?;7675 const case = tree.fullSwitchCase(case_node).?;
78607676
7861 const is_multi_case = case.ast.values.len > 1 or7677 const ranges_len: u32 = if (any_ranges) blk: {
7862 (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range);7678 var ranges_len: u32 = 0;
7679 for (case.ast.values) |value| {
7680 ranges_len += @intFromBool(tree.nodeTag(value) == .switch_range);
7681 }
7682 break :blk ranges_len;
7683 } else 0;
7684 const items_len: u32 = @intCast(case.ast.values.len - ranges_len);
7685 const is_multi_case = items_len > 1 or ranges_len > 0;
7686
7687 // item/range bodies in order of occurence
7688 var item_i: usize = 0;
7689 var range_i: usize = 0;
7690 for (case.ast.values) |value| {
7691 const is_range = tree.nodeTag(value) == .switch_range;
7692 const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined;
7693 const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value};
7694 for (nodes) |item| {
7695 // We lower enum literals, error values and number literals
7696 // manually to save space since they are very commonly used as
7697 // switch case items.
7698 const body_start: u32 = @intCast(payloads.items.len);
7699 const item_info: Zir.Inst.SwitchBlock.ItemInfo = blk: switch (tree.nodeTag(item)) {
7700 .enum_literal => {
7701 const str_index = try astgen.identAsString(tree.nodeMainToken(item));
7702 break :blk .wrap(.{ .enum_literal = str_index });
7703 },
7704 .error_value => {
7705 const ident_token = tree.nodeMainToken(item) + 2; // skip 'error', '.'
7706 const str_index = try astgen.identAsString(ident_token);
7707 break :blk .wrap(.{ .error_value = str_index });
7708 },
7709 else => if (value.toOptional() == underscore_node) {
7710 break :blk .wrap(.under);
7711 } else {
7712 scratch_scope.instructions_top = parent_gz.instructions.items.len;
7713 defer scratch_scope.unstack();
7714 const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal);
7715 if (!scratch_scope.endsWithNoReturn()) {
7716 _ = try scratch_scope.addBreakWithSrcNode(.break_inline, switch_block, item_result, item);
7717 }
7718 const item_slice = scratch_scope.instructionsSlice();
7719 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(item_slice, &.{switch_block});
7720 try payloads.ensureUnusedCapacity(gpa, body_len);
7721 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, item_slice, &.{switch_block});
7722 break :blk .wrap(.{ .body_len = body_len });
7723 },
7724 };
7725 if (is_multi_case) {
7726 if (is_range) {
7727 const offset = multi_item_offset + items_len + range_i;
7728 payloads.items[multi_item_body_table + offset] = body_start;
7729 payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
7730 range_i += 1;
7731 } else {
7732 const offset = multi_item_offset + item_i;
7733 payloads.items[multi_item_body_table + offset] = body_start;
7734 payloads.items[multi_items_infos_start + offset] = @bitCast(item_info);
7735 item_i += 1;
7736 }
7737 } else {
7738 payloads.items[scalar_body_table + scalar_case_index] = body_start;
7739 payloads.items[scalar_item_infos_start + scalar_case_index] = @bitCast(item_info);
7740 }
7741 }
7742 }
7743 if (is_multi_case) {
7744 assert(item_i == items_len and range_i == 2 * ranges_len);
7745 payloads.items[multi_case_items_lens_start + multi_case_index] = items_len;
7746 if (any_ranges) {
7747 payloads.items[multi_case_ranges_lens_start + multi_case_index] = ranges_len;
7748 }
7749 multi_item_offset += items_len + 2 * ranges_len;
7750 }
7751
7752 // Capture and prong body
78637753
7864 var dbg_var_name: Zir.NullTerminatedString = .empty;7754 var dbg_var_payload_name: Zir.NullTerminatedString = .empty;
7865 var dbg_var_inst: Zir.Inst.Ref = undefined;7755 var dbg_var_payload_inst: Zir.Inst.Ref = undefined;
7866 var dbg_var_tag_name: Zir.NullTerminatedString = .empty;7756 var dbg_var_tag_name: Zir.NullTerminatedString = .empty;
7867 var dbg_var_tag_inst: Zir.Inst.Ref = undefined;7757 var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
7868 var has_tag_capture = false;7758 var has_tag_capture = false;
7869 var capture_val_scope: Scope.LocalVal = undefined;7759 var err_capture_scope: Scope.LocalVal = undefined;
7870 var tag_scope: Scope.LocalVal = undefined;7760 var payload_capture_scope: Scope.LocalVal = undefined;
7761 var tag_capture_scope: Scope.LocalVal = undefined;
78717762
7872 var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;7763 var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
78737764
7874 const sub_scope = blk: {7765 // Check all captures and make them available to the prong body.
7875 const payload_token = case.payload_token orelse break :blk &case_scope.base;7766 // Potential captures are:
7767 // - for regular switch: payload and tag
7768 // - for error switch: switch operand and payload
7769 const prong_body_scope: *Scope = scope: {
7770 const switch_scope: *Scope = if (needs_non_err_handling) blk: {
7771 // We want to have the captured error we're switching on in scope!
7772 err_capture_scope = .{
7773 .parent = &scratch_scope.base,
7774 .gen_zir = &scratch_scope,
7775 .name = err_capture_name,
7776 .inst = switch_operand,
7777 .token_src = err_token,
7778 .id_cat = .capture,
7779 };
7780 break :blk &err_capture_scope.base;
7781 } else &scratch_scope.base;
7782
7783 const payload_token = case.payload_token orelse break :scope switch_scope;
7876 const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;7784 const capture_is_ref = tree.tokenTag(payload_token) == .asterisk;
7877 const ident = payload_token + @intFromBool(capture_is_ref);7785 const ident = payload_token + @intFromBool(capture_is_ref);
78787786
...@@ -7882,34 +7790,42 @@ fn switchExpr(...@@ -7882,34 +7790,42 @@ fn switchExpr(
7882 var payload_sub_scope: *Scope = undefined;7790 var payload_sub_scope: *Scope = undefined;
7883 if (mem.eql(u8, ident_slice, "_")) {7791 if (mem.eql(u8, ident_slice, "_")) {
7884 if (capture_is_ref) {7792 if (capture_is_ref) {
7793 // |*_, tag| is invalid, so we can fail early
7885 return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});7794 return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
7886 }7795 }
7887 payload_sub_scope = &case_scope.base;7796 capture = .none;
7797 payload_sub_scope = switch_scope;
7888 } else {7798 } else {
7889 const capture_name = try astgen.identAsString(ident);7799 const capture_name = try astgen.identAsString(ident);
7890 try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture);7800 try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture);
7891 capture_val_scope = .{7801 payload_capture_scope = .{
7892 .parent = &case_scope.base,7802 .parent = switch_scope,
7893 .gen_zir = &case_scope,7803 .gen_zir = &scratch_scope,
7894 .name = capture_name,7804 .name = capture_name,
7895 .inst = switch_block.toRef(),7805 .inst = payload_capture_inst.toRef(),
7896 .token_src = ident,7806 .token_src = ident,
7897 .id_cat = .capture,7807 .id_cat = .capture,
7898 };7808 };
7899 dbg_var_name = capture_name;7809 dbg_var_payload_name = payload_capture_scope.name;
7900 dbg_var_inst = switch_block.toRef();7810 dbg_var_payload_inst = payload_capture_scope.inst;
7901 payload_sub_scope = &capture_val_scope.base;7811 payload_sub_scope = &payload_capture_scope.base;
7902 }7812 }
79037813
7904 const tag_token = if (tree.tokenTag(ident + 1) == .comma)7814 if (is_err_switch and capture == .by_ref) {
7905 ident + 27815 return astgen.failTok(ident, "error set cannot be captured by reference", .{});
7906 else7816 }
7907 break :blk payload_sub_scope;7817
7818 const tag_token = if (tree.tokenTag(ident + 1) == .comma) blk: {
7819 break :blk ident + 2;
7820 } else if (capture == .none) {
7821 // discarding the capture is only valid if the tag is captured
7822 // whether the tag capture is discarded is handled below
7823 return astgen.failTok(payload_token, "discard of capture; omit it instead", .{});
7824 } else break :scope payload_sub_scope;
7825
7908 const tag_slice = tree.tokenSlice(tag_token);7826 const tag_slice = tree.tokenSlice(tag_token);
7909 if (mem.eql(u8, tag_slice, "_")) {7827 if (mem.eql(u8, tag_slice, "_")) {
7910 try astgen.appendErrorTok(tag_token, "discard of tag capture; omit it instead", .{});7828 return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{});
7911 } else if (case.inline_token == null) {
7912 return astgen.failTok(tag_token, "tag capture on non-inline prong", .{});
7913 }7829 }
7914 const tag_name = try astgen.identAsString(tag_token);7830 const tag_name = try astgen.identAsString(tag_token);
7915 try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");7831 try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");
...@@ -7917,123 +7833,136 @@ fn switchExpr(...@@ -7917,123 +7833,136 @@ fn switchExpr(
7917 assert(any_has_tag_capture);7833 assert(any_has_tag_capture);
7918 has_tag_capture = true;7834 has_tag_capture = true;
79197835
7920 tag_scope = .{7836 if (is_err_switch) {
7837 return astgen.failTok(tag_token, "cannot capture tag of error union", .{});
7838 }
7839
7840 tag_capture_scope = .{
7921 .parent = payload_sub_scope,7841 .parent = payload_sub_scope,
7922 .gen_zir = &case_scope,7842 .gen_zir = &scratch_scope,
7923 .name = tag_name,7843 .name = tag_name,
7924 .inst = tag_inst.toRef(),7844 .inst = tag_capture_inst.toRef(),
7925 .token_src = tag_token,7845 .token_src = tag_token,
7926 .id_cat = .@"switch tag capture",7846 .id_cat = .@"switch tag capture",
7927 };7847 };
7928 dbg_var_tag_name = tag_name;7848 dbg_var_tag_name = tag_capture_scope.name;
7929 dbg_var_tag_inst = tag_inst.toRef();7849 dbg_var_tag_inst = tag_capture_scope.inst;
7930 break :blk &tag_scope.base;7850 break :scope &tag_capture_scope.base;
7931 };7851 };
79327852
7933 const header_index: u32 = @intCast(payloads.items.len);7853 if (capture != .none) assert(any_has_payload_capture);
7934 const body_len_index = if (is_multi_case) blk: {7854 if (is_err_switch) {
7935 if (case_node.toOptional() == underscore_case_node) {7855 assert(!any_payload_is_ref); // should have failed by now
7936 payloads.items[under_case_index] = header_index;7856 assert(!any_has_tag_capture); // should have failed by now
7937 if (special_prongs.hasOneAdditionalItem()) {7857 }
7938 try payloads.resize(gpa, header_index + 2); // item, body_len
7939 const maybe_item_node = case.ast.values[0];
7940 const item_node = if (maybe_item_node.toOptional() == underscore_node)
7941 case.ast.values[1]
7942 else
7943 maybe_item_node;
7944 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7945 payloads.items[header_index] = @intFromEnum(item_inst);
7946 break :blk header_index + 1;
7947 }
7948 } else {
7949 payloads.items[multi_case_table + multi_case_index] = header_index;
7950 multi_case_index += 1;
7951 }
7952 try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
7953
7954 // items
7955 var items_len: u32 = 0;
7956 for (case.ast.values) |item_node| {
7957 if (item_node.toOptional() == underscore_node or
7958 tree.nodeTag(item_node) == .switch_range)
7959 {
7960 continue;
7961 }
7962 items_len += 1;
7963
7964 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
7965 try payloads.append(gpa, @intFromEnum(item_inst));
7966 }
7967
7968 // ranges
7969 var ranges_len: u32 = 0;
7970 for (case.ast.values) |range| {
7971 if (tree.nodeTag(range) != .switch_range) {
7972 continue;
7973 }
7974 ranges_len += 1;
7975
7976 const first_node, const last_node = tree.nodeData(range).node_and_node;
7977 const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item);
7978 const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item);
7979 try payloads.appendSlice(gpa, &[_]u32{
7980 @intFromEnum(first), @intFromEnum(last),
7981 });
7982 }
7983
7984 payloads.items[header_index] = items_len;
7985 payloads.items[header_index + 1] = ranges_len;
7986 break :blk header_index + 2;
7987 } else if (case_node.toOptional() == else_case_node) blk: {
7988 payloads.items[else_case_index] = header_index;
7989 try payloads.resize(gpa, header_index + 1); // body_len
7990 break :blk header_index;
7991 } else if (case_node.toOptional() == underscore_case_node) blk: {
7992 assert(!special_prongs.hasAdditionalItems());
7993 payloads.items[under_case_index] = header_index;
7994 try payloads.resize(gpa, header_index + 1); // body_len
7995 break :blk header_index;
7996 } else blk: {
7997 payloads.items[scalar_case_table + scalar_case_index] = header_index;
7998 scalar_case_index += 1;
7999 try payloads.resize(gpa, header_index + 2); // item, body_len
8000 const item_node = case.ast.values[0];
8001 const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item);
8002 payloads.items[header_index] = @intFromEnum(item_inst);
8003 break :blk header_index + 1;
8004 };
80057858
8006 {7859 prong_body: {
8007 // temporarily stack case_scope on parent_gz7860 scratch_scope.instructions_top = parent_gz.instructions.items.len;
8008 case_scope.instructions_top = parent_gz.instructions.items.len;7861 defer scratch_scope.unstack();
8009 defer case_scope.unstack();
80107862
8011 if (dbg_var_name != .empty) {7863 if (dbg_var_payload_name != .empty) {
8012 try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);7864 try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst);
8013 }7865 }
8014 if (dbg_var_tag_name != .empty) {7866 if (dbg_var_tag_name != .empty) {
8015 try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);7867 try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst);
7868 }
7869 if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) {
7870 _ = try scratch_scope.addSaveErrRetIndex(.always);
8016 }7871 }
8017 const target_expr_node = case.ast.target_expr;7872 const target_expr_node = case.ast.target_expr;
8018 const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);7873 const case_result = try fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint);
8019 try checkUsed(parent_gz, &case_scope.base, sub_scope);7874 if (needs_non_err_handling) {
8020 if (!parent_gz.refIsNoReturn(case_result)) {7875 // If we would check `scratch_scope` here, we would get a false
8021 _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);7876 // positive, that being the switch operand itself!
7877 try checkUsed(parent_gz, &err_capture_scope.base, prong_body_scope);
7878 } else {
7879 try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope);
7880 }
7881 if (!scratch_scope.endsWithNoReturn()) {
7882 // As our last action before the break, "pop" the error trace if needed
7883 if (do_err_trace) {
7884 try restoreErrRetIndex(
7885 &scratch_scope,
7886 .{ .block = switch_block },
7887 block_scope.break_result_info,
7888 target_expr_node,
7889 case_result,
7890 );
7891 }
7892 _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
8022 }7893 }
80237894
8024 const case_slice = case_scope.instructionsSlice();7895 const body_slice = scratch_scope.instructionsSlice();
8025 const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block};7896 const body_start: u32 = @intCast(payloads.items.len);
8026 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts);7897 const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts);
8027 try payloads.ensureUnusedCapacity(gpa, body_len);7898 try payloads.ensureUnusedCapacity(gpa, body_len);
8028 payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{7899 astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts);
7900
7901 if (case_node.toOptional() == else_case_node) {
7902 assert(case.ast.values.len == 0);
7903
7904 // Specific `else` bodies can cause Sema to omit the
7905 // "unreachable else prong" error so that certain generic code
7906 // patterns don't trigger it. We do that for these bodies:
7907 // `else => unreachable,`
7908 // `else => return,`
7909 // `else => |e| return e,` (where `e` is any identifier)
7910 const is_simple_noreturn = switch (tree.nodeTag(target_expr_node)) {
7911 .unreachable_literal => true, // `=> unreachable,`
7912 .@"return" => simple_noreturn: {
7913 const retval_node = tree.nodeData(target_expr_node).opt_node.unwrap() orelse {
7914 break :simple_noreturn true; // `=> return,`
7915 };
7916 // Check for `=> |e| return e,`
7917 if (capture != .by_val) break :simple_noreturn false;
7918 if (tree.nodeTag(retval_node) != .identifier) break :simple_noreturn false;
7919 const payload_name = try astgen.identAsString(case.payload_token.?);
7920 const retval_name = try astgen.identAsString(tree.nodeMainToken(retval_node));
7921 break :simple_noreturn payload_name == retval_name;
7922 },
7923 else => false,
7924 };
7925
7926 else_info = .{
7927 .body_len = @intCast(body_len),
7928 .capture = capture,
7929 .is_inline = case.inline_token != null,
7930 .has_tag_capture = has_tag_capture,
7931 .is_simple_noreturn = is_simple_noreturn,
7932 };
7933 else_prong_body_start = body_start;
7934 break :prong_body;
7935 }
7936
7937 // We allow prongs with error items which are not inside the error set
7938 // being switched on if their body is `=> comptime unreachable,`.
7939 const is_comptime_unreach = comptime_unreach: {
7940 if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false;
7941 const comptime_node = tree.nodeData(target_expr_node).node;
7942 break :comptime_unreach tree.nodeTag(comptime_node) == .unreachable_literal;
7943 };
7944
7945 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = .{
8029 .body_len = @intCast(body_len),7946 .body_len = @intCast(body_len),
8030 .capture = capture,7947 .capture = capture,
8031 .is_inline = case.inline_token != null,7948 .is_inline = case.inline_token != null,
8032 .has_tag_capture = has_tag_capture,7949 .has_tag_capture = has_tag_capture,
8033 });7950 .is_comptime_unreach = is_comptime_unreach,
8034 appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts);7951 };
7952
7953 if (is_multi_case) {
7954 payloads.items[multi_prong_body_table + multi_case_index] = body_start;
7955 payloads.items[multi_prong_infos_start + multi_case_index] = @bitCast(prong_info);
7956 multi_case_index += 1;
7957 } else {
7958 // prong body start is implicit, it's right behind our only item.
7959 payloads.items[scalar_prong_infos_start + scalar_case_index] = @bitCast(prong_info);
7960 scalar_case_index += 1;
7961 }
8035 }7962 }
8036 }7963 }
7964 assert(scalar_case_index + multi_case_index + @intFromBool(has_else) == case_nodes.len);
7965 assert(multi_items_infos_start + multi_item_offset == bodies_start);
80377966
8038 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {7967 if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) {
8039 try astgen.appendErrorTok(label_token, "unused switch label", .{});7968 try astgen.appendErrorTok(label_token, "unused switch label", .{});
...@@ -8042,84 +7971,100 @@ fn switchExpr(...@@ -8042,84 +7971,100 @@ fn switchExpr(
8042 // Now that the item expressions are generated we can add this.7971 // Now that the item expressions are generated we can add this.
8043 try parent_gz.instructions.append(gpa, switch_block);7972 try parent_gz.instructions.append(gpa, switch_block);
80447973
7974 // We've collected all of the data we need! Now we just have to finalize it
7975 // by copying our bodies from `payloads` to `extra`, this time in the order
7976 // expected by ZIR consumers.
7977
8045 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +7978 try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len +
8046 @intFromBool(multi_cases_len != 0) +7979 @intFromBool(multi_cases_len > 0) + // multi_cases_len
8047 @intFromBool(any_has_tag_capture) +7980 @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder
8048 payloads.items.len - scratch_top);7981 @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder
80497982 @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset
8050 const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{7983 @intFromBool(needs_non_err_handling) + // non_err_info
8051 .operand = raw_operand,7984 @intFromBool(has_else) + // else_info
8052 .bits = Zir.Inst.SwitchBlock.Bits{7985 payloads.items.len - body_table_end); // item infos and bodies
8053 .has_multi_cases = multi_cases_len != 0,7986
8054 .special_prongs = special_prongs,7987 // singular pieces of data
8055 .any_has_tag_capture = any_has_tag_capture,7988 const zir_payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
8056 .any_non_inline_capture = any_non_inline_capture,7989 .raw_operand = raw_operand,
7990 .bits = .{
7991 .has_multi_cases = multi_cases_len > 0,
7992 .any_ranges = any_ranges,
7993 .has_else = has_else,
7994 .has_under = has_under,
8057 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,7995 .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue,
7996 .any_maybe_runtime_capture = any_maybe_runtime_capture,
7997 .payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder,
7998 .tag_capture_inst_is_placeholder = tag_capture_inst_is_placeholder,
8058 .scalar_cases_len = @intCast(scalar_cases_len),7999 .scalar_cases_len = @intCast(scalar_cases_len),
8059 },8000 },
8060 });8001 });
8002 astgen.instructions.items(.data)[@intFromEnum(switch_block)].pl_node.payload_index = zir_payload_index;
80618003
8062 if (multi_cases_len != 0) {8004 if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len);
8063 astgen.extra.appendAssumeCapacity(multi_cases_len);8005 if (payload_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(payload_capture_inst));
8006 if (tag_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(tag_capture_inst));
8007 if (needs_non_err_handling) {
8008 const catch_or_if_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node);
8009 astgen.extra.appendAssumeCapacity(@bitCast(@intFromEnum(catch_or_if_src_node_offset)));
8010 astgen.extra.appendAssumeCapacity(@bitCast(non_err_info));
8064 }8011 }
8012 if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info));
80658013
8066 if (any_has_tag_capture) {8014 const extra_payloads_start = astgen.extra.items.len;
8067 astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst));
8068 }
80698015
8070 const zir_datas = astgen.instructions.items(.data);8016 // body lens
8071 zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;8017 astgen.extra.appendSliceAssumeCapacity(payloads.items[body_table_end..bodies_start]);
80728018
8019 // bodies
8020 if (needs_non_err_handling) {
8021 const body = payloads.items[non_err_prong_body_start..][0..non_err_info.body_len];
8022 astgen.extra.appendSliceAssumeCapacity(body);
8023 }
8073 if (has_else) {8024 if (has_else) {
8074 const start_index = payloads.items[else_case_index];8025 const body = payloads.items[else_prong_body_start..][0..else_info.body_len];
8075 var end_index = start_index + 1;8026 astgen.extra.appendSliceAssumeCapacity(body);
8076 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]);8027 }
8077 end_index += prong_info.body_len;8028 for (0..scalar_cases_len) |scalar_i| {
8078 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);8029 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]);
8079 }8030 const item_body_start = payloads.items[scalar_body_table + scalar_i];
8080 if (has_under) {8031 const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0];
8081 const start_index = payloads.items[under_case_index];8032 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]);
8082 var body_len_index = start_index;8033 const prong_body_start = item_body_start + item_body.len;
8083 var end_index = start_index;8034 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8084 switch (underscore_additional_items) {8035 astgen.extra.appendSliceAssumeCapacity(prong_body);
8085 .none => {8036 astgen.extra.appendSliceAssumeCapacity(item_body);
8086 end_index += 1;8037 }
8087 },8038 var multi_item_i: usize = 0;
8088 .one => {8039 for (0..multi_cases_len) |multi_i| {
8089 body_len_index += 1;8040 const prong_body_start = payloads.items[multi_prong_body_table + multi_i];
8090 end_index += 2;8041 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]);
8091 },8042 const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len];
8092 .many => {8043 astgen.extra.appendSliceAssumeCapacity(prong_body);
8093 body_len_index += 2;8044
8094 const items_len = payloads.items[start_index];8045 const items_len = payloads.items[multi_case_items_lens_start + multi_i];
8095 const ranges_len = payloads.items[start_index + 1];8046 const ranges_len = if (any_ranges) ranges_len: {
8096 end_index += 3 + items_len + 2 * ranges_len;8047 break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i];
8097 },8048 } else 0;
8098 }8049 // The table entries and body lens are already in the correct order so we
8099 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);8050 // don't have to differentiate between items and ranges here.
8100 end_index += prong_info.body_len;8051 for (0..items_len + 2 * ranges_len) |_| {
8101 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);8052 const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]);
8102 }8053 if (item_info.bodyLen()) |body_len| {
8103 for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| {8054 const body_start = payloads.items[multi_item_body_table + multi_item_i];
8104 var body_len_index = start_index;8055 const body = payloads.items[body_start..][0..body_len];
8105 var end_index = start_index;8056 astgen.extra.appendSliceAssumeCapacity(body);
8106 const table_index = scalar_case_table + i;8057 }
8107 if (table_index < multi_case_table) {8058 multi_item_i += 1;
8108 body_len_index += 1;
8109 end_index += 2;
8110 } else {
8111 body_len_index += 2;
8112 const items_len = payloads.items[start_index];
8113 const ranges_len = payloads.items[start_index + 1];
8114 end_index += 3 + items_len + 2 * ranges_len;
8115 }8059 }
8116 const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
8117 end_index += prong_info.body_len;
8118 astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
8119 }8060 }
81208061
8062 // Make sure we didn't forget anything...
8063 assert(multi_item_i == total_items_len + 2 * total_ranges_len - scalar_cases_len);
8064 assert(astgen.extra.items.len - extra_payloads_start == payloads.items.len - body_table_end);
8065
8121 if (need_result_rvalue) {8066 if (need_result_rvalue) {
8122 return rvalue(parent_gz, ri, switch_block.toRef(), node);8067 return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
8123 } else {8068 } else {
8124 return switch_block.toRef();8069 return switch_block.toRef();
8125 }8070 }
...@@ -8382,7 +8327,6 @@ fn localVarRef(...@@ -8382,7 +8327,6 @@ fn localVarRef(
8382) InnerError!Zir.Inst.Ref {8327) InnerError!Zir.Inst.Ref {
8383 const astgen = gz.astgen;8328 const astgen = gz.astgen;
8384 const name_str_index = try astgen.identAsString(ident_token);8329 const name_str_index = try astgen.identAsString(ident_token);
8385 var s = scope;
8386 var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already8330 var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
8387 var found_needs_tunnel: bool = undefined; // defined when `found_already != null`8331 var found_needs_tunnel: bool = undefined; // defined when `found_already != null`
8388 var found_namespaces_out: u32 = undefined; // defined when `found_already != null`8332 var found_namespaces_out: u32 = undefined; // defined when `found_already != null`
...@@ -8392,10 +8336,8 @@ fn localVarRef(...@@ -8392,10 +8336,8 @@ fn localVarRef(
8392 // defined by `num_namespaces_out != 0`8336 // defined by `num_namespaces_out != 0`
8393 var capturing_namespace: *Scope.Namespace = undefined;8337 var capturing_namespace: *Scope.Namespace = undefined;
83948338
8395 while (true) switch (s.tag) {8339 find_scope: switch (scope.unwrap()) {
8396 .local_val => {8340 .local_val => |local_val| {
8397 const local_val = s.cast(Scope.LocalVal).?;
8398
8399 if (local_val.name == name_str_index) {8341 if (local_val.name == name_str_index) {
8400 // Locals cannot shadow anything, so we do not need to look for ambiguous8342 // Locals cannot shadow anything, so we do not need to look for ambiguous
8401 // references in this case.8343 // references in this case.
...@@ -8418,10 +8360,9 @@ fn localVarRef(...@@ -8418,10 +8360,9 @@ fn localVarRef(
84188360
8419 return rvalueNoCoercePreRef(gz, ri, value_inst, ident);8361 return rvalueNoCoercePreRef(gz, ri, value_inst, ident);
8420 }8362 }
8421 s = local_val.parent;8363 continue :find_scope local_val.parent.unwrap();
8422 },8364 },
8423 .local_ptr => {8365 .local_ptr => |local_ptr| {
8424 const local_ptr = s.cast(Scope.LocalPtr).?;
8425 if (local_ptr.name == name_str_index) {8366 if (local_ptr.name == name_str_index) {
8426 if (ri.rl == .discard and ri.ctx == .assignment) {8367 if (ri.rl == .discard and ri.ctx == .assignment) {
8427 local_ptr.discarded = .fromToken(ident_token);8368 local_ptr.discarded = .fromToken(ident_token);
...@@ -8470,12 +8411,11 @@ fn localVarRef(...@@ -8470,12 +8411,11 @@ fn localVarRef(
8470 },8411 },
8471 }8412 }
8472 }8413 }
8473 s = local_ptr.parent;8414 continue :find_scope local_ptr.parent.unwrap();
8474 },8415 },
8475 .gen_zir => s = s.cast(GenZir).?.parent,8416 .gen_zir => |gen_zir| continue :find_scope gen_zir.parent.unwrap(),
8476 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,8417 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
8477 .namespace => {8418 .namespace => |ns| {
8478 const ns = s.cast(Scope.Namespace).?;
8479 if (ns.decls.get(name_str_index)) |i| {8419 if (ns.decls.get(name_str_index)) |i| {
8480 if (found_already) |f| {8420 if (found_already) |f| {
8481 return astgen.failNodeNotes(ident, "ambiguous reference", .{}, &.{8421 return astgen.failNodeNotes(ident, "ambiguous reference", .{}, &.{
...@@ -8490,10 +8430,10 @@ fn localVarRef(...@@ -8490,10 +8430,10 @@ fn localVarRef(
8490 }8430 }
8491 num_namespaces_out += 1;8431 num_namespaces_out += 1;
8492 capturing_namespace = ns;8432 capturing_namespace = ns;
8493 s = ns.parent;8433 continue :find_scope ns.parent.unwrap();
8494 },8434 },
8495 .top => break,8435 .top => break :find_scope,
8496 };8436 }
8497 if (found_already == null) {8437 if (found_already == null) {
8498 const ident_name = try astgen.identifierTokenString(ident_token);8438 const ident_name = try astgen.identifierTokenString(ident_token);
8499 return astgen.failNode(ident, "use of undeclared identifier '{s}'", .{ident_name});8439 return astgen.failNode(ident, "use of undeclared identifier '{s}'", .{ident_name});
...@@ -11785,6 +11725,26 @@ const Scope = struct {...@@ -11785,6 +11725,26 @@ const Scope = struct {
11785 };11725 };
11786 }11726 }
1178711727
11728 fn unwrap(base: *Scope) Unwrapped {
11729 return switch (base.tag) {
11730 inline else => |tag| @unionInit(
11731 Unwrapped,
11732 @tagName(tag),
11733 @alignCast(@fieldParentPtr("base", base)),
11734 ),
11735 };
11736 }
11737
11738 const Unwrapped = union(Tag) {
11739 gen_zir: *GenZir,
11740 local_val: *LocalVal,
11741 local_ptr: *LocalPtr,
11742 defer_normal: *Defer,
11743 defer_error: *Defer,
11744 namespace: *Namespace,
11745 top: *Top,
11746 };
11747
11788 const Tag = enum {11748 const Tag = enum {
11789 gen_zir,11749 gen_zir,
11790 local_val,11750 local_val,
...@@ -11910,8 +11870,8 @@ const GenZir = struct {...@@ -11910,8 +11870,8 @@ const GenZir = struct {
11910 /// whenever we know Sema will analyze the current block with `is_comptime`,11870 /// whenever we know Sema will analyze the current block with `is_comptime`,
11911 /// for instance when we're within a `struct_decl` or a `block_comptime`.11871 /// for instance when we're within a `struct_decl` or a `block_comptime`.
11912 is_comptime: bool,11872 is_comptime: bool,
11913 /// Whether we're in an expression within a `@TypeOf` operand. In this case, closure of runtime11873 /// Whether we're in an expression within a `@TypeOf` operand. In this case,
11914 /// variables is permitted where it is usually not.11874 /// closure of runtime variables is permitted where it is usually not.
11915 is_typeof: bool = false,11875 is_typeof: bool = false,
11916 /// This is set to true for a `GenZir` of a `block_inline`, indicating that11876 /// This is set to true for a `GenZir` of a `block_inline`, indicating that
11917 /// exits from this block should use `break_inline` rather than `break`.11877 /// exits from this block should use `break_inline` rather than `break`.
...@@ -11932,10 +11892,27 @@ const GenZir = struct {...@@ -11932,10 +11892,27 @@ const GenZir = struct {
11932 /// if use is strictly nested. This saves prior size of list for unstacking.11892 /// if use is strictly nested. This saves prior size of list for unstacking.
11933 instructions_top: usize,11893 instructions_top: usize,
11934 label: ?Label = null,11894 label: ?Label = null,
11935 break_block: Zir.Inst.OptionalIndex = .none,11895 /// If `true`, unlabeled `break` and `continue` exprs can target this `GenZir`.
11936 continue_block: Zir.Inst.OptionalIndex = .none,11896 allow_unlabeled_control_flow: bool = false,
11897 /// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
11898 /// this is unused and may be `undefined`.
11899 /// Otherwise, this is the target for a `break` instruction when a `break`
11900 /// targets this `GenZir`.
11901 break_target: Zir.Inst.Index = undefined,
11902 /// If `label` is `null` and `unlabeled_control_flow_target` is `false`,
11903 /// this is unused and may be `undefined`.
11904 continue_target: union(enum) {
11905 /// A `continue` cannot target this `GenZir`; emit an error.
11906 none,
11907 /// Emit a `break` instruction targeting this block.
11908 @"break": Zir.Inst.Index,
11909 /// Emit a `switch_continue` instruction targeting this `switch_block`.
11910 switch_continue: Zir.Inst.Index,
11911 } = undefined,
11937 /// Only valid when setBreakResultInfo is called.11912 /// Only valid when setBreakResultInfo is called.
11938 break_result_info: AstGen.ResultInfo = undefined,11913 break_result_info: AstGen.ResultInfo = undefined,
11914 /// If `continue_target` is *not* `switch_continue`, this is unused and may
11915 /// be `undefined`.
11939 continue_result_info: AstGen.ResultInfo = undefined,11916 continue_result_info: AstGen.ResultInfo = undefined,
1194011917
11941 suspend_node: Ast.Node.OptionalIndex = .none,11918 suspend_node: Ast.Node.OptionalIndex = .none,
...@@ -12002,7 +11979,6 @@ const GenZir = struct {...@@ -12002,7 +11979,6 @@ const GenZir = struct {
1200211979
12003 const Label = struct {11980 const Label = struct {
12004 token: Ast.TokenIndex,11981 token: Ast.TokenIndex,
12005 block_inst: Zir.Inst.Index,
12006 used: bool = false,11982 used: bool = false,
12007 used_for_continue: bool = false,11983 used_for_continue: bool = false,
12008 };11984 };
...@@ -13365,11 +13341,9 @@ fn detectLocalShadowing(...@@ -13365,11 +13341,9 @@ fn detectLocalShadowing(
13365 });13341 });
13366 }13342 }
1336713343
13368 var s = scope;
13369 var outer_scope = false;13344 var outer_scope = false;
13370 while (true) switch (s.tag) {13345 find_scope: switch (scope.unwrap()) {
13371 .local_val => {13346 .local_val => |local_val| {
13372 const local_val = s.cast(Scope.LocalVal).?;
13373 if (local_val.name == ident_name) {13347 if (local_val.name == ident_name) {
13374 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));13348 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));
13375 const name = try gpa.dupe(u8, name_slice);13349 const name = try gpa.dupe(u8, name_slice);
...@@ -13395,10 +13369,9 @@ fn detectLocalShadowing(...@@ -13395,10 +13369,9 @@ fn detectLocalShadowing(
13395 ),13369 ),
13396 });13370 });
13397 }13371 }
13398 s = local_val.parent;13372 continue :find_scope local_val.parent.unwrap();
13399 },13373 },
13400 .local_ptr => {13374 .local_ptr => |local_ptr| {
13401 const local_ptr = s.cast(Scope.LocalPtr).?;
13402 if (local_ptr.name == ident_name) {13375 if (local_ptr.name == ident_name) {
13403 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));13376 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));
13404 const name = try gpa.dupe(u8, name_slice);13377 const name = try gpa.dupe(u8, name_slice);
...@@ -13424,14 +13397,12 @@ fn detectLocalShadowing(...@@ -13424,14 +13397,12 @@ fn detectLocalShadowing(
13424 ),13397 ),
13425 });13398 });
13426 }13399 }
13427 s = local_ptr.parent;13400 continue :find_scope local_ptr.parent.unwrap();
13428 },13401 },
13429 .namespace => {13402 .namespace => |ns| {
13430 outer_scope = true;13403 outer_scope = true;
13431 const ns = s.cast(Scope.Namespace).?;
13432 const decl_node = ns.decls.get(ident_name) orelse {13404 const decl_node = ns.decls.get(ident_name) orelse {
13433 s = ns.parent;13405 continue :find_scope ns.parent.unwrap();
13434 continue;
13435 };13406 };
13436 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));13407 const name_slice = mem.span(astgen.nullTerminatedString(ident_name));
13437 const name = try gpa.dupe(u8, name_slice);13408 const name = try gpa.dupe(u8, name_slice);
...@@ -13442,13 +13413,13 @@ fn detectLocalShadowing(...@@ -13442,13 +13413,13 @@ fn detectLocalShadowing(
13442 try astgen.errNoteNode(decl_node, "declared here", .{}),13413 try astgen.errNoteNode(decl_node, "declared here", .{}),
13443 });13414 });
13444 },13415 },
13445 .gen_zir => {13416 .gen_zir => |gen_zir| {
13446 s = s.cast(GenZir).?.parent;
13447 outer_scope = true;13417 outer_scope = true;
13418 continue :find_scope gen_zir.parent.unwrap();
13448 },13419 },
13449 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,13420 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
13450 .top => break,13421 .top => break :find_scope,
13451 };13422 }
13452}13423}
1345313424
13454const LineColumn = struct { u32, u32 };13425const LineColumn = struct { u32, u32 };
...@@ -13685,10 +13656,8 @@ fn scanContainer(...@@ -13685,10 +13656,8 @@ fn scanContainer(
13685 continue;13656 continue;
13686 }13657 }
1368713658
13688 var s = namespace.parent;13659 find_scope: switch (namespace.parent.unwrap()) {
13689 while (true) switch (s.tag) {13660 .local_val => |local_val| {
13690 .local_val => {
13691 const local_val = s.cast(Scope.LocalVal).?;
13692 if (local_val.name == name_str_index) {13661 if (local_val.name == name_str_index) {
13693 try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{13662 try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{
13694 token_bytes, @tagName(local_val.id_cat),13663 token_bytes, @tagName(local_val.id_cat),
...@@ -13700,12 +13669,11 @@ fn scanContainer(...@@ -13700,12 +13669,11 @@ fn scanContainer(
13700 ),13669 ),
13701 });13670 });
13702 any_invalid_declarations = true;13671 any_invalid_declarations = true;
13703 break;13672 break :find_scope;
13704 }13673 }
13705 s = local_val.parent;13674 continue :find_scope local_val.parent.unwrap();
13706 },13675 },
13707 .local_ptr => {13676 .local_ptr => |local_ptr| {
13708 const local_ptr = s.cast(Scope.LocalPtr).?;
13709 if (local_ptr.name == name_str_index) {13677 if (local_ptr.name == name_str_index) {
13710 try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{13678 try astgen.appendErrorTokNotes(name_token, "declaration '{s}' shadows {s} from outer scope", .{
13711 token_bytes, @tagName(local_ptr.id_cat),13679 token_bytes, @tagName(local_ptr.id_cat),
...@@ -13717,15 +13685,15 @@ fn scanContainer(...@@ -13717,15 +13685,15 @@ fn scanContainer(
13717 ),13685 ),
13718 });13686 });
13719 any_invalid_declarations = true;13687 any_invalid_declarations = true;
13720 break;13688 break :find_scope;
13721 }13689 }
13722 s = local_ptr.parent;13690 continue :find_scope local_ptr.parent.unwrap();
13723 },13691 },
13724 .namespace => s = s.cast(Scope.Namespace).?.parent,13692 .namespace => |ns| continue :find_scope ns.parent.unwrap(),
13725 .gen_zir => s = s.cast(GenZir).?.parent,13693 .gen_zir => |gen_zir| continue :find_scope gen_zir.parent.unwrap(),
13726 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,13694 .defer_normal, .defer_error => |defer_scope| continue :find_scope defer_scope.parent.unwrap(),
13727 .top => break,13695 .top => break :find_scope,
13728 };13696 }
13729 }13697 }
1373013698
13731 if (!any_duplicates) {13699 if (!any_duplicates) {
...@@ -13776,6 +13744,19 @@ fn scanContainer(...@@ -13776,6 +13744,19 @@ fn scanContainer(
13776 return error.AnalysisFail;13744 return error.AnalysisFail;
13777}13745}
1377813746
13747fn appendPlaceholder(astgen: *AstGen) Allocator.Error!Zir.Inst.Index {
13748 const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
13749 try astgen.instructions.append(astgen.gpa, .{
13750 .tag = .extended,
13751 .data = .{ .extended = .{
13752 .opcode = .value_placeholder,
13753 .small = undefined,
13754 .operand = undefined,
13755 } },
13756 });
13757 return inst;
13758}
13759
13779/// Assumes capacity for body has already been added. Needed capacity taking into13760/// Assumes capacity for body has already been added. Needed capacity taking into
13780/// account fixups can be found with `countBodyLenAfterFixups`.13761/// account fixups can be found with `countBodyLenAfterFixups`.
13781fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void {13762fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void {
lib/std/zig/Zir.zig+397-291
...@@ -95,7 +95,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {...@@ -95,7 +95,6 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) ExtraData(T) {
95 Inst.Call.Flags,95 Inst.Call.Flags,
96 Inst.BuiltinCall.Flags,96 Inst.BuiltinCall.Flags,
97 Inst.SwitchBlock.Bits,97 Inst.SwitchBlock.Bits,
98 Inst.SwitchBlockErrUnion.Bits,
99 Inst.FuncFancy.Bits,98 Inst.FuncFancy.Bits,
100 Inst.Declaration.Flags,99 Inst.Declaration.Flags,
101 Inst.Param.Type,100 Inst.Param.Type,
...@@ -350,7 +349,8 @@ pub const Inst = struct {...@@ -350,7 +349,8 @@ pub const Inst = struct {
350 /// Uses the `break` union field.349 /// Uses the `break` union field.
351 break_inline,350 break_inline,
352 /// Branch from within a switch case to the case specified by the operand.351 /// Branch from within a switch case to the case specified by the operand.
353 /// Uses the `break` union field. `block_inst` refers to a `switch_block` or `switch_block_ref`.352 /// Uses the `break` union field. `block_inst` refers to a `switch_block`/
353 /// `switch_block_ref`/`switch_block_err_union`.
354 switch_continue,354 switch_continue,
355 /// Checks that comptime control flow does not happen inside a runtime block.355 /// Checks that comptime control flow does not happen inside a runtime block.
356 /// Uses the `un_node` union field.356 /// Uses the `un_node` union field.
...@@ -722,8 +722,10 @@ pub const Inst = struct {...@@ -722,8 +722,10 @@ pub const Inst = struct {
722 /// A switch expression. Uses the `pl_node` union field.722 /// A switch expression. Uses the `pl_node` union field.
723 /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.723 /// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.
724 switch_block_ref,724 switch_block_ref,
725 /// A switch on an error union `a catch |err| switch (err) {...}`.725 /// A switch on an error union:
726 /// Uses the `pl_node` union field. AST node is the `catch`, payload is `SwitchBlockErrUnion`.726 /// - `eu catch |err| switch (err) {...}`, AST node is the `catch`.
727 /// - `if (eu) |payload| {...} else |err| {...}`, AST node is the `if`.
728 /// Uses the `pl_node` union field. Payload is `SwitchBlock`.
727 switch_block_err_union,729 switch_block_err_union,
728 /// Check that operand type supports the dereference operand (.*).730 /// Check that operand type supports the dereference operand (.*).
729 /// Uses the `un_node` field.731 /// Uses the `un_node` field.
...@@ -3293,143 +3295,151 @@ pub const Inst = struct {...@@ -3293,143 +3295,151 @@ pub const Inst = struct {
3293 };3295 };
32943296
3295 /// Trailing:3297 /// Trailing:
3296 /// 0. multi_cases_len: u32 // if `has_multi_cases`3298 /// 0. multi_cases_len: u32, // If has_multi_cases is set.
3297 /// 1. err_capture_inst: u32 // if `any_uses_err_capture`3299 /// 1. payload_capture_placeholder: Inst.Index, // If payload_capture_inst_is_placeholder is set.
3298 /// 2. non_err_body {3300 /// // Index of instruction prongs use to refer to their payload capture.
3299 /// info: ProngInfo,3301 /// 2. tag_capture_placeholder: Inst.Index, // If tag_capture_inst_is_placeholder is set.
3300 /// inst: Index // for every `info.body_len`3302 /// // Index of instruction prongs use to refer to their tag capture.
3301 /// }3303 /// 3. catch_or_if_src_node_offset: Ast.Node.Offset, // If inst is switch_block_err_union.
3302 /// 3. else_body { // if `has_else`3304 /// 4. non_err_info: ProngInfo.NonErr, // If inst is switch_block_err_union.
3303 /// info: ProngInfo,3305 /// 5. else_info: ProngInfo.Else, // If has_else is set.
3304 /// inst: Index // for every `info.body_len`3306 /// 6. scalar_prong_info: ProngInfo, // for every scalar_cases_len
3305 /// }3307 /// 7. multi_prong_info: ProngInfo, // for every multi_cases_len
3306 /// 4. scalar_cases: { // for every `scalar_cases_len`3308 /// 8. multi_case_items_len: u32, // for every multi_cases_len
3307 /// item: Ref,3309 /// 9. multi_case_ranges_len: u32, // If has_ranges is set: for every multi_cases_len
3308 /// info: ProngInfo,3310 /// 10. scalar_item_info: ItemInfo, // for every scalar_cases_len
3309 /// inst: Index // for every `info.body_len`3311 /// 11. multi_items_info: { // for every multi_cases_len
3312 /// item_info: ItemInfo, // for each multi_case_items_len
3313 /// range_items_info: { // for each multi_case_ranges_len
3314 /// first_info: ItemInfo,
3315 /// last_info: ItemInfo,
3316 /// }
3317 /// }
3318 /// 12. non_err_body {
3319 /// body_inst: Index // for every non_err_info.body_len
3310 /// }3320 /// }
3311 /// 5. multi_cases: { // for every `multi_cases_len`3321 /// 13. else_body: { // If has_else is set.
3312 /// items_len: u32,3322 /// body_inst: Inst.Index, // for every else_info.body_len
3313 /// ranges_len: u32,3323 /// }
3314 /// info: ProngInfo,3324 /// 14. scalar_bodies: { // for every scalar_cases_len
3315 /// item: Ref // for every `items_len`3325 /// prong_body: { // for each body_len in scalar_prong_info
3316 /// ranges: { // for every `ranges_len`3326 /// body_inst: Inst.Index, // for every body_len
3317 /// item_first: Ref,3327 /// }
3318 /// item_last: Ref,3328 /// item_body: { // for each body_len in scalar_item_info
3329 /// body_inst: Inst.Index, // for every body_len
3319 /// }3330 /// }
3320 /// inst: Index // for every `info.body_len`
3321 /// }3331 /// }
3322 ///3332 /// 15. multi_bodies: { // for each multi_items_info
3323 /// When analyzing a case body, the switch instruction itself refers to the3333 /// prong_body: {
3324 /// captured error, or to the success value in `non_err_body`. Whether this3334 /// body_inst: Inst.Index, // for each multi_prong_info.body_len
3325 /// is captured by reference or by value depends on whether the `byref` bit3335 /// }
3326 /// is set for the corresponding body. `err_capture_inst` refers to the error3336 /// item_body: { // for each item_info
3327 /// capture outside of the `switch`, i.e. `err` in3337 /// body_inst: Inst.Index, // for every item_info.body_len
3328 /// `x catch |err| switch (err) { ... }`.3338 /// }
3329 pub const SwitchBlockErrUnion = struct {3339 /// range_bodies: { // for each .{first_info, last_info} in range_items_info
3330 operand: Ref,3340 /// first_body_inst: Inst.Index, // for every first_info.body_len
3341 /// last_body_inst: Inst.Index, // for every last_info.body_len
3342 /// }
3343 /// }
3344 pub const SwitchBlock = struct {
3345 /// Either `catch`/`if` or `switch` operand.
3346 raw_operand: Ref,
3331 bits: Bits,3347 bits: Bits,
3332 main_src_node_offset: Ast.Node.Offset,
33333348
3334 pub const Bits = packed struct(u32) {3349 pub const Bits = packed struct(u32) {
3335 /// If true, one or more prongs have multiple items.3350 /// If true, one or more prongs have multiple items.
3336 has_multi_cases: bool,3351 has_multi_cases: bool,
3337 /// If true, there is an else prong. This is mutually exclusive with `has_under`.3352 /// If true, one or more prongs have ranges.
3353 /// Only valid if `has_multi_cases` is also set.
3354 any_ranges: bool,
3338 has_else: bool,3355 has_else: bool,
3339 any_uses_err_capture: bool,3356 has_under: bool,
3340 payload_is_ref: bool,3357 /// If true, at least one prong contains a `continue`.
3358 /// Only valid if `has_label` is set.
3359 has_continue: bool,
3360 // If true, at least one prong has a non-inline payload/tag capture.
3361 any_maybe_runtime_capture: bool,
3362 payload_capture_inst_is_placeholder: bool,
3363 tag_capture_inst_is_placeholder: bool,
3341 scalar_cases_len: ScalarCasesLen,3364 scalar_cases_len: ScalarCasesLen,
33423365
3343 pub const ScalarCasesLen = u28;3366 // NOTE maybe don't steal any more bits from poor `scalar_cases_len`
3344 };3367 // and split `Bits` into two parts instead, `raw_operand` surely
33453368 // wouldn't mind donating a couple of bits for that purpose...
3346 pub const MultiProng = struct {3369 pub const ScalarCasesLen = u24;
3347 items: []const Ref,
3348 body: []const Index,
3349 };3370 };
3350 };
3351
3352 /// 0. multi_cases_len: u32 // If has_multi_cases is set.
3353 /// 1. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
3354 /// 2. else_body { // If special_prong.hasElse() is set.
3355 /// info: ProngInfo,
3356 /// body member Index for every info.body_len
3357 /// }
3358 /// 3. under_body { // If special_prong.hasUnder() is set.
3359 /// item: Ref, // If special_prong.hasOneAdditionalItem() is set.
3360 /// items_len: u32, // If special_prong.hasManyAdditionalItems() is set.
3361 /// ranges_len: u32, // If special_prong.hasManyAdditionalItems() is set.
3362 /// info: ProngInfo,
3363 /// item: Ref, // for every items_len
3364 /// ranges: { // for every ranges_len
3365 /// item_first: Ref,
3366 /// item_last: Ref,
3367 /// }
3368 /// body member Index for every info.body_len
3369 /// }
3370 /// 4. scalar_cases: { // for every scalar_cases_len
3371 /// item: Ref,
3372 /// info: ProngInfo,
3373 /// body member Index for every info.body_len
3374 /// }
3375 /// 5. multi_cases: { // for every multi_cases_len
3376 /// items_len: u32,
3377 /// ranges_len: u32,
3378 /// info: ProngInfo,
3379 /// item: Ref, // for every items_len
3380 /// ranges: { // for every ranges_len
3381 /// item_first: Ref,
3382 /// item_last: Ref,
3383 /// }
3384 /// body member Index for every info.body_len
3385 /// }
3386 ///
3387 /// When analyzing a case body, the switch instruction itself refers to the
3388 /// captured payload. Whether this is captured by reference or by value
3389 /// depends on whether the `byref` bit is set for the corresponding body.
3390 pub const SwitchBlock = struct {
3391 /// The operand passed to the `switch` expression. If this is a
3392 /// `switch_block`, this is the operand value; if `switch_block_ref` it
3393 /// is a pointer to the operand. `switch_block_ref` is always used if
3394 /// any prong has a byref capture.
3395 operand: Ref,
3396 bits: Bits,
33973371
3398 /// These are stored in trailing data in `extra` for each prong.
3399 pub const ProngInfo = packed struct(u32) {3372 pub const ProngInfo = packed struct(u32) {
3400 body_len: u28,3373 body_len: u27,
3401 capture: ProngInfo.Capture,3374 capture: ProngInfo.Capture,
3402 is_inline: bool,3375 is_inline: bool,
3403 has_tag_capture: bool,3376 has_tag_capture: bool,
3377 is_comptime_unreach: bool,
34043378
3405 pub const Capture = enum(u2) {3379 pub const Capture = enum(u2) {
3406 none,3380 none,
3407 by_val,3381 by_val,
3408 by_ref,3382 by_ref,
3409 };3383 };
3410 };
34113384
3412 pub const Bits = packed struct(u32) {3385 pub const NonErr = packed struct(u32) {
3413 /// If true, one or more prongs have multiple items.3386 body_len: u29,
3414 has_multi_cases: bool,3387 capture: ProngInfo.Capture,
3415 /// Information about the special prong.3388 operand_is_ref: bool,
3416 special_prongs: SpecialProngs,3389 };
3417 /// If true, at least one prong has an inline tag capture.
3418 any_has_tag_capture: bool,
3419 /// If true, at least one prong has a capture which may not
3420 /// be comptime-known via `inline`.
3421 any_non_inline_capture: bool,
3422 /// If true, at least one prong contains a `continue`.
3423 has_continue: bool,
3424 scalar_cases_len: ScalarCasesLen,
34253390
3426 pub const ScalarCasesLen = u25;3391 pub const Else = packed struct(u32) {
3392 body_len: u27,
3393 capture: ProngInfo.Capture,
3394 is_inline: bool,
3395 has_tag_capture: bool,
3396 is_simple_noreturn: bool,
3397 };
3427 };3398 };
34283399
3429 pub const MultiProng = struct {3400 pub const ItemInfo = packed struct(u32) {
3430 items: []const Ref,3401 kind: ItemInfo.Kind,
3431 body: []const Index,3402 data: u30,
3403
3404 pub const Kind = enum(u2) {
3405 enum_literal,
3406 error_value,
3407 body_len,
3408 under,
3409 };
3410
3411 pub const Unwrapped = union(ItemInfo.Kind) {
3412 enum_literal: Zir.NullTerminatedString,
3413 error_value: Zir.NullTerminatedString,
3414 body_len: u32,
3415 under,
3416 };
3417
3418 pub fn wrap(unwrapped: ItemInfo.Unwrapped) ItemInfo {
3419 const data_uncasted: u32 = switch (unwrapped) {
3420 .enum_literal => |str_index| @intFromEnum(str_index),
3421 .error_value => |str_index| @intFromEnum(str_index),
3422 .body_len => |body_len| body_len,
3423 .under => 0,
3424 };
3425 return .{ .kind = unwrapped, .data = @intCast(data_uncasted) };
3426 }
3427
3428 pub fn unwrap(item_info: ItemInfo) ItemInfo.Unwrapped {
3429 return switch (item_info.kind) {
3430 .enum_literal => .{ .enum_literal = @enumFromInt(item_info.data) },
3431 .error_value => .{ .error_value = @enumFromInt(item_info.data) },
3432 .body_len => .{ .body_len = item_info.data },
3433 .under => .under,
3434 };
3435 }
3436
3437 pub fn bodyLen(item_info: ItemInfo) ?u32 {
3438 return if (item_info.kind == .body_len) item_info.data else null;
3439 }
3432 };3440 };
3441
3442 pub const Kind = enum { default, ref, err_union };
3433 };3443 };
34343444
3435 pub const ArrayInitRefTy = struct {3445 pub const ArrayInitRefTy = struct {
...@@ -4004,69 +4014,6 @@ pub const Inst = struct {...@@ -4004,69 +4014,6 @@ pub const Inst = struct {
4004 };4014 };
4005};4015};
40064016
4007pub const SpecialProngs = enum(u3) {
4008 none = 0b000,
4009 /// Simple `else` prong.
4010 /// `else => {},`
4011 @"else" = 0b001,
4012 /// Simple `_` prong.
4013 /// `_ => {},`
4014 under = 0b010,
4015 /// Both an `else` and a `_` prong.
4016 /// `else => {},`
4017 /// `_ => {},`
4018 under_and_else = 0b011,
4019 /// `_` prong with 1 additional item.
4020 /// `a, _ => {},`
4021 under_one_item = 0b100,
4022 /// Both an `else` and a `_` prong with 1 additional item.
4023 /// `else => {},`
4024 /// `a, _ => {},`
4025 under_one_item_and_else = 0b101,
4026 /// `_` prong with >1 additional items.
4027 /// `a, _, b => {},`
4028 under_many_items = 0b110,
4029 /// Both an `else` and a `_` prong with >1 additional items.
4030 /// `else => {},`
4031 /// `a, _, b => {},`
4032 under_many_items_and_else = 0b111,
4033
4034 pub const AdditionalItems = enum(u3) {
4035 none = @intFromEnum(SpecialProngs.under),
4036 one = @intFromEnum(SpecialProngs.under_one_item),
4037 many = @intFromEnum(SpecialProngs.under_many_items),
4038 };
4039
4040 pub fn init(has_else: bool, has_under: bool, additional_items: AdditionalItems) SpecialProngs {
4041 const else_bit: u3 = @intFromBool(has_else);
4042 const under_bits: u3 = if (has_under)
4043 @intFromEnum(additional_items)
4044 else
4045 @intFromEnum(SpecialProngs.none);
4046 return @enumFromInt(else_bit | under_bits);
4047 }
4048
4049 pub fn hasElse(special_prongs: SpecialProngs) bool {
4050 return (@intFromEnum(special_prongs) & 0b001) != 0;
4051 }
4052
4053 pub fn hasUnder(special_prongs: SpecialProngs) bool {
4054 return (@intFromEnum(special_prongs) & 0b110) != 0;
4055 }
4056
4057 pub fn hasAdditionalItems(special_prongs: SpecialProngs) bool {
4058 return (@intFromEnum(special_prongs) & 0b100) != 0;
4059 }
4060
4061 pub fn hasOneAdditionalItem(special_prongs: SpecialProngs) bool {
4062 return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_one_item);
4063 }
4064
4065 pub fn hasManyAdditionalItems(special_prongs: SpecialProngs) bool {
4066 return (@intFromEnum(special_prongs) & 0b110) == @intFromEnum(SpecialProngs.under_many_items);
4067 }
4068};
4069
4070pub const DeclIterator = struct {4017pub const DeclIterator = struct {
4071 extra_index: u32,4018 extra_index: u32,
4072 decls_remaining: u32,4019 decls_remaining: u32,
...@@ -4842,8 +4789,45 @@ fn findTrackableInner(...@@ -4842,8 +4789,45 @@ fn findTrackableInner(
4842 const body = zir.bodySlice(extra.end, extra.data.body_len);4789 const body = zir.bodySlice(extra.end, extra.data.body_len);
4843 try zir.findTrackableBody(gpa, contents, defers, body);4790 try zir.findTrackableBody(gpa, contents, defers, body);
4844 },4791 },
4845 .switch_block, .switch_block_ref => return zir.findTrackableSwitch(gpa, contents, defers, inst, .normal),4792
4846 .switch_block_err_union => return zir.findTrackableSwitch(gpa, contents, defers, inst, .err_union),4793 .switch_block,
4794 .switch_block_ref,
4795 .switch_block_err_union,
4796 => {
4797 const zir_switch = zir.getSwitchBlock(inst);
4798 if (zir_switch.non_err_case) |non_err_case| {
4799 try zir.findTrackableBody(gpa, contents, defers, non_err_case.body);
4800 }
4801 if (zir_switch.else_case) |else_case| {
4802 try zir.findTrackableBody(gpa, contents, defers, else_case.body);
4803 }
4804 var extra_index = zir_switch.end;
4805 var case_it = zir_switch.iterateCases();
4806 while (case_it.next()) |case| {
4807 const prong_body = zir.bodySlice(extra_index, case.prong_info.body_len);
4808 extra_index += prong_body.len;
4809 try zir.findTrackableBody(gpa, contents, defers, prong_body);
4810 for (case.item_infos) |item_info| {
4811 if (item_info.bodyLen()) |body_len| {
4812 const item_body = zir.bodySlice(extra_index, body_len);
4813 extra_index += item_body.len;
4814 try zir.findTrackableBody(gpa, contents, defers, item_body);
4815 }
4816 }
4817 for (case.range_infos) |range_info| {
4818 if (range_info[0].bodyLen()) |body_len| {
4819 const first_body = zir.bodySlice(extra_index, body_len);
4820 extra_index += first_body.len;
4821 try zir.findTrackableBody(gpa, contents, defers, first_body);
4822 }
4823 if (range_info[1].bodyLen()) |body_len| {
4824 const last_body = zir.bodySlice(extra_index, body_len);
4825 extra_index += last_body.len;
4826 try zir.findTrackableBody(gpa, contents, defers, last_body);
4827 }
4828 }
4829 }
4830 },
48474831
4848 .suspend_block => @panic("TODO iterate suspend block"),4832 .suspend_block => @panic("TODO iterate suspend block"),
48494833
...@@ -4890,119 +4874,6 @@ fn findTrackableInner(...@@ -4890,119 +4874,6 @@ fn findTrackableInner(
4890 }4874 }
4891}4875}
48924876
4893fn findTrackableSwitch(
4894 zir: Zir,
4895 gpa: Allocator,
4896 contents: *DeclContents,
4897 defers: *std.AutoHashMapUnmanaged(u32, void),
4898 inst: Inst.Index,
4899 /// Distinguishes between `switch_block[_ref]` and `switch_block_err_union`.
4900 comptime kind: enum { normal, err_union },
4901) Allocator.Error!void {
4902 const inst_data = zir.instructions.items(.data)[@intFromEnum(inst)].pl_node;
4903 const extra = zir.extraData(switch (kind) {
4904 .normal => Inst.SwitchBlock,
4905 .err_union => Inst.SwitchBlockErrUnion,
4906 }, inst_data.payload_index);
4907
4908 var extra_index: usize = extra.end;
4909
4910 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
4911 const multi_cases_len = zir.extra[extra_index];
4912 extra_index += 1;
4913 break :blk multi_cases_len;
4914 } else 0;
4915
4916 if (switch (kind) {
4917 .normal => extra.data.bits.any_has_tag_capture,
4918 .err_union => extra.data.bits.any_uses_err_capture,
4919 }) {
4920 extra_index += 1;
4921 }
4922
4923 const has_special = switch (kind) {
4924 .normal => extra.data.bits.special_prongs != .none,
4925 .err_union => has_special: {
4926 // Handle `non_err_body` first.
4927 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4928 extra_index += 1;
4929 const body = zir.bodySlice(extra_index, prong_info.body_len);
4930 extra_index += body.len;
4931
4932 try zir.findTrackableBody(gpa, contents, defers, body);
4933
4934 break :has_special extra.data.bits.has_else;
4935 },
4936 };
4937
4938 if (has_special) {
4939 const has_else = if (kind == .normal)
4940 extra.data.bits.special_prongs.hasElse()
4941 else
4942 true;
4943 if (has_else) {
4944 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4945 extra_index += 1;
4946 const body = zir.bodySlice(extra_index, prong_info.body_len);
4947 extra_index += body.len;
4948
4949 try zir.findTrackableBody(gpa, contents, defers, body);
4950 }
4951 if (kind == .normal) {
4952 const special_prongs = extra.data.bits.special_prongs;
4953
4954 if (special_prongs.hasUnder()) {
4955 var trailing_items_len: u32 = 0;
4956 if (special_prongs.hasOneAdditionalItem()) {
4957 extra_index += 1;
4958 } else if (special_prongs.hasManyAdditionalItems()) {
4959 const items_len = zir.extra[extra_index];
4960 extra_index += 1;
4961 const ranges_len = zir.extra[extra_index];
4962 extra_index += 1;
4963 trailing_items_len = items_len + ranges_len * 2;
4964 }
4965 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4966 extra_index += 1 + trailing_items_len;
4967 const body = zir.bodySlice(extra_index, prong_info.body_len);
4968 extra_index += body.len;
4969
4970 try zir.findTrackableBody(gpa, contents, defers, body);
4971 }
4972 }
4973 }
4974
4975 {
4976 const scalar_cases_len = extra.data.bits.scalar_cases_len;
4977 for (0..scalar_cases_len) |_| {
4978 extra_index += 1;
4979 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4980 extra_index += 1;
4981 const body = zir.bodySlice(extra_index, prong_info.body_len);
4982 extra_index += body.len;
4983
4984 try zir.findTrackableBody(gpa, contents, defers, body);
4985 }
4986 }
4987 {
4988 for (0..multi_cases_len) |_| {
4989 const items_len = zir.extra[extra_index];
4990 extra_index += 1;
4991 const ranges_len = zir.extra[extra_index];
4992 extra_index += 1;
4993 const prong_info: Inst.SwitchBlock.ProngInfo = @bitCast(zir.extra[extra_index]);
4994 extra_index += 1;
4995
4996 extra_index += items_len + ranges_len * 2;
4997
4998 const body = zir.bodySlice(extra_index, prong_info.body_len);
4999 extra_index += body.len;
5000
5001 try zir.findTrackableBody(gpa, contents, defers, body);
5002 }
5003 }
5004}
5005
5006fn findTrackableBody(4877fn findTrackableBody(
5007 zir: Zir,4878 zir: Zir,
5008 gpa: Allocator,4879 gpa: Allocator,
...@@ -5337,6 +5208,241 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {...@@ -5337,6 +5208,241 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
5337 }5208 }
5338}5209}
53395210
5211pub fn getSwitchBlock(zir: *const Zir, switch_inst: Inst.Index) UnwrappedSwitchBlock {
5212 const has_non_err = switch (zir.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
5213 .switch_block, .switch_block_ref => false,
5214 .switch_block_err_union => true,
5215 else => unreachable,
5216 };
5217 const inst_data = zir.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node;
5218 const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index);
5219 const bits = extra.data.bits;
5220 var extra_index = extra.end;
5221 const multi_cases_len = if (bits.has_multi_cases) len: {
5222 const multi_cases_len = zir.extra[extra_index];
5223 extra_index += 1;
5224 break :len multi_cases_len;
5225 } else 0;
5226 const payload_capture_placeholder: Inst.OptionalIndex = if (bits.payload_capture_inst_is_placeholder) inst: {
5227 const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]);
5228 extra_index += 1;
5229 break :inst inst.toOptional();
5230 } else .none;
5231 const tag_capture_placeholder: Inst.OptionalIndex = if (bits.tag_capture_inst_is_placeholder) inst: {
5232 const inst: Inst.Index = @enumFromInt(zir.extra[extra_index]);
5233 extra_index += 1;
5234 break :inst inst.toOptional();
5235 } else .none;
5236 const catch_or_if_src_node_offset: Ast.Node.OptionalOffset = if (has_non_err) node_offset: {
5237 const node_offset: Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(zir.extra[extra_index])));
5238 extra_index += 1;
5239 break :node_offset node_offset.toOptional();
5240 } else .none;
5241 const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = if (has_non_err) non_err_info: {
5242 const non_err_info: Inst.SwitchBlock.ProngInfo.NonErr = @bitCast(zir.extra[extra_index]);
5243 extra_index += 1;
5244 break :non_err_info non_err_info;
5245 } else undefined;
5246 const else_info: Inst.SwitchBlock.ProngInfo.Else = if (bits.has_else) else_info: {
5247 const else_info: Inst.SwitchBlock.ProngInfo.Else = @bitCast(zir.extra[extra_index]);
5248 extra_index += 1;
5249 break :else_info else_info;
5250 } else undefined;
5251 const scalar_cases_len: u32 = bits.scalar_cases_len;
5252 const prong_infos: []const Inst.SwitchBlock.ProngInfo =
5253 @ptrCast(zir.extra[extra_index..][0 .. scalar_cases_len + multi_cases_len]);
5254 extra_index += prong_infos.len;
5255 const multi_case_items_lens = zir.extra[extra_index..][0..multi_cases_len];
5256 extra_index += multi_case_items_lens.len;
5257 const multi_case_ranges_lens: ?[]const u32 = if (bits.any_ranges) lens: {
5258 const multi_case_ranges_lens = zir.extra[extra_index..][0..multi_cases_len];
5259 extra_index += multi_case_ranges_lens.len;
5260 break :lens multi_case_ranges_lens;
5261 } else null;
5262 var total_items_len: usize = scalar_cases_len;
5263 for (multi_case_items_lens) |items_len| {
5264 total_items_len += items_len;
5265 }
5266 if (multi_case_ranges_lens) |ranges_lens| for (ranges_lens) |ranges_len| {
5267 total_items_len += 2 * ranges_len;
5268 };
5269 const item_infos: []const Inst.SwitchBlock.ItemInfo =
5270 @ptrCast(zir.extra[extra_index..][0..total_items_len]);
5271 extra_index += item_infos.len;
5272 const non_err_case: ?UnwrappedSwitchBlock.Case.NonErr = if (has_non_err) non_err_case: {
5273 const body = zir.bodySlice(extra_index, non_err_info.body_len);
5274 extra_index += body.len;
5275 break :non_err_case .{
5276 .body = body,
5277 .capture = non_err_info.capture,
5278 .operand_is_ref = non_err_info.operand_is_ref,
5279 };
5280 } else null;
5281 const else_case: ?UnwrappedSwitchBlock.Case.Else = if (bits.has_else) else_case: {
5282 const body = zir.bodySlice(extra_index, else_info.body_len);
5283 extra_index += body.len;
5284 break :else_case .{
5285 .index = .@"else",
5286 .body = body,
5287 .capture = else_info.capture,
5288 .is_inline = else_info.is_inline,
5289 .has_tag_capture = else_info.has_tag_capture,
5290 .is_simple_noreturn = else_info.is_simple_noreturn,
5291 };
5292 } else null;
5293 return .{
5294 .main_operand = extra.data.raw_operand,
5295 .switch_src_node_offset = inst_data.src_node,
5296 .catch_or_if_src_node_offset = catch_or_if_src_node_offset,
5297 .payload_capture_placeholder = payload_capture_placeholder,
5298 .tag_capture_placeholder = tag_capture_placeholder,
5299 .has_continue = bits.has_continue,
5300 .any_maybe_runtime_capture = bits.any_maybe_runtime_capture,
5301 .non_err_case = non_err_case,
5302 .else_case = else_case,
5303 .has_under = bits.has_under,
5304 .prong_infos = prong_infos,
5305 .multi_case_items_lens = multi_case_items_lens,
5306 .multi_case_ranges_lens = multi_case_ranges_lens,
5307 .item_infos = item_infos,
5308 .end = extra_index,
5309 };
5310}
5311
5312/// Trailing (starting at `end`):
5313/// 0. case_bodies: { // for each case in Case.Iterator.next()
5314/// prong_body: {
5315/// body_inst: Inst.Index, // for every case.prong_info.body_len,
5316/// }
5317/// item_body: { // for each body_len in case.item_infos
5318/// body_inst: Inst.Index, // for every body_len
5319/// }
5320/// range_bodies: { // for each .{first_info, last_info} in case.range_infos
5321/// first_body_inst: Inst.Index, // for every first_info.body_len
5322/// last_body_inst: Inst.Index, // for every last_info.body_len
5323/// }
5324/// }
5325pub const UnwrappedSwitchBlock = struct {
5326 /// Either `catch`/`if` or `switch` operand.
5327 main_operand: Inst.Ref,
5328 switch_src_node_offset: Ast.Node.Offset,
5329 catch_or_if_src_node_offset: Ast.Node.OptionalOffset,
5330 payload_capture_placeholder: Inst.OptionalIndex,
5331 tag_capture_placeholder: Inst.OptionalIndex,
5332 has_continue: bool,
5333 any_maybe_runtime_capture: bool,
5334 non_err_case: ?Case.NonErr,
5335 else_case: ?Case.Else,
5336 has_under: bool,
5337 // Refer to doc comment and `iterateCases` to access everything below correctly.
5338 prong_infos: []const Inst.SwitchBlock.ProngInfo,
5339 multi_case_items_lens: []const u32,
5340 multi_case_ranges_lens: ?[]const u32,
5341 item_infos: []const Inst.SwitchBlock.ItemInfo,
5342 end: usize,
5343
5344 pub fn anyRanges(unwrapped: *const UnwrappedSwitchBlock) bool {
5345 return unwrapped.multi_case_ranges_lens != null;
5346 }
5347
5348 pub fn scalarCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5349 return @intCast(unwrapped.prong_infos.len - unwrapped.multi_case_items_lens.len);
5350 }
5351
5352 pub fn multiCasesLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5353 return @intCast(unwrapped.multi_case_items_lens.len);
5354 }
5355
5356 pub fn totalItemsLen(unwrapped: *const UnwrappedSwitchBlock) u32 {
5357 var total_items_len: u32 = @intCast(unwrapped.item_infos.len);
5358 if (unwrapped.multi_case_ranges_lens) |ranges_lens| {
5359 for (ranges_lens) |len| total_items_len -= len;
5360 }
5361 return total_items_len;
5362 }
5363
5364 pub const Case = struct {
5365 index: Case.Index,
5366 prong_info: Inst.SwitchBlock.ProngInfo,
5367 item_infos: []const Inst.SwitchBlock.ItemInfo,
5368 range_infos: []const [2]Inst.SwitchBlock.ItemInfo,
5369
5370 pub const Index = packed struct(u32) {
5371 kind: enum(u1) { scalar, multi },
5372 value: u31,
5373
5374 pub const @"else": Case.Index = .{
5375 .kind = .scalar,
5376 .value = std.math.maxInt(u31),
5377 };
5378 };
5379
5380 pub const NonErr = struct {
5381 body: []const Inst.Index,
5382 capture: Inst.SwitchBlock.ProngInfo.Capture,
5383 operand_is_ref: bool,
5384 };
5385
5386 pub const Else = struct {
5387 index: Case.Index,
5388 body: []const Inst.Index,
5389 capture: Inst.SwitchBlock.ProngInfo.Capture,
5390 is_inline: bool,
5391 has_tag_capture: bool,
5392 is_simple_noreturn: bool,
5393 };
5394
5395 pub const Iterator = struct {
5396 next_idx: u32,
5397 prong_infos: []const Inst.SwitchBlock.ProngInfo,
5398 multi_case_items_lens: []const u32,
5399 multi_case_ranges_lens: ?[]const u32,
5400 item_infos: []const Inst.SwitchBlock.ItemInfo,
5401
5402 pub fn next(it: *Iterator) ?Case {
5403 const idx = it.next_idx;
5404 if (idx == it.prong_infos.len) return null;
5405 it.next_idx += 1;
5406 const scalar_cases_len = it.prong_infos.len - it.multi_case_items_lens.len;
5407 return if (idx < scalar_cases_len) .{
5408 .index = .{
5409 .kind = .scalar,
5410 .value = @intCast(idx),
5411 },
5412 .prong_info = it.prong_infos[idx],
5413 .item_infos = it.itemInfos(1),
5414 .range_infos = &.{},
5415 } else .{
5416 .index = .{
5417 .kind = .multi,
5418 .value = @intCast(idx - scalar_cases_len),
5419 },
5420 .prong_info = it.prong_infos[idx],
5421 .item_infos = it.itemInfos(it.multi_case_items_lens[idx - scalar_cases_len]),
5422 .range_infos = if (it.multi_case_ranges_lens) |ranges_lens| b: {
5423 break :b @ptrCast(it.itemInfos(2 * ranges_lens[idx - scalar_cases_len]));
5424 } else &.{},
5425 };
5426 }
5427 fn itemInfos(it: *Iterator, count: u32) []const Inst.SwitchBlock.ItemInfo {
5428 const lens = it.item_infos[0..count];
5429 it.item_infos = it.item_infos[count..];
5430 return lens;
5431 }
5432 };
5433 };
5434
5435 pub fn iterateCases(unwrapped: UnwrappedSwitchBlock) Case.Iterator {
5436 return .{
5437 .next_idx = 0,
5438 .prong_infos = unwrapped.prong_infos,
5439 .multi_case_items_lens = unwrapped.multi_case_items_lens,
5440 .multi_case_ranges_lens = unwrapped.multi_case_ranges_lens,
5441 .item_infos = unwrapped.item_infos,
5442 };
5443 }
5444};
5445
5340/// When the ZIR update tracking logic must be modified to consider new instructions,5446/// When the ZIR update tracking logic must be modified to consider new instructions,
5341/// change this constant to trigger compile errors at all relevant locations.5447/// change this constant to trigger compile errors at all relevant locations.
5342pub const inst_tracking_version = 0;5448pub const inst_tracking_version = 0;
src/Air/Liveness.zig+23-23
...@@ -825,10 +825,10 @@ fn analyzeOperands(...@@ -825,10 +825,10 @@ fn analyzeOperands(
825825
826 // This logic must synchronize with `will_die_immediately` in `AnalyzeBigOperands.init`.826 // This logic must synchronize with `will_die_immediately` in `AnalyzeBigOperands.init`.
827 const immediate_death = if (data.live_set.remove(inst)) blk: {827 const immediate_death = if (data.live_set.remove(inst)) blk: {
828 log.debug("[{}] %{d}: removed from live set", .{ pass, @intFromEnum(inst) });828 log.debug("[{t}] {f}: removed from live set", .{ pass, inst });
829 break :blk false;829 break :blk false;
830 } else blk: {830 } else blk: {
831 log.debug("[{}] %{d}: immediate death", .{ pass, @intFromEnum(inst) });831 log.debug("[{t}] {f}: immediate death", .{ pass, inst });
832 break :blk true;832 break :blk true;
833 };833 };
834834
...@@ -849,7 +849,7 @@ fn analyzeOperands(...@@ -849,7 +849,7 @@ fn analyzeOperands(
849 const mask = @as(Bpi, 1) << @as(OperandInt, @intCast(i));849 const mask = @as(Bpi, 1) << @as(OperandInt, @intCast(i));
850850
851 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {851 if ((try data.live_set.fetchPut(gpa, operand, {})) == null) {
852 log.debug("[{}] %{d}: added %{d} to live set (operand dies here)", .{ pass, @intFromEnum(inst), operand });852 log.debug("[{t}] {f}: added {f} to live set (operand dies here)", .{ pass, inst, operand });
853 tomb_bits |= mask;853 tomb_bits |= mask;
854 }854 }
855 }855 }
...@@ -988,19 +988,19 @@ fn analyzeInstBlock(...@@ -988,19 +988,19 @@ fn analyzeInstBlock(
988 },988 },
989989
990 .main_analysis => {990 .main_analysis => {
991 log.debug("[{}] %{f}: block live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });991 log.debug("[{t}] {f}: block live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
992 // We can move the live set because the body should have a noreturn992 // We can move the live set because the body should have a noreturn
993 // instruction which overrides the set.993 // instruction which overrides the set.
994 try data.block_scopes.put(gpa, inst, .{994 try data.block_scopes.put(gpa, inst, .{
995 .live_set = data.live_set.move(),995 .live_set = data.live_set.move(),
996 });996 });
997 defer {997 defer {
998 log.debug("[{}] %{f}: popped block scope", .{ pass, inst });998 log.debug("[{t}] {f}: popped block scope", .{ pass, inst });
999 var scope = data.block_scopes.fetchRemove(inst).?.value;999 var scope = data.block_scopes.fetchRemove(inst).?.value;
1000 scope.live_set.deinit(gpa);1000 scope.live_set.deinit(gpa);
1001 }1001 }
10021002
1003 log.debug("[{}] %{f}: pushed new block scope", .{ pass, inst });1003 log.debug("[{t}] {f}: pushed new block scope", .{ pass, inst });
1004 try analyzeBody(a, pass, data, body);1004 try analyzeBody(a, pass, data, body);
10051005
1006 // If the block is noreturn, block deaths not only aren't useful, they're impossible to1006 // If the block is noreturn, block deaths not only aren't useful, they're impossible to
...@@ -1027,7 +1027,7 @@ fn analyzeInstBlock(...@@ -1027,7 +1027,7 @@ fn analyzeInstBlock(
1027 }1027 }
1028 assert(measured_num == num_deaths); // post-live-set should be a subset of pre-live-set1028 assert(measured_num == num_deaths); // post-live-set should be a subset of pre-live-set
1029 try a.special.put(gpa, inst, extra_index);1029 try a.special.put(gpa, inst, extra_index);
1030 log.debug("[{}] %{f}: block deaths are {f}", .{1030 log.debug("[{t}] {f}: block deaths are {f}", .{
1031 pass,1031 pass,
1032 inst,1032 inst,
1033 fmtInstList(@ptrCast(a.extra.items[extra_index + 1 ..][0..num_deaths])),1033 fmtInstList(@ptrCast(a.extra.items[extra_index + 1 ..][0..num_deaths])),
...@@ -1064,7 +1064,7 @@ fn writeLoopInfo(...@@ -1064,7 +1064,7 @@ fn writeLoopInfo(
1064 const block_inst = key.*;1064 const block_inst = key.*;
1065 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));1065 a.extra.appendAssumeCapacity(@intFromEnum(block_inst));
1066 }1066 }
1067 log.debug("[{}] %{f}: includes breaks to {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) });1067 log.debug("[{t}] {f}: includes breaks to {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.breaks) });
10681068
1069 // Now we put the live operands from the loop body in too1069 // Now we put the live operands from the loop body in too
1070 const num_live = data.live_set.count();1070 const num_live = data.live_set.count();
...@@ -1076,7 +1076,7 @@ fn writeLoopInfo(...@@ -1076,7 +1076,7 @@ fn writeLoopInfo(
1076 const alive = key.*;1076 const alive = key.*;
1077 a.extra.appendAssumeCapacity(@intFromEnum(alive));1077 a.extra.appendAssumeCapacity(@intFromEnum(alive));
1078 }1078 }
1079 log.debug("[{}] %{f}: maintain liveness of {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) });1079 log.debug("[{t}] {f}: maintain liveness of {f}", .{ LivenessPass.loop_analysis, inst, fmtInstSet(&data.live_set) });
10801080
1081 try a.special.put(gpa, inst, extra_index);1081 try a.special.put(gpa, inst, extra_index);
10821082
...@@ -1117,7 +1117,7 @@ fn resolveLoopLiveSet(...@@ -1117,7 +1117,7 @@ fn resolveLoopLiveSet(
1117 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));1117 try data.live_set.ensureUnusedCapacity(gpa, @intCast(loop_live.len));
1118 for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {});1118 for (loop_live) |alive| data.live_set.putAssumeCapacity(alive, {});
11191119
1120 log.debug("[{}] %{f}: block live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });1120 log.debug("[{t}] {f}: block live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
11211121
1122 for (breaks) |block_inst| {1122 for (breaks) |block_inst| {
1123 // We might break to this block, so include every operand that the block needs alive1123 // We might break to this block, so include every operand that the block needs alive
...@@ -1130,7 +1130,7 @@ fn resolveLoopLiveSet(...@@ -1130,7 +1130,7 @@ fn resolveLoopLiveSet(
1130 }1130 }
1131 }1131 }
11321132
1133 log.debug("[{}] %{f}: loop live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });1133 log.debug("[{t}] {f}: loop live set is {f}", .{ LivenessPass.main_analysis, inst, fmtInstSet(&data.live_set) });
1134}1134}
11351135
1136fn analyzeInstLoop(1136fn analyzeInstLoop(
...@@ -1168,7 +1168,7 @@ fn analyzeInstLoop(...@@ -1168,7 +1168,7 @@ fn analyzeInstLoop(
1168 .live_set = data.live_set.move(),1168 .live_set = data.live_set.move(),
1169 });1169 });
1170 defer {1170 defer {
1171 log.debug("[{}] %{f}: popped loop block scop", .{ pass, inst });1171 log.debug("[{t}] {f}: popped loop block scop", .{ pass, inst });
1172 var scope = data.block_scopes.fetchRemove(inst).?.value;1172 var scope = data.block_scopes.fetchRemove(inst).?.value;
1173 scope.live_set.deinit(gpa);1173 scope.live_set.deinit(gpa);
1174 }1174 }
...@@ -1269,13 +1269,13 @@ fn analyzeInstCondBr(...@@ -1269,13 +1269,13 @@ fn analyzeInstCondBr(
1269 }1269 }
1270 }1270 }
12711271
1272 log.debug("[{}] %{f}: 'then' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });1272 log.debug("[{t}] {f}: 'then' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(then_mirrored_deaths.items) });
1273 log.debug("[{}] %{f}: 'else' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });1273 log.debug("[{t}] {f}: 'else' branch mirrored deaths are {f}", .{ pass, inst, fmtInstList(else_mirrored_deaths.items) });
12741274
1275 data.live_set.deinit(gpa);1275 data.live_set.deinit(gpa);
1276 data.live_set = then_live.move(); // Really the union of both live sets1276 data.live_set = then_live.move(); // Really the union of both live sets
12771277
1278 log.debug("[{}] %{f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });1278 log.debug("[{t}] {f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
12791279
1280 // Write the mirrored deaths to `extra`1280 // Write the mirrored deaths to `extra`
1281 const then_death_count = @as(u32, @intCast(then_mirrored_deaths.items.len));1281 const then_death_count = @as(u32, @intCast(then_mirrored_deaths.items.len));
...@@ -1343,7 +1343,7 @@ fn analyzeInstSwitchBr(...@@ -1343,7 +1343,7 @@ fn analyzeInstSwitchBr(
1343 });1343 });
1344 }1344 }
1345 defer if (is_dispatch_loop) {1345 defer if (is_dispatch_loop) {
1346 log.debug("[{}] %{f}: popped loop block scop", .{ pass, inst });1346 log.debug("[{t}] {f}: popped loop block scope", .{ pass, inst });
1347 var scope = data.block_scopes.fetchRemove(inst).?.value;1347 var scope = data.block_scopes.fetchRemove(inst).?.value;
1348 scope.live_set.deinit(gpa);1348 scope.live_set.deinit(gpa);
1349 };1349 };
...@@ -1401,13 +1401,13 @@ fn analyzeInstSwitchBr(...@@ -1401,13 +1401,13 @@ fn analyzeInstSwitchBr(
1401 }1401 }
14021402
1403 for (mirrored_deaths, 0..) |mirrored, i| {1403 for (mirrored_deaths, 0..) |mirrored, i| {
1404 log.debug("[{}] %{f}: case {} mirrored deaths are {f}", .{ pass, inst, i, fmtInstList(mirrored.items) });1404 log.debug("[{t}] {f}: case {} mirrored deaths are {f}", .{ pass, inst, i, fmtInstList(mirrored.items) });
1405 }1405 }
14061406
1407 data.live_set.deinit(gpa);1407 data.live_set.deinit(gpa);
1408 data.live_set = all_alive.move();1408 data.live_set = all_alive.move();
14091409
1410 log.debug("[{}] %{f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });1410 log.debug("[{t}] {f}: new live set is {f}", .{ pass, inst, fmtInstSet(&data.live_set) });
1411 }1411 }
14121412
1413 const else_death_count = @as(u32, @intCast(mirrored_deaths[ncases].items.len));1413 const else_death_count = @as(u32, @intCast(mirrored_deaths[ncases].items.len));
...@@ -1506,7 +1506,7 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {...@@ -1506,7 +1506,7 @@ fn AnalyzeBigOperands(comptime pass: LivenessPass) type {
15061506
1507 .main_analysis => {1507 .main_analysis => {
1508 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {1508 if ((try big.data.live_set.fetchPut(gpa, operand, {})) == null) {
1509 log.debug("[{}] %{f}: added %{f} to live set (operand dies here)", .{ pass, big.inst, operand });1509 log.debug("[{t}] {f}: added {f} to live set (operand dies here)", .{ pass, big.inst, operand });
1510 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;1510 big.extra_tombs[extra_byte] |= @as(u32, 1) << extra_bit;
1511 }1511 }
1512 },1512 },
...@@ -1568,9 +1568,9 @@ const FmtInstSet = struct {...@@ -1568,9 +1568,9 @@ const FmtInstSet = struct {
1568 return;1568 return;
1569 }1569 }
1570 var it = val.set.keyIterator();1570 var it = val.set.keyIterator();
1571 try w.print("%{f}", .{it.next().?.*});1571 try w.print("{f}", .{it.next().?.*});
1572 while (it.next()) |key| {1572 while (it.next()) |key| {
1573 try w.print(" %{f}", .{key.*});1573 try w.print(" {f}", .{key.*});
1574 }1574 }
1575 }1575 }
1576};1576};
...@@ -1587,9 +1587,9 @@ const FmtInstList = struct {...@@ -1587,9 +1587,9 @@ const FmtInstList = struct {
1587 try w.writeAll("[no instructions]");1587 try w.writeAll("[no instructions]");
1588 return;1588 return;
1589 }1589 }
1590 try w.print("%{f}", .{val.list[0]});1590 try w.print("{f}", .{val.list[0]});
1591 for (val.list[1..]) |inst| {1591 for (val.list[1..]) |inst| {
1592 try w.print(" %{f}", .{inst});1592 try w.print(" {f}", .{inst});
1593 }1593 }
1594 }1594 }
1595};1595};
src/Air/Liveness/Verify.zig+16-11
...@@ -73,7 +73,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -73,7 +73,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
73 .trap, .unreach => {73 .trap, .unreach => {
74 try self.verifyInstOperands(inst, .{ .none, .none, .none });74 try self.verifyInstOperands(inst, .{ .none, .none, .none });
75 // This instruction terminates the function, so everything should be dead75 // This instruction terminates the function, so everything should be dead
76 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});76 if (self.live.count() > 0) return invalid("{f}: instructions still alive", .{inst});
77 },77 },
7878
79 // unary79 // unary
...@@ -166,7 +166,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -166,7 +166,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
166 const un_op = data[@intFromEnum(inst)].un_op;166 const un_op = data[@intFromEnum(inst)].un_op;
167 try self.verifyInstOperands(inst, .{ un_op, .none, .none });167 try self.verifyInstOperands(inst, .{ un_op, .none, .none });
168 // This instruction terminates the function, so everything should be dead168 // This instruction terminates the function, so everything should be dead
169 if (self.live.count() > 0) return invalid("%{f}: instructions still alive", .{inst});169 if (self.live.count() > 0) return invalid("{f}: instructions still alive", .{inst});
170 },170 },
171 .dbg_var_ptr,171 .dbg_var_ptr,
172 .dbg_var_val,172 .dbg_var_val,
...@@ -441,7 +441,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -441,7 +441,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
441 .repeat => {441 .repeat => {
442 const repeat = data[@intFromEnum(inst)].repeat;442 const repeat = data[@intFromEnum(inst)].repeat;
443 const expected_live = self.loops.get(repeat.loop_inst) orelse443 const expected_live = self.loops.get(repeat.loop_inst) orelse
444 return invalid("%{d}: loop %{d} not in scope", .{ @intFromEnum(inst), @intFromEnum(repeat.loop_inst) });444 return invalid("{f}: loop {f} not in scope", .{ inst, repeat.loop_inst });
445445
446 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);446 try self.verifyMatchingLiveness(repeat.loop_inst, expected_live);
447 },447 },
...@@ -451,7 +451,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -451,7 +451,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
451 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));451 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
452452
453 const expected_live = self.loops.get(br.block_inst) orelse453 const expected_live = self.loops.get(br.block_inst) orelse
454 return invalid("%{d}: loop %{d} not in scope", .{ @intFromEnum(inst), @intFromEnum(br.block_inst) });454 return invalid("{f}: loop {f} not in scope", .{ inst, br.block_inst });
455455
456 try self.verifyMatchingLiveness(br.block_inst, expected_live);456 try self.verifyMatchingLiveness(br.block_inst, expected_live);
457 },457 },
...@@ -487,7 +487,12 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -487,7 +487,12 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
487 if (ip.isNoReturn(block_ty.toIntern())) {487 if (ip.isNoReturn(block_ty.toIntern())) {
488 assert(!self.blocks.contains(inst));488 assert(!self.blocks.contains(inst));
489 } else {489 } else {
490 var live = self.blocks.fetchRemove(inst).?.value;490 var live = if (self.blocks.fetchRemove(inst)) |kv| kv.value else {
491 return invalid(
492 "{f}: block of type '{f}' not terminated correctly",
493 .{ inst, block_ty.fmtDebug() },
494 );
495 };
491 defer live.deinit(self.gpa);496 defer live.deinit(self.gpa);
492497
493 try self.verifyMatchingLiveness(inst, live);498 try self.verifyMatchingLiveness(inst, live);
...@@ -502,7 +507,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -502,7 +507,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
502507
503 // The same stuff should be alive after the loop as before it.508 // The same stuff should be alive after the loop as before it.
504 const gop = try self.loops.getOrPut(self.gpa, inst);509 const gop = try self.loops.getOrPut(self.gpa, inst);
505 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});510 if (gop.found_existing) return invalid("{f}: loop already exists", .{inst});
506 defer {511 defer {
507 var live = self.loops.fetchRemove(inst).?;512 var live = self.loops.fetchRemove(inst).?;
508 live.value.deinit(self.gpa);513 live.value.deinit(self.gpa);
...@@ -551,7 +556,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {...@@ -551,7 +556,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
551 // after the loop as before it.556 // after the loop as before it.
552 {557 {
553 const gop = try self.loops.getOrPut(self.gpa, inst);558 const gop = try self.loops.getOrPut(self.gpa, inst);
554 if (gop.found_existing) return invalid("%{d}: loop already exists", .{@intFromEnum(inst)});559 if (gop.found_existing) return invalid("{f}: loop already exists", .{inst});
555 gop.value_ptr.* = self.live.move();560 gop.value_ptr.* = self.live.move();
556 }561 }
557 defer {562 defer {
...@@ -606,11 +611,11 @@ fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies...@@ -606,11 +611,11 @@ fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies
606 return;611 return;
607 };612 };
608 if (dies) {613 if (dies) {
609 if (!self.live.remove(operand)) return invalid("%{f}: dead operand %{f} reused and killed again", .{614 if (!self.live.remove(operand)) return invalid("{f}: dead operand {f} reused and killed again", .{
610 inst, operand,615 inst, operand,
611 });616 });
612 } else {617 } else {
613 if (!self.live.contains(operand)) return invalid("%{f}: dead operand %{f} reused", .{ inst, operand });618 if (!self.live.contains(operand)) return invalid("{f}: dead operand {f} reused", .{ inst, operand });
614 }619 }
615}620}
616621
...@@ -635,9 +640,9 @@ fn verifyInst(self: *Verify, inst: Air.Inst.Index) Error!void {...@@ -635,9 +640,9 @@ fn verifyInst(self: *Verify, inst: Air.Inst.Index) Error!void {
635}640}
636641
637fn verifyMatchingLiveness(self: *Verify, block: Air.Inst.Index, live: LiveMap) Error!void {642fn verifyMatchingLiveness(self: *Verify, block: Air.Inst.Index, live: LiveMap) Error!void {
638 if (self.live.count() != live.count()) return invalid("%{f}: different deaths across branches", .{block});643 if (self.live.count() != live.count()) return invalid("{f}: different deaths across branches", .{block});
639 var live_it = self.live.keyIterator();644 var live_it = self.live.keyIterator();
640 while (live_it.next()) |live_inst| if (!live.contains(live_inst.*)) return invalid("%{f}: different deaths across branches", .{block});645 while (live_it.next()) |live_inst| if (!live.contains(live_inst.*)) return invalid("{f}: different deaths across branches", .{block});
641}646}
642647
643fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} {648fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} {
src/RangeSet.zig+66-66
...@@ -1,102 +1,92 @@...@@ -1,102 +1,92 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Order = std.math.Order;
4
5const InternPool = @import("InternPool.zig");
6const Type = @import("Type.zig");
7const Value = @import("Value.zig");
8const Zcu = @import("Zcu.zig");
9const RangeSet = @This();1const RangeSet = @This();
10const LazySrcLoc = Zcu.LazySrcLoc;
112
12zcu: *Zcu,3ranges: std.ArrayList(Range),
13ranges: std.array_list.Managed(Range),
144
15pub const Range = struct {5pub const Range = struct {
16 first: InternPool.Index,6 first: Value,
17 last: InternPool.Index,7 last: Value,
18 src: LazySrcLoc,8 src: LazySrcLoc,
19};9};
2010
21pub fn init(allocator: std.mem.Allocator, zcu: *Zcu) RangeSet {11pub const empty: RangeSet = .{ .ranges = .empty };
22 return .{
23 .zcu = zcu,
24 .ranges = std.array_list.Managed(Range).init(allocator),
25 };
26}
2712
28pub fn deinit(self: *RangeSet) void {13pub fn deinit(self: *RangeSet, allocator: Allocator) void {
29 self.ranges.deinit();14 self.ranges.deinit(allocator);
15 self.* = undefined;
30}16}
3117
32pub fn add(18pub fn ensureUnusedCapacity(self: *RangeSet, allocator: Allocator, additional_count: usize) Allocator.Error!void {
33 self: *RangeSet,19 return self.ranges.ensureUnusedCapacity(allocator, additional_count);
34 first: InternPool.Index,20}
35 last: InternPool.Index,
36 src: LazySrcLoc,
37) !?LazySrcLoc {
38 const zcu = self.zcu;
39 const ip = &zcu.intern_pool;
40
41 const ty = ip.typeOf(first);
42 assert(ty == ip.typeOf(last));
4321
44 for (self.ranges.items) |range| {22pub fn addAssumeCapacity(set: *RangeSet, new: Range, ty: Type, zcu: *Zcu) ?LazySrcLoc {
45 assert(ty == ip.typeOf(range.first));23 assert(new.first.typeOf(zcu).eql(ty, zcu));
46 assert(ty == ip.typeOf(range.last));24 assert(new.last.typeOf(zcu).eql(ty, zcu));
4725
48 if (Value.fromInterned(last).compareScalar(.gte, Value.fromInterned(range.first), Type.fromInterned(ty), zcu) and26 for (set.ranges.items) |range| {
49 Value.fromInterned(first).compareScalar(.lte, Value.fromInterned(range.last), Type.fromInterned(ty), zcu))27 if (new.last.compareScalar(.gte, range.first, ty, zcu) and
28 new.first.compareScalar(.lte, range.last, ty, zcu))
50 {29 {
51 return range.src; // They overlap.30 return range.src; // They overlap.
52 }31 }
53 }32 }
5433 set.ranges.appendAssumeCapacity(new);
55 try self.ranges.append(.{
56 .first = first,
57 .last = last,
58 .src = src,
59 });
60 return null;34 return null;
61}35}
6236
63/// Assumes a and b do not overlap37pub fn add(set: *RangeSet, allocator: Allocator, new: Range, ty: Type, zcu: *Zcu) Allocator.Error!?LazySrcLoc {
64fn lessThan(zcu: *Zcu, a: Range, b: Range) bool {38 try set.ensureUnusedCapacity(allocator, 1);
65 const ty = Type.fromInterned(zcu.intern_pool.typeOf(a.first));39 return set.addAssumeCapacity(new, ty, zcu);
66 return Value.fromInterned(a.first).compareScalar(.lt, Value.fromInterned(b.first), ty, zcu);
67}40}
6841
69pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !bool {42const SortCtx = struct {
70 const zcu = self.zcu;43 ty: Type,
71 const ip = &zcu.intern_pool;44 zcu: *Zcu,
72 assert(ip.typeOf(first) == ip.typeOf(last));45};
7346/// Assumes a and b do not overlap
74 if (self.ranges.items.len == 0)47fn lessThan(ctx: SortCtx, a: Range, b: Range) bool {
75 return false;48 return a.first.compareScalar(.lt, b.first, ctx.ty, ctx.zcu);
7649}
77 std.mem.sort(Range, self.ranges.items, zcu, lessThan);
7850
79 if (self.ranges.items[0].first != first or51pub fn spans(
80 self.ranges.items[self.ranges.items.len - 1].last != last)52 set: *RangeSet,
53 allocator: Allocator,
54 first: Value,
55 last: Value,
56 ty: Type,
57 zcu: *Zcu,
58) Allocator.Error!bool {
59 assert(first.typeOf(zcu).eql(ty, zcu));
60 assert(last.typeOf(zcu).eql(ty, zcu));
61 if (set.ranges.items.len == 0) return false;
62
63 std.mem.sort(Range, set.ranges.items, SortCtx{ .ty = ty, .zcu = zcu }, lessThan);
64
65 if (!set.ranges.items[0].first.eql(first, ty, zcu) or
66 !set.ranges.items[set.ranges.items.len - 1].last.eql(last, ty, zcu))
81 {67 {
82 return false;68 return false;
83 }69 }
8470
85 var space: InternPool.Key.Int.Storage.BigIntSpace = undefined;71 const limbs = try allocator.alloc(
72 std.math.big.Limb,
73 std.math.big.int.calcTwosCompLimbCount(ty.intInfo(zcu).bits),
74 );
75 defer allocator.free(limbs);
76 var counter: std.math.big.int.Mutable = .init(limbs, 0);
8677
87 var counter = try std.math.big.int.Managed.init(self.ranges.allocator);78 var space: InternPool.Key.Int.Storage.BigIntSpace = undefined;
88 defer counter.deinit();
8979
90 // look for gaps80 // look for gaps
91 for (self.ranges.items[1..], 0..) |cur, i| {81 for (set.ranges.items[1..], 0..) |cur, i| {
92 // i starts counting from the second item.82 // i starts counting from the second item.
93 const prev = self.ranges.items[i];83 const prev = set.ranges.items[i];
9484
95 // prev.last + 1 == cur.first85 // prev.last + 1 == cur.first
96 try counter.copy(Value.fromInterned(prev.last).toBigInt(&space, zcu));86 counter.copy(prev.last.toBigInt(&space, zcu));
97 try counter.addScalar(&counter, 1);87 counter.addScalar(counter.toConst(), 1);
9888
99 const cur_start_int = Value.fromInterned(cur.first).toBigInt(&space, zcu);89 const cur_start_int = cur.first.toBigInt(&space, zcu);
100 if (!cur_start_int.eql(counter.toConst())) {90 if (!cur_start_int.eql(counter.toConst())) {
101 return false;91 return false;
102 }92 }
...@@ -104,3 +94,13 @@ pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !...@@ -104,3 +94,13 @@ pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !
10494
105 return true;95 return true;
106}96}
97
98const std = @import("std");
99const assert = std.debug.assert;
100const Allocator = std.mem.Allocator;
101
102const InternPool = @import("InternPool.zig");
103const Type = @import("Type.zig");
104const Value = @import("Value.zig");
105const Zcu = @import("Zcu.zig");
106const LazySrcLoc = Zcu.LazySrcLoc;
src/Sema.zig+2613-3049
...@@ -509,7 +509,7 @@ pub const Block = struct {...@@ -509,7 +509,7 @@ pub const Block = struct {
509 .parent = parent,509 .parent = parent,
510 .sema = parent.sema,510 .sema = parent.sema,
511 .namespace = parent.namespace,511 .namespace = parent.namespace,
512 .instructions = .{},512 .instructions = .empty,
513 .label = null,513 .label = null,
514 .inlining = parent.inlining,514 .inlining = parent.inlining,
515 .comptime_reason = parent.comptime_reason,515 .comptime_reason = parent.comptime_reason,
...@@ -1927,9 +1927,8 @@ fn analyzeBodyInner(...@@ -1927,9 +1927,8 @@ fn analyzeBodyInner(
1927 break :msg msg;1927 break :msg msg;
1928 });1928 });
1929 }1929 }
1930 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);1930 const is_non_err_val = (try sema.resolveIsNonErrVal(block, operand_src, err_union)).?;
1931 assert(is_non_err != .none);1931 if (is_non_err_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, operand_src, null);
1932 const is_non_err_val = try sema.resolveConstDefinedValue(block, operand_src, is_non_err, null);
1933 if (is_non_err_val.toBool()) {1932 if (is_non_err_val.toBool()) {
1934 break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false);1933 break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false);
1935 }1934 }
...@@ -1945,9 +1944,8 @@ fn analyzeBodyInner(...@@ -1945,9 +1944,8 @@ fn analyzeBodyInner(
1945 const inline_body = sema.code.bodySlice(extra.end, extra.data.body_len);1944 const inline_body = sema.code.bodySlice(extra.end, extra.data.body_len);
1946 const operand = try sema.resolveInst(extra.data.operand);1945 const operand = try sema.resolveInst(extra.data.operand);
1947 const err_union = try sema.analyzeLoad(block, src, operand, operand_src);1946 const err_union = try sema.analyzeLoad(block, src, operand, operand_src);
1948 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);1947 const is_non_err_val = (try sema.resolveIsNonErrVal(block, operand_src, err_union)).?;
1949 assert(is_non_err != .none);1948 if (is_non_err_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, operand_src, null);
1950 const is_non_err_val = try sema.resolveConstDefinedValue(block, operand_src, is_non_err, null);
1951 if (is_non_err_val.toBool()) {1949 if (is_non_err_val.toBool()) {
1952 break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false);1950 break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false);
1953 }1951 }
...@@ -6498,26 +6496,23 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com...@@ -6498,26 +6496,23 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com
64986496
6499 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {6497 switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
6500 .switch_block, .switch_block_ref => {},6498 .switch_block, .switch_block_ref => {},
6499 .switch_block_err_union => unreachable, // wrong code path!
6501 else => unreachable, // assertion failure6500 else => unreachable, // assertion failure
6502 }6501 }
65036502
6504 const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;6503 const operand_ty = (try sema.resolveInst(switch_inst.toRef())).toType();
6505 const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;6504 const operand = try sema.coerce(start_block, operand_ty, uncoerced_operand, operand_src);
6506 const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
6507
6508 const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
6509
6510 try sema.validateRuntimeValue(start_block, operand_src, operand);6505 try sema.validateRuntimeValue(start_block, operand_src, operand);
65116506
6512 // We want to generate a `switch_dispatch` instruction with the switch condition,6507 // We want to generate a `switch_dispatch` instruction with the switch condition,
6513 // possibly preceded by a store to the stack alloc containing the raw operand.6508 // possibly preceded by a store to the stack alloc containing the raw operand.
6514 // However, to avoid too much special-case state in Sema, this is handled by the6509 // However, to avoid too much special-case state in Sema, this is handled by the
6515 // `switch` lowering logic. As such, we will find the `Block` corresponding to the6510 // `switch` lowering logic. As such, we will find the `Block` corresponding to
6516 // parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge6511 // the parent `switch_block[_ref]` instruction, create a dummy `br`, and add a
6517 // to signal to the switch logic to rewrite this into an appropriate dispatch.6512 // merge to signal to the switch logic to rewrite this into an appropriate dispatch.
65186513
6519 var block = start_block;6514 var block = start_block;
6520 while (true) {6515 while (true) : (block = block.parent.?) {
6521 if (block.label) |label| {6516 if (block.label) |label| {
6522 if (label.zir_block == switch_inst) {6517 if (label.zir_block == switch_inst) {
6523 const br_ref = try start_block.addBr(label.merges.block_inst, operand);6518 const br_ref = try start_block.addBr(label.merges.block_inst, operand);
...@@ -6531,7 +6526,6 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com...@@ -6531,7 +6526,6 @@ fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) Com
6531 return;6526 return;
6532 }6527 }
6533 }6528 }
6534 block = block.parent.?;
6535 }6529 }
6536}6530}
65376531
...@@ -8485,8 +8479,20 @@ fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: b...@@ -8485,8 +8479,20 @@ fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: b
8485 sema.code.nullTerminatedString(extra.field_name_start),8479 sema.code.nullTerminatedString(extra.field_name_start),
8486 .no_embedded_nulls,8480 .no_embedded_nulls,
8487 );8481 );
8488
8489 const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;8482 const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;
8483 return sema.analyzeDeclLiteral(block, src, name, orig_ty, do_coerce);
8484}
8485
8486fn analyzeDeclLiteral(
8487 sema: *Sema,
8488 block: *Block,
8489 src: LazySrcLoc,
8490 name: InternPool.NullTerminatedString,
8491 orig_ty: Type,
8492 do_coerce: bool,
8493) CompileError!Air.Inst.Ref {
8494 const pt = sema.pt;
8495 const zcu = pt.zcu;
84908496
8491 const uncoerced_result = res: {8497 const uncoerced_result = res: {
8492 if (orig_ty.toIntern() == .generic_poison_type) {8498 if (orig_ty.toIntern() == .generic_poison_type) {
...@@ -8960,6 +8966,7 @@ fn analyzeErrUnionCode(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air...@@ -8960,6 +8966,7 @@ fn analyzeErrUnionCode(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air
8960 const result_ty = operand_ty.errorUnionSet(zcu);8966 const result_ty = operand_ty.errorUnionSet(zcu);
89618967
8962 if (try sema.resolveDefinedValue(block, src, operand)) |val| {8968 if (try sema.resolveDefinedValue(block, src, operand)) |val| {
8969 if (val.getErrorName(zcu) == .none) return .unreachable_value;
8963 return Air.internedToRef((try pt.intern(.{ .err = .{8970 return Air.internedToRef((try pt.intern(.{ .err = .{
8964 .ty = result_ty.toIntern(),8971 .ty = result_ty.toIntern(),
8965 .name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,8972 .name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,
...@@ -8997,7 +9004,7 @@ fn analyzeErrUnionCodePtr(sema: *Sema, block: *Block, src: LazySrcLoc, operand:...@@ -8997,7 +9004,7 @@ fn analyzeErrUnionCodePtr(sema: *Sema, block: *Block, src: LazySrcLoc, operand:
89979004
8998 if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {9005 if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
8999 if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| {9006 if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| {
9000 assert(val.getErrorName(zcu) != .none);9007 if (val.getErrorName(zcu) == .none) return .unreachable_value;
9001 return Air.internedToRef((try pt.intern(.{ .err = .{9008 return Air.internedToRef((try pt.intern(.{ .err = .{
9002 .ty = result_ty.toIntern(),9009 .ty = result_ty.toIntern(),
9003 .name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,9010 .name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,
...@@ -10519,1284 +10526,1581 @@ fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE...@@ -10519,1284 +10526,1581 @@ fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE
10519 return Air.internedToRef(sentinel_ty.toIntern());10526 return Air.internedToRef(sentinel_ty.toIntern());
10520}10527}
1052110528
10522/// Holds common data used when analyzing or resolving switch prong bodies,10529fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
10523/// including setting up captures.10530 const tracy = trace(@src());
10524const SwitchProngAnalysis = struct {10531 defer tracy.end();
10525 sema: *Sema,
10526 /// The block containing the `switch_block` itself.
10527 parent_block: *Block,
10528 operand: Operand,
10529 /// If this switch is on an error set, this is the type to assign to the
10530 /// `else` prong. If `null`, the prong should be unreachable.
10531 else_error_ty: ?Type,
10532 /// The index of the `switch_block` instruction itself.
10533 switch_block_inst: Zir.Inst.Index,
10534 /// The dummy index into which inline tag captures should be placed. May be
10535 /// undefined if no prong has a tag capture.
10536 tag_capture_inst: Zir.Inst.Index,
10537
10538 const Operand = union(enum) {
10539 /// This switch will be dispatched only once, with the given operand.
10540 simple: struct {
10541 /// The raw switch operand value. Always defined.
10542 by_val: Air.Inst.Ref,
10543 /// The switch operand *pointer*. Defined only if there is a prong
10544 /// with a by-ref capture.
10545 by_ref: Air.Inst.Ref,
10546 /// The switch condition value. For unions, `operand` is the union
10547 /// and `cond` is its enum tag value.
10548 cond: Air.Inst.Ref,
10549 },
10550 /// This switch may be dispatched multiple times with `continue` syntax.
10551 /// As such, the operand is stored in an alloc if needed.
10552 loop: struct {
10553 /// The `alloc` containing the `switch` operand for the active dispatch.
10554 /// Each prong must load from this `alloc` to get captures.
10555 /// If there are no captures, this may be undefined.
10556 operand_alloc: Air.Inst.Ref,
10557 /// Whether `operand_alloc` contains a by-val operand or a by-ref
10558 /// operand.
10559 operand_is_ref: bool,
10560 /// The switch condition value for the *initial* dispatch. For
10561 /// unions, this is the enum tag value.
10562 init_cond: Air.Inst.Ref,
10563 },
10564 };
10565
10566 /// Resolve a switch prong which is determined at comptime to have no peers.
10567 /// Uses `resolveBlockBody`. Sets up captures as needed.
10568 fn resolveProngComptime(
10569 spa: SwitchProngAnalysis,
10570 child_block: *Block,
10571 prong_type: enum { normal, special },
10572 prong_body: []const Zir.Inst.Index,
10573 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
10574 /// Must use the `switch_capture` field in `offset`.
10575 capture_src: LazySrcLoc,
10576 /// The set of all values which can reach this prong. May be undefined
10577 /// if the prong is special or contains ranges.
10578 case_vals: []const Air.Inst.Ref,
10579 /// The inline capture of this prong. If this is not an inline prong,
10580 /// this is `.none`.
10581 inline_case_capture: Air.Inst.Ref,
10582 /// Whether this prong has an inline tag capture. If `true`, then
10583 /// `inline_case_capture` cannot be `.none`.
10584 has_tag_capture: bool,
10585 merges: *Block.Merges,
10586 ) CompileError!Air.Inst.Ref {
10587 const sema = spa.sema;
10588 const src = spa.parent_block.nodeOffset(
10589 sema.code.instructions.items(.data)[@intFromEnum(spa.switch_block_inst)].pl_node.src_node,
10590 );
1059110532
10592 // We can propagate `.cold` hints from this branch since it's comptime-known10533 const pt = sema.pt;
10593 // to be taken from the parent branch.10534 const zcu = pt.zcu;
10594 const parent_hint = sema.branch_hint;10535 const gpa = sema.gpa;
10595 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1059610536
10597 if (has_tag_capture) {10537 const zir_switch = sema.code.getSwitchBlock(inst);
10598 const tag_ref = try spa.analyzeTagCapture(child_block, capture_src, inline_case_capture);10538 const src_node_offset = zir_switch.catch_or_if_src_node_offset.unwrap().?;
10599 sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);10539 const src = block.src(.{ .node_offset_main_token = src_node_offset });
10600 }10540 const operand_src = block.src(.{ .node_offset_if_cond = src_node_offset });
10601 defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));
1060210541
10603 switch (capture) {10542 assert(!zir_switch.has_continue); // wrong codepath!
10604 .none => {
10605 return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
10606 },
1060710543
10608 .by_val, .by_ref => {10544 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
10609 const capture_ref = try spa.analyzeCapture(10545 try sema.air_instructions.append(gpa, .{
10610 child_block,10546 .tag = .block,
10611 capture == .by_ref,10547 .data = undefined,
10612 prong_type == .special,10548 });
10613 capture_src,10549 var label: Block.Label = .{
10614 case_vals,10550 .zir_block = inst,
10615 inline_case_capture,10551 .merges = .{
10616 );10552 .src_locs = .{},
10553 .results = .{},
10554 .br_list = .{},
10555 .block_inst = block_inst,
10556 },
10557 };
10558 var child_block = block.makeSubBlock();
10559 child_block.label = &label;
10560 const merges = &child_block.label.?.merges;
10561 defer child_block.instructions.deinit(gpa);
10562 defer merges.deinit(gpa);
1061710563
10618 if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {10564 const non_err_case = zir_switch.non_err_case.?;
10619 // This prong should be unreachable!10565
10620 return .unreachable_value;10566 var non_err_block: Block = child_block.makeSubBlock();
10621 }10567 non_err_block.runtime_loop = null;
10568 non_err_block.runtime_cond = operand_src;
10569 non_err_block.runtime_index.increment();
10570 non_err_block.need_debug_scope = null;
10571 defer non_err_block.instructions.deinit(gpa);
10572
10573 var switch_block: Block = child_block.makeSubBlock();
10574 switch_block.runtime_loop = null;
10575 switch_block.runtime_cond = operand_src;
10576 switch_block.runtime_index.increment();
10577 switch_block.need_debug_scope = null;
10578 defer switch_block.instructions.deinit(gpa);
10579
10580 // We begin with unwrapping the error union we're switching on as necessary.
10581 // Then we analyze the non-error prong if it's not comptime-unreachable.
10582 // Lastly, we analyze the error prong(s) as a regular switch.
10583
10584 const raw_switch_operand, const non_err_cond, const non_err_hint = non_err: {
10585 const eu_maybe_ptr = try sema.resolveInst(zir_switch.main_operand);
10586 const err_union_ty: Type = err_union_ty: {
10587 const raw_operand_ty = sema.typeOf(eu_maybe_ptr);
10588 if (!non_err_case.operand_is_ref) break :err_union_ty raw_operand_ty;
10589 try sema.checkPtrOperand(block, operand_src, raw_operand_ty);
10590 break :err_union_ty raw_operand_ty.childType(zcu);
10591 };
10592 if (err_union_ty.zigTypeTag(zcu) != .error_union) {
10593 return sema.fail(block, operand_src, "expected error union type, found '{f}'", .{
10594 err_union_ty.fmt(pt),
10595 });
10596 }
1062210597
10623 sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);10598 const non_err_cond = if (non_err_case.operand_is_ref)
10624 defer assert(sema.inst_map.remove(spa.switch_block_inst));10599 try sema.analyzePtrIsNonErr(block, operand_src, eu_maybe_ptr)
10600 else
10601 try sema.analyzeIsNonErr(block, operand_src, eu_maybe_ptr);
1062510602
10626 return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);10603 const non_err_hint: std.builtin.BranchHint = hint: {
10627 },10604 // don't analyze the non-error body if it's unreachable
10628 }10605 if (non_err_cond == .bool_false) {
10629 }10606 break :hint undefined;
10607 }
1063010608
10631 /// Analyze a switch prong which may have peers at runtime.10609 const eu_payload: Air.Inst.Ref = switch (non_err_case.capture) {
10632 /// Uses `analyzeBodyRuntimeBreak`. Sets up captures as needed.10610 .by_val => try sema.analyzeErrUnionPayload(&non_err_block, src, err_union_ty, eu_maybe_ptr, operand_src, false),
10633 /// Returns the `BranchHint` for the prong.10611 .by_ref => try sema.analyzeErrUnionPayloadPtr(&non_err_block, src, eu_maybe_ptr, false, false),
10634 fn analyzeProngRuntime(10612 .none => undefined,
10635 spa: SwitchProngAnalysis,10613 };
10636 case_block: *Block,10614 if (non_err_case.capture != .none) sema.inst_map.putAssumeCapacity(inst, eu_payload);
10637 prong_type: enum { normal, special },10615 defer if (non_err_case.capture != .none) assert(sema.inst_map.remove(inst));
10638 prong_body: []const Zir.Inst.Index,
10639 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
10640 /// Must use the `switch_capture` field in `offset`.
10641 capture_src: LazySrcLoc,
10642 /// The set of all values which can reach this prong. May be undefined
10643 /// if the prong is special or contains ranges.
10644 case_vals: []const Air.Inst.Ref,
10645 /// The inline capture of this prong. If this is not an inline prong,
10646 /// this is `.none`.
10647 inline_case_capture: Air.Inst.Ref,
10648 /// Whether this prong has an inline tag capture. If `true`, then
10649 /// `inline_case_capture` cannot be `.none`.
10650 has_tag_capture: bool,
10651 ) CompileError!std.builtin.BranchHint {
10652 const sema = spa.sema;
1065310616
10654 if (has_tag_capture) {10617 if (non_err_cond == .bool_true) {
10655 const tag_ref = try spa.analyzeTagCapture(case_block, capture_src, inline_case_capture);10618 // Early return; we don't analyze the switch as it's unreachable.
10656 sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);10619 return sema.resolveBlockBody(block, src, &non_err_block, non_err_case.body, inst, merges);
10657 }10620 }
10658 defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));10621 break :hint try sema.analyzeBodyRuntimeBreak(&non_err_block, non_err_case.body);
10622 };
1065910623
10660 switch (capture) {10624 // Emit this into the switch block as it's our error case!
10661 .none => {10625 const eu_code = if (non_err_case.operand_is_ref)
10662 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);10626 try sema.analyzeErrUnionCodePtr(&switch_block, operand_src, eu_maybe_ptr)
10663 },10627 else
10628 try sema.analyzeErrUnionCode(&switch_block, operand_src, eu_maybe_ptr);
1066410629
10665 .by_val, .by_ref => {10630 break :non_err .{
10666 const capture_ref = try spa.analyzeCapture(10631 eu_code,
10667 case_block,10632 non_err_cond,
10668 capture == .by_ref,10633 non_err_hint,
10669 prong_type == .special,10634 };
10670 capture_src,10635 };
10671 case_vals,
10672 inline_case_capture,
10673 );
1067410636
10675 if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {10637 const validated_switch = try sema.validateSwitchBlock(block, raw_switch_operand, false, inst, &zir_switch);
10676 // No need to analyze any further, the prong is unreachable
10677 return .none;
10678 }
1067910638
10680 sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);10639 const maybe_switch_ref: ?Air.Inst.Ref = ref: {
10681 defer assert(sema.inst_map.remove(spa.switch_block_inst));10640 // make err capture (i.e. switch operand) available to switch prong bodies
10641 sema.inst_map.putAssumeCapacityNoClobber(inst, raw_switch_operand);
10642 defer assert(sema.inst_map.remove(inst));
10643 break :ref try sema.analyzeSwitchBlock(block, &switch_block, raw_switch_operand, false, merges, inst, &zir_switch, &validated_switch);
10644 };
1068210645
10683 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);10646 if (non_err_cond == .bool_false) {
10684 },10647 return maybe_switch_ref orelse {
10685 }10648 const switch_src = block.nodeOffset(zir_switch.switch_src_node_offset);
10649 return sema.resolveAnalyzedBlock(block, switch_src, &switch_block, merges, false);
10650 };
10686 }10651 }
1068710652
10688 fn analyzeTagCapture(10653 if (maybe_switch_ref) |switch_ref| {
10689 spa: SwitchProngAnalysis,10654 if (sema.typeOf(switch_ref).isNoReturn(zcu)) {
10690 block: *Block,10655 _ = try switch_block.addNoOp(.unreach);
10691 capture_src: LazySrcLoc,10656 } else {
10692 inline_case_capture: Air.Inst.Ref,10657 const br_ref = try switch_block.addBr(merges.block_inst, switch_ref);
10693 ) CompileError!Air.Inst.Ref {10658 try merges.results.append(gpa, switch_ref);
10694 const sema = spa.sema;10659 try merges.br_list.append(gpa, br_ref.toIndex().?);
10695 const pt = sema.pt;10660 try merges.src_locs.append(gpa, null);
10696 const zcu = pt.zcu;
10697 const operand_ty = switch (spa.operand) {
10698 .simple => |s| sema.typeOf(s.by_val),
10699 .loop => |l| ty: {
10700 const alloc_ty = sema.typeOf(l.operand_alloc);
10701 const alloc_child = alloc_ty.childType(zcu);
10702 if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
10703 break :ty alloc_child;
10704 },
10705 };
10706 if (operand_ty.zigTypeTag(zcu) != .@"union") {
10707 const tag_capture_src: LazySrcLoc = .{
10708 .base_node_inst = capture_src.base_node_inst,
10709 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
10710 };
10711 return sema.fail(block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
10712 operand_ty.fmt(pt),
10713 });
10714 }10661 }
10715 assert(inline_case_capture != .none);
10716 return inline_case_capture;
10717 }10662 }
1071810663
10719 fn analyzeCapture(10664 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
10720 spa: SwitchProngAnalysis,10665 non_err_block.instructions.items.len + switch_block.instructions.items.len);
10721 block: *Block,10666 const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
10722 capture_byref: bool,10667 .then_body_len = @intCast(non_err_block.instructions.items.len),
10723 is_special_prong: bool,10668 .else_body_len = @intCast(switch_block.instructions.items.len),
10724 capture_src: LazySrcLoc,10669 .branch_hints = .{
10725 case_vals: []const Air.Inst.Ref,10670 .true = non_err_hint,
10726 inline_case_capture: Air.Inst.Ref,10671 .false = .unlikely, // errors are unlikely
10727 ) CompileError!Air.Inst.Ref {10672 // Code coverage is desired for error handling.
10728 const sema = spa.sema;10673 .then_cov = .poi,
10729 const pt = sema.pt;10674 .else_cov = .poi,
10730 const zcu = pt.zcu;10675 },
10731 const ip = &zcu.intern_pool;10676 });
1073210677 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(non_err_block.instructions.items));
10733 const zir_datas = sema.code.instructions.items(.data);10678 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(switch_block.instructions.items));
10734 const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;
1073510679
10736 const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });10680 _ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
10681 .operand = non_err_cond,
10682 .payload = cond_br_payload,
10683 } } });
1073710684
10738 const operand_val, const operand_ptr = switch (spa.operand) {10685 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
10739 .simple => |s| .{ s.by_val, s.by_ref },10686}
10740 .loop => |l| op: {
10741 const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
10742 if (l.operand_is_ref) {
10743 const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
10744 break :op .{ by_val, loaded };
10745 } else {
10746 break :op .{ loaded, undefined };
10747 }
10748 },
10749 };
1075010687
10751 const operand_ty = sema.typeOf(operand_val);10688fn zirSwitchBlock(
10752 const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;10689 sema: *Sema,
10690 block: *Block,
10691 inst: Zir.Inst.Index,
10692 operand_is_ref: bool,
10693) CompileError!Air.Inst.Ref {
10694 const tracy = trace(@src());
10695 defer tracy.end();
10696 const zir_switch = sema.code.getSwitchBlock(inst);
1075310697
10754 if (inline_case_capture != .none) {10698 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
10755 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;10699 try sema.air_instructions.append(sema.gpa, .{
10756 if (operand_ty.zigTypeTag(zcu) == .@"union") {10700 .tag = .block,
10757 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);10701 .data = undefined,
10758 const union_obj = zcu.typeToUnion(operand_ty).?;10702 });
10759 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);10703 var label: Block.Label = .{
10760 if (capture_byref) {10704 .zir_block = inst,
10761 const ptr_field_ty = try pt.ptrTypeSema(.{10705 .merges = .{
10762 .child = field_ty.toIntern(),10706 .src_locs = .{},
10763 .flags = .{10707 .results = .{},
10764 .is_const = !operand_ptr_ty.ptrIsMutable(zcu),10708 .br_list = .{},
10765 .is_volatile = operand_ptr_ty.isVolatilePtr(zcu),10709 .block_inst = block_inst,
10766 .address_space = operand_ptr_ty.ptrAddressSpace(zcu),10710 },
10767 },10711 };
10768 });10712 var child_block = block.makeSubBlock();
10769 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {10713 child_block.label = &label;
10770 return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());10714 const merges = &child_block.label.?.merges;
10771 }10715 defer child_block.instructions.deinit(sema.gpa);
10772 return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);10716 defer merges.deinit(sema.gpa);
10773 } else {
10774 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
10775 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
10776 return Air.internedToRef(tag_and_val.val);
10777 }
10778 return block.addStructFieldVal(operand_val, field_index, field_ty);
10779 }
10780 } else if (capture_byref) {
10781 return sema.uavRef(item_val.toIntern());
10782 } else {
10783 return inline_case_capture;
10784 }
10785 }
1078610717
10787 if (is_special_prong) {10718 const raw_operand = try sema.resolveInst(zir_switch.main_operand);
10788 if (capture_byref) {10719 const validated_switch = try sema.validateSwitchBlock(block, raw_operand, operand_is_ref, inst, &zir_switch);
10789 return operand_ptr;10720 const maybe_ref = try sema.analyzeSwitchBlock(block, &child_block, raw_operand, operand_is_ref, merges, inst, &zir_switch, &validated_switch);
10790 }10721 return maybe_ref orelse {
10722 const src = block.nodeOffset(zir_switch.switch_src_node_offset);
10723 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
10724 };
10725}
1079110726
10792 switch (operand_ty.zigTypeTag(zcu)) {10727/// If the switch can be resolved to a value at comptime, this will return a `Ref`
10793 .error_set => if (spa.else_error_ty) |ty| {10728/// that's never `.none`.
10794 return sema.bitCast(block, ty, operand_val, operand_src, null);10729/// If not, this will return `null` and emit its instructions into `child_block`.
10795 } else {10730fn analyzeSwitchBlock(
10796 try sema.analyzeUnreachable(block, operand_src, false);10731 sema: *Sema,
10797 return .unreachable_value;10732 block: *Block,
10798 },10733 child_block: *Block,
10799 else => return operand_val,10734 raw_operand: Air.Inst.Ref,
10800 }10735 operand_is_ref: bool,
10801 }10736 merges: *Block.Merges,
10737 switch_inst: Zir.Inst.Index,
10738 zir_switch: *const Zir.UnwrappedSwitchBlock,
10739 validated_switch: *const ValidatedSwitchBlock,
10740) CompileError!?Air.Inst.Ref {
10741 const pt = sema.pt;
10742 const zcu = pt.zcu;
10743 const gpa = sema.gpa;
1080210744
10803 switch (operand_ty.zigTypeTag(zcu)) {10745 const src_node_offset = zir_switch.switch_src_node_offset;
10804 .@"union" => {10746 const src = block.nodeOffset(src_node_offset);
10805 const union_obj = zcu.typeToUnion(operand_ty).?;10747 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
10806 const first_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
1080710748
10808 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;10749 const has_else = zir_switch.else_case != null;
10809 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);10750 const has_under = zir_switch.has_under;
1081010751
10811 const field_indices = try sema.arena.alloc(u32, case_vals.len);10752 const else_case = validated_switch.else_case;
10812 for (case_vals, field_indices) |item, *field_idx| {
10813 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
10814 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
10815 }
1081610753
10817 // Fast path: if all the operands are the same type already, we don't need to hit10754 const operand: SwitchOperand, const operand_ty: Type, const maybe_operand_opv: ?Value, const item_ty: Type = operand: {
10818 // PTR! This will also allow us to emit simpler code.10755 const val, const ref = if (operand_is_ref)
10819 const same_types = for (field_indices[1..]) |field_idx| {10756 .{ try sema.analyzeLoad(block, src, raw_operand, operand_src), raw_operand }
10820 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10757 else
10821 if (!field_ty.eql(first_field_ty, zcu)) break false;10758 .{ raw_operand, undefined };
10822 } else true;
1082310759
10824 const capture_ty = if (same_types) first_field_ty else capture_ty: {10760 const operand_ty = sema.typeOf(val);
10825 // We need values to run PTR on, so make a bunch of undef constants.10761 const maybe_operand_opv = try sema.typeHasOnePossibleValue(operand_ty);
10826 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);10762 const init_cond: Air.Inst.Ref, const item_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
10827 for (dummy_captures, field_indices) |*dummy, field_idx| {10763 .@"union" => tag: {
10828 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10764 const tag_ty = operand_ty.unionTagType(zcu).?;
10829 dummy.* = try pt.undefRef(field_ty);10765 const tag_val = try sema.unionToTag(block, tag_ty, val, operand_src);
10830 }10766 break :tag .{ tag_val, tag_ty };
10767 },
10768 else => .{
10769 if (maybe_operand_opv) |operand_opv| .fromValue(operand_opv) else val,
10770 operand_ty,
10771 },
10772 };
1083110773
10832 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);10774 if (zir_switch.has_continue and !block.isComptime()) {
10833 for (case_srcs, 0..) |*case_src, i| {10775 const operand_alloc: Air.Inst.Ref = if (zir_switch.any_maybe_runtime_capture and
10834 case_src.* = .{10776 maybe_operand_opv == null)
10835 .base_node_inst = capture_src.base_node_inst,10777 alloc: {
10836 .offset = .{ .switch_case_item = .{10778 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(raw_operand));
10837 .switch_node_offset = switch_node_offset,10779 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
10838 .case_idx = capture_src.offset.switch_capture.case_idx,10780 _ = try block.addBinOp(.store, operand_alloc, raw_operand);
10839 .item_idx = .{ .kind = .single, .index = @intCast(i) },10781 break :alloc operand_alloc;
10840 } },10782 } else undefined;
10841 };10783 break :operand .{ .{ .loop = .{
10842 }10784 .operand_alloc = operand_alloc,
10785 .operand_is_ref = operand_is_ref,
10786 .init_cond = init_cond,
10787 } }, operand_ty, maybe_operand_opv, item_ty };
10788 } else {
10789 // We always use `simple` in the comptime/OPV case, because as far as the
10790 // dispatching logic is concerned, it really is dispatching a single prong.
10791 break :operand .{ .{ .simple = .{
10792 .by_val = val,
10793 .by_ref = ref,
10794 .cond = init_cond,
10795 } }, operand_ty, maybe_operand_opv, item_ty };
10796 }
10797 };
1084310798
10844 break :capture_ty sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {10799 const raw_operand_ty = sema.typeOf(raw_operand);
10845 error.AnalysisFail => {
10846 const msg = sema.err orelse return error.AnalysisFail;
10847 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
10848 return error.AnalysisFail;
10849 },
10850 else => |e| return e,
10851 };
10852 };
1085310800
10854 // By-reference captures have some further restrictions which make them easier to emit10801 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
10855 if (capture_byref) {10802 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
10856 const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
10857 const capture_ptr_ty = resolve: {
10858 // By-ref captures of hetereogeneous types are only allowed if all field
10859 // pointer types are peer resolvable to each other.
10860 // We need values to run PTR on, so make a bunch of undef constants.
10861 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
10862 for (field_indices, dummy_captures) |field_idx, *dummy| {
10863 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
10864 const field_ptr_ty = try pt.ptrTypeSema(.{
10865 .child = field_ty.toIntern(),
10866 .flags = .{
10867 .is_const = operand_ptr_info.flags.is_const,
10868 .is_volatile = operand_ptr_info.flags.is_volatile,
10869 .address_space = operand_ptr_info.flags.address_space,
10870 .alignment = union_obj.fieldAlign(ip, field_idx),
10871 },
10872 });
10873 dummy.* = try pt.undefRef(field_ptr_ty);
10874 }
10875 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
10876 for (case_srcs, 0..) |*case_src, i| {
10877 case_src.* = .{
10878 .base_node_inst = capture_src.base_node_inst,
10879 .offset = .{ .switch_case_item = .{
10880 .switch_node_offset = switch_node_offset,
10881 .case_idx = capture_src.offset.switch_capture.case_idx,
10882 .item_idx = .{ .kind = .single, .index = @intCast(i) },
10883 } },
10884 };
10885 }
1088610803
10887 break :resolve sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {10804 if (item_ty.zigTypeTag(zcu) == .@"enum" and
10888 error.AnalysisFail => {10805 validated_switch.seen_enum_fields.len == 0 and
10889 const msg = sema.err orelse return error.AnalysisFail;10806 !operand_ty.isNonexhaustiveEnum(zcu))
10890 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});10807 {
10891 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});10808 return .void_value; // switch on empty enum/union
10892 return error.AnalysisFail;10809 }
10893 },
10894 else => |e| return e,
10895 };
10896 };
1089710810
10898 if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {10811 const cond_ref = switch (operand) {
10899 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);10812 .simple => |s| s.cond,
10900 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);10813 .loop => |l| l.init_cond,
10901 return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());10814 };
10902 }
1090310815
10904 try sema.requireRuntimeBlock(block, operand_src, null);10816 // We treat `else` and `_` the same, except if both are present.
10905 return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);10817 const else_is_named_only = has_else and has_under;
10906 }10818 const catch_all_case: CatchAllSwitchCase =
10819 if (has_under) .under else if (has_else) .@"else" else .none;
1090710820
10908 if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {10821 resolve_at_comptime: {
10909 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);10822 // always runtime; evaluation in comptime scope uses `simple`
10910 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;10823 if (operand == .loop) break :resolve_at_comptime;
10911 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
10912 const uncoerced = Air.internedToRef(union_val.val);
10913 return sema.coerce(block, capture_ty, uncoerced, operand_src);
10914 }
1091510824
10916 try sema.requireRuntimeBlock(block, operand_src, null);10825 var cur_cond_val = try sema.resolveDefinedValue(child_block, src, cond_ref) orelse {
10826 break :resolve_at_comptime;
10827 };
10828 var cur_operand = operand;
1091710829
10918 if (same_types) {10830 while (true) {
10919 return block.addStructFieldVal(operand_val, first_field_index, capture_ty);10831 if (sema.resolveSwitchBlock(
10920 }10832 block,
10833 child_block,
10834 cur_operand,
10835 raw_operand_ty,
10836 cur_cond_val,
10837 catch_all_case,
10838 else_is_named_only,
10839 merges,
10840 switch_inst,
10841 zir_switch,
10842 validated_switch,
10843 )) |result| {
10844 return result;
10845 } else |err| switch (err) {
10846 error.ComptimeBreak => {
10847 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
10848 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
10849 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
10850 if (extra.block_inst != switch_inst) return error.ComptimeBreak;
10851 // This is a `switch_continue` targeting this block. Change the operand and start over.
10852 const new_operand_src = child_block.nodeOffset(extra.operand_src_node.unwrap().?);
10853 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
10854 const new_operand = try sema.coerce(child_block, raw_operand_ty, new_operand_uncoerced, new_operand_src);
10855
10856 try sema.emitBackwardBranch(child_block, src);
10857
10858 const new_val, const new_ref = if (operand_is_ref)
10859 .{ try sema.analyzeLoad(child_block, src, new_operand, new_operand_src), new_operand }
10860 else
10861 .{ new_operand, undefined };
1092110862
10922 // We may have to emit a switch block which coerces the operand to the capture type.10863 const new_cond_ref = if (union_originally)
10923 // If we can, try to avoid that using in-memory coercions.10864 try sema.unionToTag(child_block, item_ty, new_val, src)
10924 const first_non_imc = in_mem: {10865 else
10925 for (field_indices, 0..) |field_idx, i| {10866 new_val;
10926 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
10927 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {
10928 break :in_mem i;
10929 }
10930 }
10931 // All fields are in-memory coercible to the resolved type!
10932 // Just take the first field and bitcast the result.
10933 const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
10934 return block.addBitCast(capture_ty, uncoerced);
10935 };
1093610867
10937 // By-val capture with heterogeneous types which are not all in-memory coercible to10868 cur_cond_val = try sema.resolveConstDefinedValue(child_block, src, new_cond_ref, null);
10938 // the resolved capture type. We finally have to fall back to the ugly method.10869 cur_operand = .{ .simple = .{
10870 .by_val = new_val,
10871 .by_ref = new_ref,
10872 .cond = new_cond_ref,
10873 } };
10874 },
10875 else => |e| return e,
10876 }
10877 }
10878 }
1093910879
10940 // However, let's first track which operands are in-memory coercible. There may well10880 if (child_block.isComptime()) {
10941 // be several, and we can squash all of these cases into the same switch prong using10881 _ = try sema.resolveConstDefinedValue(child_block, operand_src, operand.simple.cond, null);
10942 // a simple bitcast. We'll make this the 'else' prong.10882 unreachable;
10883 }
1094310884
10944 var in_mem_coercible = try std.DynamicBitSet.initFull(sema.arena, field_indices.len);10885 if (try sema.typeHasOnePossibleValue(item_ty)) |item_opv| {
10945 in_mem_coercible.unset(first_non_imc);10886 // We simplify conditions with OPV to either a `loop` or a `block` since
10946 {10887 // we cannot switch on a value which doesn't exist at runtime.
10947 const next = first_non_imc + 1;10888 assert(operand == .loop); // `simple` should have already been comptime-resolved above!
10948 for (field_indices[next..], next..) |field_idx, i| {10889
10949 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10890 var case_block = child_block.makeSubBlock();
10950 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {10891 case_block.runtime_loop = null;
10951 in_mem_coercible.unset(i);10892 case_block.runtime_cond = operand_src;
10893 case_block.runtime_index.increment();
10894 case_block.need_debug_scope = null; // this body is emitted regardless
10895 defer case_block.instructions.deinit(gpa);
10896
10897 const case_vals = validated_switch.case_vals;
10898
10899 const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {
10900 var case_val_idx: usize = 0;
10901 var case_it = zir_switch.iterateCases();
10902 var extra_index = zir_switch.end;
10903 while (case_it.next()) |case| {
10904 const prong_info = case.prong_info;
10905 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
10906 extra_index += prong_body.len;
10907 skip_case: {
10908 if (!err_set) break :skip_case;
10909 // This case might consist of errors which are not in the set
10910 // we're switching on. If so we have to skip it!
10911 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
10912 case_val_idx += item_refs.len;
10913 assert(case.range_infos.len == 0);
10914 for (case.item_infos, item_refs) |item_info, item_ref| {
10915 if (item_info.bodyLen()) |body_len| extra_index += body_len;
10916 if (sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, false, true, prong_info.is_comptime_unreach)) {
10917 break :skip_case;
10952 }10918 }
10953 }10919 }
10920 continue;
10954 }10921 }
10922 break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };
10923 }
10924 if (has_else) {
10925 // This *has* to be checked after iterating all regular cases because
10926 // we allow simple noreturn else prongs when switching on error sets!
10927 break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };
10928 }
10929 unreachable; // malformed validated switch
10930 };
1095510931
10956 const capture_block_inst = try block.addInstAsIndex(.{10932 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, .fromValue(item_opv), operand_ty, union_originally, err_set, false);
10957 .tag = .block,10933 if (!analyze_body) return .unreachable_value;
10958 .data = .{
10959 .ty_pl = .{
10960 .ty = Air.internedToRef(capture_ty.toIntern()),
10961 .payload = undefined, // updated below
10962 },
10963 },
10964 });
10965
10966 const prong_count = field_indices.len - in_mem_coercible.count();
10967
10968 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
10969 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
10970 defer cases_extra.deinit();
10971
10972 {
10973 // All branch hints are `.none`, so just add zero elems.
10974 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
10975 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
10976 try cases_extra.appendNTimes(0, need_elems);
10977 }
10978
10979 {
10980 // Non-bitcast cases
10981 var it = in_mem_coercible.iterator(.{ .kind = .unset });
10982 while (it.next()) |idx| {
10983 var coerce_block = block.makeSubBlock();
10984 defer coerce_block.instructions.deinit(sema.gpa);
1098510934
10986 const case_src: LazySrcLoc = .{10935 if (!(err_set and
10987 .base_node_inst = capture_src.base_node_inst,10936 try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
10988 .offset = .{ .switch_case_item = .{10937 {
10989 .switch_node_offset = switch_node_offset,10938 // Set up captures manually to avoid special cases in the main logic.
10990 .case_idx = capture_src.offset.switch_capture.case_idx,10939 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
10991 .item_idx = .{ .kind = .single, .index = @intCast(idx) },10940 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
10992 } },10941 const payload_ref: Air.Inst.Ref = payload_ref: {
10993 };10942 const item_val: InternPool.Index = switch (operand_ty.zigTypeTag(zcu)) {
10943 .@"union" => item_val: {
10944 if (maybe_operand_opv) |operand_opv| {
10945 break :item_val zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val;
10946 }
10947 assert(union_originally); // operand type must be union, otherwise it would be an OPV type here
10948 assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
10949 const operand_val, const operand_ref = switch (operand) {
10950 .simple => unreachable,
10951 .loop => |l| load_operand: {
10952 const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
10953 if (l.operand_is_ref) {
10954 const by_val = try sema.analyzeLoad(block, src, loaded, src);
10955 break :load_operand .{ by_val, loaded };
10956 } else {
10957 break :load_operand .{ loaded, undefined };
10958 }
10959 },
10960 };
10961 break :payload_ref try sema.analyzeSwitchPayloadCapture(
10962 &case_block,
10963 operand,
10964 operand_val,
10965 operand_ref,
10966 operand_ty,
10967 operand_src,
10968 block.src(.{ .switch_capture = .{
10969 .switch_node_offset = src_node_offset,
10970 .case_idx = index,
10971 } }),
10972 capture == .by_ref,
10973 is_special,
10974 if (!is_special) case_vals else undefined,
10975 if (is_inline) .fromValue(item_opv) else .none,
10976 validated_switch.else_err_ty,
10977 );
10978 },
10979 else => item_opv.toIntern(),
10980 };
10981 break :payload_ref switch (capture) {
10982 .by_val => .fromIntern(item_val),
10983 .by_ref => try sema.uavRef(item_val),
10984 .none => unreachable,
10985 };
10986 };
10987 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
10988 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
10989 break :inst payload_inst;
10990 } else undefined;
10991 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1099410992
10995 const field_idx = field_indices[idx];10993 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
10996 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);10994 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
10997 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);10995 sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
10998 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);10996 break :inst tag_inst;
10999 _ = try coerce_block.addBr(capture_block_inst, coerced);10997 } else undefined;
1100010998 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
11001 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11002 1 + // `item`, no ranges
11003 coerce_block.instructions.items.len);
11004 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11005 .items_len = 1,
11006 .ranges_len = 0,
11007 .body_len = @intCast(coerce_block.instructions.items.len),
11008 }));
11009 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
11010 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
11011 }
11012 }
11013 const else_body_len = len: {
11014 // 'else' prong uses a bitcast
11015 var coerce_block = block.makeSubBlock();
11016 defer coerce_block.instructions.deinit(sema.gpa);
1101710999
11018 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;11000 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
11019 const first_imc_field_idx = field_indices[first_imc_item_idx];11001 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
11020 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
11021 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
11022 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
11023 _ = try coerce_block.addBr(capture_block_inst, coerced);
1102411002
11025 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));11003 _ = try sema.analyzeBodyRuntimeBreak(&case_block, body);
11026 break :len coerce_block.instructions.items.len;11004 }
11027 };
1102811005
11029 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +11006 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
11030 cases_extra.items.len +11007 case_block.instructions.items.len);
11031 @typeInfo(Air.Block).@"struct".fields.len +11008 const payload_index = sema.addExtraAssumeCapacity(Air.Block{
11032 1);11009 .body_len = @intCast(case_block.instructions.items.len),
1103311010 });
11034 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);11011 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11035 try sema.air_instructions.append(sema.gpa, .{
11036 .tag = .switch_br,
11037 .data = .{
11038 .pl_op = .{
11039 .operand = undefined, // set by switch below
11040 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
11041 .cases_len = @intCast(prong_count),
11042 .else_body_len = @intCast(else_body_len),
11043 }),
11044 },
11045 },
11046 });
11047 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
11048
11049 // Set up block body
11050 switch (spa.operand) {
11051 .simple => |s| {
11052 const air_datas = sema.air_instructions.items(.data);
11053 air_datas[switch_br_inst].pl_op.operand = s.cond;
11054 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11055 .body_len = 1,
11056 });
11057 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11058 },
11059 .loop => {
11060 // The block must first extract the tag from the loaded union.
11061 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
11062 try sema.air_instructions.append(sema.gpa, .{
11063 .tag = .get_union_tag,
11064 .data = .{ .ty_op = .{
11065 .ty = Air.internedToRef(union_obj.enum_tag_ty),
11066 .operand = operand_val,
11067 } },
11068 });
11069 const air_datas = sema.air_instructions.items(.data);
11070 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
11071 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
11072 .body_len = 2,
11073 });
11074 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
11075 sema.air_extra.appendAssumeCapacity(switch_br_inst);
11076 },
11077 }
1107811012
11079 return capture_block_inst.toRef();11013 const air_tag: Air.Inst.Tag = if (merges.extra_insts.items.len > 0)
11080 },11014 .loop
11081 .error_set => {11015 else
11082 if (capture_byref) {11016 .block;
11083 return sema.fail(11017 const air_loop_ref = try child_block.addInst(.{
11084 block,11018 .tag = air_tag,
11085 capture_src,11019 .data = .{ .ty_pl = .{
11086 "error set cannot be captured by reference",11020 .ty = .noreturn_type,
11087 .{},11021 .payload = payload_index,
11088 );11022 } },
11089 }11023 });
11024 try sema.fixupSwitchContinues(
11025 block,
11026 src,
11027 air_loop_ref,
11028 operand,
11029 operand_is_ref,
11030 item_ty,
11031 .opv,
11032 zir_switch.any_maybe_runtime_capture,
11033 merges,
11034 );
11035 return null;
11036 }
1109011037
11091 if (case_vals.len == 1) {11038 assert(maybe_operand_opv == null); // `operand_ty` can only be an OPV type if `item_ty` is one too!
11092 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
11093 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
11094 return sema.bitCast(block, item_ty, operand_val, operand_src, null);
11095 }
1109611039
11097 var names: InferredErrorSet.NameMap = .{};11040 try sema.finishSwitchBr(
11098 try names.ensureUnusedCapacity(sema.arena, case_vals.len);11041 block,
11099 for (case_vals) |err| {11042 child_block,
11100 const err_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, err, undefined) catch unreachable;11043 operand,
11101 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});11044 raw_operand_ty,
11102 }11045 operand_is_ref,
11103 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());11046 merges,
11104 return sema.bitCast(block, error_ty, operand_val, operand_src, null);11047 switch_inst,
11105 },11048 zir_switch,
11106 else => {11049 validated_switch,
11107 // In this case the capture value is just the passed-through value11050 );
11108 // of the switch condition.11051 return null;
11109 if (capture_byref) {11052}
11110 return operand_ptr;
11111 } else {
11112 return operand_val;
11113 }
11114 },
11115 }
11116 }
11117};
1111811053
11119fn switchCond(11054fn finishSwitchBr(
11120 sema: *Sema,11055 sema: *Sema,
11121 block: *Block,11056 block: *Block,
11122 src: LazySrcLoc,11057 child_block: *Block,
11123 operand: Air.Inst.Ref,11058 operand: SwitchOperand,
11124) CompileError!Air.Inst.Ref {11059 raw_operand_ty: Type,
11060 operand_is_ref: bool,
11061 merges: *Block.Merges,
11062 switch_inst: Zir.Inst.Index,
11063 zir_switch: *const Zir.UnwrappedSwitchBlock,
11064 validated_switch: *const ValidatedSwitchBlock,
11065) CompileError!void {
11125 const pt = sema.pt;11066 const pt = sema.pt;
11126 const zcu = pt.zcu;11067 const zcu = pt.zcu;
11127 const operand_ty = sema.typeOf(operand);11068 const ip = &zcu.intern_pool;
11128 switch (operand_ty.zigTypeTag(zcu)) {11069 const gpa = sema.gpa;
11129 .type,
11130 .void,
11131 .bool,
11132 .int,
11133 .float,
11134 .comptime_float,
11135 .comptime_int,
11136 .enum_literal,
11137 .pointer,
11138 .@"fn",
11139 .error_set,
11140 .@"enum",
11141 => {
11142 if (operand_ty.isSlice(zcu)) {
11143 return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
11144 }
11145 if ((try sema.typeHasOnePossibleValue(operand_ty))) |opv| {
11146 return Air.internedToRef(opv.toIntern());
11147 }
11148 return operand;
11149 },
1115011070
11151 .@"union" => {11071 const src_node_offset = zir_switch.switch_src_node_offset;
11152 try operand_ty.resolveFields(pt);11072 const src = block.nodeOffset(src_node_offset);
11153 const enum_ty = operand_ty.unionTagType(zcu) orelse {11073 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
11154 const msg = msg: {
11155 const msg = try sema.errMsg(src, "switch on union with no attached enum", .{});
11156 errdefer msg.destroy(sema.gpa);
11157 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11158 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11159 }
11160 break :msg msg;
11161 };
11162 return sema.failWithOwnedErrorMsg(block, msg);
11163 };
11164 return sema.unionToTag(block, enum_ty, operand, src);
11165 },
1116611074
11167 .error_union,11075 const has_else = zir_switch.else_case != null;
11168 .noreturn,11076 const has_under = zir_switch.has_under;
11169 .array,
11170 .@"struct",
11171 .undefined,
11172 .null,
11173 .optional,
11174 .@"opaque",
11175 .vector,
11176 .frame,
11177 .@"anyframe",
11178 => return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)}),
11179 }
11180}
1118111077
11182const SwitchErrorSet = std.AutoHashMap(InternPool.NullTerminatedString, LazySrcLoc);11078 const else_case = validated_switch.else_case;
1118311079
11184fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {11080 const scalar_cases_len = zir_switch.scalarCasesLen();
11185 const tracy = trace(@src());11081 const multi_cases_len = zir_switch.multiCasesLen();
11186 defer tracy.end();
1118711082
11188 const pt = sema.pt;11083 const operand_ty = if (operand_is_ref)
11189 const zcu = pt.zcu;11084 raw_operand_ty.childType(zcu)
11190 const gpa = sema.gpa;11085 else
11191 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;11086 raw_operand_ty;
11192 const switch_src = block.nodeOffset(inst_data.src_node);
11193 const switch_src_node_offset = inst_data.src_node;
11194 const switch_operand_src = block.src(.{ .node_offset_switch_operand = switch_src_node_offset });
11195 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = switch_src_node_offset });
11196 const extra = sema.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);
11197 const main_operand_src = block.src(.{ .node_offset_if_cond = extra.data.main_src_node_offset });
11198 const main_src = block.src(.{ .node_offset_main_token = extra.data.main_src_node_offset });
1119911087
11200 const raw_operand_val = try sema.resolveInst(extra.data.operand);11088 const cond_ref = switch (operand) {
11089 .simple => |s| s.cond,
11090 .loop => |l| l.init_cond,
11091 };
1120111092
11202 // AstGen guarantees that the instruction immediately preceding11093 // AstGen guarantees that the instruction immediately preceding
11203 // switch_block_err_union is a dbg_stmt11094 // switch_block[_ref]/switch_block_err_union is a dbg_stmt.
11204 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);11095 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(switch_inst) - 1);
11205
11206 var header_extra_index: usize = extra.end;
1120711096
11208 const scalar_cases_len = extra.data.bits.scalar_cases_len;11097 const else_is_named_only = has_else and has_under;
11209 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {11098 const catch_all_case: CatchAllSwitchCase =
11210 const multi_cases_len = sema.code.extra[header_extra_index];11099 if (has_under) .under else if (has_else) .@"else" else .none;
11211 header_extra_index += 1;
11212 break :blk multi_cases_len;
11213 } else 0;
1121411100
11215 const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {11101 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
11216 const err_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);11102 .@"union" => operand_ty.unionTagType(zcu).?,
11217 header_extra_index += 1;11103 else => operand_ty,
11218 // SwitchProngAnalysis wants inst_map to have space for the tag capture.11104 };
11219 // Note that the normal capture is referred to via the switch block11105 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
11220 // index, which there is already necessarily space for.11106 const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
11221 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{err_capture_inst});
11222 break :blk err_capture_inst;
11223 } else undefined;
1122411107
11225 var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);11108 const estimated_cases_len: u32 = scalar_cases_len + multi_cases_len +
11226 defer case_vals.deinit(gpa);11109 @intFromBool(has_else or has_under);
1122711110
11228 const NonError = struct {11111 const BranchHints = struct {
11229 body: []const Zir.Inst.Index,11112 bags: std.ArrayList(u32),
11230 end: usize,11113 count: u32,
11231 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,11114 const hints_per_bag = 10;
11115 fn ensureUnusedCapacity(hints: *@This(), gpa_inner: Allocator, additional_count: u32) Allocator.Error!void {
11116 const unused_hints = hints.bags.capacity * hints_per_bag - hints.count;
11117 if (unused_hints >= additional_count) return;
11118 const bags_required = std.math.divCeil(u32, hints.count + additional_count, hints_per_bag) catch unreachable;
11119 return hints.bags.ensureUnusedCapacity(gpa_inner, bags_required);
11120 }
11121 fn appendAssumeCapacity(hints: *@This(), hint: std.builtin.BranchHint) void {
11122 const idx_in_bag = hints.count % hints_per_bag;
11123 var bag: u32 = if (idx_in_bag > 0) hints.bags.pop().? else 0;
11124 bag |= @as(u32, @intFromEnum(hint)) << @intCast(@bitSizeOf(std.builtin.BranchHint) * idx_in_bag);
11125 hints.count += 1;
11126 return hints.bags.appendAssumeCapacity(bag);
11127 }
11128 fn append(hints: *@This(), gpa_inner: Allocator, hint: std.builtin.BranchHint) Allocator.Error!void {
11129 try hints.ensureUnusedCapacity(gpa_inner, 1);
11130 return hints.appendAssumeCapacity(hint);
11131 }
11232 };11132 };
1123311133 var branch_hints: BranchHints = hints: {
11234 const non_error_case: NonError = non_error: {11134 const num_bags = std.math.divCeil(u32, estimated_cases_len, BranchHints.hints_per_bag) catch unreachable;
11235 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);11135 break :hints .{ .bags = try .initCapacity(gpa, num_bags), .count = 0 };
11236 const extra_body_start = header_extra_index + 1;
11237 break :non_error .{
11238 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11239 .end = extra_body_start + info.body_len,
11240 .capture = info.capture,
11241 };
11242 };11136 };
11137 defer branch_hints.bags.deinit(gpa);
1124311138
11244 const Else = struct {11139 var cases_extra: std.ArrayList(u32) = try .initCapacity(gpa, estimated_cases_len *
11245 body: []const Zir.Inst.Index,11140 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len);
11246 end: usize,11141 defer cases_extra.deinit(gpa);
11247 is_inline: bool,
11248 has_capture: bool,
11249 };
11250
11251 const else_case: Else = if (!extra.data.bits.has_else) .{
11252 .body = &.{},
11253 .end = non_error_case.end,
11254 .is_inline = false,
11255 .has_capture = false,
11256 } else special: {
11257 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[non_error_case.end]);
11258 const extra_body_start = non_error_case.end + 1;
11259 assert(info.capture != .by_ref);
11260 assert(!info.has_tag_capture);
11261 break :special .{
11262 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11263 .end = extra_body_start + info.body_len,
11264 .is_inline = info.is_inline,
11265 .has_capture = info.capture != .none,
11266 };
11267 };
1126811142
11269 var seen_errors = SwitchErrorSet.init(gpa);11143 // We will reuse this block for each case.
11270 defer seen_errors.deinit();11144 var case_block = child_block.makeSubBlock();
11145 case_block.runtime_loop = null;
11146 case_block.runtime_cond = operand_src;
11147 case_block.runtime_index.increment();
11148 case_block.need_debug_scope = null; // this body is emitted regardless
11149 defer case_block.instructions.deinit(gpa);
1127111150
11272 const operand_ty = sema.typeOf(raw_operand_val);11151 const case_vals = validated_switch.case_vals;
11273 const operand_err_set = if (extra.data.bits.payload_is_ref)11152 var case_val_idx: usize = 0;
11274 operand_ty.childType(zcu)11153 var case_it = zir_switch.iterateCases();
11275 else11154 var extra_index = zir_switch.end;
11276 operand_ty;
1127711155
11278 if (operand_err_set.zigTypeTag(zcu) != .error_union) {11156 var under_prong: ?struct {
11279 return sema.fail(block, switch_src, "expected error union type, found '{f}'", .{11157 index: Zir.UnwrappedSwitchBlock.Case.Index,
11280 operand_ty.fmt(pt),11158 body: []const Zir.Inst.Index,
11281 });11159 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11282 }11160 has_tag_capture: bool,
11161 } = null;
1128311162
11284 const operand_err_set_ty = operand_err_set.errorUnionSet(zcu);11163 var cases_len: u32 = 0;
11164 while (case_it.next()) |case| {
11165 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
11166 case_val_idx += item_refs.len;
11167 const range_refs: []const [2]Air.Inst.Ref =
11168 @ptrCast(case_vals[case_val_idx..][0 .. 2 * case.range_infos.len]);
11169 case_val_idx += 2 * range_refs.len;
1128511170
11286 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);11171 const prong_info = case.prong_info;
11287 try sema.air_instructions.append(gpa, .{11172 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
11288 .tag = .block,11173 extra_index += prong_body.len;
11289 .data = undefined,
11290 });
11291 var label: Block.Label = .{
11292 .zir_block = inst,
11293 .merges = .{
11294 .src_locs = .{},
11295 .results = .{},
11296 .br_list = .{},
11297 .block_inst = block_inst,
11298 },
11299 };
1130011174
11301 var child_block: Block = .{11175 // Enough capacity for inlining regular items, we can't really predict
11302 .parent = block,11176 // how many range items we will end up with (at least not in a safe and
11303 .sema = sema,11177 // cheap manner) so we allocate on demand for those.
11304 .namespace = block.namespace,11178 if (prong_info.is_inline) {
11305 .instructions = .{},11179 try branch_hints.ensureUnusedCapacity(gpa, @intCast(case.item_infos.len));
11306 .label = &label,11180 }
11307 .inlining = block.inlining,
11308 .comptime_reason = block.comptime_reason,
11309 .is_typeof = block.is_typeof,
11310 .c_import_buf = block.c_import_buf,
11311 .runtime_cond = block.runtime_cond,
11312 .runtime_loop = block.runtime_loop,
11313 .runtime_index = block.runtime_index,
11314 .error_return_trace_index = block.error_return_trace_index,
11315 .want_safety = block.want_safety,
11316 .src_base_inst = block.src_base_inst,
11317 .type_name_ctx = block.type_name_ctx,
11318 };
11319 const merges = &child_block.label.?.merges;
11320 defer child_block.instructions.deinit(gpa);
11321 defer merges.deinit(gpa);
1132211181
11323 const resolved_err_set = try sema.resolveInferredErrorSetTy(block, main_src, operand_err_set_ty.toIntern());11182 var emit_bb = false;
11324 if (Type.fromInterned(resolved_err_set).errorSetIsEmpty(zcu)) {11183 var any_analyze_body = false;
11325 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);11184 var is_under_prong = false;
11326 }11185 for (case.item_infos, item_refs, 0..) |item_info, item_ref, item_i| {
11186 if (item_ref == .none) is_under_prong = true;
11187 if (item_info.bodyLen()) |body_len| extra_index += body_len;
1132711188
11328 const else_error_ty: ?Type = try validateErrSetSwitch(11189 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, union_originally, err_set, prong_info.is_comptime_unreach);
11329 sema,11190 if (analyze_body) any_analyze_body = true;
11330 block,
11331 &seen_errors,
11332 &case_vals,
11333 operand_err_set_ty,
11334 inst_data,
11335 scalar_cases_len,
11336 multi_cases_len,
11337 .{ .body = else_case.body, .end = else_case.end, .src = else_prong_src },
11338 extra.data.bits.has_else,
11339 );
1134011191
11341 var spa: SwitchProngAnalysis = .{11192 if (prong_info.is_inline) {
11342 .sema = sema,11193 cases_len += 1;
11343 .parent_block = block,11194 case_block.instructions.clearRetainingCapacity();
11344 .operand = .{11195 case_block.error_return_trace_index = child_block.error_return_trace_index;
11345 .simple = .{
11346 .by_val = undefined, // must be set to the unwrapped error code before use
11347 .by_ref = undefined,
11348 .cond = raw_operand_val,
11349 },
11350 },
11351 .else_error_ty = else_error_ty,
11352 .switch_block_inst = inst,
11353 .tag_capture_inst = undefined,
11354 };
1135511196
11356 if (try sema.resolveDefinedValue(&child_block, main_src, raw_operand_val)) |ov| {11197 if (emit_bb) {
11357 const operand_val = if (extra.data.bits.payload_is_ref)11198 const bb_src = block.src(.{ .switch_case_item = .{
11358 (try sema.pointerDeref(&child_block, main_src, ov, operand_ty)).?11199 .switch_node_offset = src_node_offset,
11359 else11200 .case_idx = case.index,
11360 ov;11201 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
11202 } });
11203 try sema.emitBackwardBranch(block, bb_src);
11204 }
11205 emit_bb = true;
1136111206
11362 if (operand_val.errorUnionIsPayload(zcu)) {11207 const prong_hint: std.builtin.BranchHint = hint: {
11363 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);11208 if (analyze_body) break :hint try sema.analyzeSwitchProng(
11364 } else {11209 &case_block,
11365 const err_val = Value.fromInterned(try pt.intern(.{11210 operand,
11366 .err = .{11211 operand_ty,
11367 .ty = operand_err_set_ty.toIntern(),11212 raw_operand_ty,
11368 .name = operand_val.getErrorName(zcu).unwrap().?,11213 prong_body,
11369 },11214 block.src(.{ .switch_capture = .{
11370 }));11215 .switch_node_offset = src_node_offset,
11371 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)11216 .case_idx = case.index,
11372 try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)11217 } }),
11373 else11218 prong_info.capture,
11374 try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);11219 prong_info.has_tag_capture,
11220 item_ref,
11221 .{ .item_refs = &.{item_ref} },
11222 validated_switch.else_err_ty,
11223 switch_inst,
11224 zir_switch,
11225 );
11226 _ = try case_block.addNoOp(.unreach);
11227 break :hint .cold; // unreachable branches are cold
11228 };
11229 branch_hints.appendAssumeCapacity(prong_hint);
1137511230
11376 if (extra.data.bits.any_uses_err_capture) {11231 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11377 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);11232 1 + // `item`, no ranges
11233 case_block.instructions.items.len);
11234 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11235 .items_len = 1,
11236 .ranges_len = 0,
11237 .body_len = @intCast(case_block.instructions.items.len),
11238 }));
11239 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11240 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11378 }11241 }
11379 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
11380
11381 return resolveSwitchComptime(
11382 sema,
11383 spa,
11384 &child_block,
11385 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11386 err_val,
11387 operand_err_set_ty,
11388 switch_src_node_offset,
11389 null,
11390 .{
11391 .body = else_case.body,
11392 .end = else_case.end,
11393 .capture = if (else_case.has_capture) .by_val else .none,
11394 .is_inline = else_case.is_inline,
11395 .has_tag_capture = false,
11396 },
11397 false,
11398 case_vals,
11399 scalar_cases_len,
11400 multi_cases_len,
11401 true,
11402 false,
11403 );
11404 }11242 }
11405 }11243 for (case.range_infos, range_refs, 0..) |range_info, range_ref, range_i| {
11244 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
11245 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
1140611246
11407 if (scalar_cases_len + multi_cases_len == 0) {11247 any_analyze_body = true; // always an integer range, always needs analysis
11408 if (else_error_ty) |ty| if (ty.errorSetIsEmpty(zcu)) {
11409 return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);
11410 };
11411 }
11412
11413 if (child_block.isComptime()) {
11414 _ = try sema.resolveConstDefinedValue(&child_block, main_operand_src, raw_operand_val, null);
11415 unreachable;
11416 }
1141711248
11418 const cond = if (extra.data.bits.payload_is_ref) blk: {11249 if (prong_info.is_inline) {
11419 try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val).elemType2(zcu));11250 var item = sema.resolveConstDefinedValue(block, .unneeded, range_ref[0], undefined) catch unreachable;
11420 const loaded = try sema.analyzeLoad(block, main_src, raw_operand_val, main_src);11251 const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_ref[1], undefined) catch unreachable;
11421 break :blk try sema.analyzeIsNonErr(block, main_src, loaded);
11422 } else blk: {
11423 try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val));
11424 break :blk try sema.analyzeIsNonErr(block, main_src, raw_operand_val);
11425 };
1142611252
11427 var sub_block = child_block.makeSubBlock();11253 if (try item.getUnsignedIntSema(pt)) |first_int| {
11428 sub_block.runtime_loop = null;11254 if (try item_last.getUnsignedIntSema(pt)) |last_int| {
11429 sub_block.runtime_cond = main_operand_src;11255 if (std.math.cast(u32, last_int - first_int)) |range_len| {
11430 sub_block.runtime_index.increment();11256 try branch_hints.ensureUnusedCapacity(gpa, range_len);
11431 sub_block.need_debug_scope = null; // this body is emitted regardless11257 }
11432 defer sub_block.instructions.deinit(gpa);11258 }
11259 }
1143311260
11434 const non_error_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, non_error_case.body);11261 var prev_result_overflowed = false;
11435 const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);11262 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
11436 defer gpa.free(true_instructions);11263 // Previous validation has resolved any possible lazy values.
11264 const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
11265 .int => .{ item, operand_ty },
11266 .@"enum" => b: {
11267 const int_val: Value = .fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
11268 break :b .{ int_val, int_val.typeOf(zcu) };
11269 },
11270 else => unreachable,
11271 };
11272 assert(!prev_result_overflowed);
11273 const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
11274 prev_result_overflowed = result.overflow;
11275 item = switch (operand_ty.zigTypeTag(zcu)) {
11276 .int => result.val,
11277 .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
11278 .ty = operand_ty.toIntern(),
11279 .int = result.val.toIntern(),
11280 } })),
11281 else => unreachable,
11282 };
11283 }) {
11284 cases_len += 1;
11285 case_block.instructions.clearRetainingCapacity();
11286 case_block.error_return_trace_index = child_block.error_return_trace_index;
1143711287
11438 spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)11288 const item_ref: Air.Inst.Ref = .fromValue(item);
11439 try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)
11440 else
11441 try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);
11442
11443 if (extra.data.bits.any_uses_err_capture) {
11444 sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
11445 }
11446 defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
11447 _ = try sema.analyzeSwitchRuntimeBlock(
11448 spa,
11449 &sub_block,
11450 switch_src,
11451 try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
11452 operand_err_set_ty,
11453 switch_operand_src,
11454 case_vals,
11455 .{
11456 .body = else_case.body,
11457 .end = else_case.end,
11458 .capture = if (else_case.has_capture) .by_val else .none,
11459 .is_inline = else_case.is_inline,
11460 .has_tag_capture = false,
11461 },
11462 scalar_cases_len,
11463 multi_cases_len,
11464 false,
11465 undefined,
11466 true,
11467 switch_src_node_offset,
11468 else_prong_src,
11469 false,
11470 undefined,
11471 seen_errors,
11472 undefined,
11473 undefined,
11474 undefined,
11475 cond_dbg_node_index,
11476 true,
11477 null,
11478 undefined,
11479 &.{},
11480 &.{},
11481 );
1148211289
11483 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +11290 if (emit_bb) {
11484 true_instructions.len + sub_block.instructions.items.len);11291 const bb_src = block.src(.{ .switch_case_item = .{
11292 .switch_node_offset = src_node_offset,
11293 .case_idx = case.index,
11294 .item_idx = .{ .kind = .range, .value = @intCast(range_i) },
11295 } });
11296 try sema.emitBackwardBranch(block, bb_src);
11297 }
11298 emit_bb = true;
1148511299
11486 _ = try child_block.addInst(.{11300 const prong_hint = try sema.analyzeSwitchProng(
11487 .tag = .cond_br,11301 &case_block,
11488 .data = .{11302 operand,
11489 .pl_op = .{11303 operand_ty,
11490 .operand = cond,11304 raw_operand_ty,
11491 .payload = sema.addExtraAssumeCapacity(Air.CondBr{11305 prong_body,
11492 .then_body_len = @intCast(true_instructions.len),11306 block.src(.{ .switch_capture = .{
11493 .else_body_len = @intCast(sub_block.instructions.items.len),11307 .switch_node_offset = src_node_offset,
11494 .branch_hints = .{11308 .case_idx = case.index,
11495 .true = non_error_hint,11309 } }),
11496 .false = .none,11310 prong_info.capture,
11497 // Code coverage is desired for error handling.11311 prong_info.has_tag_capture,
11498 .then_cov = .poi,11312 item_ref,
11499 .else_cov = .poi,11313 .has_ranges,
11500 },11314 validated_switch.else_err_ty,
11501 }),11315 switch_inst,
11502 },11316 zir_switch,
11503 },11317 );
11504 });11318 try branch_hints.append(gpa, prong_hint);
11505 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
11506 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
1150711319
11508 return sema.resolveAnalyzedBlock(block, main_src, &child_block, merges, false);11320 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11509}11321 1 + // `item`, no ranges
11322 case_block.instructions.items.len);
11323 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11324 .items_len = 1,
11325 .ranges_len = 0,
11326 .body_len = @intCast(case_block.instructions.items.len),
11327 }));
11328 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11329 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11330 }
11331 }
11332 }
1151011333
11511fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_ref: bool) CompileError!Air.Inst.Ref {11334 if (prong_info.is_inline) continue; // handled above
11512 const tracy = trace(@src());
11513 defer tracy.end();
1151411335
11515 const pt = sema.pt;11336 if (is_under_prong) {
11516 const zcu = pt.zcu;11337 under_prong = .{
11517 const ip = &zcu.intern_pool;11338 .index = case.index,
11518 const gpa = sema.gpa;11339 .body = prong_body,
11519 const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;11340 .capture = case.prong_info.capture,
11520 const src = block.nodeOffset(inst_data.src_node);11341 .has_tag_capture = case.prong_info.has_tag_capture,
11521 const src_node_offset = inst_data.src_node;11342 };
11522 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });11343 continue;
11523 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });11344 }
11524 const under_prong_src = block.src(.{ .node_offset_switch_under_prong = src_node_offset });
11525 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
1152611345
11527 const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {11346 cases_len += 1;
11528 const maybe_ptr = try sema.resolveInst(extra.data.operand);11347 case_block.instructions.clearRetainingCapacity();
11529 const val, const ref = if (operand_is_ref)11348 case_block.error_return_trace_index = child_block.error_return_trace_index;
11530 .{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
11531 else
11532 .{ maybe_ptr, undefined };
1153311349
11534 const init_cond = try sema.switchCond(block, operand_src, val);11350 const prong_hint: std.builtin.BranchHint = hint: {
11351 if (any_analyze_body) break :hint try sema.analyzeSwitchProng(
11352 &case_block,
11353 operand,
11354 operand_ty,
11355 raw_operand_ty,
11356 prong_body,
11357 block.src(.{ .switch_capture = .{
11358 .switch_node_offset = src_node_offset,
11359 .case_idx = case.index,
11360 } }),
11361 prong_info.capture,
11362 prong_info.has_tag_capture,
11363 .none,
11364 .{ .item_refs = item_refs },
11365 validated_switch.else_err_ty,
11366 switch_inst,
11367 zir_switch,
11368 );
11369 _ = try case_block.addNoOp(.unreach);
11370 break :hint .cold; // unreachable branches are cold
11371 };
11372 try branch_hints.append(gpa, prong_hint);
1153511373
11536 const operand_ty = sema.typeOf(val);11374 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11375 item_refs.len +
11376 2 * range_refs.len +
11377 case_block.instructions.items.len);
11378 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11379 .items_len = @intCast(item_refs.len),
11380 .ranges_len = @intCast(range_refs.len),
11381 .body_len = @intCast(case_block.instructions.items.len),
11382 }));
11383 cases_extra.appendSliceAssumeCapacity(@ptrCast(item_refs));
11384 cases_extra.appendSliceAssumeCapacity(@ptrCast(range_refs));
11385 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11386 }
1153711387
11538 if (extra.data.bits.has_continue and !block.isComptime()) {11388 const catch_all_extra: []const u32 = catch_all_extra: {
11539 // Even if the operand is comptime-known, this `switch` is runtime.11389 if (catch_all_case == .none and !case_block.wantSafety()) {
11540 if (try operand_ty.comptimeOnlySema(pt)) {11390 try branch_hints.append(gpa, .none);
11541 return sema.failWithOwnedErrorMsg(block, msg: {11391 break :catch_all_extra &.{};
11542 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{f}'", .{operand_ty.fmt(pt)});11392 }
11543 errdefer msg.destroy(gpa);11393 var emit_bb = false;
11544 try sema.errNote(operand_src, msg, "switch loops are evaluated at runtime outside of comptime scopes", .{});11394 if (has_else and else_case.is_inline) {
11545 break :msg msg;11395 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
11396 var error_names: InternPool.NullTerminatedString.Slice = undefined;
11397 var min_int: Value = undefined;
11398 check_enumerable: {
11399 switch (item_ty.zigTypeTag(zcu)) {
11400 .@"union" => unreachable,
11401 .@"enum" => if (else_is_named_only or
11402 !item_ty.isNonexhaustiveEnum(zcu) or union_originally)
11403 {
11404 try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len));
11405 break :check_enumerable;
11406 },
11407 .error_set => if (!operand_ty.isAnyError(zcu)) {
11408 error_names = item_ty.errorSetNames(zcu);
11409 try branch_hints.ensureUnusedCapacity(gpa, error_names.len);
11410 break :check_enumerable;
11411 },
11412 .int => {
11413 min_int = try item_ty.minInt(pt, item_ty);
11414 break :check_enumerable;
11415 },
11416 .bool, .void => break :check_enumerable,
11417 else => {},
11418 }
11419 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
11420 item_ty.fmt(pt),
11546 });11421 });
11547 }11422 }
11548 try sema.validateRuntimeValue(block, operand_src, maybe_ptr);11423 var unhandled_it = validated_switch.iterateUnhandledItems(error_names, min_int);
11549 const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {11424 while (try unhandled_it.next(sema, item_ty)) |item_val| {
11550 const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));11425 cases_len += 1;
11551 const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);11426 case_block.instructions.clearRetainingCapacity();
11552 _ = try block.addBinOp(.store, operand_alloc, maybe_ptr);11427 case_block.error_return_trace_index = child_block.error_return_trace_index;
11553 break :a operand_alloc;
11554 } else undefined;
11555 break :op .{
11556 .{ .loop = .{
11557 .operand_alloc = operand_alloc,
11558 .operand_is_ref = operand_is_ref,
11559 .init_cond = init_cond,
11560 } },
11561 operand_ty,
11562 };
11563 }
1156411428
11565 // We always use `simple` in the comptime case, because as far as the dispatching logic11429 const item_ref: Air.Inst.Ref = .fromValue(item_val);
11566 // is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will
11567 // be resposible for recursively resolving different prongs as needed.
11568 break :op .{
11569 .{ .simple = .{
11570 .by_val = val,
11571 .by_ref = ref,
11572 .cond = init_cond,
11573 } },
11574 operand_ty,
11575 };
11576 };
1157711430
11578 const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";11431 const analyze_body = sema.wantSwitchProngBodyAnalysis(block, item_ref, operand_ty, union_originally, err_set, false);
11579 const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
11580 const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
11581 .@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
11582 else => raw_operand_ty,
11583 };
1158411432
11585 // AstGen guarantees that the instruction immediately preceding11433 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
11586 // switch_block(_ref) is a dbg_stmt11434 emit_bb = true;
11587 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);
1158811435
11589 var header_extra_index: usize = extra.end;11436 const prong_hint: std.builtin.BranchHint = hint: {
11437 if (analyze_body) break :hint try sema.analyzeSwitchProng(
11438 &case_block,
11439 operand,
11440 operand_ty,
11441 raw_operand_ty,
11442 else_case.body,
11443 block.src(.{ .switch_capture = .{
11444 .switch_node_offset = src_node_offset,
11445 .case_idx = else_case.index,
11446 } }),
11447 else_case.capture,
11448 else_case.has_tag_capture,
11449 item_ref,
11450 .special,
11451 validated_switch.else_err_ty,
11452 switch_inst,
11453 zir_switch,
11454 );
11455 _ = try case_block.addNoOp(.unreach);
11456 break :hint .cold; // unreachable branches are cold
11457 };
11458 try branch_hints.append(gpa, prong_hint);
1159011459
11591 const scalar_cases_len = extra.data.bits.scalar_cases_len;11460 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11592 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {11461 1 + // `item`, no ranges
11593 const multi_cases_len = sema.code.extra[header_extra_index];11462 case_block.instructions.items.len);
11594 header_extra_index += 1;11463 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
11595 break :blk multi_cases_len;11464 .items_len = 1,
11596 } else 0;11465 .ranges_len = 0,
11466 .body_len = @intCast(case_block.instructions.items.len),
11467 }));
11468 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11469 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
11470 }
11471 }
1159711472
11598 const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {11473 case_block.instructions.clearRetainingCapacity();
11599 const tag_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);11474 case_block.error_return_trace_index = child_block.error_return_trace_index;
11600 header_extra_index += 1;
11601 // SwitchProngAnalysis wants inst_map to have space for the tag capture.
11602 // Note that the normal capture is referred to via the switch block
11603 // index, which there is already necessarily space for.
11604 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst});
11605 break :blk tag_capture_inst;
11606 } else undefined;
1160711475
11608 var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);11476 if (zcu.backendSupportsFeature(.is_named_enum_value) and
11609 defer case_vals.deinit(gpa);11477 catch_all_case != .none and block.wantSafety() and
1161011478 item_ty.zigTypeTag(zcu) == .@"enum" and
11611 var single_absorbed_item: Zir.Inst.Ref = .none;11479 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
11612 var absorbed_items: []const Zir.Inst.Ref = &.{};11480 {
11613 var absorbed_ranges: []const Zir.Inst.Ref = &.{};11481 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
1161411482 const ok = try case_block.addUnOp(.is_named_enum_value, cond_ref);
11615 const special_prongs = extra.data.bits.special_prongs;11483 if (else_is_named_only) {} else {
11616 const has_else = special_prongs.hasElse();11484 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
11617 const has_under = special_prongs.hasUnder();11485 }
11618 const special_else: SpecialProng = if (has_else) blk: {11486 }
11619 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);
11620 const extra_body_start = header_extra_index + 1;
11621 break :blk .{
11622 .body = sema.code.bodySlice(extra_body_start, info.body_len),
11623 .end = extra_body_start + info.body_len,
11624 .capture = info.capture,
11625 .is_inline = info.is_inline,
11626 .has_tag_capture = info.has_tag_capture,
11627 };
11628 } else .{
11629 .body = &.{},
11630 .end = header_extra_index,
11631 .capture = .none,
11632 .is_inline = false,
11633 .has_tag_capture = false,
11634 };
11635 const special_under: SpecialProng = if (has_under) blk: {
11636 var extra_index = special_else.end;
11637 var trailing_items_len: usize = 0;
11638 if (special_prongs.hasOneAdditionalItem()) {
11639 single_absorbed_item = @enumFromInt(sema.code.extra[extra_index]);
11640 extra_index += 1;
11641 absorbed_items = @ptrCast(&single_absorbed_item);
11642 } else if (special_prongs.hasManyAdditionalItems()) {
11643 const items_len = sema.code.extra[extra_index];
11644 extra_index += 1;
11645 const ranges_len = sema.code.extra[extra_index];
11646 extra_index += 1;
11647 absorbed_items = sema.code.refSlice(extra_index + 1, items_len);
11648 absorbed_ranges = sema.code.refSlice(extra_index + 1 + items_len, ranges_len * 2);
11649 trailing_items_len = items_len + ranges_len * 2;
11650 }
11651 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11652 extra_index += 1 + trailing_items_len;
11653 break :blk .{
11654 .body = sema.code.bodySlice(extra_index, info.body_len),
11655 .end = extra_index + info.body_len,
11656 .capture = info.capture,
11657 .is_inline = info.is_inline,
11658 .has_tag_capture = info.has_tag_capture,
11659 };
11660 } else .{
11661 .body = &.{},
11662 .end = special_else.end,
11663 .capture = .none,
11664 .is_inline = false,
11665 .has_tag_capture = false,
11666 };
11667 const special_end = special_under.end;
1166811487
11669 // Duplicate checking variables later also used for `inline else`.11488 if (else_is_named_only and !else_case.is_inline) {
11670 var seen_enum_fields: []?LazySrcLoc = &.{};11489 // If we have both an `else` and an `_` prong, all named values go
11671 var seen_errors = SwitchErrorSet.init(gpa);11490 // into the `else` prong and all unnamed ones go into the `_` prong.
11672 var range_set = RangeSet.init(gpa, zcu);11491 // We will manually enumerate all named values which haven't been
11673 var true_count: u8 = 0;11492 // encountered yet and create an extra prong for them, which will
11674 var false_count: u8 = 0;11493 // evaulate to the `else` body.
1167511494
11676 defer {11495 assert(operand_ty.isNonexhaustiveEnum(zcu));
11677 range_set.deinit();
11678 gpa.free(seen_enum_fields);
11679 seen_errors.deinit();
11680 }
1168111496
11682 var empty_enum = false;11497 cases_len += 1;
1168311498
11684 var else_error_ty: ?Type = null;11499 const prong_hint: std.builtin.BranchHint = hint: {
11500 if (!else_case.is_inline) break :hint try sema.analyzeSwitchProng(
11501 &case_block,
11502 operand,
11503 operand_ty,
11504 raw_operand_ty,
11505 else_case.body,
11506 block.src(.{ .switch_capture = .{
11507 .switch_node_offset = src_node_offset,
11508 .case_idx = else_case.index,
11509 } }),
11510 else_case.capture,
11511 else_case.has_tag_capture,
11512 .none,
11513 .special,
11514 validated_switch.else_err_ty,
11515 switch_inst,
11516 zir_switch,
11517 );
11518 _ = try case_block.addNoOp(.unreach);
11519 break :hint .cold; // unreachable branches are cold
11520 };
11521 try branch_hints.append(gpa, prong_hint);
1168511522
11686 // Validate usage of '_' prongs.11523 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
11687 if (has_under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {11524 (validated_switch.seen_enum_fields.len + 1 - zir_switch.totalItemsLen()) + // +1 because totalItemsLen includes the _
11688 const msg = msg: {11525 case_block.instructions.items.len);
11689 const msg = try sema.errMsg(11526 const extra_case = cases_extra.addManyAsArrayAssumeCapacity(
11690 src,11527 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len,
11691 "'_' prong only allowed when switching on non-exhaustive enums",
11692 .{},
11693 );
11694 errdefer msg.destroy(gpa);
11695 try sema.errNote(
11696 under_prong_src,
11697 msg,
11698 "'_' prong here",
11699 .{},
11700 );
11701 try sema.errNote(
11702 src,
11703 msg,
11704 "consider using 'else'",
11705 .{},
11706 );11528 );
11707 break :msg msg;11529 var items_len: u32 = 0;
11708 };11530 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
11709 return sema.failWithOwnedErrorMsg(block, msg);11531 if (seen_field != null) continue;
11710 }11532 const item_val = try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
11533 const item_ref: Air.Inst.Ref = .fromValue(item_val);
11534 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
11535 items_len += 1;
11536 }
11537 assert(items_len > 0); // `else` must be reachable at this point
11538 extra_case.* = payloadToExtraItems(Air.SwitchBr.Case{
11539 .items_len = items_len,
11540 .ranges_len = 0,
11541 .body_len = @intCast(case_block.instructions.items.len),
11542 });
11543 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
1171111544
11712 // Validate for duplicate items, missing else prong, and invalid range.11545 // We fall through to the regular catch-all prong generation.
11713 switch (cond_ty.zigTypeTag(zcu)) {
11714 .@"union" => unreachable, // handled in `switchCond`
11715 .@"enum" => {
11716 seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));
11717 empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);
11718 @memset(seen_enum_fields, null);
11719 // `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
1172011546
11721 for (absorbed_items, 0..) |item_ref, item_i| {11547 case_block.instructions.clearRetainingCapacity();
11722 _ = try sema.validateSwitchItemEnum(11548 case_block.error_return_trace_index = child_block.error_return_trace_index;
11723 block,11549 }
11724 seen_enum_fields,
11725 &range_set,
11726 item_ref,
11727 cond_ty,
11728 block.src(.{ .switch_case_item = .{
11729 .switch_node_offset = src_node_offset,
11730 .case_idx = .special_under,
11731 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11732 } }),
11733 );
11734 }
11735 try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset);
1173611550
11737 var extra_index: usize = special_end;11551 const analyze_catch_all_body = analyze_body: {
11738 {11552 switch (catch_all_case) {
11739 var scalar_i: u32 = 0;11553 .none => break :analyze_body false, // we still may want a safety check!
11740 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {11554 .under => break :analyze_body true, // can't be a union anyway
11741 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);11555 .@"else" => if (else_case.is_inline) break :analyze_body false,
11742 extra_index += 1;
11743 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11744 extra_index += 1 + info.body_len;
11745
11746 case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
11747 block,
11748 seen_enum_fields,
11749 &range_set,
11750 item_ref,
11751 cond_ty,
11752 block.src(.{ .switch_case_item = .{
11753 .switch_node_offset = src_node_offset,
11754 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11755 .item_idx = .{ .kind = .single, .index = 0 },
11756 } }),
11757 ));
11758 }
11759 }11556 }
11760 {11557 if (union_originally) {
11761 var multi_i: u32 = 0;11558 const union_obj = zcu.typeToUnion(operand_ty).?;
11762 while (multi_i < multi_cases_len) : (multi_i += 1) {11559 for (validated_switch.seen_enum_fields, 0..) |seen_field, field_i| {
11763 const items_len = sema.code.extra[extra_index];11560 if (seen_field != null) continue;
11764 extra_index += 1;11561 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_i]);
11765 const ranges_len = sema.code.extra[extra_index];11562 if (!field_ty.isNoReturn(zcu)) break :analyze_body true;
11766 extra_index += 1;
11767 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11768 extra_index += 1;
11769 const items = sema.code.refSlice(extra_index, items_len);
11770 extra_index += items_len + info.body_len;
11771
11772 try case_vals.ensureUnusedCapacity(gpa, items.len);
11773 for (items, 0..) |item_ref, item_i| {
11774 case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
11775 block,
11776 seen_enum_fields,
11777 &range_set,
11778 item_ref,
11779 cond_ty,
11780 block.src(.{ .switch_case_item = .{
11781 .switch_node_offset = src_node_offset,
11782 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
11783 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
11784 } }),
11785 ));
11786 }
11787
11788 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
11789 }11563 }
11564 break :analyze_body false;
11790 }11565 }
11791 const all_tags_handled = for (seen_enum_fields) |seen_src| {11566 if (err_set) {
11792 if (seen_src == null) break false;11567 const else_err_ty = validated_switch.else_err_ty orelse {
11793 } else true;11568 assert(else_case.is_simple_noreturn);
11569 break :analyze_body false;
11570 };
11571 if (else_err_ty.errorSetIsEmpty(zcu)) break :analyze_body false;
11572 }
11573 break :analyze_body true;
11574 };
1179411575
11795 if (has_else) {11576 const catch_all_hint = hint: {
11796 if (all_tags_handled) {11577 if (analyze_catch_all_body) {
11797 if (cond_ty.isNonexhaustiveEnum(zcu)) {11578 const index, const body, const capture, const has_tag_capture = switch (catch_all_case) {
11798 if (has_under) return sema.fail(11579 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture },
11799 block,11580 .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture },
11581 .none => unreachable,
11582 };
11583 break :hint try sema.analyzeSwitchProng(
11584 &case_block,
11585 operand,
11586 operand_ty,
11587 raw_operand_ty,
11588 body,
11589 block.src(.{ .switch_capture = .{
11590 .switch_node_offset = src_node_offset,
11591 .case_idx = index,
11592 } }),
11593 capture,
11594 has_tag_capture,
11595 .none,
11596 .special,
11597 validated_switch.else_err_ty,
11598 switch_inst,
11599 zir_switch,
11600 );
11601 }
11602 // We still need a terminator in this block, but we have proven
11603 // that it is unreachable.
11604 if (case_block.wantSafety()) {
11605 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
11606 try sema.safetyPanic(&case_block, src, .corrupt_switch);
11607 } else {
11608 _ = try case_block.addNoOp(.unreach);
11609 }
11610 break :hint .cold; // Safety check / unreachable branches are cold.
11611 };
11612 try branch_hints.append(gpa, catch_all_hint);
11613 break :catch_all_extra @ptrCast(case_block.instructions.items);
11614 };
11615
11616 assert(branch_hints.count == cases_len + 1); // +1 for catch-all hint
11617
11618 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
11619 branch_hints.bags.items.len +
11620 cases_extra.items.len +
11621 catch_all_extra.len);
11622 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
11623 .cases_len = @intCast(cases_len),
11624 .else_body_len = @intCast(catch_all_extra.len),
11625 });
11626 sema.air_extra.appendSliceAssumeCapacity(branch_hints.bags.items);
11627 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
11628 sema.air_extra.appendSliceAssumeCapacity(catch_all_extra);
11629
11630 const air_tag: Air.Inst.Tag = if (operand == .loop and merges.extra_insts.items.len > 0)
11631 .loop_switch_br
11632 else
11633 .switch_br;
11634 const air_switch_ref = try child_block.addInst(.{
11635 .tag = air_tag,
11636 .data = .{ .pl_op = .{
11637 .operand = cond_ref,
11638 .payload = payload_index,
11639 } },
11640 });
11641 try sema.fixupSwitchContinues(
11642 block,
11643 src,
11644 air_switch_ref,
11645 operand,
11646 operand_is_ref,
11647 item_ty,
11648 .normal,
11649 zir_switch.any_maybe_runtime_capture,
11650 merges,
11651 );
11652}
11653
11654/// This is the counterpart to `zirSwitchContinue`; replaces placeholder `br` insts
11655/// with their respective finalized inst pointing back at `switch_ref`.
11656fn fixupSwitchContinues(
11657 sema: *Sema,
11658 block: *Block,
11659 switch_src: LazySrcLoc,
11660 switch_ref: Air.Inst.Ref,
11661 operand: SwitchOperand,
11662 operand_is_ref: bool,
11663 item_ty: Type,
11664 mode: enum { normal, opv },
11665 any_non_inline_capture: bool,
11666 merges: *const Block.Merges,
11667) CompileError!void {
11668 const pt = sema.pt;
11669 const zcu = pt.zcu;
11670 const gpa = sema.gpa;
11671
11672 const air_tag = sema.air_instructions.items(.tag)[@intFromEnum(switch_ref.toIndex().?)];
11673 switch (air_tag) {
11674 .loop_switch_br, .switch_br => assert(mode == .normal),
11675 .loop, .block => assert(mode == .opv),
11676 else => unreachable,
11677 }
11678 switch (air_tag) {
11679 .loop_switch_br, .loop => assert(merges.extra_insts.items.len > 0),
11680 .switch_br, .block => assert(merges.extra_insts.items.len == 0),
11681 else => unreachable,
11682 }
11683
11684 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
11685 var replacement_block = block.makeSubBlock();
11686 defer replacement_block.instructions.deinit(gpa);
11687
11688 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
11689 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
11690
11691 if (any_non_inline_capture and mode != .opv) {
11692 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
11693 }
11694
11695 const new_operand_val = if (operand_is_ref)
11696 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
11697 else
11698 new_operand_maybe_ref;
11699
11700 const new_cond = try sema.coerce(&replacement_block, item_ty, new_operand_val, dispatch_src);
11701
11702 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
11703 item_ty.zigTypeTag(zcu) == .@"enum" and !item_ty.isNonexhaustiveEnum(zcu) and
11704 mode == .normal and !try sema.isComptimeKnown(new_cond))
11705 {
11706 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
11707 try sema.addSafetyCheck(&replacement_block, switch_src, ok, .corrupt_switch);
11708 }
11709
11710 switch (mode) {
11711 .normal => {
11712 _ = try replacement_block.addInst(.{
11713 .tag = .switch_dispatch,
11714 .data = .{ .br = .{
11715 .block_inst = switch_ref.toIndex().?,
11716 .operand = new_cond,
11717 } },
11718 });
11719 },
11720 .opv => {
11721 _ = try replacement_block.addInst(.{
11722 .tag = .repeat,
11723 .data = .{ .repeat = .{
11724 .loop_inst = switch_ref.toIndex().?,
11725 } },
11726 });
11727 },
11728 }
11729
11730 if (replacement_block.instructions.items.len == 1) {
11731 // Optimization: we don't need a block!
11732 sema.air_instructions.set(
11733 @intFromEnum(placeholder_inst),
11734 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
11735 );
11736 continue;
11737 }
11738
11739 // Replace placeholder with a block.
11740 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
11741 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
11742 replacement_block.instructions.items.len);
11743 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
11744 .tag = .block,
11745 .data = .{ .ty_pl = .{
11746 .ty = .noreturn_type,
11747 .payload = sema.addExtraAssumeCapacity(Air.Block{
11748 .body_len = @intCast(replacement_block.instructions.items.len),
11749 }),
11750 } },
11751 });
11752 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
11753 }
11754}
11755
11756const ValidatedSwitchBlock = struct {
11757 seen_enum_fields: []const ?LazySrcLoc,
11758 seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11759 seen_ranges: []const RangeSet.Range,
11760 true_src: ?LazySrcLoc,
11761 false_src: ?LazySrcLoc,
11762 void_src: ?LazySrcLoc,
11763
11764 case_vals: []const Air.Inst.Ref,
11765 else_case: Zir.UnwrappedSwitchBlock.Case.Else,
11766 else_err_ty: ?Type,
11767
11768 fn iterateUnhandledItems(
11769 validated_switch: *const ValidatedSwitchBlock,
11770 /// May be `undefined` if `item_ty` isn't an `error_set`.
11771 error_names: InternPool.NullTerminatedString.Slice,
11772 /// May be `undefined` if `item_ty` isn't an `int`.
11773 min_int: Value,
11774 ) UnhandledIterator {
11775 return .{
11776 .next_idx = 0,
11777 .next_val = min_int,
11778 .error_names = error_names,
11779 .seen_enum_fields = validated_switch.seen_enum_fields,
11780 .seen_errors = &validated_switch.seen_errors,
11781 .seen_ranges = validated_switch.seen_ranges,
11782 .seen_true = validated_switch.true_src != null,
11783 .seen_false = validated_switch.false_src != null,
11784 .seen_void = validated_switch.void_src != null,
11785 };
11786 }
11787
11788 const UnhandledIterator = struct {
11789 next_idx: u32,
11790 next_val: ?Value,
11791 error_names: InternPool.NullTerminatedString.Slice,
11792 seen_enum_fields: []const ?LazySrcLoc,
11793 seen_errors: *const std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
11794 seen_ranges: []const RangeSet.Range,
11795 seen_true: bool,
11796 seen_false: bool,
11797 seen_void: bool,
11798
11799 fn next(it: *UnhandledIterator, sema: *Sema, item_ty: Type) CompileError!?Value {
11800 const pt = sema.pt;
11801 const zcu = pt.zcu;
11802 const ip = &zcu.intern_pool;
11803 switch (item_ty.zigTypeTag(zcu)) {
11804 .@"union" => unreachable,
11805 .@"enum" => {
11806 for (it.seen_enum_fields[it.next_idx..], it.next_idx..) |seen_field, field_i| {
11807 if (seen_field != null) continue;
11808 it.next_idx = @intCast(field_i + 1);
11809 return try pt.enumValueFieldIndex(item_ty, @intCast(field_i));
11810 }
11811 return null;
11812 },
11813 .error_set => {
11814 for (it.error_names.get(ip)[it.next_idx..], it.next_idx..) |err_name, name_i| {
11815 if (it.seen_errors.contains(err_name)) continue;
11816 it.next_idx = @intCast(name_i + 1);
11817 return .fromInterned(try pt.intern(.{ .err = .{
11818 .ty = item_ty.toIntern(),
11819 .name = err_name,
11820 } }));
11821 }
11822 return null;
11823 },
11824 .int => {
11825 var cur = it.next_val orelse return null;
11826 while (it.next_idx < it.seen_ranges.len and
11827 cur.eql(it.seen_ranges[it.next_idx].first, item_ty, zcu))
11828 {
11829 defer it.next_idx += 1;
11830 const incr = try arith.incrementDefinedInt(
11831 sema,
11832 item_ty,
11833 it.seen_ranges[it.next_idx].last,
11834 );
11835 if (incr.overflow) {
11836 it.next_val = null;
11837 return null;
11838 }
11839 cur = incr.val;
11840 }
11841 const incr = try arith.incrementDefinedInt(sema, item_ty, cur);
11842 it.next_val = if (incr.overflow) null else incr.val;
11843 return cur;
11844 },
11845 .bool => {
11846 if (!it.seen_true) {
11847 it.seen_true = true;
11848 return .true;
11849 }
11850 if (!it.seen_false) {
11851 it.seen_false = true;
11852 return .false;
11853 }
11854 return null;
11855 },
11856 .void => {
11857 if (!it.seen_void) {
11858 it.seen_void = true;
11859 return .void;
11860 }
11861 return null;
11862 },
11863 else => unreachable, // item type is not enumerable
11864 }
11865 }
11866 };
11867};
11868
11869/// Validates operand type and `else`/`_` prong usage, resolves all prong items
11870/// and checks them for duplicates/invalid ranges. Does not emit into `block`.
11871/// Reserves inst map space for all placeholders associated with `zir_switch`.
11872/// Contents of returned `ValidatedSwitchBlock` belong to `sema.arena`.
11873fn validateSwitchBlock(
11874 sema: *Sema,
11875 block: *Block,
11876 raw_operand: Air.Inst.Ref,
11877 operand_is_ref: bool,
11878 switch_inst: Zir.Inst.Index,
11879 zir_switch: *const Zir.UnwrappedSwitchBlock,
11880) CompileError!ValidatedSwitchBlock {
11881 const pt = sema.pt;
11882 const zcu = pt.zcu;
11883 const ip = &zcu.intern_pool;
11884 const gpa = sema.gpa;
11885 const arena = sema.arena;
11886
11887 const src_node_offset = zir_switch.switch_src_node_offset;
11888 const src = block.nodeOffset(src_node_offset);
11889 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
11890 const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
11891 var extra_index = zir_switch.end;
11892
11893 // We want to map values to our placeholders later on.
11894 if (zir_switch.payload_capture_placeholder.unwrap()) |payload_capture_inst| {
11895 assert(payload_capture_inst != switch_inst); // malformed zir
11896 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{payload_capture_inst});
11897 }
11898 if (zir_switch.tag_capture_placeholder.unwrap()) |tag_capture_inst| {
11899 assert(tag_capture_inst != switch_inst); // malformed zir
11900 try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst});
11901 }
11902
11903 const operand_ty: Type, const item_ty: Type = check_operand: {
11904 const operand_ty = operand_ty: {
11905 const raw_operand_ty = sema.typeOf(raw_operand);
11906 if (operand_is_ref) {
11907 try sema.checkPtrType(block, operand_src, raw_operand_ty, false);
11908 break :operand_ty raw_operand_ty.childType(zcu);
11909 }
11910 break :operand_ty raw_operand_ty;
11911 };
11912
11913 const item_ty: Type = item_ty: {
11914 switch (operand_ty.zigTypeTag(zcu)) {
11915 .@"enum",
11916 .error_set,
11917 .int,
11918 .comptime_int,
11919 .type,
11920 .enum_literal,
11921 .@"fn",
11922 .bool,
11923 .void,
11924 => break :item_ty operand_ty,
11925
11926 .@"union" => {
11927 try operand_ty.resolveFields(pt);
11928 const enum_ty = operand_ty.unionTagType(zcu) orelse {
11929 return sema.failWithOwnedErrorMsg(block, msg: {
11930 const msg = try sema.errMsg(operand_src, "switch on union with no attached enum", .{});
11931 errdefer msg.destroy(sema.gpa);
11932 if (operand_ty.srcLocOrNull(zcu)) |union_src| {
11933 try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
11934 }
11935 break :msg msg;
11936 });
11937 };
11938 break :item_ty enum_ty;
11939 },
11940
11941 .pointer => {
11942 if (!operand_ty.isSlice(zcu)) {
11943 break :item_ty operand_ty;
11944 }
11945 },
11946
11947 else => {},
11948 }
11949 return sema.fail(block, operand_src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
11950 };
11951
11952 if (zir_switch.has_continue and !block.isComptime()) {
11953 if (try operand_ty.comptimeOnlySema(pt)) {
11954 // Even if the operand is comptime-known, this `switch` is runtime.
11955 return sema.failWithOwnedErrorMsg(block, msg: {
11956 const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{f}'", .{operand_ty.fmt(pt)});
11957 errdefer msg.destroy(gpa);
11958 try sema.errNote(operand_src, msg, "switch loops are evaluated at runtime outside of comptime scopes", .{});
11959 try sema.explainWhyTypeIsComptime(msg, operand_src, operand_ty);
11960 break :msg msg;
11961 });
11962 }
11963 try sema.validateRuntimeValue(block, operand_src, raw_operand);
11964 }
11965
11966 break :check_operand .{ operand_ty, item_ty };
11967 };
11968
11969 const has_else = zir_switch.else_case != null;
11970 const has_under = zir_switch.has_under;
11971
11972 var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;
11973 try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
11974
11975 // Duplicate checking variables later also used for `inline else`.
11976 var seen_enum_fields: []?LazySrcLoc = &.{};
11977 var seen_errors: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc) = .empty;
11978 var seen_sparse_values: std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc) = .empty;
11979 var range_set: RangeSet = .empty;
11980 var true_src: ?LazySrcLoc = null;
11981 var false_src: ?LazySrcLoc = null;
11982 var void_src: ?LazySrcLoc = null;
11983
11984 var else_err_ty: ?Type = null;
11985
11986 const else_case = zir_switch.else_case orelse undefined;
11987
11988 switch (item_ty.zigTypeTag(zcu)) {
11989 .@"union" => unreachable,
11990 .@"enum" => {
11991 seen_enum_fields = try arena.alloc(?LazySrcLoc, item_ty.enumFieldCount(zcu));
11992 @memset(seen_enum_fields, null);
11993 // `range_set` is used for non-exhaustive enum values that do not
11994 // correspond to any tags. Since this is rare, we only allocate on
11995 // demand in `validateSwitchItem`.
11996 },
11997 .error_set => {
11998 try seen_errors.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
11999 },
12000 .int, .comptime_int => {
12001 try range_set.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
12002 },
12003 .enum_literal, .@"fn", .pointer, .type => {
12004 try seen_sparse_values.ensureUnusedCapacity(arena, zir_switch.totalItemsLen());
12005 },
12006 .bool, .void => {},
12007
12008 else => unreachable,
12009 }
12010
12011 // Validate for duplicate items and invalid ranges.
12012 var case_it = zir_switch.iterateCases();
12013 while (case_it.next()) |case| {
12014 const prong_info = case.prong_info;
12015 extra_index += prong_info.body_len;
12016 for (case.item_infos, 0..) |item_info, item_i| {
12017 const item_src = block.src(.{ .switch_case_item = .{
12018 .switch_node_offset = src_node_offset,
12019 .case_idx = case.index,
12020 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12021 } });
12022 if (item_info.unwrap() == .under) {
12023 if (!operand_ty.isNonexhaustiveEnum(zcu)) return sema.failWithOwnedErrorMsg(block, msg: {
12024 const msg = try sema.errMsg(
12025 src,
12026 "'_' prong only allowed when switching on non-exhaustive enums",
12027 .{},
12028 );
12029 errdefer msg.destroy(gpa);
12030 try sema.errNote(
12031 item_src,
12032 msg,
12033 "'_' prong here",
12034 .{},
12035 );
12036 try sema.errNote(
12037 src,
12038 msg,
12039 "consider using 'else'",
12040 .{},
12041 );
12042 break :msg msg;
12043 });
12044 case_vals.appendAssumeCapacity(.none);
12045 } else {
12046 const item, extra_index = try sema.resolveSwitchItem(block, item_src, item_ty, item_info, extra_index, switch_inst, prong_info.is_comptime_unreach);
12047 try sema.validateSwitchItemOrRange(block, item_src, item.val, null, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
12048 case_vals.appendAssumeCapacity(item.ref);
12049 }
12050 }
12051 for (case.range_infos, 0..) |range_info, range_i| {
12052 const range_offset: LazySrcLoc.Offset.SwitchItem = .{
12053 .switch_node_offset = src_node_offset,
12054 .case_idx = case.index,
12055 .item_idx = .{ .kind = .range, .value = @intCast(range_i) },
12056 };
12057 const range_src = block.src(.{ .switch_case_item = range_offset });
12058 const first_src = block.src(.{ .switch_case_item_range_first = range_offset });
12059 const last_src = block.src(.{ .switch_case_item_range_last = range_offset });
12060 const first_item, extra_index = try sema.resolveSwitchItem(block, first_src, item_ty, range_info[0], extra_index, switch_inst, prong_info.is_comptime_unreach);
12061 const last_item, extra_index = try sema.resolveSwitchItem(block, last_src, item_ty, range_info[1], extra_index, switch_inst, prong_info.is_comptime_unreach);
12062 try sema.validateSwitchItemOrRange(block, range_src, first_item.val, last_item.val, item_ty, seen_enum_fields, &seen_errors, &seen_sparse_values, &range_set, &true_src, &false_src, &void_src);
12063 case_vals.appendSliceAssumeCapacity(&.{ first_item.ref, last_item.ref });
12064 }
12065 }
12066
12067 switch (item_ty.zigTypeTag(zcu)) {
12068 .@"union" => unreachable,
12069 .int, .comptime_int => {},
12070 else => if (zir_switch.anyRanges()) {
12071 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });
12072 const msg = msg: {
12073 const msg = try sema.errMsg(
12074 operand_src,
12075 "ranges not allowed when switching on type '{f}'",
12076 .{operand_ty.fmt(sema.pt)},
12077 );
12078 errdefer msg.destroy(sema.gpa);
12079 try sema.errNote(
12080 range_src,
12081 msg,
12082 "range here",
12083 .{},
12084 );
12085 break :msg msg;
12086 };
12087 return sema.failWithOwnedErrorMsg(block, msg);
12088 },
12089 }
12090
12091 // Validate for missing special prongs.
12092 switch (item_ty.zigTypeTag(zcu)) {
12093 .@"union" => unreachable,
12094 .@"enum" => {
12095 const all_tags_handled = for (seen_enum_fields) |seen_src| {
12096 if (seen_src == null) break false;
12097 } else true;
12098
12099 if (has_else) {
12100 if (all_tags_handled) {
12101 if (item_ty.isNonexhaustiveEnum(zcu)) {
12102 if (has_under) return sema.fail(
12103 block,
11800 else_prong_src,12104 else_prong_src,
11801 "unreachable else prong; all explicit cases already handled",12105 "unreachable else prong; all explicit cases already handled",
11802 .{},12106 .{},
...@@ -11819,9 +12123,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11819,9 +12123,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11819 for (seen_enum_fields, 0..) |seen_src, i| {12123 for (seen_enum_fields, 0..) |seen_src, i| {
11820 if (seen_src != null) continue;12124 if (seen_src != null) continue;
1182112125
11822 const field_name = cond_ty.enumFieldName(i, zcu);12126 const field_name = item_ty.enumFieldName(i, zcu);
11823 try sema.addFieldErrNote(12127 try sema.addFieldErrNote(
11824 cond_ty,12128 item_ty,
11825 i,12129 i,
11826 msg,12130 msg,
11827 "unhandled enumeration value: '{f}'",12131 "unhandled enumeration value: '{f}'",
...@@ -11829,15 +12133,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11829,15 +12133,17 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11829 );12133 );
11830 }12134 }
11831 try sema.errNote(12135 try sema.errNote(
11832 cond_ty.srcLoc(zcu),12136 item_ty.srcLoc(zcu),
11833 msg,12137 msg,
11834 "enum '{f}' declared here",12138 "enum '{f}' declared here",
11835 .{cond_ty.fmt(pt)},12139 .{item_ty.fmt(pt)},
11836 );12140 );
11837 break :msg msg;12141 break :msg msg;
11838 };12142 };
11839 return sema.failWithOwnedErrorMsg(block, msg);12143 return sema.failWithOwnedErrorMsg(block, msg);
11840 } else if (special_prongs == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {12144 } else if (!has_else and !has_under and
12145 item_ty.isNonexhaustiveEnum(zcu) and operand_ty.zigTypeTag(zcu) != .@"union")
12146 {
11841 return sema.fail(12147 return sema.fail(
11842 block,12148 block,
11843 src,12149 src,
...@@ -11846,101 +12152,83 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11846,101 +12152,83 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11846 );12152 );
11847 }12153 }
11848 },12154 },
11849 .error_set => else_error_ty = try validateErrSetSwitch(12155 .error_set => {
11850 sema,12156 else_err_ty = ty: switch (try sema.resolveInferredErrorSetTy(block, src, item_ty.toIntern())) {
11851 block,12157 .anyerror_type => {
11852 &seen_errors,12158 if (!has_else) {
11853 &case_vals,12159 return sema.fail(
11854 cond_ty,
11855 inst_data,
11856 scalar_cases_len,
11857 multi_cases_len,
11858 .{ .body = special_else.body, .end = special_else.end, .src = else_prong_src },
11859 has_else,
11860 ),
11861 .int, .comptime_int => {
11862 var extra_index: usize = special_end;
11863 {
11864 var scalar_i: u32 = 0;
11865 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
11866 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
11867 extra_index += 1;
11868 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11869 extra_index += 1 + info.body_len;
11870
11871 case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
11872 block,
11873 &range_set,
11874 item_ref,
11875 cond_ty,
11876 block.src(.{ .switch_case_item = .{
11877 .switch_node_offset = src_node_offset,
11878 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11879 .item_idx = .{ .kind = .single, .index = 0 },
11880 } }),
11881 ));
11882 }
11883 }
11884 {
11885 var multi_i: u32 = 0;
11886 while (multi_i < multi_cases_len) : (multi_i += 1) {
11887 const items_len = sema.code.extra[extra_index];
11888 extra_index += 1;
11889 const ranges_len = sema.code.extra[extra_index];
11890 extra_index += 1;
11891 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11892 extra_index += 1;
11893 const items = sema.code.refSlice(extra_index, items_len);
11894 extra_index += items_len;
11895
11896 try case_vals.ensureUnusedCapacity(gpa, items.len);
11897 for (items, 0..) |item_ref, item_i| {
11898 case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
11899 block,12160 block,
11900 &range_set,12161 src,
11901 item_ref,12162 "else prong required when switching on type 'anyerror'",
11902 cond_ty,12163 .{},
11903 block.src(.{ .switch_case_item = .{12164 );
11904 .switch_node_offset = src_node_offset,12165 }
11905 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12166 break :ty .anyerror;
11906 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },12167 },
11907 } }),12168 else => |err_set_ty_index| {
11908 ));12169 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
12170 var maybe_msg: ?*Zcu.ErrorMsg = null;
12171 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
12172
12173 var seen_errors_from_set: u32 = 0;
12174 for (error_names.get(ip)) |error_name| {
12175 if (seen_errors.contains(error_name)) {
12176 seen_errors_from_set += 1;
12177 } else if (!has_else) {
12178 const msg = maybe_msg orelse blk: {
12179 maybe_msg = try sema.errMsg(
12180 src,
12181 "switch must handle all possibilities",
12182 .{},
12183 );
12184 break :blk maybe_msg.?;
12185 };
12186
12187 try sema.errNote(
12188 src,
12189 msg,
12190 "unhandled error value: 'error.{f}'",
12191 .{error_name.fmt(ip)},
12192 );
12193 }
11909 }12194 }
1191012195
11911 try case_vals.ensureUnusedCapacity(gpa, 2 * ranges_len);12196 if (maybe_msg) |msg| {
11912 var range_i: u32 = 0;12197 maybe_msg = null;
11913 while (range_i < ranges_len) : (range_i += 1) {12198 try sema.addDeclaredHereNote(msg, operand_ty);
11914 const item_first: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);12199 return sema.failWithOwnedErrorMsg(block, msg);
11915 extra_index += 1;12200 }
11916 const item_last: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
11917 extra_index += 1;
1191812201
11919 const vals = try sema.validateSwitchRange(12202 if (has_else and seen_errors_from_set == error_names.len) {
12203 // This prong is unreachable anyway so we don't need its
12204 // error set type, but we still allow it to exist.
12205 if (else_case.is_simple_noreturn) break :ty null;
12206 return sema.fail(
11920 block,12207 block,
11921 &range_set,12208 else_prong_src,
11922 item_first,12209 "unreachable else prong; all cases already handled",
11923 item_last,12210 .{},
11924 cond_ty,
11925 block.src(.{ .switch_case_item = .{
11926 .switch_node_offset = src_node_offset,
11927 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
11928 .item_idx = .{ .kind = .range, .index = @intCast(range_i) },
11929 } }),
11930 );12211 );
11931 case_vals.appendAssumeCapacity(vals[0]);
11932 case_vals.appendAssumeCapacity(vals[1]);
11933 }12212 }
1193412213
11935 extra_index += info.body_len;12214 var names: InferredErrorSet.NameMap = .{};
11936 }12215 try names.ensureUnusedCapacity(sema.arena, error_names.len);
11937 }12216 for (error_names.get(ip)) |error_name| {
1193812217 if (seen_errors.contains(error_name)) continue;
12218 names.putAssumeCapacityNoClobber(error_name, {});
12219 }
12220 // No need to keep the hash map metadata correct; here we
12221 // extract the (sorted) keys only.
12222 break :ty try pt.errorSetFromUnsortedNames(names.keys());
12223 },
12224 };
12225 },
12226 .int, .comptime_int => |type_tag| {
11939 check_range: {12227 check_range: {
11940 if (cond_ty.zigTypeTag(zcu) == .int) {12228 if (type_tag == .int) {
11941 const min_int = try cond_ty.minInt(pt, cond_ty);12229 const min_int = try item_ty.minInt(pt, item_ty);
11942 const max_int = try cond_ty.maxInt(pt, cond_ty);12230 const max_int = try item_ty.maxInt(pt, item_ty);
11943 if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {12231 if (try range_set.spans(arena, min_int, max_int, item_ty, zcu)) {
11944 if (has_else) {12232 if (has_else) {
11945 return sema.fail(12233 return sema.fail(
11946 block,12234 block,
...@@ -11952,7 +12240,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11952,7 +12240,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11952 break :check_range;12240 break :check_range;
11953 }12241 }
11954 }12242 }
11955 if (special_prongs == .none) {12243 if (!has_else) {
11956 return sema.fail(12244 return sema.fail(
11957 block,12245 block,
11958 src,12246 src,
...@@ -11962,61 +12250,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11962,61 +12250,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11962 }12250 }
11963 }12251 }
11964 },12252 },
11965 .bool => {12253 .enum_literal, .@"fn", .pointer, .type => {
11966 var extra_index: usize = special_end;12254 if (!has_else) {
11967 {12255 return sema.fail(
11968 var scalar_i: u32 = 0;12256 block,
11969 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {12257 src,
11970 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);12258 "else prong required when switching on type '{f}'",
11971 extra_index += 1;12259 .{item_ty.fmt(pt)},
11972 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);12260 );
11973 extra_index += 1 + info.body_len;
11974
11975 case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
11976 block,
11977 &true_count,
11978 &false_count,
11979 item_ref,
11980 block.src(.{ .switch_case_item = .{
11981 .switch_node_offset = src_node_offset,
11982 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
11983 .item_idx = .{ .kind = .single, .index = 0 },
11984 } }),
11985 ));
11986 }
11987 }
11988 {
11989 var multi_i: u32 = 0;
11990 while (multi_i < multi_cases_len) : (multi_i += 1) {
11991 const items_len = sema.code.extra[extra_index];
11992 extra_index += 1;
11993 const ranges_len = sema.code.extra[extra_index];
11994 extra_index += 1;
11995 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11996 extra_index += 1;
11997 const items = sema.code.refSlice(extra_index, items_len);
11998 extra_index += items_len + info.body_len;
11999
12000 try case_vals.ensureUnusedCapacity(gpa, items.len);
12001 for (items, 0..) |item_ref, item_i| {
12002 case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
12003 block,
12004 &true_count,
12005 &false_count,
12006 item_ref,
12007 block.src(.{ .switch_case_item = .{
12008 .switch_node_offset = src_node_offset,
12009 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12010 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12011 } }),
12012 ));
12013 }
12014
12015 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12016 }
12017 }12261 }
12262 },
12263 .bool, .void => |type_tag| {
12264 const all_values_handled = switch (type_tag) {
12265 .bool => true_src != null and false_src != null,
12266 .void => void_src != null,
12267 else => unreachable,
12268 };
12018 if (has_else) {12269 if (has_else) {
12019 if (true_count + false_count == 2) {12270 if (all_values_handled) {
12020 return sema.fail(12271 return sema.fail(
12021 block,12272 block,
12022 else_prong_src,12273 else_prong_src,
...@@ -12025,7 +12276,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12025,7 +12276,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12025 );12276 );
12026 }12277 }
12027 } else {12278 } else {
12028 if (true_count + false_count < 2) {12279 if (!all_values_handled) {
12029 return sema.fail(12280 return sema.fail(
12030 block,12281 block,
12031 src,12282 src,
...@@ -12035,1775 +12286,1086 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -12035,1775 +12286,1086 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
12035 }12286 }
12036 }12287 }
12037 },12288 },
12038 .enum_literal, .void, .@"fn", .pointer, .type => {12289 else => unreachable,
12039 if (!has_else) {
12040 return sema.fail(
12041 block,
12042 src,
12043 "else prong required when switching on type '{f}'",
12044 .{cond_ty.fmt(pt)},
12045 );
12046 }
12047
12048 var seen_values = ValueSrcMap{};
12049 defer seen_values.deinit(gpa);
12050
12051 var extra_index: usize = special_end;
12052 {
12053 var scalar_i: u32 = 0;
12054 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12055 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
12056 extra_index += 1;
12057 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12058 extra_index += 1;
12059 extra_index += info.body_len;
12060
12061 case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
12062 block,
12063 &seen_values,
12064 item_ref,
12065 cond_ty,
12066 block.src(.{ .switch_case_item = .{
12067 .switch_node_offset = src_node_offset,
12068 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
12069 .item_idx = .{ .kind = .single, .index = 0 },
12070 } }),
12071 ));
12072 }
12073 }
12074 {
12075 var multi_i: u32 = 0;
12076 while (multi_i < multi_cases_len) : (multi_i += 1) {
12077 const items_len = sema.code.extra[extra_index];
12078 extra_index += 1;
12079 const ranges_len = sema.code.extra[extra_index];
12080 extra_index += 1;
12081 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12082 extra_index += 1;
12083 const items = sema.code.refSlice(extra_index, items_len);
12084 extra_index += items_len + info.body_len;
12085
12086 try case_vals.ensureUnusedCapacity(gpa, items.len);
12087 for (items, 0..) |item_ref, item_i| {
12088 case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
12089 block,
12090 &seen_values,
12091 item_ref,
12092 cond_ty,
12093 block.src(.{ .switch_case_item = .{
12094 .switch_node_offset = src_node_offset,
12095 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12096 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12097 } }),
12098 ));
12099 }
12100
12101 try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
12102 }
12103 }
12104 },
12105
12106 .error_union,
12107 .noreturn,
12108 .array,
12109 .@"struct",
12110 .undefined,
12111 .null,
12112 .optional,
12113 .@"opaque",
12114 .vector,
12115 .frame,
12116 .@"anyframe",
12117 .comptime_float,
12118 .float,
12119 => return sema.fail(block, operand_src, "invalid switch operand type '{f}'", .{
12120 raw_operand_ty.fmt(pt),
12121 }),
12122 }12290 }
1212312291
12124 var special_members_only: ?SpecialProng = null;12292 return .{
12125 var special_members_only_src: LazySrcLoc = undefined;12293 .seen_enum_fields = seen_enum_fields,
12126 const special_generic, const special_generic_src = if (has_under) b: {12294 .seen_errors = seen_errors,
12127 if (has_else) {12295 .seen_ranges = range_set.ranges.items,
12128 special_members_only = special_else;12296 .true_src = true_src,
12129 special_members_only_src = else_prong_src;12297 .false_src = false_src,
12130 }12298 .void_src = void_src,
12131 break :b .{ special_under, under_prong_src };
12132 } else .{ special_else, else_prong_src };
12133
12134 const spa: SwitchProngAnalysis = .{
12135 .sema = sema,
12136 .parent_block = block,
12137 .operand = operand,
12138 .else_error_ty = else_error_ty,
12139 .switch_block_inst = inst,
12140 .tag_capture_inst = tag_capture_inst,
12141 };
12142
12143 const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
12144 try sema.air_instructions.append(gpa, .{
12145 .tag = .block,
12146 .data = undefined,
12147 });
12148 var label: Block.Label = .{
12149 .zir_block = inst,
12150 .merges = .{
12151 .src_locs = .{},
12152 .results = .{},
12153 .br_list = .{},
12154 .block_inst = block_inst,
12155 },
12156 };
1215712299
12158 var child_block: Block = .{12300 .case_vals = case_vals.items,
12159 .parent = block,12301 .else_case = else_case,
12160 .sema = sema,12302 .else_err_ty = else_err_ty,
12161 .namespace = block.namespace,
12162 .instructions = .{},
12163 .label = &label,
12164 .inlining = block.inlining,
12165 .comptime_reason = block.comptime_reason,
12166 .is_typeof = block.is_typeof,
12167 .c_import_buf = block.c_import_buf,
12168 .runtime_cond = block.runtime_cond,
12169 .runtime_loop = block.runtime_loop,
12170 .runtime_index = block.runtime_index,
12171 .want_safety = block.want_safety,
12172 .error_return_trace_index = block.error_return_trace_index,
12173 .src_base_inst = block.src_base_inst,
12174 .type_name_ctx = block.type_name_ctx,
12175 };12303 };
12176 const merges = &child_block.label.?.merges;12304}
12177 defer child_block.instructions.deinit(gpa);
12178 defer merges.deinit(gpa);
12179
12180 if (scalar_cases_len + multi_cases_len == 0 and
12181 special_members_only == null and
12182 !special_generic.is_inline)
12183 {
12184 if (empty_enum) {
12185 return .void_value;
12186 }
12187 if (special_prongs == .none) {
12188 return sema.fail(block, src, "switch must handle all possibilities", .{});
12189 }
12190 const init_cond = switch (operand) {
12191 .simple => |s| s.cond,
12192 .loop => |l| l.init_cond,
12193 };
12194 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12195 raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))
12196 {
12197 try sema.zirDbgStmt(block, cond_dbg_node_index);
12198 const ok = try block.addUnOp(.is_named_enum_value, init_cond);
12199 try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
12200 }
12201 if (err_set and try sema.maybeErrorUnwrap(block, special_generic.body, init_cond, operand_src, false)) {
12202 return .unreachable_value;
12203 }
12204 }
12205
12206 switch (operand) {
12207 .loop => {}, // always runtime; evaluation in comptime scope uses `simple`
12208 .simple => |s| {
12209 if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {
12210 return resolveSwitchComptimeLoop(
12211 sema,
12212 spa,
12213 &child_block,
12214 if (operand_is_ref)
12215 sema.typeOf(s.by_ref)
12216 else
12217 raw_operand_ty,
12218 cond_ty,
12219 cond_val,
12220 src_node_offset,
12221 special_members_only,
12222 special_generic,
12223 has_under,
12224 case_vals,
12225 scalar_cases_len,
12226 multi_cases_len,
12227 err_set,
12228 empty_enum,
12229 operand_is_ref,
12230 );
12231 }
12232
12233 if (scalar_cases_len + multi_cases_len == 0 and
12234 special_members_only == null and
12235 !special_generic.is_inline and
12236 !extra.data.bits.has_continue)
12237 {
12238 return spa.resolveProngComptime(
12239 &child_block,
12240 .special,
12241 special_generic.body,
12242 special_generic.capture,
12243 block.src(.{ .switch_capture = .{
12244 .switch_node_offset = src_node_offset,
12245 .case_idx = if (has_under) .special_under else .special_else,
12246 } }),
12247 undefined, // case_vals may be undefined for special prongs
12248 .none,
12249 false,
12250 merges,
12251 );
12252 }
12253 },
12254 }
12255
12256 if (child_block.isComptime()) {
12257 _ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, null);
12258 unreachable;
12259 }
12260
12261 var extra_case_vals: struct {
12262 items: std.ArrayList(Air.Inst.Ref),
12263 ranges: std.ArrayList([2]Air.Inst.Ref),
12264 } = .{ .items = .empty, .ranges = .empty };
12265 defer {
12266 extra_case_vals.items.deinit(gpa);
12267 extra_case_vals.ranges.deinit(gpa);
12268 }
12269
12270 // Runtime switch, if we have a special_members_only prong we need to unroll
12271 // it to a prong with explicit items.
12272 // Although this is potentially the same as `inline else` it does not count
12273 // towards the backward branch quota because it's an implementation detail.
12274 if (special_members_only != null) gen: {
12275 assert(cond_ty.isNonexhaustiveEnum(zcu));
12276
12277 var min_i: usize = math.maxInt(usize);
12278 var max_i: usize = 0;
12279 var seen_field_count: usize = 0;
12280 for (seen_enum_fields, 0..) |seen, enum_i| {
12281 if (seen != null) {
12282 seen_field_count += 1;
12283 } else {
12284 min_i = @min(min_i, enum_i);
12285 max_i = @max(max_i, enum_i);
12286 }
12287 }
12288 if (min_i == max_i) {
12289 seen_enum_fields[min_i] = special_members_only_src;
12290 const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
12291 const item_ref = Air.internedToRef(item_val.toIntern());
12292 try extra_case_vals.items.append(gpa, item_ref);
12293 break :gen;
12294 }
12295 const missing_field_count = seen_enum_fields.len - seen_field_count;
12296
12297 extra_case_vals.items = try .initCapacity(gpa, missing_field_count / 2);
12298 extra_case_vals.ranges = try .initCapacity(gpa, missing_field_count / 4);
12299 const int_ty = cond_ty.intTagType(zcu);
12300
12301 var last_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
12302 var first_ref = Air.internedToRef(last_val.toIntern());
12303 seen_enum_fields[min_i] = special_members_only_src;
12304 for (seen_enum_fields[(min_i + 1)..(max_i + 1)], (min_i + 1)..) |seen, enum_i| {
12305 if (seen != null) continue;
12306 seen_enum_fields[enum_i] = special_members_only_src;
12307
12308 const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(enum_i));
12309 const item_ref = Air.internedToRef(item_val.toIntern());
12310
12311 const is_next = is_next: {
12312 const prev_int = ip.indexToKey(last_val.toIntern()).enum_tag.int;
12313
12314 const result = try arith.incrementDefinedInt(sema, int_ty, .fromInterned(prev_int));
12315 if (result.overflow) break :is_next false;
12316
12317 const item_int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
12318 break :is_next try sema.valuesEqual(.fromInterned(item_int), result.val, int_ty);
12319 };
12320
12321 if (is_next) {
12322 last_val = item_val;
12323 } else {
12324 const last_ref = Air.internedToRef(last_val.toIntern());
12325 if (first_ref == last_ref) {
12326 try extra_case_vals.items.append(gpa, first_ref);
12327 } else {
12328 try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
12329 }
12330 first_ref = item_ref;
12331 last_val = item_val;
12332 }
12333 }
12334 const last_ref = Air.internedToRef(last_val.toIntern());
12335 if (first_ref == last_ref) {
12336 try extra_case_vals.items.append(gpa, first_ref);
12337 } else {
12338 try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
12339 }
12340 }
12341
12342 const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
12343 spa,
12344 &child_block,
12345 src,
12346 switch (operand) {
12347 .simple => |s| s.cond,
12348 .loop => |l| l.init_cond,
12349 },
12350 cond_ty,
12351 operand_src,
12352 case_vals,
12353 special_generic,
12354 scalar_cases_len,
12355 multi_cases_len,
12356 union_originally,
12357 raw_operand_ty,
12358 err_set,
12359 src_node_offset,
12360 special_generic_src,
12361 has_under,
12362 seen_enum_fields,
12363 seen_errors,
12364 range_set,
12365 true_count,
12366 false_count,
12367 cond_dbg_node_index,
12368 false,
12369 special_members_only,
12370 special_members_only_src,
12371 extra_case_vals.items.items,
12372 extra_case_vals.ranges.items,
12373 );
12374
12375 for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
12376 var replacement_block = block.makeSubBlock();
12377 defer replacement_block.instructions.deinit(gpa);
12378
12379 assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
12380 const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
12381
12382 if (extra.data.bits.any_non_inline_capture) {
12383 _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
12384 }
12385
12386 const new_operand_val = if (operand_is_ref)
12387 try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
12388 else
12389 new_operand_maybe_ref;
12390
12391 const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
12392
12393 if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
12394 cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
12395 !try sema.isComptimeKnown(new_cond))
12396 {
12397 const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
12398 try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
12399 }
12400
12401 _ = try replacement_block.addInst(.{
12402 .tag = .switch_dispatch,
12403 .data = .{ .br = .{
12404 .block_inst = air_switch_ref.toIndex().?,
12405 .operand = new_cond,
12406 } },
12407 });
12408
12409 if (replacement_block.instructions.items.len == 1) {
12410 // Optimization: we don't need a block!
12411 sema.air_instructions.set(
12412 @intFromEnum(placeholder_inst),
12413 sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
12414 );
12415 continue;
12416 }
1241712305
12418 // Replace placeholder with a block.12306fn resolveSwitchBlock(
12419 // No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
12420 try sema.air_extra.ensureUnusedCapacity(
12421 gpa,
12422 @typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
12423 );
12424 sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
12425 .tag = .block,
12426 .data = .{ .ty_pl = .{
12427 .ty = .noreturn_type,
12428 .payload = sema.addExtraAssumeCapacity(Air.Block{
12429 .body_len = @intCast(replacement_block.instructions.items.len),
12430 }),
12431 } },
12432 });
12433 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
12434 }
12435
12436 return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
12437}
12438
12439const SpecialProng = struct {
12440 body: []const Zir.Inst.Index,
12441 end: usize,
12442 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12443 is_inline: bool,
12444 has_tag_capture: bool,
12445};
12446
12447fn analyzeSwitchRuntimeBlock(
12448 sema: *Sema,12307 sema: *Sema,
12449 spa: SwitchProngAnalysis,12308 block: *Block,
12450 child_block: *Block,
12451 src: LazySrcLoc,
12452 operand: Air.Inst.Ref,
12453 operand_ty: Type,
12454 operand_src: LazySrcLoc,
12455 case_vals: std.ArrayList(Air.Inst.Ref),
12456 else_prong: SpecialProng,
12457 scalar_cases_len: usize,
12458 multi_cases_len: usize,
12459 union_originally: bool,
12460 maybe_union_ty: Type,
12461 err_set: bool,
12462 switch_node_offset: std.zig.Ast.Node.Offset,
12463 else_prong_src: LazySrcLoc,
12464 else_prong_is_underscore: bool,
12465 seen_enum_fields: []?LazySrcLoc,
12466 seen_errors: SwitchErrorSet,
12467 range_set: RangeSet,
12468 true_count: u8,
12469 false_count: u8,
12470 cond_dbg_node_index: Zir.Inst.Index,
12471 allow_err_code_unwrap: bool,
12472 extra_prong: ?SpecialProng,
12473 /// May be `undefined` if `extra_prong` is `null`
12474 extra_prong_src: LazySrcLoc,
12475 extra_prong_items: []const Air.Inst.Ref,
12476 extra_prong_ranges: []const [2]Air.Inst.Ref,
12477) CompileError!Air.Inst.Ref {
12478 const pt = sema.pt;
12479 const zcu = pt.zcu;
12480 const gpa = sema.gpa;
12481 const ip = &zcu.intern_pool;
12482
12483 const block = child_block.parent.?;
12484
12485 const estimated_cases_extra = (scalar_cases_len + multi_cases_len) *
12486 @typeInfo(Air.SwitchBr.Case).@"struct".fields.len + 2;
12487 var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_cases_extra);
12488 defer cases_extra.deinit(gpa);
12489
12490 var branch_hints = try std.ArrayList(std.builtin.BranchHint).initCapacity(gpa, scalar_cases_len);
12491 defer branch_hints.deinit(gpa);
12492
12493 var case_block = child_block.makeSubBlock();
12494 case_block.runtime_loop = null;
12495 case_block.runtime_cond = operand_src;
12496 case_block.runtime_index.increment();
12497 case_block.need_debug_scope = null; // this body is emitted regardless
12498 defer case_block.instructions.deinit(gpa);
12499
12500 var extra_index: usize = else_prong.end;
12501
12502 var scalar_i: usize = 0;
12503 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12504 extra_index += 1;
12505 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12506 extra_index += 1;
12507 const body = sema.code.bodySlice(extra_index, info.body_len);
12508 extra_index += info.body_len;
12509
12510 case_block.instructions.shrinkRetainingCapacity(0);
12511 case_block.error_return_trace_index = child_block.error_return_trace_index;
12512
12513 const item = case_vals.items[scalar_i];
12514 // `item` is already guaranteed to be constant known.
12515
12516 const analyze_body = if (union_originally) blk: {
12517 const unresolved_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12518 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
12519 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12520 break :blk field_ty.zigTypeTag(zcu) != .noreturn;
12521 } else true;
12522
12523 const prong_hint: std.builtin.BranchHint = if (err_set and
12524 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12525 h: {
12526 // nothing to do here. weight against error branch
12527 break :h .unlikely;
12528 } else if (analyze_body) h: {
12529 break :h try spa.analyzeProngRuntime(
12530 &case_block,
12531 .normal,
12532 body,
12533 info.capture,
12534 child_block.src(.{ .switch_capture = .{
12535 .switch_node_offset = switch_node_offset,
12536 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
12537 } }),
12538 &.{item},
12539 if (info.is_inline) item else .none,
12540 info.has_tag_capture,
12541 );
12542 } else h: {
12543 _ = try case_block.addNoOp(.unreach);
12544 break :h .none;
12545 };
12546
12547 try branch_hints.append(gpa, prong_hint);
12548 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12549 1 + // `item`, no ranges
12550 case_block.instructions.items.len);
12551 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12552 .items_len = 1,
12553 .ranges_len = 0,
12554 .body_len = @intCast(case_block.instructions.items.len),
12555 }));
12556 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12557 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12558 }
12559
12560 var cases_len = scalar_cases_len;
12561 var case_val_idx: usize = scalar_cases_len;
12562 const multi_cases_len_with_extra_prong = multi_cases_len + @intFromBool(extra_prong != null);
12563 var multi_i: u32 = 0;
12564 while (multi_i < multi_cases_len_with_extra_prong) : (multi_i += 1) {
12565 const is_extra_prong = multi_i == multi_cases_len;
12566 var items: []const Air.Inst.Ref = undefined;
12567 var info: Zir.Inst.SwitchBlock.ProngInfo = undefined;
12568 var ranges: []const [2]Air.Inst.Ref = undefined;
12569 var body: []const Zir.Inst.Index = undefined;
12570 if (is_extra_prong) {
12571 const prong = extra_prong.?;
12572 items = extra_prong_items;
12573 ranges = extra_prong_ranges;
12574 body = prong.body;
12575 info = .{
12576 .body_len = undefined,
12577 .capture = prong.capture,
12578 .is_inline = prong.is_inline,
12579 .has_tag_capture = prong.has_tag_capture,
12580 };
12581 } else {
12582 @branchHint(.likely);
12583 const items_len = sema.code.extra[extra_index];
12584 extra_index += 1;
12585 const ranges_len = sema.code.extra[extra_index];
12586 extra_index += 1;
12587 info = @bitCast(sema.code.extra[extra_index]);
12588 extra_index += 1 + items_len + ranges_len * 2;
12589
12590 items = case_vals.items[case_val_idx..][0..items_len];
12591 case_val_idx += items_len;
12592 ranges = @ptrCast(case_vals.items[case_val_idx..][0 .. ranges_len * 2]);
12593 case_val_idx += ranges_len * 2;
12594
12595 body = sema.code.bodySlice(extra_index, info.body_len);
12596 extra_index += info.body_len;
12597 }
12598
12599 case_block.instructions.shrinkRetainingCapacity(0);
12600 case_block.error_return_trace_index = child_block.error_return_trace_index;
12601
12602 // Generate all possible cases as scalar prongs.
12603 if (info.is_inline) {
12604 var emit_bb = false;
12605
12606 for (ranges, 0..) |range_items, range_i| {
12607 var item = sema.resolveConstDefinedValue(block, .unneeded, range_items[0], undefined) catch unreachable;
12608 const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_items[1], undefined) catch unreachable;
12609
12610 while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
12611 // Previous validation has resolved any possible lazy values.
12612 const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
12613 .int => .{ item, operand_ty },
12614 .@"enum" => b: {
12615 const int_val = Value.fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
12616 break :b .{ int_val, int_val.typeOf(zcu) };
12617 },
12618 else => unreachable,
12619 };
12620 const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
12621 assert(!result.overflow);
12622 item = switch (operand_ty.zigTypeTag(zcu)) {
12623 .int => result.val,
12624 .@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
12625 .ty = operand_ty.toIntern(),
12626 .int = result.val.toIntern(),
12627 } })),
12628 else => unreachable,
12629 };
12630 }) {
12631 cases_len += 1;
12632
12633 const item_ref = Air.internedToRef(item.toIntern());
12634
12635 case_block.instructions.shrinkRetainingCapacity(0);
12636 case_block.error_return_trace_index = child_block.error_return_trace_index;
12637
12638 if (emit_bb) {
12639 const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
12640 .switch_node_offset = switch_node_offset,
12641 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12642 .item_idx = .{ .kind = .range, .index = @intCast(range_i) },
12643 } });
12644 try sema.emitBackwardBranch(block, bb_src);
12645 }
12646 emit_bb = true;
12647
12648 const prong_hint = try spa.analyzeProngRuntime(
12649 &case_block,
12650 .normal,
12651 body,
12652 info.capture,
12653 child_block.src(.{ .switch_capture = .{
12654 .switch_node_offset = switch_node_offset,
12655 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12656 } }),
12657 undefined, // case_vals may be undefined for ranges
12658 item_ref,
12659 info.has_tag_capture,
12660 );
12661 try branch_hints.append(gpa, prong_hint);
12662
12663 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12664 1 + // `item`, no ranges
12665 case_block.instructions.items.len);
12666 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12667 .items_len = 1,
12668 .ranges_len = 0,
12669 .body_len = @intCast(case_block.instructions.items.len),
12670 }));
12671 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12672 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12673
12674 if (item.compareScalar(.eq, item_last, operand_ty, zcu)) break;
12675 }
12676 }
12677
12678 for (items, 0..) |item, item_i| {
12679 cases_len += 1;
12680
12681 case_block.instructions.shrinkRetainingCapacity(0);
12682 case_block.error_return_trace_index = child_block.error_return_trace_index;
12683
12684 const analyze_body = if (union_originally) blk: {
12685 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12686 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12687 break :blk field_ty.zigTypeTag(zcu) != .noreturn;
12688 } else true;
12689
12690 if (emit_bb) {
12691 const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
12692 .switch_node_offset = switch_node_offset,
12693 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12694 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },
12695 } });
12696 try sema.emitBackwardBranch(block, bb_src);
12697 }
12698 emit_bb = true;
12699
12700 const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
12701 break :h try spa.analyzeProngRuntime(
12702 &case_block,
12703 .normal,
12704 body,
12705 info.capture,
12706 child_block.src(.{ .switch_capture = .{
12707 .switch_node_offset = switch_node_offset,
12708 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12709 } }),
12710 &.{item},
12711 item,
12712 info.has_tag_capture,
12713 );
12714 } else h: {
12715 _ = try case_block.addNoOp(.unreach);
12716 break :h .none;
12717 };
12718 try branch_hints.append(gpa, prong_hint);
12719
12720 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12721 1 + // `item`, no ranges
12722 case_block.instructions.items.len);
12723 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12724 .items_len = 1,
12725 .ranges_len = 0,
12726 .body_len = @intCast(case_block.instructions.items.len),
12727 }));
12728 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12729 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12730 }
12731
12732 continue;
12733 }
12734
12735 cases_len += 1;
12736
12737 const analyze_body = if (union_originally)
12738 for (items) |item| {
12739 const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
12740 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12741 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
12742 } else false
12743 else
12744 true;
12745
12746 const prong_hint: std.builtin.BranchHint = if (err_set and
12747 try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
12748 h: {
12749 // nothing to do here. weight against error branch
12750 break :h .unlikely;
12751 } else if (analyze_body) h: {
12752 break :h try spa.analyzeProngRuntime(
12753 &case_block,
12754 .normal,
12755 body,
12756 info.capture,
12757 child_block.src(.{ .switch_capture = .{
12758 .switch_node_offset = switch_node_offset,
12759 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
12760 } }),
12761 items,
12762 .none,
12763 false,
12764 );
12765 } else h: {
12766 _ = try case_block.addNoOp(.unreach);
12767 break :h .none;
12768 };
12769
12770 try branch_hints.append(gpa, prong_hint);
12771
12772 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12773 items.len + ranges.len * 2 +
12774 case_block.instructions.items.len);
12775 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12776 .items_len = @intCast(items.len),
12777 .ranges_len = @intCast(ranges.len),
12778 .body_len = @intCast(case_block.instructions.items.len),
12779 }));
12780
12781 for (items) |item| {
12782 cases_extra.appendAssumeCapacity(@intFromEnum(item));
12783 }
12784 for (ranges) |range| {
12785 cases_extra.appendSliceAssumeCapacity(&.{
12786 @intFromEnum(range[0]),
12787 @intFromEnum(range[1]),
12788 });
12789 }
12790
12791 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12792 }
12793
12794 const else_body: []const Air.Inst.Index = if (else_prong.body.len != 0 or case_block.wantSafety()) else_body: {
12795 var emit_bb = false;
12796 // If this is true we must have a 'true' else prong and not an underscore because
12797 // underscore prongs can never be inlined. We've already checked for this.
12798 if (else_prong.is_inline) switch (operand_ty.zigTypeTag(zcu)) {
12799 .@"enum" => {
12800 if (operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
12801 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
12802 operand_ty.fmt(pt),
12803 });
12804 }
12805 for (seen_enum_fields, 0..) |f, i| {
12806 if (f != null) continue;
12807 cases_len += 1;
12808
12809 const item_val = try pt.enumValueFieldIndex(operand_ty, @intCast(i));
12810 const item_ref = Air.internedToRef(item_val.toIntern());
12811
12812 case_block.instructions.shrinkRetainingCapacity(0);
12813 case_block.error_return_trace_index = child_block.error_return_trace_index;
12814
12815 const analyze_body = if (union_originally) blk: {
12816 const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
12817 break :blk field_ty.zigTypeTag(zcu) != .noreturn;
12818 } else true;
12819
12820 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12821 emit_bb = true;
12822
12823 const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
12824 break :h try spa.analyzeProngRuntime(
12825 &case_block,
12826 .special,
12827 else_prong.body,
12828 else_prong.capture,
12829 child_block.src(.{ .switch_capture = .{
12830 .switch_node_offset = switch_node_offset,
12831 .case_idx = .special_else,
12832 } }),
12833 &.{item_ref},
12834 item_ref,
12835 else_prong.has_tag_capture,
12836 );
12837 } else h: {
12838 _ = try case_block.addNoOp(.unreach);
12839 break :h .none;
12840 };
12841 try branch_hints.append(gpa, prong_hint);
12842
12843 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12844 1 + // `item`, no ranges
12845 case_block.instructions.items.len);
12846 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12847 .items_len = 1,
12848 .ranges_len = 0,
12849 .body_len = @intCast(case_block.instructions.items.len),
12850 }));
12851 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12852 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12853 }
12854 },
12855 .error_set => {
12856 if (operand_ty.isAnyError(zcu)) {
12857 return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
12858 operand_ty.fmt(pt),
12859 });
12860 }
12861 const error_names = operand_ty.errorSetNames(zcu);
12862 for (0..error_names.len) |name_index| {
12863 const error_name = error_names.get(ip)[name_index];
12864 if (seen_errors.contains(error_name)) continue;
12865 cases_len += 1;
12866
12867 const item_val = try pt.intern(.{ .err = .{
12868 .ty = operand_ty.toIntern(),
12869 .name = error_name,
12870 } });
12871 const item_ref = Air.internedToRef(item_val);
12872
12873 case_block.instructions.shrinkRetainingCapacity(0);
12874 case_block.error_return_trace_index = child_block.error_return_trace_index;
12875
12876 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12877 emit_bb = true;
12878
12879 const prong_hint = try spa.analyzeProngRuntime(
12880 &case_block,
12881 .special,
12882 else_prong.body,
12883 else_prong.capture,
12884 child_block.src(.{ .switch_capture = .{
12885 .switch_node_offset = switch_node_offset,
12886 .case_idx = .special_else,
12887 } }),
12888 &.{item_ref},
12889 item_ref,
12890 else_prong.has_tag_capture,
12891 );
12892 try branch_hints.append(gpa, prong_hint);
12893
12894 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12895 1 + // `item`, no ranges
12896 case_block.instructions.items.len);
12897 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12898 .items_len = 1,
12899 .ranges_len = 0,
12900 .body_len = @intCast(case_block.instructions.items.len),
12901 }));
12902 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12903 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12904 }
12905 },
12906 .int => {
12907 var it = try RangeSetUnhandledIterator.init(sema, operand_ty, range_set);
12908 while (try it.next()) |cur| {
12909 cases_len += 1;
12910
12911 const item_ref = Air.internedToRef(cur);
12912
12913 case_block.instructions.shrinkRetainingCapacity(0);
12914 case_block.error_return_trace_index = child_block.error_return_trace_index;
12915
12916 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12917 emit_bb = true;
12918
12919 const prong_hint = try spa.analyzeProngRuntime(
12920 &case_block,
12921 .special,
12922 else_prong.body,
12923 else_prong.capture,
12924 child_block.src(.{ .switch_capture = .{
12925 .switch_node_offset = switch_node_offset,
12926 .case_idx = .special_else,
12927 } }),
12928 &.{item_ref},
12929 item_ref,
12930 else_prong.has_tag_capture,
12931 );
12932 try branch_hints.append(gpa, prong_hint);
12933
12934 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12935 1 + // `item`, no ranges
12936 case_block.instructions.items.len);
12937 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12938 .items_len = 1,
12939 .ranges_len = 0,
12940 .body_len = @intCast(case_block.instructions.items.len),
12941 }));
12942 cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
12943 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12944 }
12945 },
12946 .bool => {
12947 if (true_count == 0) {
12948 cases_len += 1;
12949
12950 case_block.instructions.shrinkRetainingCapacity(0);
12951 case_block.error_return_trace_index = child_block.error_return_trace_index;
12952
12953 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12954 emit_bb = true;
12955
12956 const prong_hint = try spa.analyzeProngRuntime(
12957 &case_block,
12958 .special,
12959 else_prong.body,
12960 else_prong.capture,
12961 child_block.src(.{ .switch_capture = .{
12962 .switch_node_offset = switch_node_offset,
12963 .case_idx = .special_else,
12964 } }),
12965 &.{.bool_true},
12966 .bool_true,
12967 else_prong.has_tag_capture,
12968 );
12969 try branch_hints.append(gpa, prong_hint);
12970
12971 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
12972 1 + // `item`, no ranges
12973 case_block.instructions.items.len);
12974 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
12975 .items_len = 1,
12976 .ranges_len = 0,
12977 .body_len = @intCast(case_block.instructions.items.len),
12978 }));
12979 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));
12980 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
12981 }
12982 if (false_count == 0) {
12983 cases_len += 1;
12984
12985 case_block.instructions.shrinkRetainingCapacity(0);
12986 case_block.error_return_trace_index = child_block.error_return_trace_index;
12987
12988 if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
12989 emit_bb = true;
12990
12991 const prong_hint = try spa.analyzeProngRuntime(
12992 &case_block,
12993 .special,
12994 else_prong.body,
12995 else_prong.capture,
12996 child_block.src(.{ .switch_capture = .{
12997 .switch_node_offset = switch_node_offset,
12998 .case_idx = .special_else,
12999 } }),
13000 &.{.bool_false},
13001 .bool_false,
13002 else_prong.has_tag_capture,
13003 );
13004 try branch_hints.append(gpa, prong_hint);
13005
13006 try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13007 1 + // `item`, no ranges
13008 case_block.instructions.items.len);
13009 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13010 .items_len = 1,
13011 .ranges_len = 0,
13012 .body_len = @intCast(case_block.instructions.items.len),
13013 }));
13014 cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));
13015 cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
13016 }
13017 },
13018 else => return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
13019 operand_ty.fmt(pt),
13020 }),
13021 };
13022
13023 case_block.instructions.shrinkRetainingCapacity(0);
13024 case_block.error_return_trace_index = child_block.error_return_trace_index;
13025
13026 if (zcu.backendSupportsFeature(.is_named_enum_value) and
13027 else_prong.body.len != 0 and block.wantSafety() and
13028 operand_ty.zigTypeTag(zcu) == .@"enum" and
13029 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
13030 {
13031 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
13032 const ok = try case_block.addUnOp(.is_named_enum_value, operand);
13033 try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
13034 }
13035
13036 const else_src_idx: LazySrcLoc.Offset.SwitchCaseIndex = if (else_prong_is_underscore)
13037 .special_under
13038 else
13039 .special_else;
13040
13041 const analyze_body = if (union_originally and !else_prong.is_inline)
13042 for (seen_enum_fields, 0..) |seen_field, index| {
13043 if (seen_field != null) continue;
13044 const union_obj = zcu.typeToUnion(maybe_union_ty).?;
13045 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[index]);
13046 if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
13047 } else false
13048 else
13049 true;
13050 const else_hint: std.builtin.BranchHint = if (else_prong.body.len != 0 and err_set and
13051 try sema.maybeErrorUnwrap(&case_block, else_prong.body, operand, operand_src, allow_err_code_unwrap))
13052 h: {
13053 // nothing to do here. weight against error branch
13054 break :h .unlikely;
13055 } else if (else_prong.body.len != 0 and analyze_body and !else_prong.is_inline) h: {
13056 break :h try spa.analyzeProngRuntime(
13057 &case_block,
13058 .special,
13059 else_prong.body,
13060 else_prong.capture,
13061 child_block.src(.{ .switch_capture = .{
13062 .switch_node_offset = switch_node_offset,
13063 .case_idx = else_src_idx,
13064 } }),
13065 undefined, // case_vals may be undefined for special prongs
13066 .none,
13067 false,
13068 );
13069 } else h: {
13070 // We still need a terminator in this block, but we have proven
13071 // that it is unreachable.
13072 if (case_block.wantSafety()) {
13073 try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
13074 try sema.safetyPanic(&case_block, src, .corrupt_switch);
13075 } else {
13076 _ = try case_block.addNoOp(.unreach);
13077 }
13078 // Safety check / unreachable branches are cold.
13079 break :h .cold;
13080 };
13081
13082 try branch_hints.append(gpa, else_hint);
13083 break :else_body case_block.instructions.items;
13084 } else else_body: {
13085 try branch_hints.append(gpa, .none);
13086 break :else_body &.{};
13087 };
13088
13089 assert(branch_hints.items.len == cases_len + 1);
13090
13091 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
13092 cases_extra.items.len + else_body.len +
13093 (std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints
13094
13095 const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
13096 .cases_len = @intCast(cases_len),
13097 .else_body_len = @intCast(else_body.len),
13098 });
13099
13100 {
13101 // Add branch hints.
13102 var cur_bag: u32 = 0;
13103 for (branch_hints.items, 0..) |hint, idx| {
13104 const idx_in_bag = idx % 10;
13105 cur_bag |= @as(u32, @intFromEnum(hint)) << @intCast(idx_in_bag * 3);
13106 if (idx_in_bag == 9) {
13107 sema.air_extra.appendAssumeCapacity(cur_bag);
13108 cur_bag = 0;
13109 }
13110 }
13111 if (branch_hints.items.len % 10 != 0) {
13112 sema.air_extra.appendAssumeCapacity(cur_bag);
13113 }
13114 }
13115 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));
13116 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
13117
13118 const has_any_continues = spa.operand == .loop and child_block.label.?.merges.extra_insts.items.len > 0;
13119
13120 return try child_block.addInst(.{
13121 .tag = if (has_any_continues) .loop_switch_br else .switch_br,
13122 .data = .{ .pl_op = .{
13123 .operand = operand,
13124 .payload = payload_index,
13125 } },
13126 });
13127}
13128
13129fn resolveSwitchComptimeLoop(
13130 sema: *Sema,
13131 init_spa: SwitchProngAnalysis,
13132 child_block: *Block,
13133 maybe_ptr_operand_ty: Type,
13134 cond_ty: Type,
13135 init_cond_val: Value,
13136 switch_node_offset: std.zig.Ast.Node.Offset,
13137 special_members_only: ?SpecialProng,
13138 special_generic: SpecialProng,
13139 special_generic_is_under: bool,
13140 case_vals: std.ArrayList(Air.Inst.Ref),
13141 scalar_cases_len: u32,
13142 multi_cases_len: u32,
13143 err_set: bool,
13144 empty_enum: bool,
13145 operand_is_ref: bool,
13146) CompileError!Air.Inst.Ref {
13147 var spa = init_spa;
13148 var cond_val = init_cond_val;
13149
13150 while (true) {
13151 if (resolveSwitchComptime(
13152 sema,
13153 spa,
13154 child_block,
13155 spa.operand.simple.cond,
13156 cond_val,
13157 cond_ty,
13158 switch_node_offset,
13159 special_members_only,
13160 special_generic,
13161 special_generic_is_under,
13162 case_vals,
13163 scalar_cases_len,
13164 multi_cases_len,
13165 err_set,
13166 empty_enum,
13167 )) |result| {
13168 return result;
13169 } else |err| switch (err) {
13170 error.ComptimeBreak => {
13171 const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
13172 if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
13173 const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
13174 if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
13175 // This is a `switch_continue` targeting this block. Change the operand and start over.
13176 const src = child_block.nodeOffset(extra.operand_src_node.unwrap().?);
13177 const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
13178 const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
13179
13180 try sema.emitBackwardBranch(child_block, src);
13181
13182 const val, const ref = if (operand_is_ref)
13183 .{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
13184 else
13185 .{ new_operand, undefined };
13186
13187 const cond_ref = try sema.switchCond(child_block, src, val);
13188
13189 cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, null);
13190 spa.operand = .{ .simple = .{
13191 .by_val = val,
13192 .by_ref = ref,
13193 .cond = cond_ref,
13194 } };
13195 },
13196 else => |e| return e,
13197 }
13198 }
13199}
13200
13201fn resolveSwitchComptime(
13202 sema: *Sema,
13203 spa: SwitchProngAnalysis,
13204 child_block: *Block,12309 child_block: *Block,
13205 cond_operand: Air.Inst.Ref,12310 operand: SwitchOperand,
13206 operand_val: Value,12311 raw_operand_ty: Type,
13207 operand_ty: Type,12312 maybe_lazy_cond_val: Value,
13208 switch_node_offset: std.zig.Ast.Node.Offset,12313 catch_all_case: CatchAllSwitchCase,
13209 special_members_only: ?SpecialProng,12314 else_is_named_only: bool,
13210 special_generic: SpecialProng,12315 merges: *Block.Merges,
13211 special_generic_is_under: bool,12316 switch_inst: Zir.Inst.Index,
13212 case_vals: std.ArrayList(Air.Inst.Ref),12317 zir_switch: *const Zir.UnwrappedSwitchBlock,
13213 scalar_cases_len: u32,12318 validated_switch: *const ValidatedSwitchBlock,
13214 multi_cases_len: u32,
13215 err_set: bool,
13216 empty_enum: bool,
13217) CompileError!Air.Inst.Ref {12319) CompileError!Air.Inst.Ref {
13218 const zcu = sema.pt.zcu;12320 const pt = sema.pt;
13219 const merges = &child_block.label.?.merges;12321 const zcu = pt.zcu;
13220 const resolved_operand_val = try sema.resolveLazyValue(operand_val);
1322112322
13222 var extra_index: usize = special_generic.end;12323 const switch_node_offset = zir_switch.switch_src_node_offset;
13223 {12324
13224 var scalar_i: usize = 0;12325 const operand_ty = sema.typeOf(operand.simple.by_val);
13225 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {12326 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
13226 extra_index += 1;12327 .@"union" => operand_ty.unionTagType(zcu).?,
13227 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);12328 else => operand_ty,
13228 extra_index += 1;12329 };
13229 const body = sema.code.bodySlice(extra_index, info.body_len);12330 const union_originally = operand_ty.zigTypeTag(zcu) == .@"union";
13230 extra_index += info.body_len;12331 const err_set = item_ty.zigTypeTag(zcu) == .error_set;
1323112332
13232 const item = case_vals.items[scalar_i];12333 const cond_ref = operand.simple.cond;
13233 const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;12334 // We have to resolve lazy values to ensure that comparisons with switch
13234 if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {12335 // prong items don't produce false negatives.
13235 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);12336 const cond_val = try sema.resolveLazyValue(maybe_lazy_cond_val);
13236 return spa.resolveProngComptime(12337
12338 const case_vals = validated_switch.case_vals;
12339 var case_val_idx: usize = 0;
12340 var extra_index = zir_switch.end;
12341 var case_it = zir_switch.iterateCases();
12342 var under_prong: ?struct {
12343 index: Zir.UnwrappedSwitchBlock.Case.Index,
12344 body: []const Zir.Inst.Index,
12345 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12346 has_tag_capture: bool,
12347 } = null;
12348 while (case_it.next()) |case| {
12349 const prong_info = case.prong_info;
12350 const prong_body = sema.code.bodySlice(extra_index, prong_info.body_len);
12351 extra_index += prong_body.len;
12352 for (case.item_infos) |item_info| {
12353 if (item_info.bodyLen()) |body_len| extra_index += body_len;
12354 }
12355 for (case.range_infos) |range_info| {
12356 if (range_info[0].bodyLen()) |body_len| extra_index += body_len;
12357 if (range_info[1].bodyLen()) |body_len| extra_index += body_len;
12358 }
12359
12360 const item_refs = case_vals[case_val_idx..][0..case.item_infos.len];
12361 case_val_idx += item_refs.len;
12362 const range_refs: []const [2]Air.Inst.Ref = @ptrCast(case_vals[case_val_idx..][0 .. 2 * case.range_infos.len]);
12363 case_val_idx += 2 * range_refs.len;
12364 for (item_refs) |item_ref| {
12365 if (item_ref == .none) {
12366 under_prong = .{
12367 .index = case.index,
12368 .body = prong_body,
12369 .capture = case.prong_info.capture,
12370 .has_tag_capture = case.prong_info.has_tag_capture,
12371 };
12372 continue;
12373 }
12374 const item_val = sema.resolveConstDefinedValue(child_block, .unneeded, item_ref, undefined) catch unreachable;
12375 if (cond_val.eql(item_val, item_ty, zcu)) {
12376 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, prong_body, cond_ref);
12377 if (union_originally and operand_ty.unionFieldType(item_val, zcu).?.isNoReturn(zcu)) {
12378 // This prong should be unreachable!
12379 return .unreachable_value;
12380 }
12381 return sema.resolveSwitchProng(
12382 block,
13237 child_block,12383 child_block,
13238 .normal,12384 operand,
13239 body,12385 raw_operand_ty,
13240 info.capture,12386 prong_body,
13241 child_block.src(.{ .switch_capture = .{12387 block.src(.{ .switch_capture = .{
13242 .switch_node_offset = switch_node_offset,12388 .switch_node_offset = switch_node_offset,
13243 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12389 .case_idx = case.index,
13244 } }),12390 } }),
13245 &.{item},12391 prong_info.capture,
13246 if (info.is_inline) cond_operand else .none,12392 prong_info.has_tag_capture,
13247 info.has_tag_capture,12393 if (prong_info.is_inline) cond_ref else .none,
12394 .{ .item_refs = item_refs },
12395 validated_switch.else_err_ty,
13248 merges,12396 merges,
12397 switch_inst,
12398 zir_switch,
13249 );12399 );
13250 }12400 }
13251 }12401 }
13252 }12402 for (range_refs) |range_ref| {
13253 {12403 const first_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_ref[0], undefined) catch unreachable;
13254 var multi_i: usize = 0;12404 const last_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_ref[1], undefined) catch unreachable;
13255 var case_val_idx: usize = scalar_cases_len;12405 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
13256 while (multi_i < multi_cases_len) : (multi_i += 1) {12406 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
13257 const items_len = sema.code.extra[extra_index];12407 {
13258 extra_index += 1;12408 return sema.resolveSwitchProng(
13259 const ranges_len = sema.code.extra[extra_index];12409 block,
13260 extra_index += 1;12410 child_block,
13261 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);12411 operand,
13262 extra_index += 1 + items_len;12412 raw_operand_ty,
13263 const body = sema.code.bodySlice(extra_index + 2 * ranges_len, info.body_len);12413 prong_body,
1326412414 block.src(.{ .switch_capture = .{
13265 const items = case_vals.items[case_val_idx..][0..items_len];12415 .switch_node_offset = switch_node_offset,
13266 case_val_idx += items_len;12416 .case_idx = case.index,
1326712417 } }),
13268 for (items) |item| {12418 prong_info.capture,
13269 // Validation above ensured these will succeed.12419 prong_info.has_tag_capture,
13270 const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;12420 if (prong_info.is_inline) cond_ref else .none,
13271 if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {12421 .has_ranges,
13272 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);12422 validated_switch.else_err_ty,
13273 return spa.resolveProngComptime(12423 merges,
13274 child_block,12424 switch_inst,
13275 .normal,12425 zir_switch,
13276 body,12426 );
13277 info.capture,
13278 child_block.src(.{ .switch_capture = .{
13279 .switch_node_offset = switch_node_offset,
13280 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
13281 } }),
13282 items,
13283 if (info.is_inline) cond_operand else .none,
13284 info.has_tag_capture,
13285 merges,
13286 );
13287 }
13288 }12427 }
12428 }
12429 }
1328912430
13290 var range_i: usize = 0;12431 const else_case = validated_switch.else_case;
13291 while (range_i < ranges_len) : (range_i += 1) {
13292 const range_items = case_vals.items[case_val_idx..][0..2];
13293 extra_index += 2;
13294 case_val_idx += 2;
1329512432
13296 // Validation above ensured these will succeed.12433 // named-only prong
13297 const first_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable;
13298 const last_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable;
13299 if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and
13300 (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))
13301 {
13302 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
13303 return spa.resolveProngComptime(
13304 child_block,
13305 .normal,
13306 body,
13307 info.capture,
13308 child_block.src(.{ .switch_capture = .{
13309 .switch_node_offset = switch_node_offset,
13310 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
13311 } }),
13312 undefined, // case_vals may be undefined for ranges
13313 if (info.is_inline) cond_operand else .none,
13314 info.has_tag_capture,
13315 merges,
13316 );
13317 }
13318 }
1331912434
13320 extra_index += info.body_len;12435 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
13321 }12436 assert(item_ty.isNonexhaustiveEnum(zcu));
13322 }12437 return sema.resolveSwitchProng(
13323 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special_generic.body, cond_operand);12438 block,
13324 if (empty_enum) {12439 child_block,
13325 return .void_value;12440 operand,
12441 raw_operand_ty,
12442 else_case.body,
12443 block.src(.{ .switch_capture = .{
12444 .switch_node_offset = switch_node_offset,
12445 .case_idx = else_case.index,
12446 } }),
12447 else_case.capture,
12448 else_case.has_tag_capture,
12449 if (else_case.is_inline) cond_ref else .none,
12450 .special,
12451 validated_switch.else_err_ty,
12452 merges,
12453 switch_inst,
12454 zir_switch,
12455 );
13326 }12456 }
13327 if (special_members_only) |special| {12457
13328 assert(operand_ty.isNonexhaustiveEnum(zcu));12458 // catch-all prong
13329 if (operand_ty.enumTagFieldIndex(operand_val, zcu)) |_| {12459
13330 return spa.resolveProngComptime(12460 const index, const body, const capture, const has_tag_capture, const is_inline = switch (catch_all_case) {
13331 child_block,12461 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline },
13332 .special,12462 .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture, false },
13333 special.body,12463 .none => unreachable,
13334 special.capture,12464 };
13335 child_block.src(.{ .switch_capture = .{12465 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
13336 .switch_node_offset = switch_node_offset,12466 if (union_originally) {
13337 .case_idx = .special_else,12467 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
13338 } }),12468 if (maybe_seen != null) continue;
13339 undefined, // case_vals may be undefined for special prongs12469 if (!operand_ty.unionFieldTypeByIndex(field_i, zcu).isNoReturn(zcu)) break;
13340 if (special.is_inline) cond_operand else .none,12470 } else {
13341 special.has_tag_capture,12471 // This prong should be unreachable!
13342 merges,12472 return .unreachable_value;
13343 );
13344 }12473 }
13345 }12474 }
1334612475 return sema.resolveSwitchProng(
13347 return spa.resolveProngComptime(12476 block,
13348 child_block,12477 child_block,
13349 .special,12478 operand,
13350 special_generic.body,12479 raw_operand_ty,
13351 special_generic.capture,12480 body,
13352 child_block.src(.{ .switch_capture = .{12481 block.src(.{ .switch_capture = .{
13353 .switch_node_offset = switch_node_offset,12482 .switch_node_offset = switch_node_offset,
13354 .case_idx = if (special_generic_is_under)12483 .case_idx = index,
13355 .special_under
13356 else
13357 .special_else,
13358 } }),12484 } }),
13359 undefined, // case_vals may be undefined for special prongs12485 capture,
13360 if (special_generic.is_inline) cond_operand else .none,12486 has_tag_capture,
13361 special_generic.has_tag_capture,12487 if (is_inline) cond_ref else .none,
12488 .special,
12489 validated_switch.else_err_ty,
13362 merges,12490 merges,
12491 switch_inst,
12492 zir_switch,
13363 );12493 );
13364}12494}
1336512495
13366const RangeSetUnhandledIterator = struct {12496const SwitchOperand = union(enum) {
13367 pt: Zcu.PerThread,12497 /// This switch will be dispatched only once, with the given operand.
13368 cur: ?InternPool.Index,12498 simple: struct {
13369 max: InternPool.Index,12499 /// The raw switch operand value. Always defined.
13370 range_i: usize,12500 by_val: Air.Inst.Ref,
13371 ranges: []const RangeSet.Range,12501 /// The switch operand *pointer*. Defined only if there is a prong
13372 limbs: []math.big.Limb,12502 /// with a by-ref capture.
12503 by_ref: Air.Inst.Ref,
12504 /// The switch condition value. For unions, `operand` is the union
12505 /// and `cond` is its enum tag value.
12506 cond: Air.Inst.Ref,
12507 },
12508 /// This switch may be dispatched multiple times with `continue` syntax.
12509 /// As such, the operand is stored in an alloc if needed.
12510 loop: struct {
12511 /// The `alloc` containing the `switch` operand for the active dispatch.
12512 /// Each prong must load from this `alloc` to get captures.
12513 /// If there are no captures, this may be undefined.
12514 operand_alloc: Air.Inst.Ref,
12515 /// Whether `operand_alloc` contains a by-val operand or a by-ref
12516 /// operand.
12517 operand_is_ref: bool,
12518 /// The switch condition value for the *initial* dispatch. For
12519 /// unions, this is the enum tag value.
12520 init_cond: Air.Inst.Ref,
12521 },
12522};
1337312523
13374 const preallocated_limbs = math.big.int.calcTwosCompLimbCount(128);12524const CatchAllSwitchCase = enum { none, @"else", under };
1337512525
13376 fn init(sema: *Sema, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator {12526const SwitchProngKind = union(enum) {
13377 const pt = sema.pt;12527 item_refs: []const Air.Inst.Ref,
13378 const int_type = pt.zcu.intern_pool.indexToKey(ty.toIntern()).int_type;12528 has_ranges,
13379 const needed_limbs = math.big.int.calcTwosCompLimbCount(int_type.bits);12529 special,
13380 return .{12530};
13381 .pt = pt,
13382 .cur = (try ty.minInt(pt, ty)).toIntern(),
13383 .max = (try ty.maxInt(pt, ty)).toIntern(),
13384 .range_i = 0,
13385 .ranges = range_set.ranges.items,
13386 .limbs = if (needed_limbs > preallocated_limbs)
13387 try sema.arena.alloc(math.big.Limb, needed_limbs)
13388 else
13389 &.{},
13390 };
13391 }
1339212531
13393 fn addOne(it: *const RangeSetUnhandledIterator, val: InternPool.Index) !?InternPool.Index {12532/// Resolve a switch prong which is determined at comptime to have no peers.
13394 if (val == it.max) return null;12533/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
13395 const int = it.pt.zcu.intern_pool.indexToKey(val).int;12534fn resolveSwitchProng(
12535 sema: *Sema,
12536 block: *Block,
12537 child_block: *Block,
12538 operand: SwitchOperand,
12539 raw_operand_ty: Type,
12540 prong_body: []const Zir.Inst.Index,
12541 /// Must use the `switch_capture` field in `offset`.
12542 capture_src: LazySrcLoc,
12543 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12544 has_tag_capture: bool,
12545 inline_case_capture: Air.Inst.Ref,
12546 kind: SwitchProngKind,
12547 else_err_ty: ?Type,
12548 merges: *Block.Merges,
12549 switch_inst: Zir.Inst.Index,
12550 zir_switch: *const Zir.UnwrappedSwitchBlock,
12551) CompileError!Air.Inst.Ref {
12552 const src_node_offset = zir_switch.switch_src_node_offset;
12553 const src = block.nodeOffset(src_node_offset);
12554 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
1339612555
13397 switch (int.storage) {12556 // We can propagate `.cold` hints from this branch since it's comptime-known
13398 inline .u64, .i64 => |val_int| {12557 // to be taken from the parent branch.
13399 const next_int = @addWithOverflow(val_int, 1);12558 const parent_hint = sema.branch_hint;
13400 if (next_int[1] == 0)12559 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
13401 return (try it.pt.intValue(.fromInterned(int.ty), next_int[0])).toIntern();
13402 },
13403 .big_int => {},
13404 .lazy_align, .lazy_size => unreachable,
13405 }
1340612560
13407 var val_space: InternPool.Key.Int.Storage.BigIntSpace = undefined;12561 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
13408 const val_bigint = int.storage.toBigInt(&val_space);12562 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
12563 const payload_ref = try sema.analyzeSwitchPayloadCapture(
12564 child_block,
12565 operand,
12566 operand.simple.by_val,
12567 operand.simple.by_ref,
12568 sema.typeOf(operand.simple.by_val),
12569 operand_src,
12570 capture_src,
12571 capture == .by_ref,
12572 kind == .special,
12573 switch (kind) {
12574 .item_refs => |item_refs| item_refs,
12575 .has_ranges, .special => undefined,
12576 },
12577 inline_case_capture,
12578 else_err_ty,
12579 );
12580 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
12581 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
12582 break :inst payload_inst;
12583 } else undefined;
12584 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1340912585
13410 var result_limbs: [preallocated_limbs]math.big.Limb = undefined;12586 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
13411 var result_bigint = math.big.int.Mutable.init(12587 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
13412 if (it.limbs.len > 0) it.limbs else &result_limbs,12588 const tag_ref = try sema.analyzeSwitchTagCapture(
13413 0,12589 child_block,
12590 operand.simple.by_val,
12591 sema.typeOf(operand.simple.by_val),
12592 capture_src,
12593 inline_case_capture,
12594 kind,
13414 );12595 );
12596 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
12597 break :inst tag_inst;
12598 } else undefined;
12599 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
1341512600
13416 result_bigint.addScalar(val_bigint, 1);12601 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
13417 return (try it.pt.intValue_big(.fromInterned(int.ty), result_bigint.toConst())).toIntern();12602 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
12603
12604 return sema.resolveBlockBody(block, src, child_block, prong_body, switch_inst, merges);
12605}
12606
12607fn wantSwitchProngBodyAnalysis(
12608 sema: *Sema,
12609 block: *Block,
12610 item_ref: Air.Inst.Ref,
12611 operand_ty: Type,
12612 union_originally: bool,
12613 err_set: bool,
12614 prong_is_comptime_unreach: bool,
12615) bool {
12616 const zcu = sema.pt.zcu;
12617 if (union_originally) {
12618 const unresolved_item_val = sema.resolveConstDefinedValue(block, .unneeded, item_ref, undefined) catch unreachable;
12619 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
12620 const field_ty = operand_ty.unionFieldType(item_val, zcu).?;
12621 if (field_ty.isNoReturn(zcu)) return false;
12622 }
12623 if (err_set and prong_is_comptime_unreach) {
12624 const unresolved_item_val = sema.resolveConstDefinedValue(block, .unneeded, item_ref, undefined) catch unreachable;
12625 const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
12626 const err_name = item_val.getErrorName(zcu).unwrap().?;
12627 if (!Type.errorSetHasFieldIp(&zcu.intern_pool, operand_ty.toIntern(), err_name)) return false;
13418 }12628 }
12629 return true;
12630}
1341912631
13420 fn next(it: *RangeSetUnhandledIterator) !?InternPool.Index {12632/// Assumes that `operand_ty` has more than one possible value.
13421 var cur = it.cur orelse return null;12633/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
13422 while (it.range_i < it.ranges.len and cur == it.ranges[it.range_i].first) {12634fn analyzeSwitchProng(
13423 defer it.range_i += 1;12635 sema: *Sema,
13424 cur = (try it.addOne(it.ranges[it.range_i].last)) orelse {12636 case_block: *Block,
13425 it.cur = null;12637 operand: SwitchOperand,
13426 return null;12638 operand_ty: Type,
13427 };12639 raw_operand_ty: Type,
12640 prong_body: []const Zir.Inst.Index,
12641 /// Must use the `switch_capture` field in `offset`.
12642 capture_src: LazySrcLoc,
12643 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12644 has_tag_capture: bool,
12645 inline_case_capture: Air.Inst.Ref,
12646 kind: SwitchProngKind,
12647 else_err_ty: ?Type,
12648 switch_inst: Zir.Inst.Index,
12649 zir_switch: *const Zir.UnwrappedSwitchBlock,
12650) CompileError!std.builtin.BranchHint {
12651 const pt = sema.pt;
12652 const zcu = pt.zcu;
12653
12654 const operand_src = case_block.src(.{ .node_offset_switch_operand = zir_switch.switch_src_node_offset });
12655
12656 if (operand_ty.zigTypeTag(zcu) == .error_set) {
12657 const cond_ref = switch (operand) {
12658 .simple => |s| s.cond,
12659 .loop => |l| l.init_cond,
12660 };
12661 if (try sema.maybeErrorUnwrap(case_block, prong_body, cond_ref, operand_src, true)) {
12662 // nothing to do here. weight against error branch
12663 return .unlikely;
13428 }12664 }
13429 it.cur = try it.addOne(cur);
13430 return cur;
13431 }12665 }
13432};
1343312666
13434const ResolvedSwitchItem = struct {12667 const operand_val, const operand_ptr = load_operand: {
13435 ref: Air.Inst.Ref,12668 if (capture == .none and !has_tag_capture) {
13436 val: InternPool.Index,12669 // No need to load the operand for this prong!
13437};12670 break :load_operand .{ undefined, undefined };
13438fn resolveSwitchItemVal(12671 }
13439 sema: *Sema,12672 if (inline_case_capture != .none and
13440 block: *Block,12673 !(capture != .none and operand_ty.zigTypeTag(zcu) == .@"union"))
13441 item_ref: Zir.Inst.Ref,12674 {
13442 /// Coerce `item_ref` to this type.12675 // We only need to load the operand if there's a union payload capture
13443 coerce_ty: Type,12676 // since it's always runtime-known; only the tag is comptime-known here.
13444 item_src: LazySrcLoc,12677 break :load_operand .{ undefined, undefined };
13445) CompileError!ResolvedSwitchItem {12678 }
13446 const uncoerced_item = try sema.resolveInst(item_ref);12679 assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
12680 switch (operand) {
12681 .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
12682 .loop => |l| {
12683 const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
12684 if (l.operand_is_ref) {
12685 const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
12686 break :load_operand .{ by_val, loaded };
12687 } else {
12688 break :load_operand .{ loaded, undefined };
12689 }
12690 },
12691 }
12692 };
1344712693
13448 // Constructing a LazySrcLoc is costly because we only have the switch AST node.12694 const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
13449 // Only if we know for sure we need to report a compile error do we resolve the12695 const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
13450 // full source locations.12696 const payload_ref = try sema.analyzeSwitchPayloadCapture(
12697 case_block,
12698 operand,
12699 operand_val,
12700 operand_ptr,
12701 operand_ty,
12702 operand_src,
12703 capture_src,
12704 capture == .by_ref,
12705 kind == .special,
12706 switch (kind) {
12707 .item_refs => |item_refs| item_refs,
12708 .has_ranges, .special => undefined,
12709 },
12710 inline_case_capture,
12711 else_err_ty,
12712 );
12713 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
12714 sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
12715 break :inst payload_inst;
12716 } else undefined;
12717 defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
1345112718
13452 const item = try sema.coerce(block, coerce_ty, uncoerced_item, item_src);12719 const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
12720 const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
12721 const tag_ref = try sema.analyzeSwitchTagCapture(
12722 case_block,
12723 operand_val,
12724 operand_ty,
12725 capture_src,
12726 inline_case_capture,
12727 kind,
12728 );
12729 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
12730 break :inst tag_inst;
12731 } else undefined;
12732 defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
1345312733
13454 const maybe_lazy = try sema.resolveConstDefinedValue(block, item_src, item, .{ .simple = .switch_item });12734 if (zir_switch.has_continue) sema.inst_map.putAssumeCapacity(switch_inst, .fromType(raw_operand_ty));
12735 defer if (zir_switch.has_continue) assert(sema.inst_map.remove(switch_inst));
1345512736
13456 const val = try sema.resolveLazyValue(maybe_lazy);12737 return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
13457 const new_item = if (val.toIntern() != maybe_lazy.toIntern()) blk: {12738}
13458 break :blk Air.internedToRef(val.toIntern());12739
13459 } else item;12740fn analyzeSwitchTagCapture(
12741 sema: *Sema,
12742 case_block: *Block,
12743 /// May be `undefined` if `inline_case_capture` is not `.none`.
12744 operand_val: Air.Inst.Ref,
12745 operand_ty: Type,
12746 capture_src: LazySrcLoc,
12747 inline_case_capture: Air.Inst.Ref,
12748 kind: SwitchProngKind,
12749) CompileError!Air.Inst.Ref {
12750 const pt = sema.pt;
12751 const zcu = pt.zcu;
12752
12753 const tag_capture_src: LazySrcLoc = .{
12754 .base_node_inst = capture_src.base_node_inst,
12755 .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
12756 };
1346012757
13461 return .{ .ref = new_item, .val = val.toIntern() };12758 if (operand_ty.zigTypeTag(zcu) != .@"union") {
12759 return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
12760 operand_ty.fmt(pt),
12761 });
12762 }
12763 if (inline_case_capture != .none) {
12764 return inline_case_capture; // this already is the tag, it's what we're switching on!
12765 }
12766 switch (kind) {
12767 .has_ranges, .special => {},
12768 .item_refs => |refs| if (refs.len == 1) return refs[0],
12769 }
12770 const tag_ty = operand_ty.unionTagType(zcu).?;
12771 return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src);
13462}12772}
1346312773
13464fn validateErrSetSwitch(12774fn analyzeSwitchPayloadCapture(
13465 sema: *Sema,12775 sema: *Sema,
13466 block: *Block,12776 case_block: *Block,
13467 seen_errors: *SwitchErrorSet,12777 operand: SwitchOperand,
13468 case_vals: *std.ArrayList(Air.Inst.Ref),12778 /// May be `undefined` if this is an inline capture and operand is not a union.
12779 operand_val: Air.Inst.Ref,
12780 /// May be `undefined` if `capture_by_ref` is `false` or if `operand_val` is also `undefined`.
12781 operand_ptr: Air.Inst.Ref,
13469 operand_ty: Type,12782 operand_ty: Type,
13470 inst_data: @FieldType(Zir.Inst.Data, "pl_node"),12783 operand_src: LazySrcLoc,
13471 scalar_cases_len: u32,12784 capture_src: LazySrcLoc,
13472 multi_cases_len: u32,12785 capture_by_ref: bool,
13473 else_case: struct { body: []const Zir.Inst.Index, end: usize, src: LazySrcLoc },12786 is_special_prong: bool,
13474 has_else: bool,12787 /// May be `undefined` if `is_special_prong` is `true`.
13475) CompileError!?Type {12788 case_vals: []const Air.Inst.Ref,
13476 const gpa = sema.gpa;12789 /// If this is not `.none`, this is an inline capture.
12790 inline_case_capture: Air.Inst.Ref,
12791 else_err_ty: ?Type,
12792) CompileError!Air.Inst.Ref {
13477 const pt = sema.pt;12793 const pt = sema.pt;
13478 const zcu = pt.zcu;12794 const zcu = pt.zcu;
13479 const ip = &zcu.intern_pool;12795 const ip = &zcu.intern_pool;
1348012796
13481 const src_node_offset = inst_data.src_node;12797 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
13482 const src = block.nodeOffset(src_node_offset);
13483
13484 var extra_index: usize = else_case.end;
13485 {
13486 var scalar_i: u32 = 0;
13487 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
13488 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
13489 extra_index += 1;
13490 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
13491 extra_index += 1 + info.body_len;
1349212798
13493 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(12799 if (inline_case_capture != .none) {
13494 block,12800 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, inline_case_capture, undefined) catch unreachable;
13495 seen_errors,12801 if (operand_ty.zigTypeTag(zcu) == .@"union") {
13496 item_ref,12802 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
13497 operand_ty,12803 const union_obj = zcu.typeToUnion(operand_ty).?;
13498 block.src(.{ .switch_case_item = .{12804 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
13499 .switch_node_offset = src_node_offset,12805 if (capture_by_ref) {
13500 .case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },12806 const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
13501 .item_idx = .{ .kind = .single, .index = 0 },12807 const ptr_field_ty = try pt.ptrTypeSema(.{
13502 } }),12808 .child = field_ty.toIntern(),
13503 ));12809 .flags = .{
12810 .is_const = operand_ptr_info.flags.is_const,
12811 .is_volatile = operand_ptr_info.flags.is_volatile,
12812 .address_space = operand_ptr_info.flags.address_space,
12813 },
12814 });
12815 return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
12816 } else {
12817 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
12818 const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
12819 return .fromIntern(tag_and_val.val);
12820 }
12821 return case_block.addStructFieldVal(operand_val, field_index, field_ty);
12822 }
12823 } else if (capture_by_ref) {
12824 return sema.uavRef(item_val.toIntern());
12825 } else {
12826 return inline_case_capture;
13504 }12827 }
13505 }12828 }
13506 {
13507 var multi_i: u32 = 0;
13508 while (multi_i < multi_cases_len) : (multi_i += 1) {
13509 const items_len = sema.code.extra[extra_index];
13510 extra_index += 1;
13511 const ranges_len = sema.code.extra[extra_index];
13512 extra_index += 1;
13513 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
13514 extra_index += 1;
13515 const items = sema.code.refSlice(extra_index, items_len);
13516 extra_index += items_len + info.body_len;
1351712829
13518 try case_vals.ensureUnusedCapacity(gpa, items.len);12830 const operand_ptr_ty = if (capture_by_ref) sema.typeOf(operand_ptr) else undefined;
13519 for (items, 0..) |item_ref, item_i| {12831
13520 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(12832 if (is_special_prong) {
13521 block,12833 if (capture_by_ref) return operand_ptr;
13522 seen_errors,12834 return switch (operand_ty.zigTypeTag(zcu)) {
13523 item_ref,12835 .error_set => e: {
13524 operand_ty,12836 if (else_err_ty) |err_ty| {
13525 block.src(.{ .switch_case_item = .{12837 break :e sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
13526 .switch_node_offset = src_node_offset,12838 } else {
13527 .case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },12839 try sema.analyzeUnreachable(case_block, operand_src, false);
13528 .item_idx = .{ .kind = .single, .index = @intCast(item_i) },12840 break :e .unreachable_value;
13529 } }),12841 }
13530 ));12842 },
12843 else => operand_val,
12844 };
12845 }
12846
12847 switch (operand_ty.zigTypeTag(zcu)) {
12848 .@"union" => {
12849 const union_obj = zcu.typeToUnion(operand_ty).?;
12850 const first_item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable;
12851
12852 const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
12853 const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
12854
12855 const field_indices = try sema.arena.alloc(u32, case_vals.len);
12856 for (case_vals, field_indices) |item, *field_idx| {
12857 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, item, undefined) catch unreachable;
12858 field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
12859 }
12860
12861 // Fast path: if all the operands are the same type already, we don't need to hit
12862 // PTR! This will also allow us to emit simpler code.
12863 const same_types = for (field_indices[1..]) |field_idx| {
12864 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12865 if (!field_ty.eql(first_field_ty, zcu)) break false;
12866 } else true;
12867
12868 const capture_ty: Type = capture_ty: {
12869 if (same_types) break :capture_ty first_field_ty;
12870 // We need values to run PTR on, so make a bunch of undef constants.
12871 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12872 for (dummy_captures, field_indices) |*dummy, field_idx| {
12873 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12874 dummy.* = try pt.undefRef(field_ty);
12875 }
12876
12877 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12878 for (case_srcs, 0..) |*case_src, item_i| {
12879 case_src.* = .{
12880 .base_node_inst = capture_src.base_node_inst,
12881 .offset = .{ .switch_case_item = .{
12882 .switch_node_offset = switch_node_offset,
12883 .case_idx = capture_src.offset.switch_capture.case_idx,
12884 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12885 } },
12886 };
12887 }
12888
12889 break :capture_ty sema.resolvePeerTypes(
12890 case_block,
12891 capture_src,
12892 dummy_captures,
12893 .{ .override = case_srcs },
12894 ) catch |err| switch (err) {
12895 error.AnalysisFail => {
12896 const msg = sema.err orelse return error.AnalysisFail;
12897 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12898 return error.AnalysisFail;
12899 },
12900 else => |e| return e,
12901 };
12902 };
12903
12904 // By-reference captures have some further restrictions which make them easier to emit
12905 if (capture_by_ref) {
12906 const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
12907 const capture_ptr_ty = resolve: {
12908 // By-ref captures of hetereogeneous types are only allowed if all field
12909 // pointer types are peer resolvable to each other.
12910 // We need values to run PTR on, so make a bunch of undef constants.
12911 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
12912 for (field_indices, dummy_captures) |field_idx, *dummy| {
12913 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12914 const field_ptr_ty = try pt.ptrTypeSema(.{
12915 .child = field_ty.toIntern(),
12916 .flags = .{
12917 .is_const = operand_ptr_info.flags.is_const,
12918 .is_volatile = operand_ptr_info.flags.is_volatile,
12919 .address_space = operand_ptr_info.flags.address_space,
12920 .alignment = union_obj.fieldAlign(ip, field_idx),
12921 },
12922 });
12923 dummy.* = try pt.undefRef(field_ptr_ty);
12924 }
12925 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
12926 for (case_srcs, 0..) |*case_src, item_i| {
12927 case_src.* = .{
12928 .base_node_inst = capture_src.base_node_inst,
12929 .offset = .{ .switch_case_item = .{
12930 .switch_node_offset = switch_node_offset,
12931 .case_idx = capture_src.offset.switch_capture.case_idx,
12932 .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
12933 } },
12934 };
12935 }
12936
12937 break :resolve sema.resolvePeerTypes(
12938 case_block,
12939 capture_src,
12940 dummy_captures,
12941 .{ .override = case_srcs },
12942 ) catch |err| switch (err) {
12943 error.AnalysisFail => {
12944 const msg = sema.err orelse return error.AnalysisFail;
12945 try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
12946 try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
12947 return error.AnalysisFail;
12948 },
12949 else => |e| return e,
12950 };
12951 };
12952
12953 if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
12954 if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
12955 const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
12956 return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
12957 }
12958
12959 try sema.requireRuntimeBlock(case_block, operand_src, null);
12960 return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
12961 }
12962
12963 if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
12964 if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
12965 const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
12966 if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
12967 const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
12968 return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
12969 }
12970
12971 try sema.requireRuntimeBlock(case_block, operand_src, null);
12972
12973 if (same_types) {
12974 return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
12975 }
12976
12977 // We may have to emit a switch block which coerces the operand to the capture type.
12978 // If we can, try to avoid that using in-memory coercions.
12979 const first_non_imc = in_mem: {
12980 for (field_indices, 0..) |field_idx, i| {
12981 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
12982 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
12983 break :in_mem i;
12984 }
12985 }
12986 // All fields are in-memory coercible to the resolved type!
12987 // Just take the first field and bitcast the result.
12988 const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
12989 return case_block.addBitCast(capture_ty, uncoerced);
12990 };
12991
12992 // By-val capture with heterogeneous types which are not all in-memory coercible to
12993 // the resolved capture type. We finally have to fall back to the ugly method.
12994
12995 // However, let's first track which operands are in-memory coercible. There may well
12996 // be several, and we can squash all of these cases into the same switch prong using
12997 // a simple bitcast. We'll make this the 'else' prong.
12998
12999 var in_mem_coercible: std.DynamicBitSet = try .initFull(sema.arena, field_indices.len);
13000 in_mem_coercible.unset(first_non_imc);
13001 {
13002 const next = first_non_imc + 1;
13003 for (field_indices[next..], next..) |field_idx, i| {
13004 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
13005 if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
13006 in_mem_coercible.unset(i);
13007 }
13008 }
13531 }13009 }
1353213010
13533 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);13011 const capture_block_inst = try case_block.addInstAsIndex(.{
13534 }13012 .tag = .block,
13535 }13013 .data = .{
13014 .ty_pl = .{
13015 .ty = .fromType(capture_ty),
13016 .payload = undefined, // updated below
13017 },
13018 },
13019 });
1353613020
13537 switch (try sema.resolveInferredErrorSetTy(block, src, operand_ty.toIntern())) {13021 const prong_count = field_indices.len - in_mem_coercible.count();
13538 .anyerror_type => {13022
13539 if (!has_else) {13023 const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
13540 return sema.fail(13024 var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
13541 block,13025 defer cases_extra.deinit();
13542 src,13026
13543 "else prong required when switching on type 'anyerror'",13027 {
13544 .{},13028 // All branch hints are `.none`, so just add zero elems.
13545 );13029 comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
13030 const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
13031 try cases_extra.appendNTimes(0, need_elems);
13546 }13032 }
13547 return .anyerror;
13548 },
13549 else => |err_set_ty_index| else_validation: {
13550 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
13551 var maybe_msg: ?*Zcu.ErrorMsg = null;
13552 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
1355313033
13554 for (error_names.get(ip)) |error_name| {13034 {
13555 if (!seen_errors.contains(error_name) and !has_else) {13035 // Non-bitcast cases
13556 const msg = maybe_msg orelse blk: {13036 var it = in_mem_coercible.iterator(.{ .kind = .unset });
13557 maybe_msg = try sema.errMsg(13037 while (it.next()) |idx| {
13558 src,13038 var coerce_block = case_block.makeSubBlock();
13559 "switch must handle all possibilities",13039 defer coerce_block.instructions.deinit(sema.gpa);
13560 .{},13040
13561 );13041 const case_src: LazySrcLoc = .{
13562 break :blk maybe_msg.?;13042 .base_node_inst = capture_src.base_node_inst,
13043 .offset = .{ .switch_case_item = .{
13044 .switch_node_offset = switch_node_offset,
13045 .case_idx = capture_src.offset.switch_capture.case_idx,
13046 .item_idx = .{ .kind = .single, .value = @intCast(idx) },
13047 } },
13563 };13048 };
1356413049
13565 try sema.errNote(13050 const field_idx = field_indices[idx];
13566 src,13051 const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
13567 msg,13052 const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
13568 "unhandled error value: 'error.{f}'",13053 const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
13569 .{error_name.fmt(ip)},13054 _ = try coerce_block.addBr(capture_block_inst, coerced);
13570 );13055
13056 try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
13057 1 + // `item`, no ranges
13058 coerce_block.instructions.items.len);
13059 cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
13060 .items_len = 1,
13061 .ranges_len = 0,
13062 .body_len = @intCast(coerce_block.instructions.items.len),
13063 }));
13064 cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
13065 cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
13571 }13066 }
13572 }13067 }
13068 const else_body_len = len: {
13069 // 'else' prong uses a bitcast
13070 var coerce_block = case_block.makeSubBlock();
13071 defer coerce_block.instructions.deinit(sema.gpa);
1357313072
13574 if (maybe_msg) |msg| {13073 const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
13575 maybe_msg = null;13074 const first_imc_field_idx = field_indices[first_imc_item_idx];
13576 try sema.addDeclaredHereNote(msg, operand_ty);13075 const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
13577 return sema.failWithOwnedErrorMsg(block, msg);13076 const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
13578 }13077 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
13078 _ = try coerce_block.addBr(capture_block_inst, coerced);
13079
13080 try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
13081 break :len coerce_block.instructions.items.len;
13082 };
1357913083
13580 if (has_else and seen_errors.count() == error_names.len) {13084 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
13581 // In order to enable common patterns for generic code allow simple else bodies13085 cases_extra.items.len +
13582 // else => unreachable,13086 @typeInfo(Air.Block).@"struct".fields.len +
13583 // else => return,13087 1);
13584 // else => |e| return e,13088
13585 // even if all the possible errors were already handled.13089 const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
13586 const tags = sema.code.instructions.items(.tag);13090 try sema.air_instructions.append(sema.gpa, .{
13587 const datas = sema.code.instructions.items(.data);13091 .tag = .switch_br,
13588 for (else_case.body) |else_inst| switch (tags[@intFromEnum(else_inst)]) {13092 .data = .{
13589 .dbg_stmt,13093 .pl_op = .{
13590 .dbg_var_val,13094 .operand = undefined, // set by switch below
13591 .ret_type,13095 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
13592 .as_node,13096 .cases_len = @intCast(prong_count),
13593 .ret_node,13097 .else_body_len = @intCast(else_body_len),
13594 .@"unreachable",13098 }),
13595 .@"defer",
13596 .defer_err_code,
13597 .err_union_code,
13598 .ret_err_value_code,
13599 .save_err_ret_index,
13600 .restore_err_ret_index_unconditional,
13601 .restore_err_ret_index_fn_entry,
13602 .is_non_err,
13603 .ret_is_non_err,
13604 .condbr,
13605 => {},
13606 .extended => switch (datas[@intFromEnum(else_inst)].extended.opcode) {
13607 .restore_err_ret_index => {},
13608 else => break,
13609 },13099 },
13610 else => break,13100 },
13611 } else break :else_validation;13101 });
13102 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
13103
13104 // Set up block body
13105 switch (operand) {
13106 .simple => |s| {
13107 const air_datas = sema.air_instructions.items(.data);
13108 air_datas[switch_br_inst].pl_op.operand = s.cond;
13109 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
13110 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
13111 sema.air_extra.appendAssumeCapacity(switch_br_inst);
13112 },
13113 .loop => {
13114 // The block must first extract the tag from the loaded union.
13115 const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
13116 try sema.air_instructions.append(sema.gpa, .{
13117 .tag = .get_union_tag,
13118 .data = .{ .ty_op = .{
13119 .ty = .fromIntern(union_obj.enum_tag_ty),
13120 .operand = operand_val,
13121 } },
13122 });
13123 const air_datas = sema.air_instructions.items(.data);
13124 air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
13125 air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
13126 sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
13127 sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
13128 sema.air_extra.appendAssumeCapacity(switch_br_inst);
13129 },
13130 }
1361213131
13132 return capture_block_inst.toRef();
13133 },
13134 .error_set => {
13135 if (capture_by_ref) {
13613 return sema.fail(13136 return sema.fail(
13614 block,13137 case_block,
13615 else_case.src,13138 capture_src,
13616 "unreachable else prong; all cases already handled",13139 "error set cannot be captured by reference",
13617 .{},13140 .{},
13618 );13141 );
13619 }13142 }
1362013143
13621 var names: InferredErrorSet.NameMap = .{};13144 if (case_vals.len == 1) {
13622 try names.ensureUnusedCapacity(sema.arena, error_names.len);13145 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable;
13623 for (error_names.get(ip)) |error_name| {13146 const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
13624 if (seen_errors.contains(error_name)) continue;13147 return sema.bitCast(case_block, item_ty, operand_val, operand_src, null);
13148 }
1362513149
13626 names.putAssumeCapacityNoClobber(error_name, {});13150 var names: InferredErrorSet.NameMap = .{};
13151 try names.ensureUnusedCapacity(sema.arena, case_vals.len);
13152 for (case_vals) |err| {
13153 const err_val = sema.resolveConstDefinedValue(case_block, .unneeded, err, undefined) catch unreachable;
13154 names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
13155 }
13156 const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
13157 return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
13158 },
13159 else => {
13160 // In this case the capture value is just the passed-through value of the
13161 // switch condition. It is comptime-known if there is only one item.
13162 if (capture_by_ref) {
13163 return operand_ptr;
13164 } else if (case_vals.len == 1) {
13165 return case_vals[0];
13166 } else {
13167 return operand_val;
13627 }13168 }
13628 // No need to keep the hash map metadata correct; here we
13629 // extract the (sorted) keys only.
13630 return try pt.errorSetFromUnsortedNames(names.keys());
13631 },13169 },
13632 }13170 }
13633 return null;
13634}13171}
1363513172
13636fn validateSwitchRange(13173const ResolvedSwitchItem = struct {
13637 sema: *Sema,13174 ref: Air.Inst.Ref,
13638 block: *Block,13175 val: Value,
13639 range_set: *RangeSet,13176};
13640 first_ref: Zir.Inst.Ref,13177const ResolvedSwitchItemAndExtraIndex = struct { ResolvedSwitchItem, usize };
13641 last_ref: Zir.Inst.Ref,
13642 operand_ty: Type,
13643 item_src: LazySrcLoc,
13644) CompileError![2]Air.Inst.Ref {
13645 const first_src: LazySrcLoc = .{
13646 .base_node_inst = item_src.base_node_inst,
13647 .offset = .{ .switch_case_item_range_first = item_src.offset.switch_case_item },
13648 };
13649 const last_src: LazySrcLoc = .{
13650 .base_node_inst = item_src.base_node_inst,
13651 .offset = .{ .switch_case_item_range_last = item_src.offset.switch_case_item },
13652 };
13653 const first = try sema.resolveSwitchItemVal(block, first_ref, operand_ty, first_src);
13654 const last = try sema.resolveSwitchItemVal(block, last_ref, operand_ty, last_src);
13655 if (try Value.fromInterned(first.val).compareAll(.gt, Value.fromInterned(last.val), operand_ty, sema.pt)) {
13656 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
13657 }
13658 const maybe_prev_src = try range_set.add(first.val, last.val, item_src);
13659 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
13660 return .{ first.ref, last.ref };
13661}
1366213178
13663fn validateSwitchItemInt(13179fn resolveSwitchItem(
13664 sema: *Sema,13180 sema: *Sema,
13665 block: *Block,13181 block: *Block,
13666 range_set: *RangeSet,
13667 item_ref: Zir.Inst.Ref,
13668 operand_ty: Type,
13669 item_src: LazySrcLoc,13182 item_src: LazySrcLoc,
13670) CompileError!Air.Inst.Ref {13183 item_ty: Type,
13671 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13184 item_info: Zir.Inst.SwitchBlock.ItemInfo,
13672 const maybe_prev_src = try range_set.add(item.val, item.val, item_src);13185 extra_index: usize,
13673 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13186 switch_inst: Zir.Inst.Index,
13674 return item.ref;13187 prong_is_comptime_unreach: bool,
13675}13188) CompileError!ResolvedSwitchItemAndExtraIndex {
13189 const pt = sema.pt;
13190 const zcu = pt.zcu;
13191 const ip = &zcu.intern_pool;
13192 const comp = zcu.comp;
13193 const gpa = comp.gpa;
13194 const io = comp.io;
1367613195
13677fn validateSwitchItemEnum(13196 var end = extra_index;
13678 sema: *Sema,13197 const uncoerced: Air.Inst.Ref, const uncoerced_ty: Type = uncoerced: switch (item_info.unwrap()) {
13679 block: *Block,13198 .under => unreachable, // caller must check this before calling us
13680 seen_fields: []?LazySrcLoc,13199 .enum_literal => |str_index| {
13681 range_set: *RangeSet,13200 const zir_str = sema.code.nullTerminatedString(str_index);
13682 item_ref: Zir.Inst.Ref,13201 const name = try ip.getOrPutString(gpa, io, pt.tid, zir_str, .no_embedded_nulls);
13683 operand_ty: Type,13202 const uncoerced = try sema.analyzeDeclLiteral(block, item_src, name, item_ty, false);
13684 item_src: LazySrcLoc,13203 break :uncoerced .{ uncoerced, .enum_literal };
13685) CompileError!Air.Inst.Ref {13204 },
13686 const ip = &sema.pt.zcu.intern_pool;13205 .error_value => |str_index| {
13687 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13206 const zir_str = sema.code.nullTerminatedString(str_index);
13688 const int = ip.indexToKey(item.val).enum_tag.int;13207 const name = try ip.getOrPutString(gpa, io, pt.tid, zir_str, .no_embedded_nulls);
13689 const field_index = ip.loadEnumType(ip.typeOf(item.val)).tagValueIndex(ip, int) orelse {13208 // Make sure there's an error integer value associated with `name`.
13690 const maybe_prev_src = try range_set.add(int, int, item_src);13209 _ = try pt.getErrorValue(name);
13691 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13210 const err_set_ty = try pt.singleErrorSetType(name);
13692 return item.ref;13211 const uncoerced = Air.internedToRef(try pt.intern(.{ .err = .{
13212 .ty = err_set_ty.toIntern(),
13213 .name = name,
13214 } }));
13215 break :uncoerced .{ uncoerced, err_set_ty };
13216 },
13217 .body_len => |body_len| {
13218 const body = sema.code.bodySlice(extra_index, body_len);
13219 end += body.len;
13220
13221 const uncoerced = ref: {
13222 // The result location of item bodies is `.{ .coerce_ty = switch_inst }`.
13223 sema.inst_map.putAssumeCapacity(switch_inst, .fromType(item_ty));
13224 defer assert(sema.inst_map.remove(switch_inst));
13225 const old_comptime_reason = block.comptime_reason;
13226 defer block.comptime_reason = old_comptime_reason;
13227 block.comptime_reason = .{ .reason = .{
13228 .src = item_src,
13229 .r = .{ .simple = .switch_item },
13230 } };
13231 break :ref try sema.resolveInlineBody(block, body, switch_inst);
13232 };
13233 break :uncoerced .{ uncoerced, sema.typeOf(uncoerced) };
13234 },
13693 };13235 };
13694 const maybe_prev_src = seen_fields[field_index];13236 const item_ref: Air.Inst.Ref = item_ref: {
13695 seen_fields[field_index] = item_src;13237 if (item_ty.zigTypeTag(zcu) == .error_set and
13696 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);13238 uncoerced_ty.zigTypeTag(zcu) == .error_set)
13697 return item.ref;13239 {
13698}13240 // We allow prongs with errors which are not part of the error set
1369913241 // being switched on if their prong body is `=> comptime unreachable,`.
13700fn validateSwitchItemError(13242 switch (try sema.coerceInMemoryAllowedErrorSets(block, item_ty, uncoerced_ty, item_src, item_src)) {
13701 sema: *Sema,13243 .ok => if (try sema.resolveValue(uncoerced)) |uncoerced_val| {
13702 block: *Block,13244 break :item_ref try sema.coerceInMemory(uncoerced_val, item_ty);
13703 seen_errors: *SwitchErrorSet,13245 },
13704 item_ref: Zir.Inst.Ref,13246 .missing_error => if (prong_is_comptime_unreach) {
13705 operand_ty: Type,13247 break :item_ref uncoerced;
13706 item_src: LazySrcLoc,13248 },
13707) CompileError!Air.Inst.Ref {13249 .from_anyerror => {},
13708 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13250 else => unreachable,
13709 const error_name = sema.pt.zcu.intern_pool.indexToKey(item.val).err.name;13251 }
13710 const maybe_prev_src = if (try seen_errors.fetchPut(error_name, item_src)) |prev|13252 }
13711 prev.value13253 break :item_ref try sema.coerce(block, item_ty, uncoerced, item_src);
13712 else13254 };
13713 null;13255 const maybe_lazy = try sema.resolveConstDefinedValue(block, item_src, item_ref, .{ .simple = .switch_item });
13714 try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
13715 return item.ref;
13716}
1371713256
13718fn validateSwitchDupe(13257 // We have to resolve lazy values here to avoid false negatives when detecting
13719 sema: *Sema,13258 // duplicate items and comparing items to a comptime-known switch operand.
13720 block: *Block,
13721 maybe_prev_src: ?LazySrcLoc,
13722 item_src: LazySrcLoc,
13723) CompileError!void {
13724 const prev_item_src = maybe_prev_src orelse return;
13725 return sema.failWithOwnedErrorMsg(block, msg: {
13726 const msg = try sema.errMsg(
13727 item_src,
13728 "duplicate switch value",
13729 .{},
13730 );
13731 errdefer msg.destroy(sema.gpa);
13732 try sema.errNote(
13733 prev_item_src,
13734 msg,
13735 "previous value here",
13736 .{},
13737 );
13738 break :msg msg;
13739 });
13740}
1374113259
13742fn validateSwitchItemBool(13260 const val = try sema.resolveLazyValue(maybe_lazy);
13743 sema: *Sema,13261 const ref: Air.Inst.Ref = if (val.toIntern() == maybe_lazy.toIntern())
13744 block: *Block,13262 item_ref
13745 true_count: *u8,13263 else
13746 false_count: *u8,13264 .fromValue(val);
13747 item_ref: Zir.Inst.Ref,13265 return .{ .{ .ref = ref, .val = val }, end };
13748 item_src: LazySrcLoc,
13749) CompileError!Air.Inst.Ref {
13750 const item = try sema.resolveSwitchItemVal(block, item_ref, .bool, item_src);
13751 if (Value.fromInterned(item.val).toBool()) {
13752 true_count.* += 1;
13753 } else {
13754 false_count.* += 1;
13755 }
13756 if (true_count.* > 1 or false_count.* > 1) {
13757 return sema.fail(block, item_src, "duplicate switch value", .{});
13758 }
13759 return item.ref;
13760}13266}
1376113267
13762const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc);13268fn validateSwitchItemOrRange(
13763
13764fn validateSwitchItemSparse(
13765 sema: *Sema,13269 sema: *Sema,
13766 block: *Block,13270 block: *Block,
13767 seen_values: *ValueSrcMap,
13768 item_ref: Zir.Inst.Ref,
13769 operand_ty: Type,
13770 item_src: LazySrcLoc,13271 item_src: LazySrcLoc,
13771) CompileError!Air.Inst.Ref {13272 /// If `opt_last_val` is not `null`, this refers to the first val of a range.
13772 const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);13273 item_val: Value,
13773 const kv = try seen_values.fetchPut(sema.gpa, item.val, item_src) orelse return item.ref;13274 opt_last_val: ?Value,
13774 try sema.validateSwitchDupe(block, kv.value, item_src);13275 item_ty: Type,
13775 unreachable;13276 seen_enum_fields: []?LazySrcLoc,
13776}13277 seen_errors: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, LazySrcLoc),
1377713278 seen_sparse_values: *std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc),
13778fn validateSwitchNoRange(13279 range_set: *RangeSet,
13779 sema: *Sema,13280 true_src: *?LazySrcLoc,
13780 block: *Block,13281 false_src: *?LazySrcLoc,
13781 ranges_len: u32,13282 void_src: *?LazySrcLoc,
13782 operand_ty: Type,
13783 src_node_offset: std.zig.Ast.Node.Offset,
13784) CompileError!void {13283) CompileError!void {
13785 if (ranges_len == 0)13284 const pt = sema.pt;
13786 return;13285 const zcu = pt.zcu;
1378713286 const ip = &zcu.intern_pool;
13788 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });13287 const maybe_prev_src: ?LazySrcLoc = maybe_prev_src: switch (item_ty.zigTypeTag(zcu)) {
13789 const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });13288 .@"union" => unreachable,
1379013289 .@"enum" => {
13791 const msg = msg: {13290 const int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
13792 const msg = try sema.errMsg(13291 if (ip.loadEnumType(item_ty.toIntern()).tagValueIndex(ip, int)) |field_index| {
13793 operand_src,13292 const maybe_prev_src = seen_enum_fields[field_index];
13794 "ranges not allowed when switching on type '{f}'",13293 seen_enum_fields[field_index] = item_src;
13795 .{operand_ty.fmt(sema.pt)},13294 break :maybe_prev_src maybe_prev_src;
13796 );13295 } else {
13797 errdefer msg.destroy(sema.gpa);13296 break :maybe_prev_src try range_set.add(sema.arena, .{
13798 try sema.errNote(13297 .first = .fromInterned(int),
13799 range_src,13298 .last = .fromInterned(int),
13800 msg,13299 .src = item_src,
13801 "range here",13300 }, .fromInterned(ip.typeOf(int)), zcu);
13802 .{},13301 }
13803 );13302 },
13804 break :msg msg;13303 .error_set => {
13304 const error_name = ip.indexToKey(item_val.toIntern()).err.name;
13305 break :maybe_prev_src if (seen_errors.fetchPutAssumeCapacity(error_name, item_src)) |prev|
13306 prev.value
13307 else
13308 null;
13309 },
13310 .int, .comptime_int => {
13311 if (opt_last_val) |last_val| {
13312 const first_val = item_val;
13313 if (try first_val.compareAll(.gt, last_val, item_ty, pt)) {
13314 return sema.fail(block, item_src, "range start value is greater than the end value", .{});
13315 }
13316 break :maybe_prev_src range_set.addAssumeCapacity(.{
13317 .first = first_val,
13318 .last = last_val,
13319 .src = item_src,
13320 }, item_ty, zcu);
13321 } else {
13322 break :maybe_prev_src range_set.addAssumeCapacity(.{
13323 .first = item_val,
13324 .last = item_val,
13325 .src = item_src,
13326 }, item_ty, zcu);
13327 }
13328 },
13329 .enum_literal, .@"fn", .pointer, .type => {
13330 break :maybe_prev_src if (seen_sparse_values.fetchPutAssumeCapacity(item_val.toIntern(), item_src)) |prev|
13331 prev.value
13332 else
13333 null;
13334 },
13335 .bool => {
13336 if (item_val.toBool()) {
13337 if (true_src.*) |prev_src| break :maybe_prev_src prev_src;
13338 true_src.* = item_src;
13339 } else {
13340 if (false_src.*) |prev_src| break :maybe_prev_src prev_src;
13341 false_src.* = item_src;
13342 }
13343 break :maybe_prev_src null;
13344 },
13345 .void => {
13346 if (void_src.*) |prev_src| break :maybe_prev_src prev_src;
13347 void_src.* = item_src;
13348 break :maybe_prev_src null;
13349 },
13350 else => unreachable, // should have already checked for invalid types
13805 };13351 };
13806 return sema.failWithOwnedErrorMsg(block, msg);13352 if (maybe_prev_src) |prev_src| {
13353 return sema.failWithOwnedErrorMsg(block, msg: {
13354 const msg = try sema.errMsg(
13355 item_src,
13356 "duplicate switch value",
13357 .{},
13358 );
13359 errdefer msg.destroy(sema.gpa);
13360 try sema.errNote(
13361 prev_src,
13362 msg,
13363 "previous value here",
13364 .{},
13365 );
13366 break :msg msg;
13367 });
13368 }
13807}13369}
1380813370
13809fn maybeErrorUnwrap(13371fn maybeErrorUnwrap(
...@@ -18687,14 +18249,13 @@ fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!...@@ -18687,14 +18249,13 @@ fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!
18687 break :msg msg;18249 break :msg msg;
18688 });18250 });
18689 }18251 }
18690 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union);18252 if (try sema.resolveIsNonErrVal(parent_block, operand_src, err_union)) |is_non_err_val| {
18691 if (is_non_err != .none) {
18692 // We can propagate `.cold` hints from this branch since it's comptime-known18253 // We can propagate `.cold` hints from this branch since it's comptime-known
18693 // to be taken from the parent branch.18254 // to be taken from the parent branch.
18694 const parent_hint = sema.branch_hint;18255 const parent_hint = sema.branch_hint;
18695 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;18256 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1869618257
18697 const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?;18258 if (is_non_err_val.isUndef(zcu)) return sema.failWithUseOfUndef(parent_block, operand_src, null);
18698 if (is_non_err_val.toBool()) {18259 if (is_non_err_val.toBool()) {
18699 return sema.analyzeErrUnionPayload(parent_block, src, err_union_ty, err_union, operand_src, false);18260 return sema.analyzeErrUnionPayload(parent_block, src, err_union_ty, err_union, operand_src, false);
18700 }18261 }
...@@ -18751,14 +18312,13 @@ fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErr...@@ -18751,14 +18312,13 @@ fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErr
18751 break :msg msg;18312 break :msg msg;
18752 });18313 });
18753 }18314 }
18754 const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union);18315 if (try sema.resolveIsNonErrVal(parent_block, operand_src, err_union)) |is_non_err_val| {
18755 if (is_non_err != .none) {
18756 // We can propagate `.cold` hints from this branch since it's comptime-known18316 // We can propagate `.cold` hints from this branch since it's comptime-known
18757 // to be taken from the parent branch.18317 // to be taken from the parent branch.
18758 const parent_hint = sema.branch_hint;18318 const parent_hint = sema.branch_hint;
18759 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;18319 defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
1876018320
18761 const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?;18321 if (is_non_err_val.isUndef(zcu)) return sema.failWithUseOfUndef(parent_block, operand_src, null);
18762 if (is_non_err_val.toBool()) {18322 if (is_non_err_val.toBool()) {
18763 return sema.analyzeErrUnionPayloadPtr(parent_block, src, operand, false, false);18323 return sema.analyzeErrUnionPayloadPtr(parent_block, src, operand, false, false);
18764 }18324 }
...@@ -31798,58 +31358,73 @@ fn analyzeIsNull(...@@ -31798,58 +31358,73 @@ fn analyzeIsNull(
31798 return block.addUnOp(air_tag, operand);31358 return block.addUnOp(air_tag, operand);
31799}31359}
3180031360
31801fn analyzePtrIsNonErrComptimeOnly(31361fn resolvePtrIsNonErrVal(
31802 sema: *Sema,31362 sema: *Sema,
31803 block: *Block,31363 block: *Block,
31804 src: LazySrcLoc,31364 src: LazySrcLoc,
31805 operand: Air.Inst.Ref,31365 operand: Air.Inst.Ref,
31806) CompileError!Air.Inst.Ref {31366) CompileError!?Value {
31807 const pt = sema.pt;31367 const pt = sema.pt;
31808 const zcu = pt.zcu;31368 const zcu = pt.zcu;
31809 const ptr_ty = sema.typeOf(operand);31369 const ptr_ty = sema.typeOf(operand);
31810 assert(ptr_ty.zigTypeTag(zcu) == .pointer);31370 assert(ptr_ty.zigTypeTag(zcu) == .pointer);
31811 const child_ty = ptr_ty.childType(zcu);31371 const child_ty = ptr_ty.childType(zcu);
3181231372
31813 const child_tag = child_ty.zigTypeTag(zcu);31373 if (try sema.resolveIsNonErrFromType(block, src, child_ty)) |res| {
31814 if (child_tag != .error_set and child_tag != .error_union) return .bool_true;31374 return res;
31815 if (child_tag == .error_set) return .bool_false;31375 }
31816 assert(child_tag == .error_union);31376 assert(child_ty.zigTypeTag(zcu) == .error_union);
3181731377
31818 _ = block;31378 if (try sema.resolveValue(operand)) |eu_ptr_val| {
31819 _ = src;31379 if (eu_ptr_val.isUndef(zcu)) return .undef_bool;
31380 if (try sema.pointerDeref(block, src, eu_ptr_val, ptr_ty)) |err_union| {
31381 if (err_union.isUndef(zcu)) return .undef_bool;
31382 return .makeBool(err_union.getErrorName(zcu) == .none);
31383 }
31384 }
3182031385
31821 return .none;31386 return null;
31822}31387}
3182331388
31824fn analyzeIsNonErrComptimeOnly(31389fn resolveIsNonErrVal(
31825 sema: *Sema,31390 sema: *Sema,
31826 block: *Block,31391 block: *Block,
31827 src: LazySrcLoc,31392 src: LazySrcLoc,
31828 operand: Air.Inst.Ref,31393 operand: Air.Inst.Ref,
31829) CompileError!Air.Inst.Ref {31394) CompileError!?Value {
31395 const zcu = sema.pt.zcu;
31396 if (try sema.resolveIsNonErrFromType(block, src, sema.typeOf(operand))) |res| {
31397 return res;
31398 }
31399 assert(sema.typeOf(operand).zigTypeTag(zcu) == .error_union);
31400
31401 if (try sema.resolveValue(operand)) |err_union| {
31402 if (err_union.isUndef(zcu)) return .undef_bool;
31403 return .makeBool(err_union.getErrorName(zcu) == .none);
31404 }
31405
31406 return null;
31407}
31408
31409fn resolveIsNonErrFromType(
31410 sema: *Sema,
31411 block: *Block,
31412 src: LazySrcLoc,
31413 operand_ty: Type,
31414) CompileError!?Value {
31830 const pt = sema.pt;31415 const pt = sema.pt;
31831 const zcu = pt.zcu;31416 const zcu = pt.zcu;
31832 const ip = &zcu.intern_pool;31417 const ip = &zcu.intern_pool;
31833 const operand_ty = sema.typeOf(operand);
31834 const ot = operand_ty.zigTypeTag(zcu);31418 const ot = operand_ty.zigTypeTag(zcu);
31835 if (ot != .error_set and ot != .error_union) return .bool_true;31419 if (ot != .error_set and ot != .error_union) return .true;
31836 if (ot == .error_set) return .bool_false;31420 if (ot == .error_set) return .false;
31837 assert(ot == .error_union);31421 assert(ot == .error_union);
3183831422
31839 const payload_ty = operand_ty.errorUnionPayload(zcu);31423 const payload_ty = operand_ty.errorUnionPayload(zcu);
31840 if (payload_ty.zigTypeTag(zcu) == .noreturn) {31424 if (payload_ty.zigTypeTag(zcu) == .noreturn) {
31841 return .bool_false;31425 return .false;
31842 }
31843
31844 if (operand == .undef) {
31845 return .undef_bool;
31846 } else if (@intFromEnum(operand) < InternPool.static_len) {
31847 // None of the ref tags can be errors.
31848 return .bool_true;
31849 }31426 }
3185031427
31851 const maybe_operand_val = try sema.resolveValue(operand);
31852
31853 // exception if the error union error set is known to be empty,31428 // exception if the error union error set is known to be empty,
31854 // we allow the comparison but always make it comptime-known.31429 // we allow the comparison but always make it comptime-known.
31855 const set_ty = ip.errorUnionSet(operand_ty.toIntern());31430 const set_ty = ip.errorUnionSet(operand_ty.toIntern());
...@@ -31865,26 +31440,23 @@ fn analyzeIsNonErrComptimeOnly(...@@ -31865,26 +31440,23 @@ fn analyzeIsNonErrComptimeOnly(
31865 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,31440 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
31866 }31441 }
3186731442
31868 if (maybe_operand_val != null) break :blk;31443 if (ies.errors.count() != 0) return null;
31869
31870 // Try to avoid resolving inferred error set if possible.
31871 if (ies.errors.count() != 0) return .none;
31872 switch (ies.resolved) {31444 switch (ies.resolved) {
31873 .anyerror_type => return .none,31445 .anyerror_type => return null,
31874 .none => {},31446 .none => {},
31875 else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {31447 else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {
31876 0 => return .bool_true,31448 0 => return .true,
31877 else => return .none,31449 else => return null,
31878 },31450 },
31879 }31451 }
31880 // We do not have a comptime answer because this inferred error31452 // We do not have a comptime answer because this inferred error
31881 // set is not resolved, and an instruction later in this function31453 // set is not resolved, and an instruction later in this function
31882 // body may or may not cause an error to be added to this set.31454 // body may or may not cause an error to be added to this set.
31883 return .none;31455 return null;
31884 },31456 },
31885 else => switch (ip.indexToKey(set_ty)) {31457 else => switch (ip.indexToKey(set_ty)) {
31886 .error_set_type => |error_set_type| {31458 .error_set_type => |error_set_type| {
31887 if (error_set_type.names.len == 0) return .bool_true;31459 if (error_set_type.names.len == 0) return .true;
31888 },31460 },
31889 .inferred_error_set_type => |func_index| blk: {31461 .inferred_error_set_type => |func_index| blk: {
31890 // If the error set is empty, we must return a comptime true or false.31462 // If the error set is empty, we must return a comptime true or false.
...@@ -31896,39 +31468,35 @@ fn analyzeIsNonErrComptimeOnly(...@@ -31896,39 +31468,35 @@ fn analyzeIsNonErrComptimeOnly(
31896 .none => {},31468 .none => {},
31897 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,31469 else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
31898 }31470 }
31899 if (maybe_operand_val != null) break :blk;
31900 if (sema.fn_ret_ty_ies) |ies| {31471 if (sema.fn_ret_ty_ies) |ies| {
31901 if (ies.func == func_index) {31472 if (ies.func == func_index) {
31902 // Try to avoid resolving inferred error set if possible.31473 // Try to avoid resolving inferred error set if possible.
31903 if (ies.errors.count() != 0) return .none;31474 if (ies.errors.count() != 0) return null;
31904 switch (ies.resolved) {31475 switch (ies.resolved) {
31905 .anyerror_type => return .none,31476 .anyerror_type => return null,
31906 .none => {},31477 .none => {},
31907 else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {31478 else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {
31908 0 => return .bool_true,31479 0 => return .true,
31909 else => return .none,31480 else => return null,
31910 },31481 },
31911 }31482 }
31912 // We do not have a comptime answer because this inferred error31483 // We do not have a comptime answer because this inferred error
31913 // set is not resolved, and an instruction later in this function31484 // set is not resolved, and an instruction later in this function
31914 // body may or may not cause an error to be added to this set.31485 // body may or may not cause an error to be added to this set.
31915 return .none;31486 return null;
31916 }31487 }
31917 }31488 }
31918 const resolved_ty = try sema.resolveInferredErrorSet(block, src, set_ty);31489 const resolved_ty = try sema.resolveInferredErrorSet(block, src, set_ty);
31919 if (resolved_ty == .anyerror_type)31490 if (resolved_ty == .anyerror_type)
31920 break :blk;31491 break :blk;
31921 if (ip.indexToKey(resolved_ty).error_set_type.names.len == 0)31492 if (ip.indexToKey(resolved_ty).error_set_type.names.len == 0)
31922 return .bool_true;31493 return .true;
31923 },31494 },
31924 else => unreachable,31495 else => unreachable,
31925 },31496 },
31926 }31497 }
3192731498
31928 if (maybe_operand_val) |err_union| {31499 return null;
31929 return if (err_union.isUndef(zcu)) .undef_bool else if (err_union.getErrorName(zcu) == .none) .bool_true else .bool_false;
31930 }
31931 return .none;
31932}31500}
3193331501
31934fn analyzeIsNonErr(31502fn analyzeIsNonErr(
...@@ -31937,12 +31505,10 @@ fn analyzeIsNonErr(...@@ -31937,12 +31505,10 @@ fn analyzeIsNonErr(
31937 src: LazySrcLoc,31505 src: LazySrcLoc,
31938 operand: Air.Inst.Ref,31506 operand: Air.Inst.Ref,
31939) CompileError!Air.Inst.Ref {31507) CompileError!Air.Inst.Ref {
31940 const result = try sema.analyzeIsNonErrComptimeOnly(block, src, operand);31508 if (try sema.resolveIsNonErrVal(block, src, operand)) |val| {
31941 if (result == .none) {31509 return .fromValue(val);
31942 try sema.requireRuntimeBlock(block, src, null);
31943 return block.addUnOp(.is_non_err, operand);
31944 } else {31510 } else {
31945 return result;31511 return block.addUnOp(.is_non_err, operand);
31946 }31512 }
31947}31513}
3194831514
...@@ -31952,12 +31518,10 @@ fn analyzePtrIsNonErr(...@@ -31952,12 +31518,10 @@ fn analyzePtrIsNonErr(
31952 src: LazySrcLoc,31518 src: LazySrcLoc,
31953 operand: Air.Inst.Ref,31519 operand: Air.Inst.Ref,
31954) CompileError!Air.Inst.Ref {31520) CompileError!Air.Inst.Ref {
31955 const result = try sema.analyzePtrIsNonErrComptimeOnly(block, src, operand);31521 if (try sema.resolvePtrIsNonErrVal(block, src, operand)) |val| {
31956 if (result == .none) {31522 return .fromValue(val);
31957 try sema.requireRuntimeBlock(block, src, null);
31958 return block.addUnOp(.is_non_err_ptr, operand);
31959 } else {31523 } else {
31960 return result;31524 return block.addUnOp(.is_non_err_ptr, operand);
31961 }31525 }
31962}31526}
3196331527
src/Type.zig+15-15
...@@ -1933,12 +1933,12 @@ pub fn isPtrLikeOptional(ty: Type, zcu: *const Zcu) bool {...@@ -1933,12 +1933,12 @@ pub fn isPtrLikeOptional(ty: Type, zcu: *const Zcu) bool {
1933 };1933 };
1934}1934}
19351935
1936/// For *[N]T, returns [N]T.1936/// For `*[N]T`, returns `[N]T`.
1937/// For *T, returns T.1937/// For `*T`, returns `T`.
1938/// For [*]T, returns T.1938/// For `[*]T`, returns `T`.
1939/// For @Vector(N, T), returns T.1939/// For `@Vector(N, T)`, returns `T`.
1940/// For [N]T, returns T.1940/// For `[N]T`, returns `T`.
1941/// For ?T, returns T.1941/// For `?T`, returns `T`.
1942pub fn childType(ty: Type, zcu: *const Zcu) Type {1942pub fn childType(ty: Type, zcu: *const Zcu) Type {
1943 return childTypeIp(ty, &zcu.intern_pool);1943 return childTypeIp(ty, &zcu.intern_pool);
1944}1944}
...@@ -1947,15 +1947,15 @@ pub fn childTypeIp(ty: Type, ip: *const InternPool) Type {...@@ -1947,15 +1947,15 @@ pub fn childTypeIp(ty: Type, ip: *const InternPool) Type {
1947 return Type.fromInterned(ip.childType(ty.toIntern()));1947 return Type.fromInterned(ip.childType(ty.toIntern()));
1948}1948}
19491949
1950/// For *[N]T, returns T.1950/// For `*[N]T`, returns `T`.
1951/// For ?*T, returns T.1951/// For `?*T`, returns `T`.
1952/// For ?*[N]T, returns T.1952/// For `?*[N]T`, returns `T`.
1953/// For ?[*]T, returns T.1953/// For `?[*]T`, returns `T`.
1954/// For *T, returns T.1954/// For `*T`, returns `T`.
1955/// For [*]T, returns T.1955/// For `[*]T`, returns `T`.
1956/// For [N]T, returns T.1956/// For `[N]T`, returns `T`.
1957/// For []T, returns T.1957/// For `[]T`, returns `T`.
1958/// For anyframe->T, returns T.1958/// For `anyframe->T`, returns `T`.
1959pub fn elemType2(ty: Type, zcu: *const Zcu) Type {1959pub fn elemType2(ty: Type, zcu: *const Zcu) Type {
1960 return switch (zcu.intern_pool.indexToKey(ty.toIntern())) {1960 return switch (zcu.intern_pool.indexToKey(ty.toIntern())) {
1961 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {1961 .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
src/Zcu.zig+21-68
...@@ -878,7 +878,7 @@ pub const Namespace = struct {...@@ -878,7 +878,7 @@ pub const Namespace = struct {
878 ns: Namespace,878 ns: Namespace,
879 zcu: *Zcu,879 zcu: *Zcu,
880 name: InternPool.NullTerminatedString,880 name: InternPool.NullTerminatedString,
881 writer: anytype,881 writer: *Writer,
882 ) @TypeOf(writer).Error!void {882 ) @TypeOf(writer).Error!void {
883 const sep: u8 = if (ns.parent.unwrap()) |parent| sep: {883 const sep: u8 = if (ns.parent.unwrap()) |parent| sep: {
884 try zcu.namespacePtr(parent).renderFullyQualifiedDebugName(884 try zcu.namespacePtr(parent).renderFullyQualifiedDebugName(
...@@ -1125,7 +1125,7 @@ pub const File = struct {...@@ -1125,7 +1125,7 @@ pub const File = struct {
1125 return file.sub_file_path.len - ext.len;1125 return file.sub_file_path.len - ext.len;
1126 }1126 }
11271127
1128 pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {1128 pub fn renderFullyQualifiedName(file: File, writer: *Writer) !void {
1129 // Convert all the slashes into dots and truncate the extension.1129 // Convert all the slashes into dots and truncate the extension.
1130 const ext = std.fs.path.extension(file.sub_file_path);1130 const ext = std.fs.path.extension(file.sub_file_path);
1131 const noext = file.sub_file_path[0 .. file.sub_file_path.len - ext.len];1131 const noext = file.sub_file_path[0 .. file.sub_file_path.len - ext.len];
...@@ -1135,7 +1135,7 @@ pub const File = struct {...@@ -1135,7 +1135,7 @@ pub const File = struct {
1135 };1135 };
1136 }1136 }
11371137
1138 pub fn renderFullyQualifiedDebugName(file: File, writer: anytype) !void {1138 pub fn renderFullyQualifiedDebugName(file: File, writer: *Writer) !void {
1139 for (file.sub_file_path) |byte| switch (byte) {1139 for (file.sub_file_path) |byte| switch (byte) {
1140 '/', '\\' => try writer.writeByte('/'),1140 '/', '\\' => try writer.writeByte('/'),
1141 else => try writer.writeByte(byte),1141 else => try writer.writeByte(byte),
...@@ -1742,27 +1742,6 @@ pub const SrcLoc = struct {...@@ -1742,27 +1742,6 @@ pub const SrcLoc = struct {
1742 } else unreachable;1742 } else unreachable;
1743 },1743 },
17441744
1745 .node_offset_switch_under_prong => |node_off| {
1746 const tree = try src_loc.file_scope.getTree(zcu);
1747 const switch_node = node_off.toAbsolute(src_loc.base_node);
1748 _, const extra_index = tree.nodeData(switch_node).node_and_extra;
1749 const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
1750 for (case_nodes) |case_node| {
1751 const case = tree.fullSwitchCase(case_node).?;
1752 for (case.ast.values) |val| {
1753 if (tree.nodeTag(val) == .identifier and
1754 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
1755 {
1756 return tree.tokensToSpan(
1757 tree.firstToken(case_node),
1758 tree.lastToken(case_node),
1759 tree.nodeMainToken(val),
1760 );
1761 }
1762 }
1763 } else unreachable;
1764 },
1765
1766 .node_offset_switch_range => |node_off| {1745 .node_offset_switch_range => |node_off| {
1767 const tree = try src_loc.file_scope.getTree(zcu);1746 const tree = try src_loc.file_scope.getTree(zcu);
1768 const switch_node = node_off.toAbsolute(src_loc.base_node);1747 const switch_node = node_off.toAbsolute(src_loc.base_node);
...@@ -2176,34 +2155,22 @@ pub const SrcLoc = struct {...@@ -2176,34 +2155,22 @@ pub const SrcLoc = struct {
21762155
2177 var multi_i: u32 = 0;2156 var multi_i: u32 = 0;
2178 var scalar_i: u32 = 0;2157 var scalar_i: u32 = 0;
2179 var underscore_node: Ast.Node.OptionalIndex = .none;2158 const case: Ast.full.SwitchCase = case: for (case_nodes) |case_node| {
2180 const case = case: for (case_nodes) |case_node| {
2181 const case = tree.fullSwitchCase(case_node).?;2159 const case = tree.fullSwitchCase(case_node).?;
2182 if (case.ast.values.len == 0) {2160 if (case.ast.values.len == 0) {
2183 if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_else) {2161 if (want_case_idx == Zir.UnwrappedSwitchBlock.Case.Index.@"else") {
2184 break :case case;2162 break :case case;
2185 }2163 }
2186 continue :case;2164 continue :case;
2187 }2165 }
2188 if (underscore_node == .none) for (case.ast.values) |val_node| {
2189 if (tree.nodeTag(val_node) == .identifier and
2190 mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val_node)), "_"))
2191 {
2192 underscore_node = val_node.toOptional();
2193 if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_under) {
2194 break :case case;
2195 }
2196 continue :case;
2197 }
2198 };
21992166
2200 const is_multi = case.ast.values.len != 1 or2167 const is_multi = case.ast.values.len != 1 or
2201 tree.nodeTag(case.ast.values[0]) == .switch_range;2168 tree.nodeTag(case.ast.values[0]) == .switch_range;
22022169
2203 switch (want_case_idx.kind) {2170 switch (want_case_idx.kind) {
2204 .scalar => if (!is_multi and want_case_idx.index == scalar_i)2171 .scalar => if (!is_multi and want_case_idx.value == scalar_i)
2205 break :case case,2172 break :case case,
2206 .multi => if (is_multi and want_case_idx.index == multi_i)2173 .multi => if (is_multi and want_case_idx.value == multi_i)
2207 break :case case,2174 break :case case,
2208 }2175 }
22092176
...@@ -2214,12 +2181,12 @@ pub const SrcLoc = struct {...@@ -2214,12 +2181,12 @@ pub const SrcLoc = struct {
2214 }2181 }
2215 } else unreachable;2182 } else unreachable;
22162183
2217 const want_item = switch (src_loc.lazy) {2184 const want_item_idx = switch (src_loc.lazy) {
2218 .switch_case_item,2185 .switch_case_item,
2219 .switch_case_item_range_first,2186 .switch_case_item_range_first,
2220 .switch_case_item_range_last,2187 .switch_case_item_range_last,
2221 => |x| item_idx: {2188 => |x| item_idx: {
2222 assert(want_case_idx != LazySrcLoc.Offset.SwitchCaseIndex.special_else);2189 assert(want_case_idx != Zir.UnwrappedSwitchBlock.Case.Index.@"else");
2223 break :item_idx x.item_idx;2190 break :item_idx x.item_idx;
2224 },2191 },
2225 .switch_capture, .switch_tag_capture => {2192 .switch_capture, .switch_tag_capture => {
...@@ -2242,16 +2209,14 @@ pub const SrcLoc = struct {...@@ -2242,16 +2209,14 @@ pub const SrcLoc = struct {
2242 else => unreachable,2209 else => unreachable,
2243 };2210 };
22442211
2245 switch (want_item.kind) {2212 switch (want_item_idx.kind) {
2246 .single => {2213 .single => {
2247 var item_i: u32 = 0;2214 var item_i: u32 = 0;
2248 for (case.ast.values) |item_node| {2215 for (case.ast.values) |item_node| {
2249 if (item_node.toOptional() == underscore_node or2216 if (tree.nodeTag(item_node) == .switch_range) {
2250 tree.nodeTag(item_node) == .switch_range)
2251 {
2252 continue;2217 continue;
2253 }2218 }
2254 if (item_i != want_item.index) {2219 if (item_i != want_item_idx.value) {
2255 item_i += 1;2220 item_i += 1;
2256 continue;2221 continue;
2257 }2222 }
...@@ -2264,7 +2229,7 @@ pub const SrcLoc = struct {...@@ -2264,7 +2229,7 @@ pub const SrcLoc = struct {
2264 if (tree.nodeTag(item_node) != .switch_range) {2229 if (tree.nodeTag(item_node) != .switch_range) {
2265 continue;2230 continue;
2266 }2231 }
2267 if (range_i != want_item.index) {2232 if (range_i != want_item_idx.value) {
2268 range_i += 1;2233 range_i += 1;
2269 continue;2234 continue;
2270 }2235 }
...@@ -2446,10 +2411,6 @@ pub const LazySrcLoc = struct {...@@ -2446,10 +2411,6 @@ pub const LazySrcLoc = struct {
2446 /// by taking this AST node index offset from the containing base node,2411 /// by taking this AST node index offset from the containing base node,
2447 /// which points to a switch expression AST node. Next, navigate to the else prong.2412 /// which points to a switch expression AST node. Next, navigate to the else prong.
2448 node_offset_switch_else_prong: Ast.Node.Offset,2413 node_offset_switch_else_prong: Ast.Node.Offset,
2449 /// The source location points to the `_` prong of a switch expression, found
2450 /// by taking this AST node index offset from the containing base node,
2451 /// which points to a switch expression AST node. Next, navigate to the `_` prong.
2452 node_offset_switch_under_prong: Ast.Node.Offset,
2453 /// The source location points to all the ranges of a switch expression, found2414 /// The source location points to all the ranges of a switch expression, found
2454 /// by taking this AST node index offset from the containing base node,2415 /// by taking this AST node index offset from the containing base node,
2455 /// which points to a switch expression AST node. Next, navigate to any of the2416 /// which points to a switch expression AST node. Next, navigate to any of the
...@@ -2642,29 +2603,21 @@ pub const LazySrcLoc = struct {...@@ -2642,29 +2603,21 @@ pub const LazySrcLoc = struct {
2642 /// The offset of the switch AST node.2603 /// The offset of the switch AST node.
2643 switch_node_offset: Ast.Node.Offset,2604 switch_node_offset: Ast.Node.Offset,
2644 /// The index of the case to point to within this switch.2605 /// The index of the case to point to within this switch.
2645 case_idx: SwitchCaseIndex,2606 case_idx: Zir.UnwrappedSwitchBlock.Case.Index,
2646 /// The index of the item to point to within this case.2607 /// The index of the item to point to within this case.
2647 item_idx: SwitchItemIndex,2608 item_idx: SwitchItem.Index,
2609
2610 pub const Index = packed struct(u32) {
2611 kind: enum(u1) { single, range },
2612 value: u31,
2613 };
2648 };2614 };
26492615
2650 pub const SwitchCapture = struct {2616 pub const SwitchCapture = struct {
2651 /// The offset of the switch AST node.2617 /// The offset of the switch AST node.
2652 switch_node_offset: Ast.Node.Offset,2618 switch_node_offset: Ast.Node.Offset,
2653 /// The index of the case whose capture to point to.2619 /// The index of the case whose capture to point to.
2654 case_idx: SwitchCaseIndex,2620 case_idx: Zir.UnwrappedSwitchBlock.Case.Index,
2655 };
2656
2657 pub const SwitchCaseIndex = packed struct(u32) {
2658 kind: enum(u1) { scalar, multi },
2659 index: u31,
2660
2661 pub const special_else: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32)));
2662 pub const special_under: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32) - 1));
2663 };
2664
2665 pub const SwitchItemIndex = packed struct(u32) {
2666 kind: enum(u1) { single, range },
2667 index: u31,
2668 };2621 };
26692622
2670 pub const ArrayCat = struct {2623 pub const ArrayCat = struct {
src/codegen/llvm.zig+6-2
...@@ -6432,7 +6432,7 @@ pub const FuncGen = struct {...@@ -6432,7 +6432,7 @@ pub const FuncGen = struct {
64326432
6433 // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense.6433 // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense.
6434 // If they are, then we will construct a jump table.6434 // If they are, then we will construct a jump table.
6435 const min, const max = self.switchCaseItemRange(switch_br);6435 const min, const max = self.switchCaseItemRange(switch_br) orelse break :jmp_table null;
6436 const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null;6436 const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null;
6437 const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null;6437 const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null;
6438 const table_len = max_int - min_int + 1;6438 const table_len = max_int - min_int + 1;
...@@ -6595,7 +6595,7 @@ pub const FuncGen = struct {...@@ -6595,7 +6595,7 @@ pub const FuncGen = struct {
6595 }6595 }
6596 }6596 }
65976597
6598 fn switchCaseItemRange(self: *FuncGen, switch_br: Air.UnwrappedSwitch) [2]Value {6598 fn switchCaseItemRange(self: *FuncGen, switch_br: Air.UnwrappedSwitch) ?[2]Value {
6599 const zcu = self.ng.pt.zcu;6599 const zcu = self.ng.pt.zcu;
6600 var it = switch_br.iterateCases();6600 var it = switch_br.iterateCases();
6601 var min: ?Value = null;6601 var min: ?Value = null;
...@@ -6619,6 +6619,10 @@ pub const FuncGen = struct {...@@ -6619,6 +6619,10 @@ pub const FuncGen = struct {
6619 if (high) max = vals[1];6619 if (high) max = vals[1];
6620 }6620 }
6621 }6621 }
6622 if (min == null) {
6623 assert(max == null);
6624 return null;
6625 }
6622 return .{ min.?, max.? };6626 return .{ min.?, max.? };
6623 }6627 }
66246628
src/codegen/wasm/CodeGen.zig+4-1
...@@ -4114,7 +4114,10 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode, op_kind...@@ -4114,7 +4114,10 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode, op_kind
4114 const zcu = cg.pt.zcu;4114 const zcu = cg.pt.zcu;
4115 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;4115 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4116 const operand = try cg.resolveInst(un_op);4116 const operand = try cg.resolveInst(un_op);
4117 const err_union_ty = cg.typeOf(un_op);4117 const err_union_ty = switch (op_kind) {
4118 .value => cg.typeOf(un_op),
4119 .ptr => cg.typeOf(un_op).childType(zcu),
4120 };
4118 const pl_ty = err_union_ty.errorUnionPayload(zcu);4121 const pl_ty = err_union_ty.errorUnionPayload(zcu);
41194122
4120 const result: WValue = result: {4123 const result: WValue = result: {
src/print_zir.zig+104-287
...@@ -447,10 +447,9 @@ const Writer = struct {...@@ -447,10 +447,9 @@ const Writer = struct {
447447
448 .switch_block,448 .switch_block,
449 .switch_block_ref,449 .switch_block_ref,
450 .switch_block_err_union,
450 => try self.writeSwitchBlock(stream, inst),451 => try self.writeSwitchBlock(stream, inst),
451452
452 .switch_block_err_union => try self.writeSwitchBlockErrUnion(stream, inst),
453
454 .field_ptr_load,453 .field_ptr_load,
455 .field_ptr,454 .field_ptr,
456 .decl_literal,455 .decl_literal,
...@@ -1987,322 +1986,140 @@ const Writer = struct {...@@ -1987,322 +1986,140 @@ const Writer = struct {
1987 try self.writeSrcNode(stream, inst_data.src_node);1986 try self.writeSrcNode(stream, inst_data.src_node);
1988 }1987 }
19891988
1990 fn writeSwitchBlockErrUnion(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {1989 fn writeSwitchBlock(
1991 const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;1990 self: *Writer,
1992 const extra = self.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);1991 stream: *std.Io.Writer,
19931992 inst: Zir.Inst.Index,
1994 var extra_index: usize = extra.end;1993 ) !void {
19951994 const zir_switch = self.code.getSwitchBlock(inst);
1996 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {1995 var extra_index = zir_switch.end;
1997 const multi_cases_len = self.code.extra[extra_index];
1998 extra_index += 1;
1999 break :blk multi_cases_len;
2000 } else 0;
2001
2002 const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {
2003 const tag_capture_inst = self.code.extra[extra_index];
2004 extra_index += 1;
2005 break :blk @enumFromInt(tag_capture_inst);
2006 } else undefined;
2007
2008 try self.writeInstRef(stream, extra.data.operand);
20091996
2010 if (extra.data.bits.any_uses_err_capture) {1997 try self.writeInstRef(stream, zir_switch.main_operand);
2011 try stream.writeAll(", err_capture=");
2012 try self.writeInstIndex(stream, err_capture_inst);
2013 }
20141998
2015 self.indent += 2;1999 self.indent += 2;
20162000
2017 {2001 if (zir_switch.non_err_case) |non_err_case| {
2018 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));2002 if (non_err_case.operand_is_ref) try stream.writeAll(" ref");
2019 extra_index += 1;
2020
2021 assert(!info.is_inline);
2022 const body = self.code.bodySlice(extra_index, info.body_len);
2023 extra_index += body.len;
20242003
2025 try stream.writeAll(",\n");2004 try stream.writeAll(",\n");
2026 try stream.splatByteAll(' ', self.indent);2005 try stream.splatByteAll(' ', self.indent);
2027 try stream.writeAll("non_err => ");
2028 try self.writeBracedBody(stream, body);
2029 }
20302006
2031 if (extra.data.bits.has_else) {2007 try self.writeSwitchCaptures(stream, non_err_case.capture, false, inst, &zir_switch);
2032 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2033 extra_index += 1;
2034 const capture_text = switch (info.capture) {
2035 .none => "",
2036 .by_val => "by_val ",
2037 .by_ref => "by_ref ",
2038 };
2039 const inline_text = if (info.is_inline) "inline " else "";
2040 const body = self.code.bodySlice(extra_index, info.body_len);
2041 extra_index += body.len;
20422008
2009 try stream.writeAll("non_err => ");
2010 try self.writeBracedBody(stream, non_err_case.body);
2011 try stream.writeAll(" ");
2012 try self.writeSrcNode(stream, zir_switch.catch_or_if_src_node_offset.unwrap().?);
2013 }
2014 if (zir_switch.else_case) |else_case| {
2043 try stream.writeAll(",\n");2015 try stream.writeAll(",\n");
2044 try stream.splatByteAll(' ', self.indent);2016 try stream.splatByteAll(' ', self.indent);
2045 try stream.print("{s}{s}else => ", .{ capture_text, inline_text });
2046 try self.writeBracedBody(stream, body);
2047 }
2048
2049 {
2050 const scalar_cases_len = extra.data.bits.scalar_cases_len;
2051 var scalar_i: usize = 0;
2052 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2053 const item_ref = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2054 extra_index += 1;
2055 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2056 extra_index += 1;
2057 const body = self.code.bodySlice(extra_index, info.body_len);
2058 extra_index += info.body_len;
2059
2060 try stream.writeAll(",\n");
2061 try stream.splatByteAll(' ', self.indent);
2062 switch (info.capture) {
2063 .none => {},
2064 .by_val => try stream.writeAll("by_val "),
2065 .by_ref => try stream.writeAll("by_ref "),
2066 }
2067 if (info.is_inline) try stream.writeAll("inline ");
2068 try self.writeInstRef(stream, item_ref);
2069 try stream.writeAll(" => ");
2070 try self.writeBracedBody(stream, body);
2071 }
2072 }
2073 {
2074 var multi_i: usize = 0;
2075 while (multi_i < multi_cases_len) : (multi_i += 1) {
2076 const items_len = self.code.extra[extra_index];
2077 extra_index += 1;
2078 const ranges_len = self.code.extra[extra_index];
2079 extra_index += 1;
2080 const info = @as(Zir.Inst.SwitchBlock.ProngInfo, @bitCast(self.code.extra[extra_index]));
2081 extra_index += 1;
2082 const items = self.code.refSlice(extra_index, items_len);
2083 extra_index += items_len;
2084
2085 try stream.writeAll(",\n");
2086 try stream.splatByteAll(' ', self.indent);
2087 switch (info.capture) {
2088 .none => {},
2089 .by_val => try stream.writeAll("by_val "),
2090 .by_ref => try stream.writeAll("by_ref "),
2091 }
2092 if (info.is_inline) try stream.writeAll("inline ");
2093
2094 for (items, 0..) |item_ref, item_i| {
2095 if (item_i != 0) try stream.writeAll(", ");
2096 try self.writeInstRef(stream, item_ref);
2097 }
2098
2099 var range_i: usize = 0;
2100 while (range_i < ranges_len) : (range_i += 1) {
2101 const item_first = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2102 extra_index += 1;
2103 const item_last = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
2104 extra_index += 1;
2105
2106 if (range_i != 0 or items.len != 0) {
2107 try stream.writeAll(", ");
2108 }
2109 try self.writeInstRef(stream, item_first);
2110 try stream.writeAll("...");
2111 try self.writeInstRef(stream, item_last);
2112 }
2113
2114 const body = self.code.bodySlice(extra_index, info.body_len);
2115 extra_index += info.body_len;
2116 try stream.writeAll(" => ");
2117 try self.writeBracedBody(stream, body);
2118 }
2119 }
2120
2121 self.indent -= 2;
2122
2123 try stream.writeAll(") ");
2124 try self.writeSrcNode(stream, inst_data.src_node);
2125 }
2126
2127 fn writeSwitchBlock(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
2128 const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
2129 const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
2130
2131 var extra_index: usize = extra.end;
2132
2133 const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
2134 const multi_cases_len = self.code.extra[extra_index];
2135 extra_index += 1;
2136 break :blk multi_cases_len;
2137 } else 0;
2138
2139 const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {
2140 const tag_capture_inst = self.code.extra[extra_index];
2141 extra_index += 1;
2142 break :blk @enumFromInt(tag_capture_inst);
2143 } else undefined;
21442017
2145 try self.writeInstRef(stream, extra.data.operand);2018 try self.writeSwitchCaptures(stream, else_case.capture, else_case.has_tag_capture, inst, &zir_switch);
21462019 if (else_case.is_inline) try stream.writeAll("inline ");
2147 if (extra.data.bits.any_has_tag_capture) {
2148 try stream.writeAll(", tag_capture=");
2149 try self.writeInstIndex(stream, tag_capture_inst);
2150 }
2151
2152 self.indent += 2;
2153
2154 const special_prongs = extra.data.bits.special_prongs;
2155
2156 if (special_prongs.hasElse()) {
2157 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2158 const capture_text = switch (info.capture) {
2159 .none => "",
2160 .by_val => "by_val ",
2161 .by_ref => "by_ref ",
2162 };
2163 const inline_text = if (info.is_inline) "inline " else "";
2164 extra_index += 1;
2165 const body = self.code.bodySlice(extra_index, info.body_len);
2166 extra_index += body.len;
21672020
2168 try stream.writeAll(",\n");2021 try stream.writeAll("else => ");
2169 try stream.splatByteAll(' ', self.indent);2022 try self.writeBracedBody(stream, else_case.body);
2170 try stream.print("{s}{s}else => ", .{ capture_text, inline_text });
2171 try self.writeBracedBody(stream, body);
2172 }2023 }
21732024
2174 if (special_prongs.hasUnder()) {2025 var case_it = zir_switch.iterateCases();
2175 var single_item_ref: Zir.Inst.Ref = .none;2026 while (case_it.next()) |case| {
2176 var items_len: u32 = 0;
2177 var ranges_len: u32 = 0;
2178 if (special_prongs.hasOneAdditionalItem()) {
2179 single_item_ref = @enumFromInt(self.code.extra[extra_index]);
2180 extra_index += 1;
2181 } else if (special_prongs.hasManyAdditionalItems()) {
2182 items_len = self.code.extra[extra_index];
2183 extra_index += 1;
2184 ranges_len = self.code.extra[extra_index];
2185 extra_index += 1;
2186 }
2187 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2188 extra_index += 1;
2189 const items = self.code.refSlice(extra_index, items_len);
2190 extra_index += items_len;
2191
2192 try stream.writeAll(",\n");2027 try stream.writeAll(",\n");
2193 try stream.splatByteAll(' ', self.indent);2028 try stream.splatByteAll(' ', self.indent);
2194 switch (info.capture) {
2195 .none => {},
2196 .by_val => try stream.writeAll("by_val "),
2197 .by_ref => try stream.writeAll("by_ref "),
2198 }
2199 if (info.is_inline) try stream.writeAll("inline ");
2200
2201 try stream.writeAll("_");
2202 if (single_item_ref != .none) {
2203 try stream.writeAll(", ");
2204 try self.writeInstRef(stream, single_item_ref);
2205 }
2206 for (items) |item_ref| {
2207 try stream.writeAll(", ");
2208 try self.writeInstRef(stream, item_ref);
2209 }
2210
2211 var range_i: usize = 0;
2212 while (range_i < ranges_len) : (range_i += 1) {
2213 const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2214 extra_index += 1;
2215 const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2216 extra_index += 1;
22172029
2218 try stream.writeAll(", ");2030 const prong_info = case.prong_info;
2219 try self.writeInstRef(stream, item_first);2031 try self.writeSwitchCaptures(stream, prong_info.capture, prong_info.has_tag_capture, inst, &zir_switch);
2220 try stream.writeAll("...");2032 if (prong_info.is_inline) try stream.writeAll("inline ");
2221 try self.writeInstRef(stream, item_last);
2222 }
22232033
2224 const body = self.code.bodySlice(extra_index, info.body_len);2034 const prong_body = self.code.bodySlice(extra_index, prong_info.body_len);
2225 extra_index += info.body_len;2035 extra_index += prong_body.len;
2226 try stream.writeAll(" => ");
2227 try self.writeBracedBody(stream, body);
2228 }
22292036
2230 {2037 for (case.item_infos, 0..) |item_info, i| {
2231 const scalar_cases_len = extra.data.bits.scalar_cases_len;2038 if (i > 0) try stream.writeAll(", ");
2232 var scalar_i: usize = 0;
2233 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
2234 const item_ref: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);
2235 extra_index += 1;
2236 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);
2237 extra_index += 1;
2238 const body = self.code.bodySlice(extra_index, info.body_len);
2239 extra_index += info.body_len;
22402039
2241 try stream.writeAll(",\n");2040 switch (item_info.unwrap()) {
2242 try stream.splatByteAll(' ', self.indent);2041 .enum_literal => |str_index| {
2243 switch (info.capture) {2042 const str = self.code.nullTerminatedString(str_index);
2244 .none => {},2043 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2245 .by_val => try stream.writeAll("by_val "),2044 },
2246 .by_ref => try stream.writeAll("by_ref "),2045 .error_value => |str_index| {
2046 const str = self.code.nullTerminatedString(str_index);
2047 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2048 },
2049 .under => try stream.writeByte('_'),
2050 .body_len => |body_len| {
2051 const item_body = self.code.bodySlice(extra_index, body_len);
2052 extra_index += item_body.len;
2053 try self.writeBracedDecl(stream, item_body);
2054 },
2247 }2055 }
2248 if (info.is_inline) try stream.writeAll("inline ");
2249 try self.writeInstRef(stream, item_ref);
2250 try stream.writeAll(" => ");
2251 try self.writeBracedBody(stream, body);
2252 }2056 }
2253 }2057 for (case.range_infos, 0..) |range_info, i| {
2254 {2058 if (i > 0 and case.item_infos.len == 0) try stream.writeAll(", ");
2255 var multi_i: usize = 0;2059 switch (range_info[0].unwrap()) {
2256 while (multi_i < multi_cases_len) : (multi_i += 1) {2060 .enum_literal => |str_index| {
2257 const items_len = self.code.extra[extra_index];2061 const str = self.code.nullTerminatedString(str_index);
2258 extra_index += 1;2062 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2259 const ranges_len = self.code.extra[extra_index];2063 },
2260 extra_index += 1;2064 .error_value => |str_index| {
2261 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(self.code.extra[extra_index]);2065 const str = self.code.nullTerminatedString(str_index);
2262 extra_index += 1;2066 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2263 const items = self.code.refSlice(extra_index, items_len);2067 },
2264 extra_index += items_len;2068 .under => unreachable, // '_..._' is not allowed
22652069 .body_len => |body_len| {
2266 try stream.writeAll(",\n");2070 const item_body = self.code.bodySlice(extra_index, body_len);
2267 try stream.splatByteAll(' ', self.indent);2071 extra_index += item_body.len;
2268 switch (info.capture) {2072 try self.writeBracedDecl(stream, item_body);
2269 .none => {},2073 },
2270 .by_val => try stream.writeAll("by_val "),
2271 .by_ref => try stream.writeAll("by_ref "),
2272 }
2273 if (info.is_inline) try stream.writeAll("inline ");
2274
2275 for (items, 0..) |item_ref, item_i| {
2276 if (item_i != 0) try stream.writeAll(", ");
2277 try self.writeInstRef(stream, item_ref);
2278 }2074 }
22792075 try stream.writeAll("...");
2280 var range_i: usize = 0;2076 switch (range_info[1].unwrap()) {
2281 while (range_i < ranges_len) : (range_i += 1) {2077 .enum_literal => |str_index| {
2282 const item_first: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);2078 const str = self.code.nullTerminatedString(str_index);
2283 extra_index += 1;2079 try stream.print("\".{f}\"", .{std.zig.fmtString(str)});
2284 const item_last: Zir.Inst.Ref = @enumFromInt(self.code.extra[extra_index]);2080 },
2285 extra_index += 1;2081 .error_value => |str_index| {
22862082 const str = self.code.nullTerminatedString(str_index);
2287 if (range_i != 0 or items.len != 0) {2083 try stream.print("\"error.{f}\"", .{std.zig.fmtString(str)});
2288 try stream.writeAll(", ");2084 },
2289 }2085 .under => unreachable, // '_..._' is not allowed
2290 try self.writeInstRef(stream, item_first);2086 .body_len => |body_len| {
2291 try stream.writeAll("...");2087 const item_body = self.code.bodySlice(extra_index, body_len);
2292 try self.writeInstRef(stream, item_last);2088 extra_index += item_body.len;
2089 try self.writeBracedDecl(stream, item_body);
2090 },
2293 }2091 }
2294
2295 const body = self.code.bodySlice(extra_index, info.body_len);
2296 extra_index += info.body_len;
2297 try stream.writeAll(" => ");
2298 try self.writeBracedBody(stream, body);
2299 }2092 }
2093 try stream.writeAll(" => ");
2094 try self.writeBracedBody(stream, prong_body);
2300 }2095 }
23012096
2302 self.indent -= 2;2097 self.indent -= 2;
23032098
2304 try stream.writeAll(") ");2099 try stream.writeAll(") ");
2305 try self.writeSrcNode(stream, inst_data.src_node);2100 try self.writeSrcNode(stream, zir_switch.switch_src_node_offset);
2101 }
2102
2103 fn writeSwitchCaptures(
2104 self: *Writer,
2105 stream: *std.Io.Writer,
2106 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
2107 has_tag_capture: bool,
2108 switch_inst: Zir.Inst.Index,
2109 zir_switch: *const Zir.UnwrappedSwitchBlock,
2110 ) !void {
2111 if (capture != .none) {
2112 try stream.print("{t}=", .{capture});
2113 const capture_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
2114 try self.writeInstIndex(stream, capture_inst);
2115 try stream.writeAll(" ");
2116 }
2117 if (has_tag_capture) {
2118 try stream.writeAll("tag=");
2119 const capture_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
2120 try self.writeInstIndex(stream, capture_inst);
2121 try stream.writeAll(" ");
2122 }
2306 }2123 }
23072124
2308 fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {2125 fn writePlNodeField(self: *Writer, stream: *std.Io.Writer, inst: Zir.Inst.Index) !void {
test/behavior/for.zig+17
...@@ -524,3 +524,20 @@ test "for loop 0 length range" {...@@ -524,3 +524,20 @@ test "for loop 0 length range" {
524 comptime unreachable;524 comptime unreachable;
525 }525 }
526}526}
527
528test "labeled break from else" {
529 const S = struct {
530 fn doTheTest(x: u32) !void {
531 var y: u32 = 0;
532 const ok = label: while (y < x) : (y += 1) {
533 if (y == 10) break :label false;
534 } else {
535 break :label true;
536 };
537 try expect(ok);
538 }
539 };
540
541 try S.doTheTest(5);
542 try comptime S.doTheTest(5);
543}
test/behavior/packed-struct.zig+14-28
...@@ -513,35 +513,21 @@ test "@intFromPtr on a packed struct field unaligned and nested" {...@@ -513,35 +513,21 @@ test "@intFromPtr on a packed struct field unaligned and nested" {
513 };513 };
514 };514 };
515515
516 switch (comptime @alignOf(S2)) {516 {
517 4 => {517 const a = @alignOf(S2);
518 comptime assert(@TypeOf(&S2.s.base) == *align(4) u8);518 comptime assert(@TypeOf(&S2.s.base) == *align(a:0:8) u8);
519 comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4);519 comptime assert(@TypeOf(&S2.s.p0.a) == *align(a:8:8) u4);
520 comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4);520 comptime assert(@TypeOf(&S2.s.p0.b) == *align(a:12:8) u4);
521 comptime assert(@TypeOf(&S2.s.p0.c) == *u8);521 comptime assert(@TypeOf(&S2.s.p0.c) == *align(a:16:8) u8);
522 comptime assert(@TypeOf(&S2.s.bit0) == *align(4:24:8) u1);522 comptime assert(@TypeOf(&S2.s.bit0) == *align(a:24:8) u1);
523 comptime assert(@TypeOf(&S2.s.p1.a) == *align(4:25:8) u8);523 comptime assert(@TypeOf(&S2.s.p1.a) == *align(a:25:8) u8);
524 comptime assert(@TypeOf(&S2.s.p2.a) == *align(4:33:8) u7);524 comptime assert(@TypeOf(&S2.s.p2.a) == *align(a:33:8) u7);
525 comptime assert(@TypeOf(&S2.s.p2.b) == *u8);525 comptime assert(@TypeOf(&S2.s.p2.b) == *align(a:40:8) u8);
526 comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4);526 comptime assert(@TypeOf(&S2.s.p3.a) == *align(a:48:8) u4);
527 comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4);527 comptime assert(@TypeOf(&S2.s.p3.b) == *align(a:52:8) u4);
528 comptime assert(@TypeOf(&S2.s.p3.c) == *u8);528 comptime assert(@TypeOf(&S2.s.p3.c) == *align(a:56:8) u8);
529 },
530 8 => {
531 comptime assert(@TypeOf(&S2.s.base) == *align(8) u8);
532 comptime assert(@TypeOf(&S2.s.p0.a) == *align(1:0:2) u4);
533 comptime assert(@TypeOf(&S2.s.p0.b) == *align(1:4:2) u4);
534 comptime assert(@TypeOf(&S2.s.p0.c) == *u8);
535 comptime assert(@TypeOf(&S2.s.bit0) == *align(8:24:8) u1);
536 comptime assert(@TypeOf(&S2.s.p1.a) == *align(8:25:8) u8);
537 comptime assert(@TypeOf(&S2.s.p2.a) == *align(8:33:8) u7);
538 comptime assert(@TypeOf(&S2.s.p2.b) == *u8);
539 comptime assert(@TypeOf(&S2.s.p3.a) == *align(2:0:2) u4);
540 comptime assert(@TypeOf(&S2.s.p3.b) == *align(2:4:2) u4);
541 comptime assert(@TypeOf(&S2.s.p3.c) == *u8);
542 },
543 else => {},
544 }529 }
530
545 try expect(@intFromPtr(&S2.s.base) - @intFromPtr(&S2.s) == 0);531 try expect(@intFromPtr(&S2.s.base) - @intFromPtr(&S2.s) == 0);
546 try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 0);532 try expect(@intFromPtr(&S2.s.p0.a) - @intFromPtr(&S2.s) == 0);
547 try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 0);533 try expect(@intFromPtr(&S2.s.p0.b) - @intFromPtr(&S2.s) == 0);
test/behavior/switch.zig+187
...@@ -1120,3 +1120,190 @@ test "switch on non-exhaustive enum" {...@@ -1120,3 +1120,190 @@ test "switch on non-exhaustive enum" {
1120 try E.doTheTest(.a);1120 try E.doTheTest(.a);
1121 try comptime E.doTheTest(.a);1121 try comptime E.doTheTest(.a);
1122}1122}
1123
1124test "decl literals as switch cases" {
1125 const E = enum(u8) {
1126 bar = 3,
1127 _,
1128
1129 const foo: @This() = @enumFromInt(0xa);
1130
1131 fn doTheTest(e: @This()) !void {
1132 switch (e) {
1133 .bar => return error.TestFailed,
1134 .foo => {},
1135 else => return error.TestFailed,
1136 }
1137 }
1138 };
1139
1140 try E.doTheTest(.foo);
1141 try comptime E.doTheTest(.foo);
1142}
1143
1144// TODO audit after #15909 and/or #19855 are decided/implemented
1145test "switch with uninstantiable union fields" {
1146 const U = union(enum) {
1147 ok: void,
1148 a: noreturn,
1149 b: noreturn,
1150 c: error{},
1151
1152 fn doTheTest(u: @This()) void {
1153 switch (u) {
1154 .ok => {},
1155 .a => comptime unreachable,
1156 .b => comptime unreachable,
1157 .c => comptime unreachable,
1158 }
1159 switch (u) {
1160 .ok => {},
1161 .a, .b, .c => comptime unreachable,
1162 }
1163 switch (u) {
1164 .ok => {},
1165 else => comptime unreachable,
1166 }
1167 switch (u) {
1168 .a => comptime unreachable,
1169 .ok, .b, .c => {},
1170 }
1171 }
1172 };
1173
1174 U.doTheTest(.ok);
1175 comptime U.doTheTest(.ok);
1176}
1177
1178test "switch with tag capture" {
1179 const U = union(enum) {
1180 a,
1181 b: i32,
1182 c: u8,
1183 d: i32,
1184 e: noreturn,
1185
1186 fn doTheTest() !void {
1187 try doTheSwitch(.a);
1188 try doTheSwitch(.{ .b = 123 });
1189 try doTheSwitch(.{ .c = 0xFF });
1190 }
1191 fn doTheSwitch(u: @This()) !void {
1192 switch (u) {
1193 .a => |nothing, tag| {
1194 comptime assert(nothing == {});
1195 comptime assert(tag == .a);
1196 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
1197 },
1198 .b, .d => |_, tag| {
1199 try expect(tag == .b or tag == .d);
1200 },
1201 .e => |payload, tag| {
1202 _ = &payload;
1203 _ = &tag;
1204 comptime unreachable;
1205 },
1206 else => |un, tag| {
1207 try expect(tag == .c);
1208 try expect(un == .c);
1209 try expect(un.c == 0xFF);
1210 },
1211 }
1212 switch (u) {
1213 inline .a, .b, .c => |payload, tag| {
1214 if (@TypeOf(payload) == void) comptime assert(tag == .a);
1215 if (@TypeOf(payload) == i32) comptime assert(tag == .b);
1216 if (@TypeOf(payload) == u8) comptime assert(tag == .c);
1217 },
1218 inline else => |payload, tag| {
1219 if (@TypeOf(payload) == i32) comptime assert(tag == .d);
1220 comptime assert(tag != .e);
1221 },
1222 }
1223 }
1224 };
1225
1226 try U.doTheTest();
1227 try comptime U.doTheTest();
1228}
1229
1230test "switch with complex item expressions" {
1231 const S = struct {
1232 fn doTheTest() !void {
1233 try doTheSwitch(2000, 20);
1234 try doTheSwitch(2000, 10);
1235 try doTheSwitch(2000, 5);
1236
1237 try doTheOtherSwitch(@enumFromInt(123));
1238 try doTheOtherSwitch(@enumFromInt(456));
1239 }
1240 fn doTheSwitch(x: u32, comptime factor: u32) !void {
1241 const ok = switch (x) {
1242 num(factor) => true,
1243 typedNum(u32, factor) => true,
1244 blk: {
1245 var val = 400;
1246 val *= factor;
1247 break :blk val;
1248 } => true,
1249 else => false,
1250 };
1251 try expect(ok);
1252 }
1253 fn num(factor: u32) u32 {
1254 return 100 * factor;
1255 }
1256 fn typedNum(comptime T: type, factor: T) T {
1257 return 200 * factor;
1258 }
1259
1260 const E = enum(u32) { _ };
1261 fn doTheOtherSwitch(e: E) !void {
1262 const ok = switch (e) {
1263 @enumFromInt(123) => true,
1264 @enumFromInt(456) => true,
1265 else => false,
1266 };
1267 try expect(ok);
1268 }
1269 };
1270
1271 try S.doTheTest();
1272 try comptime S.doTheTest();
1273}
1274
1275test "switch evaluation order" {
1276 const eu: anyerror!u32 = 0;
1277 _ = eu catch |err| switch (err) {
1278 if (true) @compileError("unreachable") => unreachable,
1279 else => unreachable,
1280 };
1281}
1282
1283test "switch resolves lazy values correctly" {
1284 const S = extern struct {
1285 a: u16,
1286 b: i16,
1287 };
1288 switch (@sizeOf(S)) {
1289 4 => {},
1290 else => comptime unreachable,
1291 }
1292}
1293
1294test "single-item prong in switch on enum has comptime-known capture" {
1295 const E = enum {
1296 a,
1297 b,
1298 c,
1299 fn doTheTest(e: @This()) !void {
1300 switch (e) {
1301 .a => |tag| comptime assert(tag == .a),
1302 .b => return error.TestFailed,
1303 .c => return error.TestFailed,
1304 }
1305 }
1306 };
1307 try E.doTheTest(.a);
1308 try comptime E.doTheTest(.a);
1309}
test/behavior/switch_loop.zig+239
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const assert = std.debug.assert;
3const expect = std.testing.expect;4const expect = std.testing.expect;
45
5test "simple switch loop" {6test "simple switch loop" {
...@@ -270,3 +271,241 @@ test "switch loop on non-exhaustive enum" {...@@ -270,3 +271,241 @@ test "switch loop on non-exhaustive enum" {
270 try S.doTheTest();271 try S.doTheTest();
271 try comptime S.doTheTest();272 try comptime S.doTheTest();
272}273}
274
275test "switch loop with discarded tag capture" {
276 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
277 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
278 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
279
280 const S = struct {
281 const U = union(enum) {
282 a: u32,
283 b: u32,
284 c: u32,
285 };
286
287 fn doTheTest() void {
288 const a: U = .{ .a = 10 };
289 blk: switch (a) {
290 inline .b => |_, tag| {
291 _ = tag;
292 continue :blk .{ .c = 20 };
293 },
294 else => {},
295 }
296 }
297 };
298 S.doTheTest();
299 comptime S.doTheTest();
300}
301
302test "switch loop with single catch-all prong" {
303 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
304
305 const S = struct {
306 const E = enum { a, b, c };
307 const U = union(E) { a: u32, b: u16, c: u8 };
308
309 fn doTheTest() !void {
310 var x: usize = 0;
311 label: switch (E.a) {
312 else => {
313 x += 1;
314 if (x == 10) break :label;
315 if (x >= 5) continue :label .b;
316 continue :label .c;
317 },
318 }
319 try expect(x == 10);
320
321 label: switch (E.a) {
322 .a, .b, .c => {
323 x += 1;
324 if (x == 20) break :label;
325 if (x >= 15) continue :label .b;
326 continue :label .c;
327 },
328 }
329 try expect(x == 20);
330
331 label: switch (E.a) {
332 else => if (false) continue :label true,
333 }
334
335 const ok = label: switch (U{ .a = 123 }) {
336 else => |u| {
337 const y: u32 = switch (u) {
338 inline else => |y| y,
339 };
340 if (y == 456) break :label true;
341 continue :label .{ .b = 456 };
342 },
343 };
344 comptime assert(ok);
345 }
346 };
347 try S.doTheTest();
348 try comptime S.doTheTest();
349}
350
351test "switch loop on type with opv" {
352 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
353
354 const S = struct {
355 const E = enum { opv };
356 const U = union(E) { opv: u0 };
357
358 fn doTheTest() !void {
359 var x: usize = 0;
360 label: switch (E.opv) {
361 .opv => {
362 x += 1;
363 if (x == 10) break :label;
364 if (x >= 5) continue :label .opv;
365 continue :label .opv;
366 },
367 }
368 try expect(x == 10);
369
370 label: switch (E.opv) {
371 else => {
372 x += 1;
373 if (x == 20) break :label;
374 if (x >= 15) continue :label .opv;
375 continue :label .opv;
376 },
377 }
378 try expect(x == 20);
379
380 label: switch (E.opv) {
381 .opv => if (false) continue :label true,
382 }
383
384 label: switch (U{ .opv = 0 }) {
385 .opv => |val| {
386 x += 1;
387 if (x == 30) break :label;
388 if (x >= 25) continue :label .{ .opv = val };
389 continue :label .{ .opv = 0 };
390 },
391 }
392 try expect(x == 30);
393 }
394 };
395 try S.doTheTest();
396 try comptime S.doTheTest();
397}
398
399test "switch loop with tag capture" {
400 const U = union(enum) {
401 a,
402 b: i32,
403 c: u8,
404 d: i32,
405 e: noreturn,
406
407 fn doTheTest() !void {
408 try doTheSwitch(.a);
409 try doTheSwitch(.{ .b = 123 });
410 try doTheSwitch(.{ .c = 0xFF });
411 }
412 fn doTheSwitch(u: @This()) !void {
413 const ok1 = label: switch (u) {
414 .a => |nothing, tag| {
415 comptime assert(nothing == {});
416 comptime assert(tag == .a);
417 try expect(@intFromEnum(tag) == @intFromEnum(@This().a));
418 continue :label .{ .d = 456 };
419 },
420 .b, .d => |_, tag| {
421 try expect(tag == .b or tag == .d);
422 continue :label .{ .c = 0x0F };
423 },
424 .e => |payload, tag| {
425 _ = &payload;
426 _ = &tag;
427 return error.AnalyzedNoreturnProng;
428 },
429 else => |un, tag| {
430 try expect(tag == .c);
431 try expect(un == .c);
432 if (un.c == 0xFF) continue :label .a;
433 if (un.c == 0x00) break :label false;
434 break :label true;
435 },
436 };
437 try expect(ok1);
438
439 const ok2 = label: switch (u) {
440 inline .a, .b, .c => |payload, tag| {
441 if (@TypeOf(payload) == void) {
442 comptime assert(tag == .a);
443 continue :label .{ .b = 456 };
444 }
445 if (@TypeOf(payload) == i32) {
446 comptime assert(tag == .b);
447 continue :label .{ .d = payload };
448 }
449 if (@TypeOf(payload) == u8) {
450 comptime assert(tag == .c);
451 continue :label .{ .d = payload };
452 }
453 },
454 inline else => |payload, tag| {
455 if (@TypeOf(payload) == i32) comptime assert(tag == .d);
456 comptime assert(tag != .e);
457 if (payload == 0) break :label false;
458 break :label true;
459 },
460 };
461 try expect(ok2);
462 }
463 };
464
465 try U.doTheTest();
466 try comptime U.doTheTest();
467}
468
469test "switch loop for error handling" {
470 const Error = error{ MyError, MyOtherError };
471 const S = struct {
472 fn doTheTest() !void {
473 try doThePayloadSwitch(123);
474 try doTheErrSwitch(error.MyError);
475 try doTheErrSwitch(error.MyOtherError);
476 }
477 fn doThePayloadSwitch(eu: Error!u32) !void {
478 const x = eu catch |err| label: switch (err) {
479 error.MyError => continue :label error.MyOtherError,
480 error.MyOtherError => break :label 0,
481 };
482 try expect(x == 123);
483
484 const y = if (eu) |payload| label: {
485 break :label payload * 2;
486 } else |err| label: switch (err) {
487 error.MyError => continue :label error.MyOtherError,
488 error.MyOtherError => break :label 0,
489 };
490 try expect(y == 246);
491 }
492 fn doTheErrSwitch(eu: Error!u32) !void {
493 const x = eu catch |err| label: switch (err) {
494 error.MyError => continue :label error.MyOtherError,
495 error.MyOtherError => break :label 123,
496 };
497 try expect(x == 123);
498
499 const y = if (eu) |payload| label: {
500 break :label payload * 2;
501 } else |err| label: switch (err) {
502 error.MyError => continue :label error.MyOtherError,
503 error.MyOtherError => break :label 123,
504 };
505 try expect(y == 123);
506 }
507 };
508
509 try S.doTheTest();
510 try comptime S.doTheTest();
511}
test/behavior/switch_on_captured_error.zig+200-12
...@@ -18,6 +18,8 @@ test "switch on error union catch capture" {...@@ -18,6 +18,8 @@ test "switch on error union catch capture" {
18 try testCapture();18 try testCapture();
19 try testInline();19 try testInline();
20 try testEmptyErrSet();20 try testEmptyErrSet();
21 try testUnreachableElseProng();
22 try testErrNotInSet();
21 try testAddressOf();23 try testAddressOf();
22 }24 }
2325
...@@ -240,22 +242,90 @@ test "switch on error union catch capture" {...@@ -240,22 +242,90 @@ test "switch on error union catch capture" {
240 {242 {
241 var a: error{}!u64 = 0;243 var a: error{}!u64 = 0;
242 _ = &a;244 _ = &a;
243 const b: u64 = a catch |err| switch (err) {245 const b = a catch |err| switch (err) {
244 else => |e| return e,246 undefined => @compileError("unreachable"),
245 };247 };
246 try expectEqual(@as(u64, 0), b);248 try expectEqual(@as(u64, 0), b);
247 }249 }
250 }
251
252 fn testUnreachableElseProng() !void {
248 {253 {
249 var a: error{}!u64 = 0;254 var a: error{}!u64 = 0;
250 _ = &a;255 _ = &a;
251 const b: u64 = a catch |err| switch (err) {256 const b = a catch |err| switch (err) {
252 error.UnknownError => return error.Fail,257 else => unreachable,
258 };
259 try expectEqual(@as(u64, 0), b);
260 }
261 {
262 var a: error{}!u64 = 0;
263 _ = &a;
264 const b = a catch |err| switch (err) {
265 else => return,
266 };
267 try expectEqual(@as(u64, 0), b);
268 }
269 {
270 var a: error{}!u64 = 0;
271 _ = &a;
272 const b = a catch |err| switch (err) {
273 else => |e| return e,
274 };
275 try expectEqual(@as(u64, 0), b);
276 }
277 {
278 var a: error{MyError}!u64 = error.MyError;
279 _ = &a;
280 const b = a catch |err| switch (err) {
281 error.MyError => 0,
282 else => unreachable,
283 };
284 try expectEqual(@as(u64, 0), b);
285 }
286 {
287 var a: error{MyError}!u64 = error.MyError;
288 _ = &a;
289 const b = a catch |err| switch (err) {
290 error.MyError => 0,
291 else => return,
292 };
293 try expectEqual(@as(u64, 0), b);
294 }
295 {
296 var a: error{MyError}!u64 = error.MyError;
297 _ = &a;
298 const b = a catch |err| switch (err) {
299 error.MyError => 0,
253 else => |e| return e,300 else => |e| return e,
254 };301 };
255 try expectEqual(@as(u64, 0), b);302 try expectEqual(@as(u64, 0), b);
256 }303 }
257 }304 }
258305
306 fn testErrNotInSet() !void {
307 {
308 var a: error{MyError}!u64 = 0;
309 _ = &a;
310 const b = a catch |err| switch (err) {
311 error.MyError => 1,
312 error.MyOtherError => comptime unreachable,
313 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
314 };
315 try expectEqual(@as(u64, 0), b);
316 }
317 {
318 var a: error{MyError}!u64 = error.MyError;
319 _ = &a;
320 const b = a catch |err| switch (err) {
321 error.MyError => 0,
322 error.MyOtherError => comptime unreachable,
323 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
324 };
325 try expectEqual(@as(u64, 0), b);
326 }
327 }
328
259 fn testAddressOf() !void {329 fn testAddressOf() !void {
260 {330 {
261 const a: anyerror!usize = 0;331 const a: anyerror!usize = 0;
...@@ -318,6 +388,9 @@ test "switch on error union if else capture" {...@@ -318,6 +388,9 @@ test "switch on error union if else capture" {
318 try testInlinePtr();388 try testInlinePtr();
319 try testEmptyErrSet();389 try testEmptyErrSet();
320 try testEmptyErrSetPtr();390 try testEmptyErrSetPtr();
391 try testUnreachableElseProng();
392 try testUnreachableElseProngPtr();
393 try testErrNotInSet();
321 try testAddressOf();394 try testAddressOf();
322 }395 }
323396
...@@ -755,40 +828,155 @@ test "switch on error union if else capture" {...@@ -755,40 +828,155 @@ test "switch on error union if else capture" {
755 {828 {
756 var a: error{}!u64 = 0;829 var a: error{}!u64 = 0;
757 _ = &a;830 _ = &a;
758 const b: u64 = if (a) |x| x else |err| switch (err) {831 const b = if (a) |x| x else |err| switch (err) {
759 else => |e| return e,832 undefined => @compileError("unreachable"),
760 };833 };
761 try expectEqual(@as(u64, 0), b);834 try expectEqual(@as(u64, 0), b);
762 }835 }
836 }
837
838 fn testEmptyErrSetPtr() !void {
763 {839 {
764 var a: error{}!u64 = 0;840 var a: error{}!u64 = 0;
765 _ = &a;841 _ = &a;
766 const b: u64 = if (a) |x| x else |err| switch (err) {842 const b = if (a) |*x| x.* else |err| switch (err) {
843 undefined => @compileError("unreachable"),
844 };
845 try expectEqual(@as(u64, 0), b);
846 }
847 }
848
849 fn testUnreachableElseProng() !void {
850 {
851 var a: error{}!u64 = 0;
852 _ = &a;
853 const b = if (a) |x| x else |err| switch (err) {
854 else => unreachable,
855 };
856 try expectEqual(@as(u64, 0), b);
857 }
858 {
859 var a: error{}!u64 = 0;
860 _ = &a;
861 const b = if (a) |x| x else |err| switch (err) {
862 error.UnknownError => return error.Fail,
863 else => return,
864 };
865 try expectEqual(@as(u64, 0), b);
866 }
867 {
868 var a: error{}!u64 = 0;
869 _ = &a;
870 const b = if (a) |x| x else |err| switch (err) {
767 error.UnknownError => return error.Fail,871 error.UnknownError => return error.Fail,
768 else => |e| return e,872 else => |e| return e,
769 };873 };
770 try expectEqual(@as(u64, 0), b);874 try expectEqual(@as(u64, 0), b);
771 }875 }
876 {
877 var a: error{MyError}!u64 = error.MyError;
878 _ = &a;
879 const b = if (a) |x| x else |err| switch (err) {
880 error.MyError => 0,
881 else => unreachable,
882 };
883 try expectEqual(@as(u64, 0), b);
884 }
885 {
886 var a: error{MyError}!u64 = error.MyError;
887 _ = &a;
888 const b = if (a) |x| x else |err| switch (err) {
889 error.MyError => 0,
890 else => return,
891 };
892 try expectEqual(@as(u64, 0), b);
893 }
894 {
895 var a: error{MyError}!u64 = error.MyError;
896 _ = &a;
897 const b = if (a) |x| x else |err| switch (err) {
898 error.MyError => 0,
899 else => |e| return e,
900 };
901 try expectEqual(@as(u64, 0), b);
902 }
772 }903 }
773904
774 fn testEmptyErrSetPtr() !void {905 fn testUnreachableElseProngPtr() !void {
775 {906 {
776 var a: error{}!u64 = 0;907 var a: error{}!u64 = 0;
777 _ = &a;908 _ = &a;
778 const b: u64 = if (a) |*x| x.* else |err| switch (err) {909 const b = if (a) |*x| x.* else |err| switch (err) {
779 else => |e| return e,910 else => unreachable,
780 };911 };
781 try expectEqual(@as(u64, 0), b);912 try expectEqual(@as(u64, 0), b);
782 }913 }
783 {914 {
784 var a: error{}!u64 = 0;915 var a: error{}!u64 = 0;
785 _ = &a;916 _ = &a;
786 const b: u64 = if (a) |*x| x.* else |err| switch (err) {917 const b = if (a) |*x| x.* else |err| switch (err) {
787 error.UnknownError => return error.Fail,918 else => return,
919 };
920 try expectEqual(@as(u64, 0), b);
921 }
922 {
923 var a: error{}!u64 = 0;
924 _ = &a;
925 const b = if (a) |*x| x.* else |err| switch (err) {
788 else => |e| return e,926 else => |e| return e,
789 };927 };
790 try expectEqual(@as(u64, 0), b);928 try expectEqual(@as(u64, 0), b);
791 }929 }
930 {
931 var a: error{MyError}!u64 = error.MyError;
932 _ = &a;
933 const b = if (a) |*x| x.* else |err| switch (err) {
934 error.MyError => 0,
935 else => unreachable,
936 };
937 try expectEqual(@as(u64, 0), b);
938 }
939 {
940 var a: error{MyError}!u64 = error.MyError;
941 _ = &a;
942 const b = if (a) |*x| x.* else |err| switch (err) {
943 error.MyError => 0,
944 else => return,
945 };
946 try expectEqual(@as(u64, 0), b);
947 }
948 {
949 var a: error{MyError}!u64 = error.MyError;
950 _ = &a;
951 const b = if (a) |*x| x.* else |err| switch (err) {
952 error.MyError => 0,
953 else => |e| return e,
954 };
955 try expectEqual(@as(u64, 0), b);
956 }
957 }
958
959 fn testErrNotInSet() !void {
960 {
961 var a: error{MyError}!u64 = 0;
962 _ = &a;
963 const b = if (a) |x| x else |err| switch (err) {
964 error.MyError => 1,
965 error.MyOtherError => comptime unreachable,
966 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
967 };
968 try expectEqual(@as(u64, 0), b);
969 }
970 {
971 var a: error{MyError}!u64 = error.MyError;
972 _ = &a;
973 const b = if (a) |x| x else |err| switch (err) {
974 error.MyError => 0,
975 error.MyOtherError => comptime unreachable,
976 error.YetAnotherError, error.ThereIsAnother => comptime unreachable,
977 };
978 try expectEqual(@as(u64, 0), b);
979 }
792 }980 }
793981
794 fn testAddressOf() !void {982 fn testAddressOf() !void {
test/behavior/while.zig+17
...@@ -399,3 +399,20 @@ test "breaking from a loop in an if statement" {...@@ -399,3 +399,20 @@ test "breaking from a loop in an if statement" {
399 } else 2;399 } else 2;
400 _ = opt;400 _ = opt;
401}401}
402
403test "labeled break from else" {
404 const S = struct {
405 fn doTheTest(x: u32) !void {
406 const arr: []const u32 = &.{ 1, 3, 10 };
407 const ok = label: for (arr) |y| {
408 if (y == x) break :label false;
409 } else {
410 break :label true;
411 };
412 try expect(ok);
413 }
414 };
415
416 try S.doTheTest(5);
417 try comptime S.doTheTest(5);
418}
test/cases/compile_errors/continue_loop_from_else_block.zig created+17
...@@ -0,0 +1,17 @@
1export fn entry1() void {
2 var x: u32 = 0;
3 result: while (x < 5) : (x += 1) {} else {
4 continue :result;
5 }
6}
7
8export fn entry2() void {
9 result: for (0..5) |_| {} else {
10 continue :result;
11 }
12}
13
14// error
15//
16// :4:9: error: continue outside of loop or labeled switch expression
17// :10:9: error: continue outside of loop or labeled switch expression
test/cases/compile_errors/duplicate_boolean_switch_value.zig+2
...@@ -18,4 +18,6 @@ comptime {...@@ -18,4 +18,6 @@ comptime {
18// error18// error
19//19//
20// :5:9: error: duplicate switch value20// :5:9: error: duplicate switch value
21// :3:9: note: previous value here
21// :13:9: error: duplicate switch value22// :13:9: error: duplicate switch value
23// :11:9: note: previous value here
test/cases/compile_errors/invalid_switch_item.zig+4-4
...@@ -36,11 +36,11 @@ export fn f3() void {...@@ -36,11 +36,11 @@ export fn f3() void {
3636
37// error37// error
38//38//
39// :8:10: error: no field named 'x' in enum 'tmp.E'39// :8:10: error: enum 'tmp.E' has no member named 'x'
40// :1:11: note: enum declared here40// :1:11: note: enum declared here
41// :16:10: error: no field named 'x' in enum 'tmp.E'41// :16:10: error: enum 'tmp.E' has no member named 'x'
42// :1:11: note: enum declared here42// :1:11: note: enum declared here
43// :24:10: error: no field named 'x' in enum 'tmp.E'43// :24:10: error: enum 'tmp.E' has no member named 'x'
44// :1:11: note: enum declared here44// :1:11: note: enum declared here
45// :32:10: error: no field named 'x' in enum 'tmp.E'45// :32:10: error: enum 'tmp.E' has no member named 'x'
46// :1:11: note: enum declared here46// :1:11: note: enum declared here
test/cases/compile_errors/labeled_block_continue.zig created+10
...@@ -0,0 +1,10 @@
1export fn foo() void {
2 const result: u32 = b: {
3 continue :b 123;
4 };
5 _ = result;
6}
7
8// error
9//
10// :3:9: error: continue outside of loop or labeled switch expression
test/cases/compile_errors/switch_loop_discarded_capture.zig created+16
...@@ -0,0 +1,16 @@
1export fn foo() void {
2 const S = struct {
3 fn doTheTest() void {
4 blk: switch (@as(u8, 'a')) {
5 '1' => |_| continue :blk '1',
6 else => {},
7 }
8 }
9 };
10 S.doTheTest();
11 comptime S.doTheTest();
12}
13
14// error
15//
16// :5:25: error: discard of capture; omit it instead
test/cases/compile_errors/switch_on_error_with_1_field_with_no_prongs.zig+23-7
...@@ -1,18 +1,34 @@...@@ -1,18 +1,34 @@
1const Error = error{M};1const Error = error{M};
22
3export fn entry() void {3export fn entry1() void {
4 const f: Error!void = void{};4 var f: Error!void = {};
5 _ = &f;
5 if (f) {} else |e| switch (e) {}6 if (f) {} else |e| switch (e) {}
6}7}
78
8export fn entry2() void {9export fn entry2() void {
9 const f: Error!void = void{};10 var f: Error!void = {};
11 _ = &f;
12 f catch |e| switch (e) {};
13}
14
15export fn entry3() void {
16 const f: Error!void = error.M;
17 if (f) {} else |e| switch (e) {}
18}
19
20export fn entry4() void {
21 const f: Error!void = error.M;
10 f catch |e| switch (e) {};22 f catch |e| switch (e) {};
11}23}
1224
13// error25// error
14//26//
15// :5:24: error: switch must handle all possibilities27// :6:24: error: switch must handle all possibilities
16// :5:24: note: unhandled error value: 'error.M'28// :6:24: note: unhandled error value: 'error.M'
17// :10:17: error: switch must handle all possibilities29// :12:17: error: switch must handle all possibilities
18// :10:17: note: unhandled error value: 'error.M'30// :12:17: note: unhandled error value: 'error.M'
31// :17:24: error: switch must handle all possibilities
32// :17:24: note: unhandled error value: 'error.M'
33// :22:17: error: switch must handle all possibilities
34// :22:17: note: unhandled error value: 'error.M'
test/cases/compile_errors/switch_on_non_err_union.zig+1-2
...@@ -5,6 +5,5 @@ pub fn main() void {...@@ -5,6 +5,5 @@ pub fn main() void {
5}5}
66
7// error7// error
8// target=x86_64-linux
9//8//
10// :2:23: error: expected error union type, found 'bool'9// :2:11: error: expected error union type, found 'bool'
test/cases/compile_errors/tag_capture_on_non_inline_prong.zig deleted-12
...@@ -1,12 +0,0 @@
1const E = enum { a, b, c, d };
2pub export fn entry() void {
3 var x: E = .a;
4 switch (x) {
5 .a, .b => |aorb, d| @compileLog(aorb, d),
6 inline .c, .d => |*cord| @compileLog(cord),
7 }
8}
9
10// error
11//
12// :5:26: error: tag capture on non-inline prong