| ... | @@ -1015,10 +1015,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE | ... | @@ -1015,10 +1015,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 1015 | }, | 1015 | }, |
| 1016 | .@"catch" => { | 1016 | .@"catch" => { |
| 1017 | const catch_token = main_tokens[node]; | 1017 | const catch_token = main_tokens[node]; |
| 1018 | const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) | 1018 | const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) blk: { |
| 1019 | catch_token + 2 | 1019 | if (token_tags.len > catch_token + 6 and |
| 1020 | else | 1020 | token_tags[catch_token + 4] == .keyword_switch) |
| 1021 | null; | 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 | switch (ri.rl) { | 1028 | switch (ri.rl) { |
| 1023 | .ref, .ref_coerced_ty => return orelseCatchExpr( | 1029 | .ref, .ref_coerced_ty => return orelseCatchExpr( |
| 1024 | gz, | 1030 | gz, |
| ... | @@ -6859,6 +6865,387 @@ fn forExpr( | ... | @@ -6859,6 +6865,387 @@ fn forExpr( |
| 6859 | return result; | 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 | fn switchExpr( | 7249 | fn switchExpr( |
| 6863 | parent_gz: *GenZir, | 7250 | parent_gz: *GenZir, |
| 6864 | scope: *Scope, | 7251 | scope: *Scope, |