| ... | ... | @@ -1015,10 +1015,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 1015 | 1015 | }, |
| 1016 | 1016 | .@"catch" => { |
| 1017 | 1017 | const catch_token = main_tokens[node]; |
| 1018 | | const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) |
| 1019 | | catch_token + 2 |
| 1020 | | else |
| 1021 | | null; |
| 1018 | const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) blk: { |
| 1019 | if (token_tags.len > catch_token + 6 and |
| 1020 | token_tags[catch_token + 4] == .keyword_switch) |
| 1021 | { |
| 1022 | if (std.mem.eql(u8, tree.tokenSlice(catch_token + 2), tree.tokenSlice(catch_token + 6))) { |
| 1023 | return switchExprErrUnion(gz, scope, ri.br(), node); |
| 1024 | } |
| 1025 | } |
| 1026 | break :blk catch_token + 2; |
| 1027 | } else null; |
| 1022 | 1028 | switch (ri.rl) { |
| 1023 | 1029 | .ref, .ref_coerced_ty => return orelseCatchExpr( |
| 1024 | 1030 | gz, |
| ... | ... | @@ -6859,6 +6865,387 @@ fn forExpr( |
| 6859 | 6865 | return result; |
| 6860 | 6866 | } |
| 6861 | 6867 | |
| 6868 | fn switchExprErrUnion( |
| 6869 | parent_gz: *GenZir, |
| 6870 | scope: *Scope, |
| 6871 | ri: ResultInfo, |
| 6872 | catch_node: Ast.Node.Index, |
| 6873 | ) InnerError!Zir.Inst.Ref { |
| 6874 | const astgen = parent_gz.astgen; |
| 6875 | const gpa = astgen.gpa; |
| 6876 | const tree = astgen.tree; |
| 6877 | const node_datas = tree.nodes.items(.data); |
| 6878 | const node_tags = tree.nodes.items(.tag); |
| 6879 | const main_tokens = tree.nodes.items(.main_token); |
| 6880 | const token_tags = tree.tokens.items(.tag); |
| 6881 | const operand_node = node_datas[catch_node].lhs; |
| 6882 | const switch_node = node_datas[catch_node].rhs; |
| 6883 | const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange); |
| 6884 | const case_nodes = tree.extra_data[extra.start..extra.end]; |
| 6885 | |
| 6886 | const need_rl = astgen.nodes_need_rl.contains(catch_node); |
| 6887 | const block_ri: ResultInfo = if (need_rl) ri else .{ |
| 6888 | .rl = switch (ri.rl) { |
| 6889 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_node)).? }, |
| 6890 | .inferred_ptr => .none, |
| 6891 | else => ri.rl, |
| 6892 | }, |
| 6893 | .ctx = ri.ctx, |
| 6894 | }; |
| 6895 | |
| 6896 | // We need to call `rvalue` to write through to the pointer only if we had a |
| 6897 | // result pointer and aren't forwarding it. |
| 6898 | const LocTag = @typeInfo(ResultInfo.Loc).Union.tag_type.?; |
| 6899 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); |
| 6900 | var scalar_cases_len: u32 = 0; |
| 6901 | var multi_cases_len: u32 = 0; |
| 6902 | var inline_cases_len: u32 = 0; |
| 6903 | var has_else = false; |
| 6904 | var else_node: Ast.Node.Index = 0; |
| 6905 | var else_src: ?Ast.TokenIndex = null; |
| 6906 | for (case_nodes) |case_node| { |
| 6907 | const case = tree.fullSwitchCase(case_node).?; |
| 6908 | |
| 6909 | if (case.ast.values.len == 0) { |
| 6910 | const case_src = case.ast.arrow_token - 1; |
| 6911 | if (else_src) |src| { |
| 6912 | return astgen.failTokNotes( |
| 6913 | case_src, |
| 6914 | "multiple else prongs in switch expression", |
| 6915 | .{}, |
| 6916 | &[_]u32{ |
| 6917 | try astgen.errNoteTok( |
| 6918 | src, |
| 6919 | "previous else prong here", |
| 6920 | .{}, |
| 6921 | ), |
| 6922 | }, |
| 6923 | ); |
| 6924 | } |
| 6925 | has_else = true; |
| 6926 | else_node = case_node; |
| 6927 | else_src = case_src; |
| 6928 | continue; |
| 6929 | } else if (case.ast.values.len == 1 and |
| 6930 | node_tags[case.ast.values[0]] == .identifier and |
| 6931 | mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_")) |
| 6932 | { |
| 6933 | const case_src = case.ast.arrow_token - 1; |
| 6934 | return astgen.failTokNotes( |
| 6935 | case_src, |
| 6936 | "'_' prong is not allowed when switching on errors", |
| 6937 | .{}, |
| 6938 | &[_]u32{ |
| 6939 | try astgen.errNoteTok( |
| 6940 | case_src, |
| 6941 | "consider using 'else'", |
| 6942 | .{}, |
| 6943 | ), |
| 6944 | }, |
| 6945 | ); |
| 6946 | } |
| 6947 | |
| 6948 | for (case.ast.values) |val| { |
| 6949 | if (node_tags[val] == .string_literal) |
| 6950 | return astgen.failNode(val, "cannot switch on strings", .{}); |
| 6951 | } |
| 6952 | |
| 6953 | if (case.ast.values.len == 1 and node_tags[case.ast.values[0]] != .switch_range) { |
| 6954 | scalar_cases_len += 1; |
| 6955 | } else { |
| 6956 | multi_cases_len += 1; |
| 6957 | } |
| 6958 | if (case.inline_token != null) { |
| 6959 | inline_cases_len += 1; |
| 6960 | } |
| 6961 | } |
| 6962 | |
| 6963 | const operand_ri: ResultInfo = .{ .rl = .none, .ctx = .error_handling_expr }; |
| 6964 | |
| 6965 | astgen.advanceSourceCursorToNode(operand_node); |
| 6966 | const operand_lc = LineColumn{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; |
| 6967 | |
| 6968 | const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, node_datas[catch_node].rhs); |
| 6969 | const item_ri: ResultInfo = .{ .rl = .none }; |
| 6970 | |
| 6971 | // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except |
| 6972 | // the first cases_nodes.len slots are a table that indexes payloads later in the array, |
| 6973 | // with the non-error and else case indices coming first, then scalar_cases_len indexes, then |
| 6974 | // multi_cases_len indexes |
| 6975 | const payloads = &astgen.scratch; |
| 6976 | const scratch_top = astgen.scratch.items.len; |
| 6977 | const case_table_start = scratch_top; |
| 6978 | const scalar_case_table = case_table_start + 1 + @intFromBool(has_else); |
| 6979 | const multi_case_table = scalar_case_table + scalar_cases_len; |
| 6980 | const case_table_end = multi_case_table + multi_cases_len; |
| 6981 | |
| 6982 | try astgen.scratch.resize(gpa, case_table_end); |
| 6983 | defer astgen.scratch.items.len = scratch_top; |
| 6984 | |
| 6985 | var block_scope = parent_gz.makeSubBlock(scope); |
| 6986 | // block_scope not used for collecting instructions |
| 6987 | block_scope.instructions_top = GenZir.unstacked_top; |
| 6988 | block_scope.setBreakResultInfo(block_ri); |
| 6989 | |
| 6990 | // Sema expects a dbg_stmt immediately before switch_block_err_union |
| 6991 | try emitDbgStmt(parent_gz, operand_lc); |
| 6992 | // This gets added to the parent block later, after the item expressions. |
| 6993 | const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node); |
| 6994 | |
| 6995 | // We re-use this same scope for all cases, including the special prong, if any. |
| 6996 | var case_scope = parent_gz.makeSubBlock(&block_scope.base); |
| 6997 | case_scope.instructions_top = GenZir.unstacked_top; |
| 6998 | |
| 6999 | { |
| 7000 | const body_len_index: u32 = @intCast(payloads.items.len); |
| 7001 | payloads.items[case_table_start] = body_len_index; |
| 7002 | try payloads.resize(gpa, body_len_index + 1); // body_len |
| 7003 | |
| 7004 | case_scope.instructions_top = parent_gz.instructions.items.len; |
| 7005 | defer case_scope.unstack(); |
| 7006 | |
| 7007 | try case_scope.addDbgBlockBegin(); |
| 7008 | |
| 7009 | const unwrapped_payload = try case_scope.addUnNode(.err_union_payload_unsafe, raw_operand, catch_node); |
| 7010 | const case_result = switch (ri.rl) { |
| 7011 | .ref, .ref_coerced_ty => unwrapped_payload, |
| 7012 | else => try rvalue(&case_scope, block_scope.break_result_info, unwrapped_payload, catch_node), |
| 7013 | }; |
| 7014 | try case_scope.addDbgBlockEnd(); |
| 7015 | _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, catch_node); |
| 7016 | |
| 7017 | const case_slice = case_scope.instructionsSlice(); |
| 7018 | // Since we use the switch_block_err_union instruction itself to refer |
| 7019 | // to the capture, which will not be added to the child block, we need |
| 7020 | // to handle ref_table manually, and the same for the inline tag |
| 7021 | // capture instruction. |
| 7022 | const refs_len = refs: { |
| 7023 | var n: usize = 0; |
| 7024 | var check_inst = switch_block; |
| 7025 | while (astgen.ref_table.get(check_inst)) |ref_inst| { |
| 7026 | n += 1; |
| 7027 | check_inst = ref_inst; |
| 7028 | } |
| 7029 | break :refs n; |
| 7030 | }; |
| 7031 | const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice); |
| 7032 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| 7033 | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ |
| 7034 | .body_len = @intCast(body_len), |
| 7035 | .capture = .none, |
| 7036 | .is_inline = false, |
| 7037 | .has_tag_capture = false, |
| 7038 | }); |
| 7039 | if (astgen.ref_table.fetchRemove(switch_block)) |kv| { |
| 7040 | appendPossiblyRefdBodyInst(astgen, payloads, kv.value); |
| 7041 | } |
| 7042 | appendBodyWithFixupsArrayList(astgen, payloads, case_slice); |
| 7043 | } |
| 7044 | |
| 7045 | const err_name, const error_payload = blk: { |
| 7046 | const error_payload = main_tokens[catch_node] + 2; |
| 7047 | const err_str = tree.tokenSlice(error_payload); |
| 7048 | if (mem.eql(u8, err_str, "_")) { |
| 7049 | return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{}); |
| 7050 | } |
| 7051 | const err_name = try astgen.identAsString(error_payload); |
| 7052 | try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture); |
| 7053 | |
| 7054 | break :blk .{ err_name, error_payload }; |
| 7055 | }; |
| 7056 | |
| 7057 | // In this pass we generate all the item and prong expressions for error cases. |
| 7058 | var multi_case_index: u32 = 0; |
| 7059 | var scalar_case_index: u32 = 0; |
| 7060 | for (case_nodes) |case_node| { |
| 7061 | const case = tree.fullSwitchCase(case_node).?; |
| 7062 | |
| 7063 | const is_multi_case = case.ast.values.len > 1 or |
| 7064 | (case.ast.values.len == 1 and node_tags[case.ast.values[0]] == .switch_range); |
| 7065 | |
| 7066 | var dbg_var_name: ?u32 = null; |
| 7067 | var dbg_var_inst: Zir.Inst.Ref = undefined; |
| 7068 | var err_scope: Scope.LocalVal = undefined; |
| 7069 | var tag_scope: Scope.LocalVal = undefined; |
| 7070 | |
| 7071 | const sub_scope = blk: { |
| 7072 | const tag_token = case.payload_token orelse break :blk &case_scope.base; |
| 7073 | assert(token_tags[tag_token] == .identifier); |
| 7074 | |
| 7075 | const tag_slice = tree.tokenSlice(tag_token); |
| 7076 | if (mem.eql(u8, tag_slice, "_")) { |
| 7077 | return astgen.failTok(tag_token, "discard of error capture; omit it instead", .{}); |
| 7078 | } |
| 7079 | const tag_name = try astgen.identAsString(tag_token); |
| 7080 | try astgen.detectLocalShadowing(&case_scope.base, tag_name, tag_token, tag_slice, .capture); |
| 7081 | |
| 7082 | tag_scope = .{ |
| 7083 | .parent = &case_scope.base, |
| 7084 | .gen_zir = &case_scope, |
| 7085 | .name = tag_name, |
| 7086 | .inst = switch_block.toRef(), |
| 7087 | .token_src = tag_token, |
| 7088 | .id_cat = .capture, |
| 7089 | }; |
| 7090 | dbg_var_name = tag_name; |
| 7091 | dbg_var_inst = switch_block.toRef(); |
| 7092 | |
| 7093 | break :blk &tag_scope.base; |
| 7094 | }; |
| 7095 | |
| 7096 | const header_index: u32 = @intCast(payloads.items.len); |
| 7097 | const body_len_index = if (is_multi_case) blk: { |
| 7098 | payloads.items[multi_case_table + multi_case_index] = header_index; |
| 7099 | multi_case_index += 1; |
| 7100 | try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len |
| 7101 | |
| 7102 | // items |
| 7103 | var items_len: u32 = 0; |
| 7104 | for (case.ast.values) |item_node| { |
| 7105 | if (node_tags[item_node] == .switch_range) continue; |
| 7106 | items_len += 1; |
| 7107 | |
| 7108 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node); |
| 7109 | try payloads.append(gpa, @intFromEnum(item_inst)); |
| 7110 | } |
| 7111 | |
| 7112 | // ranges |
| 7113 | var ranges_len: u32 = 0; |
| 7114 | for (case.ast.values) |range| { |
| 7115 | if (node_tags[range] != .switch_range) continue; |
| 7116 | ranges_len += 1; |
| 7117 | |
| 7118 | const first = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].lhs); |
| 7119 | const last = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].rhs); |
| 7120 | try payloads.appendSlice(gpa, &[_]u32{ |
| 7121 | @intFromEnum(first), @intFromEnum(last), |
| 7122 | }); |
| 7123 | } |
| 7124 | |
| 7125 | payloads.items[header_index] = items_len; |
| 7126 | payloads.items[header_index + 1] = ranges_len; |
| 7127 | break :blk header_index + 2; |
| 7128 | } else if (case_node == else_node) blk: { |
| 7129 | payloads.items[case_table_start + 1] = header_index; |
| 7130 | try payloads.resize(gpa, header_index + 1); // body_len |
| 7131 | break :blk header_index; |
| 7132 | } else blk: { |
| 7133 | payloads.items[scalar_case_table + scalar_case_index] = header_index; |
| 7134 | scalar_case_index += 1; |
| 7135 | try payloads.resize(gpa, header_index + 2); // item, body_len |
| 7136 | const item_node = case.ast.values[0]; |
| 7137 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node); |
| 7138 | payloads.items[header_index] = @intFromEnum(item_inst); |
| 7139 | break :blk header_index + 1; |
| 7140 | }; |
| 7141 | |
| 7142 | { |
| 7143 | // temporarily stack case_scope on parent_gz |
| 7144 | case_scope.instructions_top = parent_gz.instructions.items.len; |
| 7145 | defer case_scope.unstack(); |
| 7146 | |
| 7147 | const err_code_inst = try case_scope.addUnNode(.err_union_code, raw_operand, operand_node); |
| 7148 | err_scope = .{ |
| 7149 | .parent = sub_scope, |
| 7150 | .gen_zir = &case_scope, |
| 7151 | .name = err_name, |
| 7152 | .inst = err_code_inst, |
| 7153 | .token_src = error_payload, |
| 7154 | .id_cat = .capture, |
| 7155 | }; |
| 7156 | |
| 7157 | try case_scope.addDbgBlockBegin(); |
| 7158 | if (dbg_var_name) |some| { |
| 7159 | try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst); |
| 7160 | } |
| 7161 | const target_expr_node = case.ast.target_expr; |
| 7162 | const case_result = try expr(&case_scope, &err_scope.base, block_scope.break_result_info, target_expr_node); |
| 7163 | // check sub_scope, not err_scope to avoid false positive unused error capture |
| 7164 | try checkUsed(parent_gz, &case_scope.base, sub_scope); |
| 7165 | try case_scope.addDbgBlockEnd(); |
| 7166 | if (!parent_gz.refIsNoReturn(case_result)) { |
| 7167 | _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); |
| 7168 | } |
| 7169 | |
| 7170 | const case_slice = case_scope.instructionsSlice(); |
| 7171 | // Since we use the switch_block instruction itself to refer to the |
| 7172 | // capture, which will not be added to the child block, we need to |
| 7173 | // handle ref_table manually, and the same for the inline tag |
| 7174 | // capture instruction. |
| 7175 | const refs_len = refs: { |
| 7176 | var n: usize = 0; |
| 7177 | var check_inst = switch_block; |
| 7178 | while (astgen.ref_table.get(check_inst)) |ref_inst| { |
| 7179 | n += 1; |
| 7180 | check_inst = ref_inst; |
| 7181 | } |
| 7182 | break :refs n; |
| 7183 | }; |
| 7184 | const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice); |
| 7185 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| 7186 | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ |
| 7187 | .body_len = @intCast(body_len), |
| 7188 | .capture = if (case.payload_token != null) .by_val else .none, |
| 7189 | .is_inline = case.inline_token != null, |
| 7190 | .has_tag_capture = false, |
| 7191 | }); |
| 7192 | if (astgen.ref_table.fetchRemove(switch_block)) |kv| { |
| 7193 | appendPossiblyRefdBodyInst(astgen, payloads, kv.value); |
| 7194 | } |
| 7195 | appendBodyWithFixupsArrayList(astgen, payloads, case_slice); |
| 7196 | } |
| 7197 | } |
| 7198 | // Now that the item expressions are generated we can add this. |
| 7199 | try parent_gz.instructions.append(gpa, switch_block); |
| 7200 | |
| 7201 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).Struct.fields.len + |
| 7202 | @intFromBool(multi_cases_len != 0) + |
| 7203 | payloads.items.len - case_table_end + |
| 7204 | (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).Struct.fields.len); |
| 7205 | |
| 7206 | const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{ |
| 7207 | .operand = raw_operand, |
| 7208 | .bits = Zir.Inst.SwitchBlockErrUnion.Bits{ |
| 7209 | .has_multi_cases = multi_cases_len != 0, |
| 7210 | .has_else = has_else, |
| 7211 | .scalar_cases_len = @intCast(scalar_cases_len), |
| 7212 | }, |
| 7213 | }); |
| 7214 | |
| 7215 | if (multi_cases_len != 0) { |
| 7216 | astgen.extra.appendAssumeCapacity(multi_cases_len); |
| 7217 | } |
| 7218 | |
| 7219 | const zir_datas = astgen.instructions.items(.data); |
| 7220 | zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; |
| 7221 | |
| 7222 | for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| { |
| 7223 | var body_len_index = start_index; |
| 7224 | var end_index = start_index; |
| 7225 | const table_index = case_table_start + i; |
| 7226 | if (table_index < scalar_case_table) { |
| 7227 | end_index += 1; |
| 7228 | } else if (table_index < multi_case_table) { |
| 7229 | body_len_index += 1; |
| 7230 | end_index += 2; |
| 7231 | } else { |
| 7232 | body_len_index += 2; |
| 7233 | const items_len = payloads.items[start_index]; |
| 7234 | const ranges_len = payloads.items[start_index + 1]; |
| 7235 | end_index += 3 + items_len + 2 * ranges_len; |
| 7236 | } |
| 7237 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); |
| 7238 | end_index += prong_info.body_len; |
| 7239 | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); |
| 7240 | } |
| 7241 | |
| 7242 | if (need_result_rvalue) { |
| 7243 | return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); |
| 7244 | } else { |
| 7245 | return switch_block.toRef(); |
| 7246 | } |
| 7247 | } |
| 7248 | |
| 6862 | 7249 | fn switchExpr( |
| 6863 | 7250 | parent_gz: *GenZir, |
| 6864 | 7251 | scope: *Scope, |