authorgravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2023-11-17 02:41:08+11:00
committergravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2024-01-09 14:42:12+11:00
log2cf648fba7f00bb42df1c8dad213a23427d528da
tree5ac9109c2fcc94b65d501565481bf556ef6834db
parentb784f64a6e4495f65d684e0afb00c4f7a62b950c

astgen: use switch_block_err_union


1 files changed, 391 insertions(+), 4 deletions(-)

src/AstGen.zig+391-4
...@@ -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 + 21019 if (token_tags.len > catch_token + 6 and
1020 else1020 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}
68616867
6868fn 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
6862fn switchExpr(7249fn switchExpr(
6863 parent_gz: *GenZir,7250 parent_gz: *GenZir,
6864 scope: *Scope,7251 scope: *Scope,