| author | |
| committer | |
| log | 76dd39d5316d302de6aa9bcab45ccb3f5a123bcb |
| tree | 19eb970a346b362eac55f7e73eb7ee3bfbf90cd7 |
| parent | c4345991340af5ff2e0155a9832f4eed9ec677fc |
| parent | 01546e68cd0d82ef78498a10649e6bc2937680da |
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 eligible | 2463 | 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 |
| 2594 | 2595 | ||
| 2595 | {#header_close#} | 2596 | {#header_close#} |
| 2596 | 2597 | ||
| 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 a | 2607 | 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#} |
| 2660 | 2668 | ||
| 2661 | <p> | 2669 | <p> |
| 2662 | When using an inline prong switching on an union an additional | 2670 | 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#} |
| 2666 | 2675 | ||
| 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#} |
| 2670 | 2679 |
doc/langref/test_switch_on_errors.zig created+55| ... | @@ -0,0 +1,55 @@ | ||
| 1 | const FileOpenError0 = error{ | ||
| 2 | AccessDenied, | ||
| 3 | OutOfMemory, | ||
| 4 | FileNotFound, | ||
| 5 | }; | ||
| 6 | |||
| 7 | fn openFile0() FileOpenError0 { | ||
| 8 | return error.OutOfMemory; | ||
| 9 | } | ||
| 10 | |||
| 11 | test "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 | |||
| 27 | const FileOpenError1 = error{ | ||
| 28 | AccessDenied, | ||
| 29 | SystemResources, | ||
| 30 | FileNotFound, | ||
| 31 | }; | ||
| 32 | |||
| 33 | fn openFile1() FileOpenError1 { | ||
| 34 | return error.SystemResources; | ||
| 35 | } | ||
| 36 | |||
| 37 | fn 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 | |||
| 47 | test "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 | } |
| 22 | 30 | ||
| 23 | test "get tag type" { | 31 | test "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 | } |
| 137 | 137 |
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 | }, |
| 815 | 815 | ||
| 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), |
| 1112 | 1111 | ||
| 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; |
| 2162 | 2161 | ||
| 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).?; | ||
| 2169 | 2166 | ||
| 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 | } |
| 2180 | 2177 | ||
| 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 parent | 2184 | } |
| 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 expr | 2188 | // This `break` is unlabeled and the gz we've found doesn't allow |
| 2197 | 2189 | // 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_inline | 2191 | } |
| 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); | ||
| 2211 | 2192 | ||
| 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"; | ||
| 2215 | 2197 | ||
| 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); | ||
| 2217 | 2201 | ||
| 2218 | try genDefers(parent_gz, scope, parent_scope, .normal_only); | 2202 | try genDefers(parent_gz, scope, parent_scope, .normal_only); |
| 2219 | 2203 | ||
| 2220 | // As our last action before the break, "pop" the error trace if needed | 2204 | // 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); |
| 2223 | 2207 | } | |
| 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 | } |
| 2253 | 2249 | ||
| ... | @@ -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 | } |
| 2262 | 2258 | ||
| 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).?; | ||
| 2269 | 2263 | ||
| 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 label | 2289 | 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 | } | ||
| 2314 | 2310 | ||
| 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 match | 2313 | .@"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); |
| 2321 | 2315 | ||
| 2322 | // As our last action before the continue, "pop" the error trace if needed | 2316 | 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 |
| 2325 | 2319 | .@"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); | ||
| 2331 | 2323 | ||
| 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_inline | 2325 | if (!gen_zir.is_comptime) { |
| 2334 | else | 2326 | _ = 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); | ||
| 2339 | 2334 | ||
| 2340 | // As our last action before the continue, "pop" the error trace if needed | 2335 | 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); | ||
| 2343 | 2336 | ||
| 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 | } |
| 2361 | 2360 | ||
| ... | @@ -2441,33 +2440,29 @@ fn blockExpr( | ... | @@ -2441,33 +2440,29 @@ fn blockExpr( |
| 2441 | 2440 | ||
| 2442 | fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: Ast.TokenIndex) !void { | 2441 | fn 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 | } |
| 2473 | 2468 | ||
| ... | @@ -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; |
| 2993 | 2986 | ||
| 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; |
| 2999 | 2991 | ||
| 3000 | have_err = true; | 2992 | have_err = true; |
| ... | @@ -3030,17 +3022,15 @@ fn genDefers( | ... | @@ -3030,17 +3022,15 @@ fn genDefers( |
| 3030 | 3022 | ||
| 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 |
| 3083 | 3073 | ||
| 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( |
| 4781 | 4761 | ||
| 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 already | 4764 | 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 | }; |
| 6133 | 6109 | ||
| 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 needed | 6135 | // 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 needed | 6507 | // 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( |
| 6574 | 6592 | ||
| 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(); |
| 6579 | 6596 | ||
| 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); |
| 6708 | 6725 | ||
| 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); | ||
| 6718 | 6734 | ||
| 6719 | // done adding instructions to loop_scope, can now stack then_scope | 6735 | // 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); |
| 6981 | 6998 | ||
| 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 }; | ||
| 6990 | 7005 | ||
| 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( |
| 7077 | 7092 | ||
| 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 | } |
| 7135 | 7151 | ||
| 7136 | fn switchExprErrUnion( | 7152 | const 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) { ... }` |
| 7146 | 7162 | /// | |
| 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) { ... }` |
| 7151 | 7167 | /// `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 | } | ||
| 7633 | 7180 | ||
| 7634 | fn switchExpr( | 7181 | fn 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; |
| 7646 | 7211 | ||
| 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 a | 7229 | // 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); |
| 7660 | 7233 | ||
| 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 | } |
| 7664 | 7246 | ||
| 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 information | 7258 | // 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 | } | ||
| 7690 | 7294 | ||
| 7691 | // If the first capture is ignored, then there is no runtime-known | 7295 | 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, the | 7297 | } |
| 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 | } |
| 7700 | 7300 | ||
| 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 | } |
| 7722 | 7316 | ||
| 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; | ||
| 7762 | 7362 | ||
| 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 | } |
| 7772 | 7370 | ||
| 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 }; | ||
| 7782 | 7375 | ||
| 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 |
| 7785 | 7378 | // 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; |
| 7808 | 7418 | ||
| 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 instructions | 7425 | // 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); | ||
| 7813 | 7427 | ||
| 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; | ||
| 7819 | 7524 | ||
| 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 | else | 7531 | else |
| 7826 | .{ .coerced_ty = raw_operand_ty_ref }, | 7532 | .{ .coerced_ty = switch_block.toRef() }, |
| 7827 | }; | 7533 | }; |
| 7534 | switch_block_inst_is_occupied = true; | ||
| 7828 | 7535 | ||
| 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 | }; | ||
| 7836 | 7562 | ||
| 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 | }; | ||
| 7840 | 7571 | ||
| 7841 | // If any prong has an inline tag capture, allocate a shared dummy instruction for it | 7572 | 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 | } | ||
| 7854 | 7669 | ||
| 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).?; |
| 7860 | 7676 | ||
| 7861 | const is_multi_case = case.ast.values.len > 1 or | 7677 | 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 | ||
| 7863 | 7753 | ||
| 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; | ||
| 7871 | 7762 | ||
| 7872 | var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; | 7763 | var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; |
| 7873 | 7764 | ||
| 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); |
| 7878 | 7786 | ||
| ... | @@ -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 | } |
| 7903 | 7813 | ||
| 7904 | const tag_token = if (tree.tokenTag(ident + 1) == .comma) | 7814 | if (is_err_switch and capture == .by_ref) { |
| 7905 | ident + 2 | 7815 | return astgen.failTok(ident, "error set cannot be captured by reference", .{}); |
| 7906 | else | 7816 | } |
| 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; |
| 7919 | 7835 | ||
| 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 | }; |
| 7932 | 7852 | ||
| 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 | }; | ||
| 8005 | 7858 | ||
| 8006 | { | 7859 | prong_body: { |
| 8007 | // temporarily stack case_scope on parent_gz | 7860 | 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(); | ||
| 8010 | 7862 | ||
| 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 | } |
| 8023 | 7894 | ||
| 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); | ||
| 8037 | 7966 | ||
| 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); |
| 8044 | 7973 | ||
| 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 |
| 8049 | 7982 | @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; | ||
| 8061 | 8003 | ||
| 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)); | ||
| 8065 | 8013 | ||
| 8066 | if (any_has_tag_capture) { | 8014 | const extra_payloads_start = astgen.extra.items.len; |
| 8067 | astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst)); | ||
| 8068 | } | ||
| 8069 | 8015 | ||
| 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]); |
| 8072 | 8018 | ||
| 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 | } |
| 8120 | 8061 | ||
| 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 already | 8330 | 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; |
| 8394 | 8338 | ||
| 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 ambiguous | 8342 | // 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( |
| 8418 | 8360 | ||
| 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 | } |
| 11787 | 11727 | ||
| 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 runtime | 11873 | /// 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 that | 11876 | /// 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, |
| 11940 | 11917 | ||
| 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 { |
| 12002 | 11979 | ||
| 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 | } |
| 13367 | 13343 | ||
| 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 | } |
| 13453 | 13424 | ||
| 13454 | const LineColumn = struct { u32, u32 }; | 13425 | const LineColumn = struct { u32, u32 }; |
| ... | @@ -13685,10 +13656,8 @@ fn scanContainer( | ... | @@ -13685,10 +13656,8 @@ fn scanContainer( |
| 13685 | continue; | 13656 | continue; |
| 13686 | } | 13657 | } |
| 13687 | 13658 | ||
| 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 | } |
| 13730 | 13698 | ||
| 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 | } |
| 13778 | 13746 | ||
| 13747 | fn 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 into | 13760 | /// 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`. |
| 13781 | fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void { | 13762 | fn 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 | }; |
| 3294 | 3296 | ||
| 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 the | 3333 | /// prong_body: { |
| 3324 | /// captured error, or to the success value in `non_err_body`. Whether this | 3334 | /// body_inst: Inst.Index, // for each multi_prong_info.body_len |
| 3325 | /// is captured by reference or by value depends on whether the `byref` bit | 3335 | /// } |
| 3326 | /// is set for the corresponding body. `err_capture_inst` refers to the error | 3336 | /// item_body: { // for each item_info |
| 3327 | /// capture outside of the `switch`, i.e. `err` in | 3337 | /// 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, | ||
| 3333 | 3348 | ||
| 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, |
| 3342 | 3365 | ||
| 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 |
| 3345 | 3368 | // 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, | ||
| 3397 | 3371 | ||
| 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, | ||
| 3404 | 3378 | ||
| 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 | }; | ||
| 3411 | 3384 | ||
| 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, | ||
| 3425 | 3390 | ||
| 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 | }; |
| 3428 | 3399 | ||
| 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 | }; |
| 3434 | 3444 | ||
| 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 | }; |
| 4006 | 4016 | ||
| 4007 | pub 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 | |||
| 4070 | pub const DeclIterator = struct { | 4017 | pub 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 | }, | ||
| 4847 | 4831 | ||
| 4848 | .suspend_block => @panic("TODO iterate suspend block"), | 4832 | .suspend_block => @panic("TODO iterate suspend block"), |
| 4849 | 4833 | ||
| ... | @@ -4890,119 +4874,6 @@ fn findTrackableInner( | ... | @@ -4890,119 +4874,6 @@ fn findTrackableInner( |
| 4890 | } | 4874 | } |
| 4891 | } | 4875 | } |
| 4892 | 4876 | ||
| 4893 | fn 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 | |||
| 5006 | fn findTrackableBody( | 4877 | fn 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 | } |
| 5339 | 5210 | ||
| 5211 | pub 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 | /// } | ||
| 5325 | pub 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. |
| 5342 | pub const inst_tracking_version = 0; | 5448 | pub const inst_tracking_version = 0; |
src/Air/Liveness.zig+23-23| ... | @@ -825,10 +825,10 @@ fn analyzeOperands( | ... | @@ -825,10 +825,10 @@ fn analyzeOperands( |
| 825 | 825 | ||
| 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 | }; |
| 834 | 834 | ||
| ... | @@ -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)); |
| 850 | 850 | ||
| 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 | }, |
| 989 | 989 | ||
| 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 noreturn | 992 | // 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 | } |
| 1002 | 1002 | ||
| 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); |
| 1005 | 1005 | ||
| 1006 | // If the block is noreturn, block deaths not only aren't useful, they're impossible to | 1006 | // 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-set | 1028 | 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) }); |
| 1068 | 1068 | ||
| 1069 | // Now we put the live operands from the loop body in too | 1069 | // 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) }); |
| 1080 | 1080 | ||
| 1081 | try a.special.put(gpa, inst, extra_index); | 1081 | try a.special.put(gpa, inst, extra_index); |
| 1082 | 1082 | ||
| ... | @@ -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, {}); |
| 1119 | 1119 | ||
| 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) }); |
| 1121 | 1121 | ||
| 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 alive | 1123 | // 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 | } |
| 1132 | 1132 | ||
| 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 | } |
| 1135 | 1135 | ||
| 1136 | fn analyzeInstLoop( | 1136 | fn 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 | } |
| 1271 | 1271 | ||
| 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) }); |
| 1274 | 1274 | ||
| 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 sets | 1276 | data.live_set = then_live.move(); // Really the union of both live sets |
| 1277 | 1277 | ||
| 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) }); |
| 1279 | 1279 | ||
| 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 | } |
| 1402 | 1402 | ||
| 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 | } |
| 1406 | 1406 | ||
| 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(); |
| 1409 | 1409 | ||
| 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 | } |
| 1412 | 1412 | ||
| 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 { |
| 1506 | 1506 | ||
| 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 dead | 75 | // 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 | }, |
| 78 | 78 | ||
| 79 | // unary | 79 | // 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 dead | 168 | // 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) orelse | 443 | 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 }); |
| 445 | 445 | ||
| 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)); |
| 452 | 452 | ||
| 453 | const expected_live = self.loops.get(br.block_inst) orelse | 453 | 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 }); |
| 455 | 455 | ||
| 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); |
| 492 | 497 | ||
| 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 { |
| 502 | 507 | ||
| 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 | } |
| 616 | 621 | ||
| ... | @@ -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 | } |
| 636 | 641 | ||
| 637 | fn verifyMatchingLiveness(self: *Verify, block: Air.Inst.Index, live: LiveMap) Error!void { | 642 | fn 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 | } |
| 642 | 647 | ||
| 643 | fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} { | 648 | fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} { |
src/RangeSet.zig+66-66| ... | @@ -1,102 +1,92 @@ | ... | @@ -1,102 +1,92 @@ |
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const Order = std.math.Order; | ||
| 4 | |||
| 5 | const InternPool = @import("InternPool.zig"); | ||
| 6 | const Type = @import("Type.zig"); | ||
| 7 | const Value = @import("Value.zig"); | ||
| 8 | const Zcu = @import("Zcu.zig"); | ||
| 9 | const RangeSet = @This(); | 1 | const RangeSet = @This(); |
| 10 | const LazySrcLoc = Zcu.LazySrcLoc; | ||
| 11 | 2 | ||
| 12 | zcu: *Zcu, | 3 | ranges: std.ArrayList(Range), |
| 13 | ranges: std.array_list.Managed(Range), | ||
| 14 | 4 | ||
| 15 | pub const Range = struct { | 5 | pub 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 | }; |
| 20 | 10 | ||
| 21 | pub fn init(allocator: std.mem.Allocator, zcu: *Zcu) RangeSet { | 11 | pub const empty: RangeSet = .{ .ranges = .empty }; |
| 22 | return .{ | ||
| 23 | .zcu = zcu, | ||
| 24 | .ranges = std.array_list.Managed(Range).init(allocator), | ||
| 25 | }; | ||
| 26 | } | ||
| 27 | 12 | ||
| 28 | pub fn deinit(self: *RangeSet) void { | 13 | pub fn deinit(self: *RangeSet, allocator: Allocator) void { |
| 29 | self.ranges.deinit(); | 14 | self.ranges.deinit(allocator); |
| 15 | self.* = undefined; | ||
| 30 | } | 16 | } |
| 31 | 17 | ||
| 32 | pub fn add( | 18 | pub 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)); | ||
| 43 | 21 | ||
| 44 | for (self.ranges.items) |range| { | 22 | pub 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)); |
| 47 | 25 | ||
| 48 | if (Value.fromInterned(last).compareScalar(.gte, Value.fromInterned(range.first), Type.fromInterned(ty), zcu) and | 26 | 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 | } |
| 54 | 33 | 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 | } |
| 62 | 36 | ||
| 63 | /// Assumes a and b do not overlap | 37 | pub fn add(set: *RangeSet, allocator: Allocator, new: Range, ty: Type, zcu: *Zcu) Allocator.Error!?LazySrcLoc { |
| 64 | fn 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 | } |
| 68 | 41 | ||
| 69 | pub fn spans(self: *RangeSet, first: InternPool.Index, last: InternPool.Index) !bool { | 42 | const 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 | }; |
| 73 | 46 | /// Assumes a and b do not overlap | |
| 74 | if (self.ranges.items.len == 0) | 47 | fn lessThan(ctx: SortCtx, a: Range, b: Range) bool { |
| 75 | return false; | 48 | return a.first.compareScalar(.lt, b.first, ctx.ty, ctx.zcu); |
| 76 | 49 | } | |
| 77 | std.mem.sort(Range, self.ranges.items, zcu, lessThan); | ||
| 78 | 50 | ||
| 79 | if (self.ranges.items[0].first != first or | 51 | pub 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 | } |
| 84 | 70 | ||
| 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); | ||
| 86 | 77 | ||
| 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(); | ||
| 89 | 79 | ||
| 90 | // look for gaps | 80 | // 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]; |
| 94 | 84 | ||
| 95 | // prev.last + 1 == cur.first | 85 | // 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); |
| 98 | 88 | ||
| 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) ! |
| 104 | 94 | ||
| 105 | return true; | 95 | return true; |
| 106 | } | 96 | } |
| 97 | |||
| 98 | const std = @import("std"); | ||
| 99 | const assert = std.debug.assert; | ||
| 100 | const Allocator = std.mem.Allocator; | ||
| 101 | |||
| 102 | const InternPool = @import("InternPool.zig"); | ||
| 103 | const Type = @import("Type.zig"); | ||
| 104 | const Value = @import("Value.zig"); | ||
| 105 | const Zcu = @import("Zcu.zig"); | ||
| 106 | const 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 |
| 6498 | 6496 | ||
| 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 failure | 6500 | else => unreachable, // assertion failure |
| 6502 | } | 6501 | } |
| 6503 | 6502 | ||
| 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); |
| 6511 | 6506 | ||
| 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 the | 6509 | // 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 the | 6510 | // `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 merge | 6511 | // 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. |
| 6518 | 6513 | ||
| 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 | } |
| 6537 | 6531 | ||
| ... | @@ -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 | |||
| 8486 | fn 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; | ||
| 8490 | 8496 | ||
| 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); |
| 8961 | 8967 | ||
| 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: |
| 8997 | 9004 | ||
| 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 | } |
| 10521 | 10528 | ||
| 10522 | /// Holds common data used when analyzing or resolving switch prong bodies, | 10529 | fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 10523 | /// including setting up captures. | 10530 | const tracy = trace(@src()); |
| 10524 | const 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 | ); | ||
| 10591 | 10532 | ||
| 10592 | // We can propagate `.cold` hints from this branch since it's comptime-known | 10533 | 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; | ||
| 10596 | 10536 | ||
| 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)); | ||
| 10602 | 10541 | ||
| 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 | }, | ||
| 10607 | 10543 | ||
| 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); | ||
| 10617 | 10563 | ||
| 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 | } | ||
| 10622 | 10597 | ||
| 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); | ||
| 10625 | 10602 | ||
| 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 | } | ||
| 10630 | 10608 | ||
| 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; | ||
| 10653 | 10616 | ||
| 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 | }; | ||
| 10659 | 10623 | ||
| 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); | ||
| 10664 | 10629 | ||
| 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 | ); | ||
| 10674 | 10636 | ||
| 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 | } | ||
| 10679 | 10638 | ||
| 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 | }; | ||
| 10682 | 10645 | ||
| 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 | } |
| 10687 | 10652 | ||
| 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 | } |
| 10718 | 10663 | ||
| 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 | }); |
| 10732 | 10677 | 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; | ||
| 10735 | 10679 | ||
| 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 | } } }); | ||
| 10737 | 10684 | ||
| 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 | }; | ||
| 10750 | 10687 | ||
| 10751 | const operand_ty = sema.typeOf(operand_val); | 10688 | fn 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); | ||
| 10753 | 10697 | ||
| 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 | } | ||
| 10786 | 10717 | ||
| 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 | } | ||
| 10791 | 10726 | ||
| 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 { | 10730 | fn 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; | ||
| 10802 | 10744 | ||
| 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; | ||
| 10807 | 10748 | ||
| 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; |
| 10810 | 10751 | ||
| 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 | } | ||
| 10816 | 10753 | ||
| 10817 | // Fast path: if all the operands are the same type already, we don't need to hit | 10754 | 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; | ||
| 10823 | 10759 | ||
| 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 | }; | ||
| 10831 | 10773 | ||
| 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 | }; | ||
| 10843 | 10798 | ||
| 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 | }; | ||
| 10853 | 10800 | ||
| 10854 | // By-reference captures have some further restrictions which make them easier to emit | 10801 | 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 | } | ||
| 10886 | 10803 | ||
| 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 | }; | ||
| 10897 | 10810 | ||
| 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 | } | ||
| 10903 | 10815 | ||
| 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; | ||
| 10907 | 10820 | ||
| 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 | } | ||
| 10915 | 10824 | ||
| 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; | ||
| 10917 | 10829 | ||
| 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 }; | ||
| 10921 | 10862 | ||
| 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 | }; | ||
| 10936 | 10867 | ||
| 10937 | // By-val capture with heterogeneous types which are not all in-memory coercible to | 10868 | 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 | } | ||
| 10939 | 10879 | ||
| 10940 | // However, let's first track which operands are in-memory coercible. There may well | 10880 | if (child_block.isComptime()) { |
| 10941 | // be several, and we can squash all of these cases into the same switch prong using | 10881 | _ = 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 | } | ||
| 10943 | 10884 | ||
| 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 | }; | ||
| 10955 | 10931 | ||
| 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); | ||
| 10985 | 10934 | ||
| 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)); | ||
| 10994 | 10992 | ||
| 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; |
| 11000 | 10998 | 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); | ||
| 11017 | 10999 | ||
| 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); | ||
| 11024 | 11002 | ||
| 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 | }; | ||
| 11028 | 11005 | ||
| 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), |
| 11033 | 11010 | }); | |
| 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 | } | ||
| 11078 | 11012 | ||
| 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 | } | ||
| 11090 | 11037 | ||
| 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 | } | ||
| 11096 | 11039 | ||
| 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 value | 11050 | ); |
| 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 | }; | ||
| 11118 | 11053 | ||
| 11119 | fn switchCond( | 11054 | fn 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 | }, | ||
| 11150 | 11070 | ||
| 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 | }, | ||
| 11166 | 11074 | ||
| 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 | } | ||
| 11181 | 11077 | ||
| 11182 | const SwitchErrorSet = std.AutoHashMap(InternPool.NullTerminatedString, LazySrcLoc); | 11078 | const else_case = validated_switch.else_case; |
| 11183 | 11079 | ||
| 11184 | fn 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(); | ||
| 11187 | 11082 | ||
| 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 }); | ||
| 11199 | 11087 | ||
| 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 | }; | ||
| 11201 | 11092 | ||
| 11202 | // AstGen guarantees that the instruction immediately preceding | 11093 | // AstGen guarantees that the instruction immediately preceding |
| 11203 | // switch_block_err_union is a dbg_stmt | 11094 | // 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; | ||
| 11207 | 11096 | ||
| 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; | ||
| 11214 | 11100 | ||
| 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 block | 11105 | 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; | ||
| 11224 | 11107 | ||
| 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); |
| 11227 | 11110 | ||
| 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 | }; |
| 11233 | 11133 | 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); | ||
| 11243 | 11138 | ||
| 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 | }; | ||
| 11268 | 11142 | ||
| 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); | ||
| 11271 | 11150 | ||
| 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 | else | 11154 | var extra_index = zir_switch.end; |
| 11276 | operand_ty; | ||
| 11277 | 11155 | ||
| 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; | ||
| 11283 | 11162 | ||
| 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; | ||
| 11285 | 11170 | ||
| 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 | }; | ||
| 11300 | 11174 | ||
| 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); | ||
| 11322 | 11181 | ||
| 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; | ||
| 11327 | 11188 | ||
| 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 | ); | ||
| 11340 | 11191 | ||
| 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 | }; | ||
| 11355 | 11196 | ||
| 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 | else | 11200 | .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; | ||
| 11361 | 11206 | ||
| 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 | else | 11218 | 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); | ||
| 11375 | 11230 | ||
| 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; | ||
| 11406 | 11246 | ||
| 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 | } | ||
| 11417 | 11248 | ||
| 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 | }; | ||
| 11426 | 11252 | ||
| 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 regardless | 11257 | } |
| 11432 | defer sub_block.instructions.deinit(gpa); | 11258 | } |
| 11259 | } | ||
| 11433 | 11260 | ||
| 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; | ||
| 11437 | 11287 | ||
| 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 | ); | ||
| 11482 | 11289 | ||
| 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; | ||
| 11485 | 11299 | ||
| 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)); | ||
| 11507 | 11319 | ||
| 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 | } | ||
| 11510 | 11333 | ||
| 11511 | fn 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(); | ||
| 11514 | 11335 | ||
| 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); | ||
| 11526 | 11345 | ||
| 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 }; | ||
| 11533 | 11349 | ||
| 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); | ||
| 11535 | 11373 | ||
| 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 | } | ||
| 11537 | 11387 | ||
| 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 | } | ||
| 11564 | 11428 | ||
| 11565 | // We always use `simple` in the comptime case, because as far as the dispatching logic | 11429 | 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 | }; | ||
| 11577 | 11430 | ||
| 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 | }; | ||
| 11584 | 11432 | ||
| 11585 | // AstGen guarantees that the instruction immediately preceding | 11433 | if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src); |
| 11586 | // switch_block(_ref) is a dbg_stmt | 11434 | emit_bb = true; |
| 11587 | const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1); | ||
| 11588 | 11435 | ||
| 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); | ||
| 11590 | 11459 | ||
| 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 | } | ||
| 11597 | 11472 | ||
| 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; | ||
| 11607 | 11475 | ||
| 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 |
| 11610 | 11478 | 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); |
| 11614 | 11482 | 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; | ||
| 11668 | 11487 | ||
| 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. |
| 11675 | 11494 | ||
| 11676 | defer { | 11495 | assert(operand_ty.isNonexhaustiveEnum(zcu)); |
| 11677 | range_set.deinit(); | ||
| 11678 | gpa.free(seen_enum_fields); | ||
| 11679 | seen_errors.deinit(); | ||
| 11680 | } | ||
| 11681 | 11496 | ||
| 11682 | var empty_enum = false; | 11497 | cases_len += 1; |
| 11683 | 11498 | ||
| 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); | ||
| 11685 | 11522 | ||
| 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)); | ||
| 11711 | 11544 | ||
| 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. | ||
| 11720 | 11546 | ||
| 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); | ||
| 11736 | 11550 | ||
| 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 | }; | ||
| 11794 | 11575 | ||
| 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`. | ||
| 11656 | fn 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 | |||
| 11756 | const 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`. | ||
| 11873 | fn 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; |
| 11821 | 12125 | ||
| 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 | } |
| 11910 | 12195 | ||
| 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; | ||
| 11918 | 12201 | ||
| 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 | } |
| 11934 | 12213 | ||
| 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| { |
| 11938 | 12217 | 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 | } |
| 12123 | 12291 | ||
| 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 | }; | ||
| 12157 | 12299 | ||
| 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 | } | ||
| 12417 | 12305 | ||
| 12418 | // Replace placeholder with a block. | 12306 | fn 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 | |||
| 12439 | const 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 | |||
| 12447 | fn 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 | |||
| 13129 | fn 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 | |||
| 13201 | fn 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); | ||
| 13221 | 12322 | ||
| 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; |
| 13231 | 12332 | ||
| 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, |
| 13264 | 12414 | 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, |
| 13267 | 12417 | } }), | |
| 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 | } | ||
| 13289 | 12430 | ||
| 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; | ||
| 13295 | 12432 | ||
| 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 | } | ||
| 13319 | 12434 | ||
| 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 prongs | 12469 | 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 | } |
| 13346 | 12475 | 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 prongs | 12485 | 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 | } |
| 13365 | 12495 | ||
| 13366 | const RangeSetUnhandledIterator = struct { | 12496 | const 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 | }; | ||
| 13373 | 12523 | ||
| 13374 | const preallocated_limbs = math.big.int.calcTwosCompLimbCount(128); | 12524 | const CatchAllSwitchCase = enum { none, @"else", under }; |
| 13375 | 12525 | ||
| 13376 | fn init(sema: *Sema, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator { | 12526 | const 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 | } | ||
| 13392 | 12531 | ||
| 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; | 12534 | fn 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 }); | ||
| 13396 | 12555 | ||
| 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 | } | ||
| 13406 | 12560 | ||
| 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)); | ||
| 13409 | 12585 | ||
| 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)); | ||
| 13415 | 12600 | ||
| 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 | |||
| 12607 | fn 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 | } | ||
| 13419 | 12631 | ||
| 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) { | 12634 | fn 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 | }; | ||
| 13433 | 12666 | ||
| 13434 | const 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 }; |
| 13438 | fn 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 | }; | ||
| 13447 | 12693 | ||
| 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 the | 12695 | 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)); | ||
| 13451 | 12718 | ||
| 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)); | ||
| 13453 | 12733 | ||
| 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)); | ||
| 13455 | 12736 | ||
| 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; | 12740 | fn 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 | }; | ||
| 13460 | 12757 | ||
| 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 | } |
| 13463 | 12773 | ||
| 13464 | fn validateErrSetSwitch( | 12774 | fn 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; |
| 13480 | 12796 | ||
| 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; | ||
| 13492 | 12798 | ||
| 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; | ||
| 13517 | 12829 | ||
| 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 | } |
| 13532 | 13010 | ||
| 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 | }); | ||
| 13536 | 13020 | ||
| 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); | ||
| 13553 | 13033 | ||
| 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 | }; |
| 13564 | 13049 | ||
| 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); | ||
| 13573 | 13072 | ||
| 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 | }; | ||
| 13579 | 13083 | ||
| 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 bodies | 13085 | 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 | } | ||
| 13612 | 13131 | ||
| 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 | } |
| 13620 | 13143 | ||
| 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 | } | ||
| 13625 | 13149 | ||
| 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 | } |
| 13635 | 13172 | ||
| 13636 | fn validateSwitchRange( | 13173 | const 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, | 13177 | const 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 | } | ||
| 13662 | 13178 | ||
| 13663 | fn validateSwitchItemInt( | 13179 | fn 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; | ||
| 13676 | 13195 | ||
| 13677 | fn 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 |
| 13699 | 13241 | // being switched on if their prong body is `=> comptime unreachable,`. | |
| 13700 | fn 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.value | 13253 | break :item_ref try sema.coerce(block, item_ty, uncoerced, item_src); |
| 13712 | else | 13254 | }; |
| 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 | } | ||
| 13717 | 13256 | ||
| 13718 | fn 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 | } | ||
| 13741 | 13259 | ||
| 13742 | fn 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 | } |
| 13761 | 13267 | ||
| 13762 | const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc); | 13268 | fn validateSwitchItemOrRange( |
| 13763 | |||
| 13764 | fn 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), |
| 13777 | 13278 | seen_sparse_values: *std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc), | |
| 13778 | fn 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; |
| 13787 | 13286 | 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, |
| 13790 | 13289 | .@"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 | } |
| 13808 | 13370 | ||
| 13809 | fn maybeErrorUnwrap( | 13371 | fn 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-known | 18253 | // 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; |
| 18696 | 18257 | ||
| 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-known | 18316 | // 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; |
| 18760 | 18320 | ||
| 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 | } |
| 31800 | 31360 | ||
| 31801 | fn analyzePtrIsNonErrComptimeOnly( | 31361 | fn 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); |
| 31812 | 31372 | ||
| 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); |
| 31817 | 31377 | ||
| 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 | } | ||
| 31820 | 31385 | ||
| 31821 | return .none; | 31386 | return null; |
| 31822 | } | 31387 | } |
| 31823 | 31388 | ||
| 31824 | fn analyzeIsNonErrComptimeOnly( | 31389 | fn 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 | |||
| 31409 | fn 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); |
| 31838 | 31422 | ||
| 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 | } |
| 31850 | 31427 | ||
| 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 | } |
| 31867 | 31442 | ||
| 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 error | 31452 | // We do not have a comptime answer because this inferred error |
| 31881 | // set is not resolved, and an instruction later in this function | 31453 | // 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 error | 31483 | // We do not have a comptime answer because this inferred error |
| 31913 | // set is not resolved, and an instruction later in this function | 31484 | // 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 | } |
| 31927 | 31498 | ||
| 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 | } |
| 31933 | 31501 | ||
| 31934 | fn analyzeIsNonErr( | 31502 | fn 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 | } |
| 31948 | 31514 | ||
| ... | @@ -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 | } |
| 31963 | 31527 |
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 | } |
| 1935 | 1935 | ||
| 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`. |
| 1942 | pub fn childType(ty: Type, zcu: *const Zcu) Type { | 1942 | pub 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 | } |
| 1949 | 1949 | ||
| 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`. |
| 1959 | pub fn elemType2(ty: Type, zcu: *const Zcu) Type { | 1959 | pub 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 | } |
| 1127 | 1127 | ||
| 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 | } |
| 1137 | 1137 | ||
| 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 | }, |
| 1744 | 1744 | ||
| 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 { |
| 2176 | 2155 | ||
| 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 | }; | ||
| 2199 | 2166 | ||
| 2200 | const is_multi = case.ast.values.len != 1 or | 2167 | 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; |
| 2202 | 2169 | ||
| 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 | } |
| 2209 | 2176 | ||
| ... | @@ -2214,12 +2181,12 @@ pub const SrcLoc = struct { | ... | @@ -2214,12 +2181,12 @@ pub const SrcLoc = struct { |
| 2214 | } | 2181 | } |
| 2215 | } else unreachable; | 2182 | } else unreachable; |
| 2216 | 2183 | ||
| 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 | }; |
| 2244 | 2211 | ||
| 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 or | 2216 | 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, found | 2414 | /// 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 the | 2416 | /// 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 | }; |
| 2649 | 2615 | ||
| 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 | }; |
| 2669 | 2622 | ||
| 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 { |
| 6432 | 6432 | ||
| 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 | } |
| 6597 | 6597 | ||
| 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 | } |
| 6624 | 6628 |
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); |
| 4119 | 4122 | ||
| 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 { |
| 447 | 447 | ||
| 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), |
| 451 | 452 | ||
| 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 | } |
| 1989 | 1988 | ||
| 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, |
| 1993 | 1992 | inst: Zir.Inst.Index, | |
| 1994 | var extra_index: usize = extra.end; | 1993 | ) !void { |
| 1995 | 1994 | 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); | ||
| 2009 | 1996 | ||
| 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 | } | ||
| 2014 | 1998 | ||
| 2015 | self.indent += 2; | 1999 | self.indent += 2; |
| 2016 | 2000 | ||
| 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; | ||
| 2024 | 2003 | ||
| 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 | } | ||
| 2030 | 2006 | ||
| 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; | ||
| 2042 | 2008 | ||
| 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; | ||
| 2144 | 2017 | ||
| 2145 | try self.writeInstRef(stream, extra.data.operand); | 2018 | try self.writeSwitchCaptures(stream, else_case.capture, else_case.has_tag_capture, inst, &zir_switch); |
| 2146 | 2019 | 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; | ||
| 2167 | 2020 | ||
| 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 | } |
| 2173 | 2024 | ||
| 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; | ||
| 2217 | 2029 | ||
| 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 | } | ||
| 2223 | 2033 | ||
| 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 | } | ||
| 2229 | 2036 | ||
| 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; | ||
| 2240 | 2039 | ||
| 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 |
| 2265 | 2069 | .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 | } |
| 2279 | 2075 | 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| { |
| 2286 | 2082 | 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 | } |
| 2301 | 2096 | ||
| 2302 | self.indent -= 2; | 2097 | self.indent -= 2; |
| 2303 | 2098 | ||
| 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 | } |
| 2307 | 2124 | ||
| 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 | |||
| 528 | test "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 | }; |
| 515 | 515 | ||
| 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 | |||
| 1124 | test "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 | ||
| 1145 | test "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 | |||
| 1178 | test "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 | |||
| 1230 | test "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 | |||
| 1275 | test "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 | |||
| 1283 | test "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 | |||
| 1294 | test "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 @@ |
| 1 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | const assert = std.debug.assert; | ||
| 3 | const expect = std.testing.expect; | 4 | const expect = std.testing.expect; |
| 4 | 5 | ||
| 5 | test "simple switch loop" { | 6 | test "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 | |||
| 275 | test "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 | |||
| 302 | test "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 | |||
| 351 | test "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 | |||
| 399 | test "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 | |||
| 469 | test "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 | } |
| 23 | 25 | ||
| ... | @@ -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 | } |
| 258 | 305 | ||
| 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 | } |
| 323 | 396 | ||
| ... | @@ -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 | } |
| 773 | 904 | ||
| 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 | } |
| 793 | 981 | ||
| 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 | |||
| 403 | test "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 @@ | ||
| 1 | export fn entry1() void { | ||
| 2 | var x: u32 = 0; | ||
| 3 | result: while (x < 5) : (x += 1) {} else { | ||
| 4 | continue :result; | ||
| 5 | } | ||
| 6 | } | ||
| 7 | |||
| 8 | export 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 | // error | 18 | // error |
| 19 | // | 19 | // |
| 20 | // :5:9: error: duplicate switch value | 20 | // :5:9: error: duplicate switch value |
| 21 | // :3:9: note: previous value here | ||
| 21 | // :13:9: error: duplicate switch value | 22 | // :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 { |
| 36 | 36 | ||
| 37 | // error | 37 | // 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 here | 40 | // :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 here | 42 | // :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 here | 44 | // :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 here | 46 | // :1:11: note: enum declared here |
test/cases/compile_errors/labeled_block_continue.zig created+10| ... | @@ -0,0 +1,10 @@ | ||
| 1 | export 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 @@ | ||
| 1 | export 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 @@ |
| 1 | const Error = error{M}; | 1 | const Error = error{M}; |
| 2 | 2 | ||
| 3 | export fn entry() void { | 3 | export 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 | } |
| 7 | 8 | ||
| 8 | export fn entry2() void { | 9 | export fn entry2() void { |
| 9 | const f: Error!void = void{}; | 10 | var f: Error!void = {}; |
| 11 | _ = &f; | ||
| 12 | f catch |e| switch (e) {}; | ||
| 13 | } | ||
| 14 | |||
| 15 | export fn entry3() void { | ||
| 16 | const f: Error!void = error.M; | ||
| 17 | if (f) {} else |e| switch (e) {} | ||
| 18 | } | ||
| 19 | |||
| 20 | export fn entry4() void { | ||
| 21 | const f: Error!void = error.M; | ||
| 10 | f catch |e| switch (e) {}; | 22 | f catch |e| switch (e) {}; |
| 11 | } | 23 | } |
| 12 | 24 | ||
| 13 | // error | 25 | // error |
| 14 | // | 26 | // |
| 15 | // :5:24: error: switch must handle all possibilities | 27 | // :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 possibilities | 29 | // :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 | } |
| 6 | 6 | ||
| 7 | // error | 7 | // 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 @@ | ||
| 1 | const E = enum { a, b, c, d }; | ||
| 2 | pub 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 | ||