| ... | @@ -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), |
| ... | @@ -3134,14 +3133,7 @@ fn deferStmt( | ... | @@ -3134,14 +3133,7 @@ fn deferStmt( |
| 3134 | } | 3133 | } |
| 3135 | 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); |
| 3136 | opt_remapped_err_code = remapped_err_code.toOptional(); | 3135 | opt_remapped_err_code = remapped_err_code.toOptional(); |
| 3137 | try gz.astgen.instructions.append(gz.astgen.gpa, .{ | 3136 | _ = try gz.astgen.appendPlaceholder(); |
| 3138 | .tag = .extended, | | |
| 3139 | .data = .{ .extended = .{ | | |
| 3140 | .opcode = .value_placeholder, | | |
| 3141 | .small = undefined, | | |
| 3142 | .operand = undefined, | | |
| 3143 | } }, | | |
| 3144 | }); | | |
| 3145 | const remapped_err_code_ref = remapped_err_code.toRef(); | 3137 | const remapped_err_code_ref = remapped_err_code.toRef(); |
| 3146 | local_val_scope = .{ | 3138 | local_val_scope = .{ |
| 3147 | .parent = &defer_gen.base, | 3139 | .parent = &defer_gen.base, |
| ... | @@ -6115,7 +6107,30 @@ fn orelseCatchExpr( | ... | @@ -6115,7 +6107,30 @@ fn orelseCatchExpr( |
| 6115 | break :blk &err_val_scope.base; | 6107 | break :blk &err_val_scope.base; |
| 6116 | }; | 6108 | }; |
| 6117 | | 6109 | |
| 6118 | 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 | }; |
| 6119 | if (!else_scope.endsWithNoReturn()) { | 6134 | if (!else_scope.endsWithNoReturn()) { |
| 6120 | // 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 |
| 6121 | if (do_err_trace) | 6136 | if (do_err_trace) |
| ... | @@ -6468,7 +6483,26 @@ fn ifExpr( | ... | @@ -6468,7 +6483,26 @@ fn ifExpr( |
| 6468 | break :s &else_scope.base; | 6483 | break :s &else_scope.base; |
| 6469 | } | 6484 | } |
| 6470 | }; | 6485 | }; |
| 6471 | 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 | }; |
| 6472 | if (!else_scope.endsWithNoReturn()) { | 6506 | if (!else_scope.endsWithNoReturn()) { |
| 6473 | // 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 |
| 6474 | if (do_err_trace) | 6508 | if (do_err_trace) |
| ... | @@ -7117,568 +7151,156 @@ fn forExpr( | ... | @@ -7117,568 +7151,156 @@ fn forExpr( |
| 7117 | return result; | 7151 | return result; |
| 7118 | } | 7152 | } |
| 7119 | | 7153 | |
| 7120 | fn switchExprErrUnion( | 7154 | const SwitchNonErr = union(enum) { |
| | 7155 | /// A regular switch expression. |
| | 7156 | /// Emits `switch_block[_ref]`. |
| | 7157 | none, |
| | 7158 | /// `eu catch |err| switch (err) { ... }` |
| | 7159 | /// |
| | 7160 | /// `switch` must not be labeled. |
| | 7161 | /// Emits `switch_block_err_union`. |
| | 7162 | @"catch", |
| | 7163 | /// `if (eu) |payload| { ... } else |err| switch (err) { ... }` |
| | 7164 | /// |
| | 7165 | /// `switch` must not be labeled. |
| | 7166 | /// Emits `switch_block_err_union`. |
| | 7167 | @"if": Ast.full.If, |
| | 7168 | /// `eu catch |err| label: switch (err) { ... }` |
| | 7169 | /// `if (eu) |payload| { ... } else |err| label: switch (err) { ... }` |
| | 7170 | /// |
| | 7171 | /// `switch` must be labeled. |
| | 7172 | /// Emits a `condbr` on the non-error body and a regular switch, though the |
| | 7173 | /// non-error prong and all `break`s from switch prongs are peers. |
| | 7174 | /// Exists to avoid a rather complex special case of `switch_block_err_union`. |
| | 7175 | peer_break_target: struct { |
| | 7176 | /// Refers to the enclosing block of the entire switch-on-err expression. |
| | 7177 | block_inst: Zir.Inst.Index, |
| | 7178 | /// Belongs to `block_inst`. |
| | 7179 | block_ri: ResultInfo, |
| | 7180 | }, |
| | 7181 | }; |
| | 7182 | |
| | 7183 | fn switchExpr( |
| 7121 | parent_gz: *GenZir, | 7184 | parent_gz: *GenZir, |
| 7122 | scope: *Scope, | 7185 | scope: *Scope, |
| 7123 | ri: ResultInfo, | 7186 | ri: ResultInfo, |
| 7124 | catch_or_if_node: Ast.Node.Index, | 7187 | node: Ast.Node.Index, |
| 7125 | node_ty: enum { @"catch", @"if" }, | 7188 | switch_full: Ast.full.Switch, |
| | 7189 | non_err: SwitchNonErr, |
| 7126 | ) InnerError!Zir.Inst.Ref { | 7190 | ) InnerError!Zir.Inst.Ref { |
| 7127 | const astgen = parent_gz.astgen; | 7191 | const astgen = parent_gz.astgen; |
| 7128 | const gpa = astgen.gpa; | 7192 | const gpa = astgen.gpa; |
| 7129 | const tree = astgen.tree; | 7193 | const tree = astgen.tree; |
| 7130 | | 7194 | |
| 7131 | const if_full = switch (node_ty) { | 7195 | const switch_node, const operand_node, const err_token = switch (non_err) { |
| 7132 | .@"catch" => undefined, | 7196 | .none, .peer_break_target => .{ |
| 7133 | .@"if" => tree.fullIf(catch_or_if_node).?, | 7197 | node, |
| 7134 | }; | 7198 | switch_full.ast.condition, |
| 7135 | | 7199 | undefined, |
| 7136 | const switch_node, const operand_node, const error_payload = switch (node_ty) { | 7200 | }, |
| 7137 | .@"catch" => .{ | 7201 | .@"catch" => .{ |
| 7138 | tree.nodeData(catch_or_if_node).node_and_node[1], | 7202 | tree.nodeData(node).node_and_node[1], |
| 7139 | tree.nodeData(catch_or_if_node).node_and_node[0], | 7203 | tree.nodeData(node).node_and_node[0], |
| 7140 | tree.nodeMainToken(catch_or_if_node) + 2, | 7204 | tree.nodeMainToken(node) + 2, |
| 7141 | }, | 7205 | }, |
| 7142 | .@"if" => .{ | 7206 | .@"if" => |if_full| .{ |
| 7143 | if_full.ast.else_expr.unwrap().?, | 7207 | if_full.ast.else_expr.unwrap().?, |
| 7144 | if_full.ast.cond_expr, | 7208 | if_full.ast.cond_expr, |
| 7145 | if_full.error_token.?, | 7209 | if_full.error_token.?, |
| 7146 | }, | 7210 | }, |
| 7147 | }; | 7211 | }; |
| 7148 | const switch_full = tree.fullSwitch(switch_node).?; | 7212 | const case_nodes = switch_full.ast.cases; |
| | 7213 | |
| | 7214 | const is_err_switch = non_err != .none; |
| | 7215 | const needs_non_err_handling = switch (non_err) { |
| | 7216 | .none => false, |
| | 7217 | .peer_break_target => false, // handled by parent expression |
| | 7218 | .@"catch", .@"if" => true, |
| | 7219 | }; |
| 7149 | | 7220 | |
| 7150 | const do_err_trace = astgen.fn_block != null; | 7221 | const need_rl = astgen.nodes_need_rl.contains(node); |
| 7151 | const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node); | | |
| 7152 | const block_ri: ResultInfo = if (need_rl) ri else .{ | 7222 | const block_ri: ResultInfo = if (need_rl) ri else .{ |
| 7153 | .rl = switch (ri.rl) { | 7223 | .rl = switch (ri.rl) { |
| 7154 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? }, | 7224 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, |
| 7155 | .inferred_ptr => .none, | 7225 | .inferred_ptr => .none, |
| 7156 | else => ri.rl, | 7226 | else => ri.rl, |
| 7157 | }, | 7227 | }, |
| 7158 | .ctx = ri.ctx, | 7228 | .ctx = ri.ctx, |
| 7159 | }; | 7229 | }; |
| 7160 | | 7230 | |
| 7161 | const payload_is_ref = switch (node_ty) { | | |
| 7162 | .@"if" => if_full.payload_token != null and tree.tokenTag(if_full.payload_token.?) == .asterisk, | | |
| 7163 | .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty, | | |
| 7164 | }; | | |
| 7165 | | | |
| 7166 | // We need to call `rvalue` to write through to the pointer only if we had a | 7231 | // We need to call `rvalue` to write through to the pointer only if we had a |
| 7167 | // result pointer and aren't forwarding it. | 7232 | // result pointer and aren't forwarding it. |
| 7168 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; | 7233 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; |
| 7169 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); | 7234 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); |
| 7170 | var scalar_cases_len: u32 = 0; | | |
| 7171 | var multi_cases_len: u32 = 0; | | |
| 7172 | var inline_cases_len: u32 = 0; | | |
| 7173 | var has_else = false; | | |
| 7174 | var else_node: Ast.Node.OptionalIndex = .none; | | |
| 7175 | var else_src: ?Ast.TokenIndex = null; | | |
| 7176 | for (switch_full.ast.cases) |case_node| { | | |
| 7177 | const case = tree.fullSwitchCase(case_node).?; | | |
| 7178 | | 7235 | |
| 7179 | if (case.ast.values.len == 0) { | 7236 | const catch_or_if_node = if (needs_non_err_handling) node else undefined; |
| 7180 | const case_src = case.ast.arrow_token - 1; | 7237 | const do_err_trace = needs_non_err_handling and astgen.fn_block != null; |
| 7181 | if (else_src) |src| { | 7238 | const non_err_is_ref: bool = switch (non_err) { |
| 7182 | return astgen.failTokNotes( | 7239 | .none, .peer_break_target => undefined, |
| 7183 | case_src, | 7240 | .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty, |
| 7184 | "multiple else prongs in switch expression", | 7241 | .@"if" => |if_full| if_full.payload_token != null and |
| 7185 | .{}, | 7242 | tree.tokenTag(if_full.payload_token.?) == .asterisk, |
| 7186 | &[_]u32{ | | |
| 7187 | try astgen.errNoteTok( | | |
| 7188 | src, | | |
| 7189 | "previous else prong here", | | |
| 7190 | .{}, | | |
| 7191 | ), | | |
| 7192 | }, | | |
| 7193 | ); | | |
| 7194 | } | | |
| 7195 | has_else = true; | | |
| 7196 | else_node = case_node.toOptional(); | | |
| 7197 | else_src = case_src; | | |
| 7198 | continue; | | |
| 7199 | } else if (case.ast.values.len == 1 and | | |
| 7200 | tree.nodeTag(case.ast.values[0]) == .identifier and | | |
| 7201 | mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(case.ast.values[0])), "_")) | | |
| 7202 | { | | |
| 7203 | const case_src = case.ast.arrow_token - 1; | | |
| 7204 | return astgen.failTokNotes( | | |
| 7205 | case_src, | | |
| 7206 | "'_' prong is not allowed when switching on errors", | | |
| 7207 | .{}, | | |
| 7208 | &[_]u32{ | | |
| 7209 | try astgen.errNoteTok( | | |
| 7210 | case_src, | | |
| 7211 | "consider using 'else'", | | |
| 7212 | .{}, | | |
| 7213 | ), | | |
| 7214 | }, | | |
| 7215 | ); | | |
| 7216 | } | | |
| 7217 | | | |
| 7218 | for (case.ast.values) |val| { | | |
| 7219 | if (tree.nodeTag(val) == .string_literal) | | |
| 7220 | return astgen.failNode(val, "cannot switch on strings", .{}); | | |
| 7221 | } | | |
| 7222 | | | |
| 7223 | if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { | | |
| 7224 | scalar_cases_len += 1; | | |
| 7225 | } else { | | |
| 7226 | multi_cases_len += 1; | | |
| 7227 | } | | |
| 7228 | if (case.inline_token != null) { | | |
| 7229 | inline_cases_len += 1; | | |
| 7230 | } | | |
| 7231 | } | | |
| 7232 | | | |
| 7233 | const operand_ri: ResultInfo = .{ | | |
| 7234 | .rl = if (payload_is_ref) .ref else .none, | | |
| 7235 | .ctx = .error_handling_expr, | | |
| 7236 | }; | 7243 | }; |
| 7237 | | 7244 | |
| 7238 | astgen.advanceSourceCursorToNode(operand_node); | 7245 | if (switch_full.label_token) |label_token| { |
| 7239 | const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; | 7246 | try astgen.checkLabelRedefinition(scope, label_token); |
| 7240 | | | |
| 7241 | const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node); | | |
| 7242 | const item_ri: ResultInfo = .{ .rl = .none }; | | |
| 7243 | | | |
| 7244 | // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except | | |
| 7245 | // the first cases_nodes.len slots are a table that indexes payloads later in the array, | | |
| 7246 | // with the non-error and else case indices coming first, then scalar_cases_len indexes, then | | |
| 7247 | // multi_cases_len indexes | | |
| 7248 | const payloads = &astgen.scratch; | | |
| 7249 | const scratch_top = astgen.scratch.items.len; | | |
| 7250 | const case_table_start = scratch_top; | | |
| 7251 | const scalar_case_table = case_table_start + 1 + @intFromBool(has_else); | | |
| 7252 | const multi_case_table = scalar_case_table + scalar_cases_len; | | |
| 7253 | const case_table_end = multi_case_table + multi_cases_len; | | |
| 7254 | | | |
| 7255 | try astgen.scratch.resize(gpa, case_table_end); | | |
| 7256 | defer astgen.scratch.items.len = scratch_top; | | |
| 7257 | | | |
| 7258 | var block_scope = parent_gz.makeSubBlock(scope); | | |
| 7259 | // block_scope not used for collecting instructions | | |
| 7260 | block_scope.instructions_top = GenZir.unstacked_top; | | |
| 7261 | block_scope.setBreakResultInfo(block_ri); | | |
| 7262 | | | |
| 7263 | // Sema expects a dbg_stmt immediately before switch_block_err_union | | |
| 7264 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); | | |
| 7265 | // This gets added to the parent block later, after the item expressions. | | |
| 7266 | const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node); | | |
| 7267 | | | |
| 7268 | // We re-use this same scope for all cases, including the special prong, if any. | | |
| 7269 | var case_scope = parent_gz.makeSubBlock(&block_scope.base); | | |
| 7270 | case_scope.instructions_top = GenZir.unstacked_top; | | |
| 7271 | | | |
| 7272 | { | | |
| 7273 | const body_len_index: u32 = @intCast(payloads.items.len); | | |
| 7274 | payloads.items[case_table_start] = body_len_index; | | |
| 7275 | try payloads.resize(gpa, body_len_index + 1); // body_len | | |
| 7276 | | | |
| 7277 | case_scope.instructions_top = parent_gz.instructions.items.len; | | |
| 7278 | defer case_scope.unstack(); | | |
| 7279 | | | |
| 7280 | const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref) | | |
| 7281 | .err_union_payload_unsafe_ptr | | |
| 7282 | else | | |
| 7283 | .err_union_payload_unsafe; | | |
| 7284 | | | |
| 7285 | const unwrapped_payload = try case_scope.addUnNode( | | |
| 7286 | unwrap_payload_tag, | | |
| 7287 | raw_operand, | | |
| 7288 | catch_or_if_node, | | |
| 7289 | ); | | |
| 7290 | | | |
| 7291 | switch (node_ty) { | | |
| 7292 | .@"catch" => { | | |
| 7293 | const case_result = switch (ri.rl) { | | |
| 7294 | .ref, .ref_coerced_ty => unwrapped_payload, | | |
| 7295 | else => try rvalue( | | |
| 7296 | &case_scope, | | |
| 7297 | block_scope.break_result_info, | | |
| 7298 | unwrapped_payload, | | |
| 7299 | catch_or_if_node, | | |
| 7300 | ), | | |
| 7301 | }; | | |
| 7302 | _ = try case_scope.addBreakWithSrcNode( | | |
| 7303 | .@"break", | | |
| 7304 | switch_block, | | |
| 7305 | case_result, | | |
| 7306 | catch_or_if_node, | | |
| 7307 | ); | | |
| 7308 | }, | | |
| 7309 | .@"if" => { | | |
| 7310 | var payload_val_scope: Scope.LocalVal = undefined; | | |
| 7311 | | | |
| 7312 | const then_node = if_full.ast.then_expr; | | |
| 7313 | const then_sub_scope = s: { | | |
| 7314 | assert(if_full.error_token != null); | | |
| 7315 | if (if_full.payload_token) |payload_token| { | | |
| 7316 | const token_name_index = payload_token + @intFromBool(payload_is_ref); | | |
| 7317 | const ident_name = try astgen.identAsString(token_name_index); | | |
| 7318 | const token_name_str = tree.tokenSlice(token_name_index); | | |
| 7319 | if (mem.eql(u8, "_", token_name_str)) | | |
| 7320 | break :s &case_scope.base; | | |
| 7321 | try astgen.detectLocalShadowing( | | |
| 7322 | &case_scope.base, | | |
| 7323 | ident_name, | | |
| 7324 | token_name_index, | | |
| 7325 | token_name_str, | | |
| 7326 | .capture, | | |
| 7327 | ); | | |
| 7328 | payload_val_scope = .{ | | |
| 7329 | .parent = &case_scope.base, | | |
| 7330 | .gen_zir = &case_scope, | | |
| 7331 | .name = ident_name, | | |
| 7332 | .inst = unwrapped_payload, | | |
| 7333 | .token_src = token_name_index, | | |
| 7334 | .id_cat = .capture, | | |
| 7335 | }; | | |
| 7336 | try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload); | | |
| 7337 | break :s &payload_val_scope.base; | | |
| 7338 | } else { | | |
| 7339 | _ = try case_scope.addUnNode( | | |
| 7340 | .ensure_err_union_payload_void, | | |
| 7341 | raw_operand, | | |
| 7342 | catch_or_if_node, | | |
| 7343 | ); | | |
| 7344 | break :s &case_scope.base; | | |
| 7345 | } | | |
| 7346 | }; | | |
| 7347 | const then_result = try expr( | | |
| 7348 | &case_scope, | | |
| 7349 | then_sub_scope, | | |
| 7350 | block_scope.break_result_info, | | |
| 7351 | then_node, | | |
| 7352 | ); | | |
| 7353 | try checkUsed(parent_gz, &case_scope.base, then_sub_scope); | | |
| 7354 | if (!case_scope.endsWithNoReturn()) { | | |
| 7355 | _ = try case_scope.addBreakWithSrcNode( | | |
| 7356 | .@"break", | | |
| 7357 | switch_block, | | |
| 7358 | then_result, | | |
| 7359 | then_node, | | |
| 7360 | ); | | |
| 7361 | } | | |
| 7362 | }, | | |
| 7363 | } | | |
| 7364 | | | |
| 7365 | const case_slice = case_scope.instructionsSlice(); | | |
| 7366 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, &.{switch_block}); | | |
| 7367 | try payloads.ensureUnusedCapacity(gpa, body_len); | | |
| 7368 | const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) { | | |
| 7369 | .@"catch" => .none, | | |
| 7370 | .@"if" => if (if_full.payload_token == null) | | |
| 7371 | .none | | |
| 7372 | else if (payload_is_ref) | | |
| 7373 | .by_ref | | |
| 7374 | else | | |
| 7375 | .by_val, | | |
| 7376 | }; | | |
| 7377 | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ | | |
| 7378 | .body_len = @intCast(body_len), | | |
| 7379 | .capture = capture, | | |
| 7380 | .is_inline = false, | | |
| 7381 | .has_tag_capture = false, | | |
| 7382 | }); | | |
| 7383 | appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, &.{switch_block}); | | |
| 7384 | } | 7247 | } |
| 7385 | | 7248 | |
| 7386 | const err_name = blk: { | 7249 | const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: { |
| 7387 | const err_str = tree.tokenSlice(error_payload); | 7250 | const err_str = tree.tokenSlice(err_token); |
| 7388 | if (mem.eql(u8, err_str, "_")) { | 7251 | if (mem.eql(u8, err_str, "_")) { |
| 7389 | // This is fatal because we already know we're switching on the captured error. | 7252 | // This is fatal because we already know we're switching on the captured error. |
| 7390 | return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{}); | 7253 | return astgen.failTok(err_token, "discard of error capture; omit it instead", .{}); |
| 7391 | } | 7254 | } |
| 7392 | const err_name = try astgen.identAsString(error_payload); | 7255 | const err_name = try astgen.identAsString(err_token); |
| 7393 | try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture); | 7256 | try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture); |
| 7394 | | | |
| 7395 | break :blk err_name; | 7257 | break :blk err_name; |
| 7396 | }; | 7258 | } else undefined; |
| 7397 | | | |
| 7398 | // allocate a shared dummy instruction for the error capture | | |
| 7399 | const err_inst = err_inst: { | | |
| 7400 | const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); | | |
| 7401 | try astgen.instructions.append(astgen.gpa, .{ | | |
| 7402 | .tag = .extended, | | |
| 7403 | .data = .{ .extended = .{ | | |
| 7404 | .opcode = .value_placeholder, | | |
| 7405 | .small = undefined, | | |
| 7406 | .operand = undefined, | | |
| 7407 | } }, | | |
| 7408 | }); | | |
| 7409 | break :err_inst inst; | | |
| 7410 | }; | | |
| 7411 | | | |
| 7412 | // In this pass we generate all the item and prong expressions for error cases. | | |
| 7413 | var multi_case_index: u32 = 0; | | |
| 7414 | var scalar_case_index: u32 = 0; | | |
| 7415 | var any_uses_err_capture = false; | | |
| 7416 | for (switch_full.ast.cases) |case_node| { | | |
| 7417 | const case = tree.fullSwitchCase(case_node).?; | | |
| 7418 | | | |
| 7419 | const is_multi_case = case.ast.values.len > 1 or | | |
| 7420 | (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range); | | |
| 7421 | | | |
| 7422 | var dbg_var_name: Zir.NullTerminatedString = .empty; | | |
| 7423 | var dbg_var_inst: Zir.Inst.Ref = undefined; | | |
| 7424 | var err_scope: Scope.LocalVal = undefined; | | |
| 7425 | var capture_scope: Scope.LocalVal = undefined; | | |
| 7426 | | | |
| 7427 | const sub_scope = blk: { | | |
| 7428 | err_scope = .{ | | |
| 7429 | .parent = &case_scope.base, | | |
| 7430 | .gen_zir = &case_scope, | | |
| 7431 | .name = err_name, | | |
| 7432 | .inst = err_inst.toRef(), | | |
| 7433 | .token_src = error_payload, | | |
| 7434 | .id_cat = .capture, | | |
| 7435 | }; | | |
| 7436 | | | |
| 7437 | const capture_token = case.payload_token orelse break :blk &err_scope.base; | | |
| 7438 | if (tree.tokenTag(capture_token) != .identifier) { | | |
| 7439 | return astgen.failTok(capture_token + 1, "error set cannot be captured by reference", .{}); | | |
| 7440 | } | | |
| 7441 | | | |
| 7442 | const capture_slice = tree.tokenSlice(capture_token); | | |
| 7443 | if (mem.eql(u8, capture_slice, "_")) { | | |
| 7444 | try astgen.appendErrorTok(capture_token, "discard of error capture; omit it instead", .{}); | | |
| 7445 | } | | |
| 7446 | const tag_name = try astgen.identAsString(capture_token); | | |
| 7447 | try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture); | | |
| 7448 | | | |
| 7449 | capture_scope = .{ | | |
| 7450 | .parent = &case_scope.base, | | |
| 7451 | .gen_zir = &case_scope, | | |
| 7452 | .name = tag_name, | | |
| 7453 | .inst = switch_block.toRef(), | | |
| 7454 | .token_src = capture_token, | | |
| 7455 | .id_cat = .capture, | | |
| 7456 | }; | | |
| 7457 | dbg_var_name = tag_name; | | |
| 7458 | dbg_var_inst = switch_block.toRef(); | | |
| 7459 | | | |
| 7460 | err_scope.parent = &capture_scope.base; | | |
| 7461 | | | |
| 7462 | break :blk &err_scope.base; | | |
| 7463 | }; | | |
| 7464 | | | |
| 7465 | const header_index: u32 = @intCast(payloads.items.len); | | |
| 7466 | const body_len_index = if (is_multi_case) blk: { | | |
| 7467 | payloads.items[multi_case_table + multi_case_index] = header_index; | | |
| 7468 | multi_case_index += 1; | | |
| 7469 | try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len | | |
| 7470 | | | |
| 7471 | // items | | |
| 7472 | var items_len: u32 = 0; | | |
| 7473 | for (case.ast.values) |item_node| { | | |
| 7474 | if (tree.nodeTag(item_node) == .switch_range) continue; | | |
| 7475 | items_len += 1; | | |
| 7476 | | | |
| 7477 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); | | |
| 7478 | try payloads.append(gpa, @intFromEnum(item_inst)); | | |
| 7479 | } | | |
| 7480 | | | |
| 7481 | // ranges | | |
| 7482 | var ranges_len: u32 = 0; | | |
| 7483 | for (case.ast.values) |range| { | | |
| 7484 | if (tree.nodeTag(range) != .switch_range) continue; | | |
| 7485 | ranges_len += 1; | | |
| 7486 | | | |
| 7487 | const first_node, const last_node = tree.nodeData(range).node_and_node; | | |
| 7488 | const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item); | | |
| 7489 | const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item); | | |
| 7490 | try payloads.appendSlice(gpa, &[_]u32{ | | |
| 7491 | @intFromEnum(first), @intFromEnum(last), | | |
| 7492 | }); | | |
| 7493 | } | | |
| 7494 | | | |
| 7495 | payloads.items[header_index] = items_len; | | |
| 7496 | payloads.items[header_index + 1] = ranges_len; | | |
| 7497 | break :blk header_index + 2; | | |
| 7498 | } else if (case_node.toOptional() == else_node) blk: { | | |
| 7499 | payloads.items[case_table_start + 1] = header_index; | | |
| 7500 | try payloads.resize(gpa, header_index + 1); // body_len | | |
| 7501 | break :blk header_index; | | |
| 7502 | } else blk: { | | |
| 7503 | payloads.items[scalar_case_table + scalar_case_index] = header_index; | | |
| 7504 | scalar_case_index += 1; | | |
| 7505 | try payloads.resize(gpa, header_index + 2); // item, body_len | | |
| 7506 | const item_node = case.ast.values[0]; | | |
| 7507 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); | | |
| 7508 | payloads.items[header_index] = @intFromEnum(item_inst); | | |
| 7509 | break :blk header_index + 1; | | |
| 7510 | }; | | |
| 7511 | | | |
| 7512 | { | | |
| 7513 | // temporarily stack case_scope on parent_gz | | |
| 7514 | case_scope.instructions_top = parent_gz.instructions.items.len; | | |
| 7515 | defer case_scope.unstack(); | | |
| 7516 | | | |
| 7517 | if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) | | |
| 7518 | _ = try case_scope.addSaveErrRetIndex(.always); | | |
| 7519 | | | |
| 7520 | if (dbg_var_name != .empty) { | | |
| 7521 | try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst); | | |
| 7522 | } | | |
| 7523 | | | |
| 7524 | const target_expr_node = case.ast.target_expr; | | |
| 7525 | const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); | | |
| 7526 | // check capture_scope, not err_scope to avoid false positive unused error capture | | |
| 7527 | try checkUsed(parent_gz, &case_scope.base, err_scope.parent); | | |
| 7528 | const uses_err = err_scope.used != .none or err_scope.discarded != .none; | | |
| 7529 | if (uses_err) { | | |
| 7530 | try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef()); | | |
| 7531 | any_uses_err_capture = true; | | |
| 7532 | } | | |
| 7533 | | | |
| 7534 | if (!parent_gz.refIsNoReturn(case_result)) { | | |
| 7535 | if (do_err_trace) | | |
| 7536 | try restoreErrRetIndex( | | |
| 7537 | &case_scope, | | |
| 7538 | .{ .block = switch_block }, | | |
| 7539 | block_scope.break_result_info, | | |
| 7540 | target_expr_node, | | |
| 7541 | case_result, | | |
| 7542 | ); | | |
| 7543 | | | |
| 7544 | _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); | | |
| 7545 | } | | |
| 7546 | | | |
| 7547 | const case_slice = case_scope.instructionsSlice(); | | |
| 7548 | const extra_insts: []const Zir.Inst.Index = if (uses_err) &.{ switch_block, err_inst } else &.{switch_block}; | | |
| 7549 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts); | | |
| 7550 | try payloads.ensureUnusedCapacity(gpa, body_len); | | |
| 7551 | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ | | |
| 7552 | .body_len = @intCast(body_len), | | |
| 7553 | .capture = if (case.payload_token != null) .by_val else .none, | | |
| 7554 | .is_inline = case.inline_token != null, | | |
| 7555 | .has_tag_capture = false, | | |
| 7556 | }); | | |
| 7557 | appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts); | | |
| 7558 | } | | |
| 7559 | } | | |
| 7560 | // Now that the item expressions are generated we can add this. | | |
| 7561 | try parent_gz.instructions.append(gpa, switch_block); | | |
| 7562 | | | |
| 7563 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).@"struct".fields.len + | | |
| 7564 | @intFromBool(multi_cases_len != 0) + | | |
| 7565 | payloads.items.len - case_table_end + | | |
| 7566 | (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).@"struct".fields.len); | | |
| 7567 | | | |
| 7568 | const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{ | | |
| 7569 | .operand = raw_operand, | | |
| 7570 | .bits = Zir.Inst.SwitchBlockErrUnion.Bits{ | | |
| 7571 | .has_multi_cases = multi_cases_len != 0, | | |
| 7572 | .has_else = has_else, | | |
| 7573 | .scalar_cases_len = @intCast(scalar_cases_len), | | |
| 7574 | .any_uses_err_capture = any_uses_err_capture, | | |
| 7575 | .payload_is_ref = payload_is_ref, | | |
| 7576 | }, | | |
| 7577 | .main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node), | | |
| 7578 | }); | | |
| 7579 | | | |
| 7580 | if (multi_cases_len != 0) { | | |
| 7581 | astgen.extra.appendAssumeCapacity(multi_cases_len); | | |
| 7582 | } | | |
| 7583 | | | |
| 7584 | if (any_uses_err_capture) { | | |
| 7585 | astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst)); | | |
| 7586 | } | | |
| 7587 | | | |
| 7588 | const zir_datas = astgen.instructions.items(.data); | | |
| 7589 | zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; | | |
| 7590 | | | |
| 7591 | for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| { | | |
| 7592 | var body_len_index = start_index; | | |
| 7593 | var end_index = start_index; | | |
| 7594 | const table_index = case_table_start + i; | | |
| 7595 | if (table_index < scalar_case_table) { | | |
| 7596 | end_index += 1; | | |
| 7597 | } else if (table_index < multi_case_table) { | | |
| 7598 | body_len_index += 1; | | |
| 7599 | end_index += 2; | | |
| 7600 | } else { | | |
| 7601 | body_len_index += 2; | | |
| 7602 | const items_len = payloads.items[start_index]; | | |
| 7603 | const ranges_len = payloads.items[start_index + 1]; | | |
| 7604 | end_index += 3 + items_len + 2 * ranges_len; | | |
| 7605 | } | | |
| 7606 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); | | |
| 7607 | end_index += prong_info.body_len; | | |
| 7608 | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); | | |
| 7609 | } | | |
| 7610 | | | |
| 7611 | if (need_result_rvalue) { | | |
| 7612 | return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); | | |
| 7613 | } else { | | |
| 7614 | return switch_block.toRef(); | | |
| 7615 | } | | |
| 7616 | } | | |
| 7617 | | | |
| 7618 | fn switchExpr( | | |
| 7619 | parent_gz: *GenZir, | | |
| 7620 | scope: *Scope, | | |
| 7621 | ri: ResultInfo, | | |
| 7622 | node: Ast.Node.Index, | | |
| 7623 | switch_full: Ast.full.Switch, | | |
| 7624 | ) InnerError!Zir.Inst.Ref { | | |
| 7625 | const astgen = parent_gz.astgen; | | |
| 7626 | const gpa = astgen.gpa; | | |
| 7627 | const tree = astgen.tree; | | |
| 7628 | const operand_node = switch_full.ast.condition; | | |
| 7629 | const case_nodes = switch_full.ast.cases; | | |
| 7630 | | | |
| 7631 | const need_rl = astgen.nodes_need_rl.contains(node); | | |
| 7632 | const block_ri: ResultInfo = if (need_rl) ri else .{ | | |
| 7633 | .rl = switch (ri.rl) { | | |
| 7634 | .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? }, | | |
| 7635 | .inferred_ptr => .none, | | |
| 7636 | else => ri.rl, | | |
| 7637 | }, | | |
| 7638 | .ctx = ri.ctx, | | |
| 7639 | }; | | |
| 7640 | // We need to call `rvalue` to write through to the pointer only if we had a | | |
| 7641 | // result pointer and aren't forwarding it. | | |
| 7642 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; | | |
| 7643 | const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl); | | |
| 7644 | | | |
| 7645 | if (switch_full.label_token) |label_token| { | | |
| 7646 | try astgen.checkLabelRedefinition(scope, label_token); | | |
| 7647 | } | | |
| 7648 | | 7259 | |
| 7649 | // We perform two passes over the AST. This first pass is to collect information | 7260 | // We perform two passes over the AST. This first pass is to collect information |
| 7650 | // for the following variables, make note of the special prong AST node index, | 7261 | // for the following variables, make note of the special prong AST node indices, |
| 7651 | // and bail out with a compile error if there are multiple special prongs present. | 7262 | // and bail out with a compile error if there are incompatible special prongs present. |
| 7652 | var any_payload_is_ref = false; | 7263 | var any_payload_is_ref = false; |
| | 7264 | var any_has_payload_capture = false; |
| 7653 | var any_has_tag_capture = false; | 7265 | var any_has_tag_capture = false; |
| 7654 | var any_non_inline_capture = false; | 7266 | var any_maybe_runtime_capture = false; |
| 7655 | var scalar_cases_len: u32 = 0; | 7267 | var scalar_cases_len: u32 = 0; |
| 7656 | var multi_cases_len: u32 = 0; | 7268 | var multi_cases_len: u32 = 0; |
| 7657 | var inline_cases_len: u32 = 0; | 7269 | var total_items_len: usize = 0; |
| | 7270 | var total_ranges_len: usize = 0; |
| 7658 | var else_case_node: Ast.Node.OptionalIndex = .none; | 7271 | var else_case_node: Ast.Node.OptionalIndex = .none; |
| 7659 | var else_src: ?Ast.TokenIndex = null; | 7272 | var else_src: ?Ast.TokenIndex = null; |
| 7660 | var underscore_case_node: Ast.Node.OptionalIndex = .none; | 7273 | var under_case_node: Ast.Node.OptionalIndex = .none; |
| 7661 | var underscore_node: Ast.Node.OptionalIndex = .none; | 7274 | var underscore_node: Ast.Node.OptionalIndex = .none; |
| 7662 | var underscore_src: ?Ast.TokenIndex = null; | 7275 | var underscore_src: ?Ast.TokenIndex = null; |
| 7663 | var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none; | 7276 | var under_is_bare = false; |
| 7664 | for (case_nodes) |case_node| { | 7277 | for (case_nodes) |case_node| { |
| 7665 | const case = tree.fullSwitchCase(case_node).?; | 7278 | const case = tree.fullSwitchCase(case_node).?; |
| 7666 | if (case.payload_token) |payload_token| { | 7279 | if (case.payload_token) |payload_token| { |
| 7667 | const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: { | 7280 | const ident = if (tree.tokenTag(payload_token) == .asterisk) blk: { |
| | 7281 | // Capturing errors by reference is never allowed, but as we will |
| | 7282 | // check for this again later we will fail as late as possible. |
| 7668 | any_payload_is_ref = true; | 7283 | any_payload_is_ref = true; |
| 7669 | break :blk payload_token + 1; | 7284 | break :blk payload_token + 1; |
| 7670 | } else payload_token; | 7285 | } else payload_token; |
| | 7286 | |
| | 7287 | if (!mem.eql(u8, tree.tokenSlice(ident), "_")) { |
| | 7288 | any_has_payload_capture = true; |
| | 7289 | |
| | 7290 | // If we're capturing a union, its payload value cannot always be |
| | 7291 | // comptime-known, even if its prong is inlined as inlining only |
| | 7292 | // affects its enum tag. |
| | 7293 | // This check isn't perfect, because for things like enums, the |
| | 7294 | // entire capture *is* comptime-known for inline prongs! But such |
| | 7295 | // knowledge requires semantic analysis. |
| | 7296 | any_maybe_runtime_capture = true; |
| | 7297 | } |
| 7671 | if (tree.tokenTag(ident + 1) == .comma) { | 7298 | if (tree.tokenTag(ident + 1) == .comma) { |
| 7672 | any_has_tag_capture = true; | 7299 | any_has_tag_capture = true; |
| 7673 | } | | |
| 7674 | | 7300 | |
| 7675 | // If the first capture is ignored, then there is no runtime-known | 7301 | if (case.inline_token == null) { |
| 7676 | // capture, as the tag capture must be for an inline prong. | 7302 | any_maybe_runtime_capture = true; |
| 7677 | // This check isn't perfect, because for things like enums, the | 7303 | } |
| 7678 | // first prong *is* comptime-known for inline prongs! But such | | |
| 7679 | // knowledge requires semantic analysis. | | |
| 7680 | if (!mem.eql(u8, tree.tokenSlice(ident), "_")) { | | |
| 7681 | any_non_inline_capture = true; | | |
| 7682 | } | 7304 | } |
| 7683 | } | 7305 | } |
| 7684 | | 7306 | |
| ... | @@ -7690,13 +7312,7 @@ fn switchExpr( | ... | @@ -7690,13 +7312,7 @@ fn switchExpr( |
| 7690 | case_src, | 7312 | case_src, |
| 7691 | "multiple else prongs in switch expression", | 7313 | "multiple else prongs in switch expression", |
| 7692 | .{}, | 7314 | .{}, |
| 7693 | &[_]u32{ | 7315 | &.{try astgen.errNoteTok(src, "previous else prong here", .{})}, |
| 7694 | try astgen.errNoteTok( | | |
| 7695 | src, | | |
| 7696 | "previous else prong here", | | |
| 7697 | .{}, | | |
| 7698 | ), | | |
| 7699 | }, | | |
| 7700 | ); | 7316 | ); |
| 7701 | } | 7317 | } |
| 7702 | else_case_node = case_node.toOptional(); | 7318 | else_case_node = case_node.toOptional(); |
| ... | @@ -7704,156 +7320,492 @@ fn switchExpr( | ... | @@ -7704,156 +7320,492 @@ fn switchExpr( |
| 7704 | continue; | 7320 | continue; |
| 7705 | } | 7321 | } |
| 7706 | | 7322 | |
| 7707 | // Check for '_' prong. | 7323 | // Check for '_' prong and ranges. |
| 7708 | var case_has_underscore = false; | 7324 | var case_has_underscore = false; |
| | 7325 | var case_has_ranges = false; |
| 7709 | for (case.ast.values) |val| { | 7326 | for (case.ast.values) |val| { |
| 7710 | switch (tree.nodeTag(val)) { | 7327 | switch (tree.nodeTag(val)) { |
| 7711 | .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { | 7328 | .switch_range => { |
| 7712 | const val_src = tree.nodeMainToken(val); | 7329 | total_ranges_len += 1; |
| 7713 | if (underscore_src) |src| { | 7330 | case_has_ranges = true; |
| 7714 | return astgen.failTokNotes( | | |
| 7715 | val_src, | | |
| 7716 | "multiple '_' prongs in switch expression", | | |
| 7717 | .{}, | | |
| 7718 | &[_]u32{ | | |
| 7719 | try astgen.errNoteTok( | | |
| 7720 | src, | | |
| 7721 | "previous '_' prong here", | | |
| 7722 | .{}, | | |
| 7723 | ), | | |
| 7724 | }, | | |
| 7725 | ); | | |
| 7726 | } | | |
| 7727 | if (case.inline_token != null) { | | |
| 7728 | return astgen.failTok(val_src, "cannot inline '_' prong", .{}); | | |
| 7729 | } | | |
| 7730 | underscore_case_node = case_node.toOptional(); | | |
| 7731 | underscore_src = val_src; | | |
| 7732 | underscore_node = val.toOptional(); | | |
| 7733 | underscore_additional_items = switch (case.ast.values.len) { | | |
| 7734 | 0 => unreachable, | | |
| 7735 | 1 => .none, | | |
| 7736 | 2 => .one, | | |
| 7737 | else => .many, | | |
| 7738 | }; | | |
| 7739 | case_has_underscore = true; | | |
| 7740 | }, | 7331 | }, |
| 7741 | .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}), | 7332 | .string_literal => return astgen.failNode(val, "cannot switch on strings", .{}), |
| 7742 | else => {}, | 7333 | else => |tag| { |
| | 7334 | if (tag == .identifier and |
| | 7335 | mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) |
| | 7336 | { |
| | 7337 | const val_src = tree.nodeMainToken(val); |
| | 7338 | if (is_err_switch) { |
| | 7339 | const case_src = case.ast.arrow_token - 1; |
| | 7340 | return astgen.failTokNotes( |
| | 7341 | case_src, |
| | 7342 | "'_' prong is not allowed when switching on errors", |
| | 7343 | .{}, |
| | 7344 | &.{ |
| | 7345 | try astgen.errNoteTok( |
| | 7346 | case_src, |
| | 7347 | "consider using 'else'", |
| | 7348 | .{}, |
| | 7349 | ), |
| | 7350 | }, |
| | 7351 | ); |
| | 7352 | } |
| | 7353 | if (underscore_src) |src| { |
| | 7354 | return astgen.failTokNotes( |
| | 7355 | val_src, |
| | 7356 | "multiple '_' prongs in switch expression", |
| | 7357 | .{}, |
| | 7358 | &.{try astgen.errNoteTok(src, "previous '_' prong here", .{})}, |
| | 7359 | ); |
| | 7360 | } |
| | 7361 | if (case.inline_token != null) { |
| | 7362 | return astgen.failTok(val_src, "cannot inline '_' prong", .{}); |
| | 7363 | } |
| | 7364 | under_case_node = case_node.toOptional(); |
| | 7365 | underscore_src = val_src; |
| | 7366 | underscore_node = val.toOptional(); |
| | 7367 | under_is_bare = case.ast.values.len == 1; |
| | 7368 | case_has_underscore = true; |
| | 7369 | } else { |
| | 7370 | total_items_len += 1; |
| | 7371 | } |
| | 7372 | }, |
| 7743 | } | 7373 | } |
| 7744 | } | 7374 | } |
| 7745 | if (case_has_underscore) continue; | | |
| 7746 | | 7375 | |
| 7747 | if (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) != .switch_range) { | 7376 | const case_len = case.ast.values.len - @intFromBool(case_has_underscore); |
| | 7377 | if (case_len == 1 and !case_has_ranges) { |
| 7748 | scalar_cases_len += 1; | 7378 | scalar_cases_len += 1; |
| 7749 | } else { | 7379 | } else if (case_len >= 1) { |
| 7750 | multi_cases_len += 1; | 7380 | multi_cases_len += 1; |
| 7751 | } | 7381 | } |
| 7752 | if (case.inline_token != null) { | | |
| 7753 | inline_cases_len += 1; | | |
| 7754 | } | | |
| 7755 | } | 7382 | } |
| 7756 | | 7383 | |
| 7757 | const special_prongs: Zir.SpecialProngs = .init( | 7384 | const has_else = else_src != null; |
| 7758 | else_src != null, | 7385 | const has_under = underscore_src != null; |
| 7759 | underscore_src != null, | 7386 | if (under_is_bare) assert(has_under); // make sure that the former implies the latter |
| 7760 | underscore_additional_items, | 7387 | if (is_err_switch) assert(!has_under); // should have failed by now |
| 7761 | ); | 7388 | const any_ranges = total_ranges_len > 0; |
| 7762 | const has_else = special_prongs.hasElse(); | | |
| 7763 | const has_under = special_prongs.hasUnder(); | | |
| 7764 | | | |
| 7765 | const operand_ri: ResultInfo = .{ .rl = if (any_payload_is_ref) .ref else .none }; | | |
| 7766 | | | |
| 7767 | astgen.advanceSourceCursorToNode(operand_node); | | |
| 7768 | const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; | | |
| 7769 | | | |
| 7770 | const raw_operand = try expr(parent_gz, scope, operand_ri, operand_node); | | |
| 7771 | const item_ri: ResultInfo = .{ .rl = .none }; | | |
| 7772 | | 7389 | |
| 7773 | // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type | 7390 | // This contains all of the body lengths (already in the correct order) and |
| 7774 | // to provide a result type. | 7391 | // the bodies they belong to that go into the `extra` array later, except the |
| 7775 | const raw_operand_ty_ref = if (switch_full.label_token != null) t: { | 7392 | // first item_table_end slots are a table that indexes the item bodies (and |
| 7776 | break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node); | 7393 | // also indirectly the prong bodies, as they are always trailing after their |
| 7777 | } else undefined; | 7394 | // item bodies). |
| 7778 | | | |
| 7779 | // This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti, | | |
| 7780 | // except the first cases_nodes.len slots are a table that indexes payloads later in the array, with | | |
| 7781 | // the special case index coming first, then scalar_case_len indexes, then multi_cases_len indexes | | |
| 7782 | const payloads = &astgen.scratch; | 7395 | const payloads = &astgen.scratch; |
| 7783 | const scratch_top = astgen.scratch.items.len; | 7396 | const scratch_top = astgen.scratch.items.len; |
| 7784 | const case_table_start = scratch_top; | 7397 | var payloads_end = scratch_top; |
| 7785 | const else_case_index = if (has_else) case_table_start else undefined; | 7398 | |
| 7786 | const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined; | 7399 | // Since range item body pairs are always contiguous we don't technically |
| 7787 | const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under); | 7400 | // have to keep track of the position of the second body. However handling |
| 7788 | const multi_case_table = scalar_case_table + scalar_cases_len; | 7401 | // all of the several indices and offsets is complicated enough as it is, |
| 7789 | const case_table_end = multi_case_table + multi_cases_len; | 7402 | // so for the sake of keeping this function a little bit more simple we do |
| 7790 | try astgen.scratch.resize(gpa, case_table_end); | 7403 | // it anyway. |
| | 7404 | |
| | 7405 | const scalar_body_table = payloads_end; |
| | 7406 | payloads_end += scalar_cases_len; |
| | 7407 | const multi_item_body_table = payloads_end; |
| | 7408 | payloads_end += total_items_len + 2 * total_ranges_len - scalar_cases_len; |
| | 7409 | const multi_prong_body_table = payloads_end; |
| | 7410 | payloads_end += multi_cases_len; |
| | 7411 | const body_table_end = payloads_end; |
| | 7412 | |
| | 7413 | const scalar_prong_infos_start = payloads_end; |
| | 7414 | payloads_end += scalar_cases_len; |
| | 7415 | const multi_prong_infos_start = payloads_end; |
| | 7416 | payloads_end += multi_cases_len; |
| | 7417 | const multi_case_items_lens_start = payloads_end; |
| | 7418 | payloads_end += multi_cases_len; |
| | 7419 | const multi_case_ranges_lens_start = if (any_ranges) blk: { |
| | 7420 | const multi_case_ranges_lens_start = payloads_end; |
| | 7421 | payloads_end += multi_cases_len; |
| | 7422 | break :blk multi_case_ranges_lens_start; |
| | 7423 | } else undefined; |
| | 7424 | const scalar_item_infos_start = payloads_end; |
| | 7425 | payloads_end += scalar_cases_len; |
| | 7426 | const multi_items_infos_start = payloads_end; |
| | 7427 | payloads_end += total_items_len - scalar_cases_len + 2 * total_ranges_len; |
| | 7428 | const bodies_start = payloads_end; |
| | 7429 | |
| | 7430 | try payloads.resize(gpa, bodies_start); |
| 7791 | defer astgen.scratch.items.len = scratch_top; | 7431 | defer astgen.scratch.items.len = scratch_top; |
| 7792 | | 7432 | |
| | 7433 | var non_err_prong_body_start: u32 = undefined; |
| | 7434 | var else_prong_body_start: u32 = undefined; |
| | 7435 | var bare_under_prong_body_start: u32 = undefined; |
| | 7436 | var non_err_info: Zir.Inst.SwitchBlock.ProngInfo.NonErr = undefined; |
| | 7437 | var else_info: Zir.Inst.SwitchBlock.ProngInfo.Else = undefined; |
| | 7438 | var under_extra: u32 = undefined; |
| | 7439 | |
| 7793 | var block_scope = parent_gz.makeSubBlock(scope); | 7440 | var block_scope = parent_gz.makeSubBlock(scope); |
| 7794 | // block_scope not used for collecting instructions | 7441 | // block_scope not used for collecting instructions |
| 7795 | block_scope.instructions_top = GenZir.unstacked_top; | 7442 | block_scope.instructions_top = GenZir.unstacked_top; |
| 7796 | block_scope.setBreakResultInfo(block_ri); | | |
| 7797 | | 7443 | |
| 7798 | // Sema expects a dbg_stmt immediately before switch_block(_ref) | 7444 | const operand_ri: ResultInfo = .{ |
| | 7445 | .rl = if (any_payload_is_ref or |
| | 7446 | (needs_non_err_handling and non_err_is_ref)) .ref else .none, |
| | 7447 | .ctx = if (do_err_trace) .error_handling_expr else .none, |
| | 7448 | }; |
| | 7449 | |
| | 7450 | astgen.advanceSourceCursorToNode(operand_node); |
| | 7451 | const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; |
| | 7452 | |
| | 7453 | const raw_operand: Zir.Inst.Ref = if (needs_non_err_handling) |
| | 7454 | try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node) |
| | 7455 | else |
| | 7456 | try expr(parent_gz, scope, operand_ri, operand_node); |
| | 7457 | |
| | 7458 | // Sema expects a dbg_stmt immediately before any kind of switch_block inst. |
| 7799 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); | 7459 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); |
| 7800 | // This gets added to the parent block later, after the item expressions. | 7460 | // This gets added to the parent block later, after the item expressions. |
| 7801 | const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block; | 7461 | const switch_tag: Zir.Inst.Tag = switch (non_err) { |
| 7802 | const switch_block = try parent_gz.makeBlockInst(switch_tag, node); | 7462 | .none, .peer_break_target => if (any_payload_is_ref) .switch_block_ref else .switch_block, |
| | 7463 | .@"if", .@"catch" => .switch_block_err_union, |
| | 7464 | }; |
| | 7465 | const switch_block = try parent_gz.makeBlockInst(switch_tag, switch_node); |
| | 7466 | |
| | 7467 | // Set `break` target if applicable; `continue` target may differ! |
| | 7468 | switch (non_err) { |
| | 7469 | .none => { |
| | 7470 | if (switch_full.label_token != null) { |
| | 7471 | block_scope.break_target = switch_block; |
| | 7472 | } |
| | 7473 | block_scope.setBreakResultInfo(block_ri); |
| | 7474 | }, |
| | 7475 | .@"catch", .@"if" => { |
| | 7476 | assert(switch_full.label_token == null); // use `peer_break_target` code path instead! |
| | 7477 | block_scope.setBreakResultInfo(block_ri); |
| | 7478 | }, |
| | 7479 | .peer_break_target => |peer_break_target| { |
| | 7480 | |
| | 7481 | // Special case; we have an error switch + label situation and we |
| | 7482 | // want to generate this: |
| | 7483 | // ``` |
| | 7484 | // %1 = block({ |
| | 7485 | // %2 = is_non_err(%operand) |
| | 7486 | // %3 = condbr(%2, { |
| | 7487 | // %4 = err_union_payload_unsafe(%operand) |
| | 7488 | // %5 = break(%1, result) // targets enclosing `block` |
| | 7489 | // }, { |
| | 7490 | // %6 = err_union_code(%operand) |
| | 7491 | // %7 = switch_block(%6, |
| | 7492 | // { ... } => { |
| | 7493 | // %8 = break(%1, result) // targets enclosing `block` |
| | 7494 | // }, |
| | 7495 | // { ... } => { |
| | 7496 | // %9 = switch_continue(%7, result) // targets `switch_block` |
| | 7497 | // }, |
| | 7498 | // ) |
| | 7499 | // %10 = break(%1, @void_value) |
| | 7500 | // }) |
| | 7501 | // }) |
| | 7502 | // ``` |
| | 7503 | // to ensure that the non-err case and the switch are only peers when |
| | 7504 | // breaking from either, but not when continuing the switch. We use |
| | 7505 | // this lowering to avoiding a rather complex special case in Sema. |
| | 7506 | |
| | 7507 | assert(switch_full.label_token != null); // use `switch_block_err_union` code path instead! |
| | 7508 | assert(.block == astgen.instructions.items(.tag)[@intFromEnum(peer_break_target.block_inst)]); |
| | 7509 | block_scope.break_target = peer_break_target.block_inst; |
| | 7510 | block_scope.setBreakResultInfo(peer_break_target.block_ri); |
| | 7511 | }, |
| | 7512 | } |
| | 7513 | |
| | 7514 | // We need a bunch of separate locations to store several capture values: |
| | 7515 | // `... |err| switch (err) { else => |e| { ... } }` // `err` and `e` |
| | 7516 | // `... => |payload, tag| { ... }` // `payload` and `tag` |
| | 7517 | // and result types: |
| | 7518 | // `foo => { ... }` // `foo` needs a result type |
| | 7519 | // `... => continue :sw val` // `val` needs a result type |
| | 7520 | // Some observations: |
| | 7521 | // - If we just use the switch inst itself we don't need a placeholder! |
| | 7522 | // - We can always tell for sure whether a capture exists. We also know |
| | 7523 | // that its existence implies that it has to be used. |
| | 7524 | // - We can't know whether there are any `continue`s before analyzing all |
| | 7525 | // prong bodies. At that point we already need a result location. We do |
| | 7526 | // know whether there even *could* be any though by looking for a label. |
| | 7527 | // - Sema wants a result location in `zirSwitchContinue`. If that's the |
| | 7528 | // switch inst itself, there's no need to look at the switch inst data. |
| | 7529 | // Some conclusions: |
| | 7530 | // - We should use the switch inst as the continue result location if needed. |
| | 7531 | // - If we need more insts for captures and our switch inst is already used |
| | 7532 | // for something else, we start creating placeholder insts. |
| | 7533 | |
| | 7534 | // Prong items use the switch block instruction as their result type. |
| | 7535 | // No other components of the switch statement are in scope while they are |
| | 7536 | // being resolved, so this is never a problem. |
| | 7537 | const item_ri: ResultInfo = .{ .rl = .{ .coerced_ty = switch_block.toRef() } }; |
| | 7538 | |
| | 7539 | var switch_block_inst_is_occupied: bool = false; |
| 7803 | | 7540 | |
| 7804 | if (switch_full.label_token) |label_token| { | 7541 | if (switch_full.label_token) |label_token| { |
| 7805 | block_scope.label = .{ .token = label_token }; | 7542 | block_scope.label = .{ .token = label_token }; |
| 7806 | block_scope.break_target = switch_block; | | |
| 7807 | block_scope.continue_target = .{ .switch_continue = switch_block }; | 7543 | block_scope.continue_target = .{ .switch_continue = switch_block }; |
| 7808 | block_scope.continue_result_info = .{ | 7544 | block_scope.continue_result_info = .{ |
| 7809 | .rl = if (any_payload_is_ref) | 7545 | .rl = if (any_payload_is_ref) |
| 7810 | .{ .ref_coerced_ty = raw_operand_ty_ref } | 7546 | .{ .ref_coerced_ty = switch_block.toRef() } |
| 7811 | else | 7547 | else |
| 7812 | .{ .coerced_ty = raw_operand_ty_ref }, | 7548 | .{ .coerced_ty = switch_block.toRef() }, |
| 7813 | }; | 7549 | }; |
| | 7550 | switch_block_inst_is_occupied = true; |
| 7814 | | 7551 | |
| 7815 | // `break_result_info` already set by `setBreakResultInfo` above. | 7552 | // `break_target` and `break_result_info` already set above. |
| 7816 | } | 7553 | } |
| | 7554 | if (needs_non_err_handling) { |
| | 7555 | // `switch_block_err_union` uses the switch block inst as its err capture/ |
| | 7556 | // switch operand. This is always ok as its switch can never have a label. |
| | 7557 | assert(!switch_block_inst_is_occupied); |
| | 7558 | switch_block_inst_is_occupied = true; |
| | 7559 | } |
| | 7560 | // `... => |payload| { ... }` |
| | 7561 | const payload_capture_inst, const payload_capture_inst_is_placeholder = inst: { |
| | 7562 | if (!any_has_payload_capture) break :inst .{ undefined, false }; |
| | 7563 | if (!switch_block_inst_is_occupied) { |
| | 7564 | switch_block_inst_is_occupied = true; |
| | 7565 | break :inst .{ switch_block, false }; |
| | 7566 | } |
| | 7567 | break :inst .{ try astgen.appendPlaceholder(), true }; |
| | 7568 | }; |
| | 7569 | // `... => |_, tag| { ... }` |
| | 7570 | const tag_capture_inst, const tag_capture_inst_is_placeholder = inst: { |
| | 7571 | if (!any_has_tag_capture) break :inst .{ undefined, false }; |
| | 7572 | if (!switch_block_inst_is_occupied) { |
| | 7573 | switch_block_inst_is_occupied = true; |
| | 7574 | break :inst .{ switch_block, false }; |
| | 7575 | } |
| | 7576 | break :inst .{ try astgen.appendPlaceholder(), true }; |
| | 7577 | }; |
| 7817 | | 7578 | |
| 7818 | // We re-use this same scope for all cases, including the special prong, if any. | 7579 | var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined; |
| 7819 | var case_scope = parent_gz.makeSubBlock(&block_scope.base); | 7580 | const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: { |
| 7820 | case_scope.instructions_top = GenZir.unstacked_top; | 7581 | var extra_insts: std.ArrayList(Zir.Inst.Index) = .initBuffer(&prong_body_extra_insts_buf); |
| | 7582 | if (switch_block_inst_is_occupied) extra_insts.appendAssumeCapacity(switch_block); |
| | 7583 | if (payload_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(payload_capture_inst); |
| | 7584 | if (tag_capture_inst_is_placeholder) extra_insts.appendAssumeCapacity(tag_capture_inst); |
| | 7585 | break :extra_insts extra_insts.items; |
| | 7586 | }; |
| 7821 | | 7587 | |
| 7822 | // If any prong has an inline tag capture, allocate a shared dummy instruction for it | 7588 | const switch_operand, const catch_or_if_operand = if (needs_non_err_handling) |
| 7823 | const tag_inst = if (any_has_tag_capture) tag_inst: { | 7589 | .{ switch_block.toRef(), raw_operand } |
| 7824 | const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); | 7590 | else |
| 7825 | try astgen.instructions.append(astgen.gpa, .{ | 7591 | .{ raw_operand, undefined }; |
| 7826 | .tag = .extended, | 7592 | |
| 7827 | .data = .{ .extended = .{ | 7593 | // We re-use this same scope for all case items and contents. |
| 7828 | .opcode = .value_placeholder, | 7594 | var scratch_scope = parent_gz.makeSubBlock(&block_scope.base); |
| 7829 | .small = undefined, | 7595 | scratch_scope.instructions_top = GenZir.unstacked_top; |
| 7830 | .operand = undefined, | 7596 | |
| 7831 | } }, | 7597 | // We have to take care of the non-error body first if there is one. |
| 7832 | }); | 7598 | non_err_body: { |
| 7833 | break :tag_inst inst; | 7599 | if (!needs_non_err_handling) break :non_err_body; |
| 7834 | } else undefined; | 7600 | |
| | 7601 | scratch_scope.instructions_top = parent_gz.instructions.items.len; |
| | 7602 | defer scratch_scope.unstack(); |
| | 7603 | |
| | 7604 | // It's always ok to use the switch block inst to refer to the error union |
| | 7605 | // payload as the actual switch statement isn't even in scope yet. |
| | 7606 | const non_err_payload_inst = switch_block; |
| | 7607 | var non_err_capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; |
| | 7608 | |
| | 7609 | switch (non_err) { |
| | 7610 | .none, .peer_break_target => unreachable, |
| | 7611 | .@"catch" => { |
| | 7612 | // We always effectively capture the error union payload; we use |
| | 7613 | // it to `break` from the entire `switch_block_err_union`. |
| | 7614 | non_err_capture = if (non_err_is_ref) .by_ref else .by_val; |
| | 7615 | |
| | 7616 | const then_result = switch (ri.rl) { |
| | 7617 | .ref, .ref_coerced_ty => non_err_payload_inst.toRef(), |
| | 7618 | else => try rvalue( |
| | 7619 | &scratch_scope, |
| | 7620 | block_scope.break_result_info, |
| | 7621 | non_err_payload_inst.toRef(), |
| | 7622 | catch_or_if_node, |
| | 7623 | ), |
| | 7624 | }; |
| | 7625 | _ = try scratch_scope.addBreakWithSrcNode( |
| | 7626 | .@"break", |
| | 7627 | switch_block, |
| | 7628 | then_result, |
| | 7629 | catch_or_if_node, |
| | 7630 | ); |
| | 7631 | }, |
| | 7632 | .@"if" => |if_full| { |
| | 7633 | var payload_val_scope: Scope.LocalVal = undefined; |
| | 7634 | |
| | 7635 | const then_node = if_full.ast.then_expr; |
| | 7636 | const then_sub_scope: *Scope = scope: { |
| | 7637 | if (if_full.payload_token) |payload_token| { |
| | 7638 | const ident_token = payload_token + @intFromBool(non_err_is_ref); |
| | 7639 | const ident_name = try astgen.identAsString(ident_token); |
| | 7640 | const ident_name_str = tree.tokenSlice(ident_token); |
| | 7641 | if (mem.eql(u8, "_", ident_name_str)) { |
| | 7642 | break :scope &scratch_scope.base; |
| | 7643 | } |
| | 7644 | non_err_capture = if (non_err_is_ref) .by_ref else .by_val; |
| | 7645 | try astgen.detectLocalShadowing(&scratch_scope.base, ident_name, ident_token, ident_name_str, .capture); |
| | 7646 | payload_val_scope = .{ |
| | 7647 | .parent = &scratch_scope.base, |
| | 7648 | .gen_zir = &scratch_scope, |
| | 7649 | .name = ident_name, |
| | 7650 | .inst = non_err_payload_inst.toRef(), |
| | 7651 | .token_src = ident_token, |
| | 7652 | .id_cat = .capture, |
| | 7653 | }; |
| | 7654 | try scratch_scope.addDbgVar(.dbg_var_val, ident_name, non_err_payload_inst.toRef()); |
| | 7655 | break :scope &payload_val_scope.base; |
| | 7656 | } else { |
| | 7657 | _ = try scratch_scope.addUnNode( |
| | 7658 | .ensure_err_union_payload_void, |
| | 7659 | catch_or_if_operand, |
| | 7660 | catch_or_if_node, |
| | 7661 | ); |
| | 7662 | break :scope &scratch_scope.base; |
| | 7663 | } |
| | 7664 | }; |
| | 7665 | const then_result = try fullBodyExpr(&scratch_scope, then_sub_scope, block_scope.break_result_info, then_node, .allow_branch_hint); |
| | 7666 | try checkUsed(parent_gz, &scratch_scope.base, then_sub_scope); |
| | 7667 | if (!scratch_scope.endsWithNoReturn()) { |
| | 7668 | _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, then_result, then_node); |
| | 7669 | } |
| | 7670 | }, |
| | 7671 | } |
| | 7672 | const body_slice = scratch_scope.instructionsSlice(); |
| | 7673 | const body_start: u32 = @intCast(payloads.items.len); |
| | 7674 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, &.{non_err_payload_inst}); |
| | 7675 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| | 7676 | astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, &.{non_err_payload_inst}); |
| | 7677 | |
| | 7678 | non_err_prong_body_start = body_start; |
| | 7679 | non_err_info = .{ |
| | 7680 | .body_len = @intCast(body_len), |
| | 7681 | .capture = non_err_capture, |
| | 7682 | .operand_is_ref = non_err_is_ref, |
| | 7683 | }; |
| | 7684 | } |
| 7835 | | 7685 | |
| 7836 | // In this pass we generate all the item and prong expressions. | 7686 | // In this pass we generate all the item and prong expressions. |
| 7837 | var multi_case_index: u32 = 0; | 7687 | var multi_case_index: u32 = 0; |
| 7838 | var scalar_case_index: u32 = 0; | 7688 | var scalar_case_index: u32 = 0; |
| | 7689 | var multi_item_offset: usize = 0; |
| 7839 | for (case_nodes) |case_node| { | 7690 | for (case_nodes) |case_node| { |
| 7840 | const case = tree.fullSwitchCase(case_node).?; | 7691 | const case = tree.fullSwitchCase(case_node).?; |
| 7841 | | 7692 | |
| 7842 | const is_multi_case = case.ast.values.len > 1 or | 7693 | const case_has_under = case_node.toOptional() == under_case_node; |
| 7843 | (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range); | 7694 | const ranges_len: u32 = if (any_ranges) blk: { |
| | 7695 | var ranges_len: u32 = 0; |
| | 7696 | for (case.ast.values) |value| { |
| | 7697 | ranges_len += @intFromBool(tree.nodeTag(value) == .switch_range); |
| | 7698 | } |
| | 7699 | break :blk ranges_len; |
| | 7700 | } else 0; |
| | 7701 | const items_len: u32 = @intCast(case.ast.values.len - ranges_len - @intFromBool(case_has_under)); |
| | 7702 | const is_multi_case = items_len > 1 or ranges_len > 0; |
| | 7703 | |
| | 7704 | // item/range bodies in order of occurence |
| | 7705 | var item_i: usize = 0; |
| | 7706 | var range_i: usize = 0; |
| | 7707 | for (case.ast.values) |value| { |
| | 7708 | if (value.toOptional() == underscore_node) continue; |
| | 7709 | const is_range = tree.nodeTag(value) == .switch_range; |
| | 7710 | const range: [2]Ast.Node.Index = if (is_range) tree.nodeData(value).node_and_node else undefined; |
| | 7711 | const nodes: []const Ast.Node.Index = if (is_range) &range else &.{value}; |
| | 7712 | for (nodes) |item| { |
| | 7713 | // We lower enum literals, error values and number literals |
| | 7714 | // manually to save space since they are very commonly used as |
| | 7715 | // switch case items. |
| | 7716 | const body_start: u32 = @intCast(payloads.items.len); |
| | 7717 | const item_info: Zir.Inst.SwitchBlock.ItemInfo = blk: switch (tree.nodeTag(item)) { |
| | 7718 | .enum_literal => { |
| | 7719 | const str_index = try astgen.identAsString(tree.nodeMainToken(item)); |
| | 7720 | break :blk .wrap(.{ .enum_literal = str_index }); |
| | 7721 | }, |
| | 7722 | .error_value => { |
| | 7723 | const ident_token = tree.nodeMainToken(item) + 2; // skip 'error', '.' |
| | 7724 | const str_index = try astgen.identAsString(ident_token); |
| | 7725 | break :blk .wrap(.{ .error_value = str_index }); |
| | 7726 | }, |
| | 7727 | .number_literal => { |
| | 7728 | // We don't actually need a final result type for number |
| | 7729 | // literals, they can just be turned into `comptime_int` |
| | 7730 | // or `comptime_float` as usual and then be coerced to |
| | 7731 | // the correct type later during semantic analysis. |
| | 7732 | assert(scratch_scope.instructions_top == GenZir.unstacked_top); // important! we emit into `parent_gz` which `scratch_scope` is stacked on top of |
| | 7733 | const zir_ref = try comptimeExpr(parent_gz, scope, .{ .rl = .none }, item, .switch_item); |
| | 7734 | break :blk .wrap(.{ .number_literal = zir_ref }); |
| | 7735 | }, |
| | 7736 | else => { |
| | 7737 | scratch_scope.instructions_top = parent_gz.instructions.items.len; |
| | 7738 | defer scratch_scope.unstack(); |
| | 7739 | const item_result = try fullBodyExpr(&scratch_scope, scope, item_ri, item, .normal); |
| | 7740 | if (!scratch_scope.endsWithNoReturn()) { |
| | 7741 | _ = try scratch_scope.addBreakWithSrcNode(.break_inline, switch_block, item_result, item); |
| | 7742 | } |
| | 7743 | const item_slice = scratch_scope.instructionsSlice(); |
| | 7744 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(item_slice, &.{switch_block}); |
| | 7745 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| | 7746 | astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, item_slice, &.{switch_block}); |
| | 7747 | break :blk .wrap(.{ .body_len = body_len }); |
| | 7748 | }, |
| | 7749 | }; |
| | 7750 | if (is_multi_case) { |
| | 7751 | if (is_range) { |
| | 7752 | const offset = multi_item_offset + items_len + range_i; |
| | 7753 | payloads.items[multi_item_body_table + offset] = body_start; |
| | 7754 | payloads.items[multi_items_infos_start + offset] = @bitCast(item_info); |
| | 7755 | range_i += 1; |
| | 7756 | } else { |
| | 7757 | const offset = multi_item_offset + item_i; |
| | 7758 | payloads.items[multi_item_body_table + offset] = body_start; |
| | 7759 | payloads.items[multi_items_infos_start + offset] = @bitCast(item_info); |
| | 7760 | item_i += 1; |
| | 7761 | } |
| | 7762 | } else { |
| | 7763 | payloads.items[scalar_body_table + scalar_case_index] = body_start; |
| | 7764 | payloads.items[scalar_item_infos_start + scalar_case_index] = @bitCast(item_info); |
| | 7765 | } |
| | 7766 | } |
| | 7767 | } |
| | 7768 | if (is_multi_case) { |
| | 7769 | assert(item_i == items_len and range_i == 2 * ranges_len); |
| | 7770 | payloads.items[multi_case_items_lens_start + multi_case_index] = items_len; |
| | 7771 | if (any_ranges) { |
| | 7772 | payloads.items[multi_case_ranges_lens_start + multi_case_index] = ranges_len; |
| | 7773 | } |
| | 7774 | multi_item_offset += items_len + 2 * ranges_len; |
| | 7775 | } |
| 7844 | | 7776 | |
| 7845 | var dbg_var_name: Zir.NullTerminatedString = .empty; | 7777 | // Capture and prong body |
| 7846 | var dbg_var_inst: Zir.Inst.Ref = undefined; | 7778 | |
| | 7779 | var dbg_var_payload_name: Zir.NullTerminatedString = .empty; |
| | 7780 | var dbg_var_payload_inst: Zir.Inst.Ref = undefined; |
| 7847 | var dbg_var_tag_name: Zir.NullTerminatedString = .empty; | 7781 | var dbg_var_tag_name: Zir.NullTerminatedString = .empty; |
| 7848 | var dbg_var_tag_inst: Zir.Inst.Ref = undefined; | 7782 | var dbg_var_tag_inst: Zir.Inst.Ref = undefined; |
| 7849 | var has_tag_capture = false; | 7783 | var has_tag_capture = false; |
| 7850 | var capture_val_scope: Scope.LocalVal = undefined; | 7784 | var err_capture_scope: Scope.LocalVal = undefined; |
| 7851 | var tag_scope: Scope.LocalVal = undefined; | 7785 | var payload_capture_scope: Scope.LocalVal = undefined; |
| | 7786 | var tag_capture_scope: Scope.LocalVal = undefined; |
| 7852 | | 7787 | |
| 7853 | var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; | 7788 | var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; |
| 7854 | | 7789 | |
| 7855 | const sub_scope = blk: { | 7790 | // Check all captures and make them available to the prong body. |
| 7856 | const payload_token = case.payload_token orelse break :blk &case_scope.base; | 7791 | // Potential captures are: |
| | 7792 | // - for regular switch: payload and tag |
| | 7793 | // - for error switch: switch operand and payload |
| | 7794 | const prong_body_scope: *Scope = scope: { |
| | 7795 | const switch_scope: *Scope = if (needs_non_err_handling) blk: { |
| | 7796 | // We want to have the captured error we're switching on in scope! |
| | 7797 | err_capture_scope = .{ |
| | 7798 | .parent = &scratch_scope.base, |
| | 7799 | .gen_zir = &scratch_scope, |
| | 7800 | .name = err_capture_name, |
| | 7801 | .inst = switch_operand, |
| | 7802 | .token_src = err_token, |
| | 7803 | .id_cat = .capture, |
| | 7804 | }; |
| | 7805 | break :blk &err_capture_scope.base; |
| | 7806 | } else &scratch_scope.base; |
| | 7807 | |
| | 7808 | const payload_token = case.payload_token orelse break :scope switch_scope; |
| 7857 | const capture_is_ref = tree.tokenTag(payload_token) == .asterisk; | 7809 | const capture_is_ref = tree.tokenTag(payload_token) == .asterisk; |
| 7858 | const ident = payload_token + @intFromBool(capture_is_ref); | 7810 | const ident = payload_token + @intFromBool(capture_is_ref); |
| 7859 | | 7811 | |
| ... | @@ -7867,36 +7819,38 @@ fn switchExpr( | ... | @@ -7867,36 +7819,38 @@ fn switchExpr( |
| 7867 | return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); | 7819 | return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); |
| 7868 | } | 7820 | } |
| 7869 | capture = .none; | 7821 | capture = .none; |
| 7870 | payload_sub_scope = &case_scope.base; | 7822 | payload_sub_scope = switch_scope; |
| 7871 | } else { | 7823 | } else { |
| 7872 | const capture_name = try astgen.identAsString(ident); | 7824 | const capture_name = try astgen.identAsString(ident); |
| 7873 | try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture); | 7825 | try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture); |
| 7874 | capture_val_scope = .{ | 7826 | payload_capture_scope = .{ |
| 7875 | .parent = &case_scope.base, | 7827 | .parent = switch_scope, |
| 7876 | .gen_zir = &case_scope, | 7828 | .gen_zir = &scratch_scope, |
| 7877 | .name = capture_name, | 7829 | .name = capture_name, |
| 7878 | .inst = switch_block.toRef(), | 7830 | .inst = payload_capture_inst.toRef(), |
| 7879 | .token_src = ident, | 7831 | .token_src = ident, |
| 7880 | .id_cat = .capture, | 7832 | .id_cat = .capture, |
| 7881 | }; | 7833 | }; |
| 7882 | dbg_var_name = capture_name; | 7834 | dbg_var_payload_name = payload_capture_scope.name; |
| 7883 | dbg_var_inst = switch_block.toRef(); | 7835 | dbg_var_payload_inst = payload_capture_scope.inst; |
| 7884 | payload_sub_scope = &capture_val_scope.base; | 7836 | payload_sub_scope = &payload_capture_scope.base; |
| 7885 | } | 7837 | } |
| 7886 | | 7838 | |
| 7887 | const tag_token = if (tree.tokenTag(ident + 1) == .comma) | 7839 | if (is_err_switch and capture == .by_ref) { |
| 7888 | ident + 2 | 7840 | return astgen.failTok(ident, "error set cannot be captured by reference", .{}); |
| 7889 | else if (capture == .none) { | 7841 | } |
| 7890 | // discarding the capture is only valid iff the tag is captured | 7842 | |
| | 7843 | const tag_token = if (tree.tokenTag(ident + 1) == .comma) blk: { |
| | 7844 | break :blk ident + 2; |
| | 7845 | } else if (capture == .none) { |
| | 7846 | // discarding the capture is only valid if the tag is captured |
| 7891 | // whether the tag capture is discarded is handled below | 7847 | // whether the tag capture is discarded is handled below |
| 7892 | return astgen.failTok(payload_token, "discard of capture; omit it instead", .{}); | 7848 | return astgen.failTok(payload_token, "discard of capture; omit it instead", .{}); |
| 7893 | } else break :blk payload_sub_scope; | 7849 | } else break :scope payload_sub_scope; |
| 7894 | | 7850 | |
| 7895 | const tag_slice = tree.tokenSlice(tag_token); | 7851 | const tag_slice = tree.tokenSlice(tag_token); |
| 7896 | if (mem.eql(u8, tag_slice, "_")) { | 7852 | if (mem.eql(u8, tag_slice, "_")) { |
| 7897 | return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{}); | 7853 | return astgen.failTok(tag_token, "discard of tag capture; omit it instead", .{}); |
| 7898 | } else if (case.inline_token == null) { | | |
| 7899 | return astgen.failTok(tag_token, "tag capture on non-inline prong", .{}); | | |
| 7900 | } | 7854 | } |
| 7901 | const tag_name = try astgen.identAsString(tag_token); | 7855 | const tag_name = try astgen.identAsString(tag_token); |
| 7902 | try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture"); | 7856 | try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture"); |
| ... | @@ -7904,123 +7858,155 @@ fn switchExpr( | ... | @@ -7904,123 +7858,155 @@ fn switchExpr( |
| 7904 | assert(any_has_tag_capture); | 7858 | assert(any_has_tag_capture); |
| 7905 | has_tag_capture = true; | 7859 | has_tag_capture = true; |
| 7906 | | 7860 | |
| 7907 | tag_scope = .{ | 7861 | if (is_err_switch) { |
| | 7862 | return astgen.failTok(tag_token, "cannot capture tag of error union", .{}); |
| | 7863 | } |
| | 7864 | |
| | 7865 | tag_capture_scope = .{ |
| 7908 | .parent = payload_sub_scope, | 7866 | .parent = payload_sub_scope, |
| 7909 | .gen_zir = &case_scope, | 7867 | .gen_zir = &scratch_scope, |
| 7910 | .name = tag_name, | 7868 | .name = tag_name, |
| 7911 | .inst = tag_inst.toRef(), | 7869 | .inst = tag_capture_inst.toRef(), |
| 7912 | .token_src = tag_token, | 7870 | .token_src = tag_token, |
| 7913 | .id_cat = .@"switch tag capture", | 7871 | .id_cat = .@"switch tag capture", |
| 7914 | }; | 7872 | }; |
| 7915 | dbg_var_tag_name = tag_name; | 7873 | dbg_var_tag_name = tag_capture_scope.name; |
| 7916 | dbg_var_tag_inst = tag_inst.toRef(); | 7874 | dbg_var_tag_inst = tag_capture_scope.inst; |
| 7917 | break :blk &tag_scope.base; | 7875 | break :scope &tag_capture_scope.base; |
| 7918 | }; | 7876 | }; |
| 7919 | | 7877 | |
| 7920 | const header_index: u32 = @intCast(payloads.items.len); | 7878 | if (capture != .none) assert(any_has_payload_capture); |
| 7921 | const body_len_index = if (is_multi_case) blk: { | 7879 | if (is_err_switch) { |
| 7922 | if (case_node.toOptional() == underscore_case_node) { | 7880 | assert(!any_payload_is_ref); // should have failed by now |
| 7923 | payloads.items[under_case_index] = header_index; | 7881 | assert(!any_has_tag_capture); // should have failed by now |
| 7924 | if (special_prongs.hasOneAdditionalItem()) { | 7882 | } |
| 7925 | try payloads.resize(gpa, header_index + 2); // item, body_len | 7883 | |
| 7926 | const maybe_item_node = case.ast.values[0]; | 7884 | prong_body: { |
| 7927 | const item_node = if (maybe_item_node.toOptional() == underscore_node) | 7885 | scratch_scope.instructions_top = parent_gz.instructions.items.len; |
| 7928 | case.ast.values[1] | 7886 | defer scratch_scope.unstack(); |
| 7929 | else | 7887 | |
| 7930 | maybe_item_node; | 7888 | if (dbg_var_payload_name != .empty) { |
| 7931 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); | 7889 | try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst); |
| 7932 | payloads.items[header_index] = @intFromEnum(item_inst); | 7890 | } |
| 7933 | break :blk header_index + 1; | 7891 | if (dbg_var_tag_name != .empty) { |
| 7934 | } | 7892 | try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst); |
| | 7893 | } |
| | 7894 | if (do_err_trace and nodeMayAppendToErrorTrace(tree, operand_node)) { |
| | 7895 | _ = try scratch_scope.addSaveErrRetIndex(.always); |
| | 7896 | } |
| | 7897 | const target_expr_node = case.ast.target_expr; |
| | 7898 | const case_result = try fullBodyExpr(&scratch_scope, prong_body_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); |
| | 7899 | if (needs_non_err_handling) { |
| | 7900 | // If we would check `scratch_scope` here, we would get a false |
| | 7901 | // positive, that being the switch operand itself! |
| | 7902 | try checkUsed(parent_gz, &err_capture_scope.base, prong_body_scope); |
| 7935 | } else { | 7903 | } else { |
| 7936 | payloads.items[multi_case_table + multi_case_index] = header_index; | 7904 | try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope); |
| 7937 | multi_case_index += 1; | | |
| 7938 | } | 7905 | } |
| 7939 | try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len | 7906 | if (!scratch_scope.endsWithNoReturn()) { |
| 7940 | | 7907 | // As our last action before the break, "pop" the error trace if needed |
| 7941 | // items | 7908 | if (do_err_trace) { |
| 7942 | var items_len: u32 = 0; | 7909 | try restoreErrRetIndex( |
| 7943 | for (case.ast.values) |item_node| { | 7910 | &scratch_scope, |
| 7944 | if (item_node.toOptional() == underscore_node or | 7911 | .{ .block = switch_block }, |
| 7945 | tree.nodeTag(item_node) == .switch_range) | 7912 | block_scope.break_result_info, |
| 7946 | { | 7913 | target_expr_node, |
| 7947 | continue; | 7914 | case_result, |
| | 7915 | ); |
| 7948 | } | 7916 | } |
| 7949 | items_len += 1; | 7917 | _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); |
| 7950 | | | |
| 7951 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); | | |
| 7952 | try payloads.append(gpa, @intFromEnum(item_inst)); | | |
| 7953 | } | 7918 | } |
| 7954 | | 7919 | |
| 7955 | // ranges | 7920 | const body_slice = scratch_scope.instructionsSlice(); |
| 7956 | var ranges_len: u32 = 0; | 7921 | const body_start: u32 = @intCast(payloads.items.len); |
| 7957 | for (case.ast.values) |range| { | 7922 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts); |
| 7958 | if (tree.nodeTag(range) != .switch_range) { | 7923 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| 7959 | continue; | 7924 | astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts); |
| 7960 | } | 7925 | |
| 7961 | ranges_len += 1; | 7926 | if (case_node.toOptional() == else_case_node) { |
| | 7927 | assert(case.ast.values.len == 0); |
| | 7928 | |
| | 7929 | // Specific `else` bodies can cause Sema to omit the |
| | 7930 | // "unreachable else prong" error so that certain generic code |
| | 7931 | // patterns don't trigger it. We do that for these bodies: |
| | 7932 | // `else => unreachable,` |
| | 7933 | // `else => return,` |
| | 7934 | // `else => |e| return e,` (where `e` is any identifier) |
| | 7935 | const is_simple_noreturn = switch (tree.nodeTag(target_expr_node)) { |
| | 7936 | .unreachable_literal => true, // `=> unreachable,` |
| | 7937 | .@"return" => simple_noreturn: { |
| | 7938 | const retval_node = tree.nodeData(target_expr_node).opt_node.unwrap() orelse { |
| | 7939 | break :simple_noreturn true; // `=> return,` |
| | 7940 | }; |
| | 7941 | // Check for `=> |e| return e,` |
| | 7942 | if (capture != .by_val) break :simple_noreturn false; |
| | 7943 | if (tree.nodeTag(retval_node) != .identifier) break :simple_noreturn false; |
| | 7944 | const payload_name = try astgen.identAsString(case.payload_token.?); |
| | 7945 | const retval_name = try astgen.identAsString(tree.nodeMainToken(retval_node)); |
| | 7946 | break :simple_noreturn payload_name == retval_name; |
| | 7947 | }, |
| | 7948 | else => false, |
| | 7949 | }; |
| 7962 | | 7950 | |
| 7963 | const first_node, const last_node = tree.nodeData(range).node_and_node; | 7951 | else_info = .{ |
| 7964 | const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item); | 7952 | .body_len = @intCast(body_len), |
| 7965 | const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item); | 7953 | .capture = capture, |
| 7966 | try payloads.appendSlice(gpa, &[_]u32{ | 7954 | .is_inline = case.inline_token != null, |
| 7967 | @intFromEnum(first), @intFromEnum(last), | 7955 | .has_tag_capture = has_tag_capture, |
| 7968 | }); | 7956 | .is_simple_noreturn = is_simple_noreturn, |
| | 7957 | }; |
| | 7958 | else_prong_body_start = body_start; |
| | 7959 | break :prong_body; |
| 7969 | } | 7960 | } |
| 7970 | | 7961 | |
| 7971 | payloads.items[header_index] = items_len; | 7962 | if (case_has_under) { |
| 7972 | payloads.items[header_index + 1] = ranges_len; | 7963 | // We're either writing under_prong_info or under_index here. |
| 7973 | break :blk header_index + 2; | 7964 | if (under_is_bare) { |
| 7974 | } else if (case_node.toOptional() == else_case_node) blk: { | 7965 | assert(case.ast.values.len == 1); // only `_` |
| 7975 | payloads.items[else_case_index] = header_index; | 7966 | const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{ |
| 7976 | try payloads.resize(gpa, header_index + 1); // body_len | 7967 | .body_len = @intCast(body_len), |
| 7977 | break :blk header_index; | 7968 | .capture = capture, |
| 7978 | } else if (case_node.toOptional() == underscore_case_node) blk: { | 7969 | .has_tag_capture = has_tag_capture, |
| 7979 | assert(!special_prongs.hasAdditionalItems()); | 7970 | }; |
| 7980 | payloads.items[under_case_index] = header_index; | 7971 | under_extra = @bitCast(bare_under_info); |
| 7981 | try payloads.resize(gpa, header_index + 1); // body_len | 7972 | bare_under_prong_body_start = body_start; |
| 7982 | break :blk header_index; | 7973 | break :prong_body; |
| 7983 | } else blk: { | 7974 | } else if (is_multi_case) { |
| 7984 | payloads.items[scalar_case_table + scalar_case_index] = header_index; | 7975 | under_extra = scalar_cases_len + multi_case_index; |
| 7985 | scalar_case_index += 1; | 7976 | } else { |
| 7986 | try payloads.resize(gpa, header_index + 2); // item, body_len | 7977 | under_extra = scalar_case_index; |
| 7987 | const item_node = case.ast.values[0]; | 7978 | } |
| 7988 | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); | | |
| 7989 | payloads.items[header_index] = @intFromEnum(item_inst); | | |
| 7990 | break :blk header_index + 1; | | |
| 7991 | }; | | |
| 7992 | | | |
| 7993 | { | | |
| 7994 | // temporarily stack case_scope on parent_gz | | |
| 7995 | case_scope.instructions_top = parent_gz.instructions.items.len; | | |
| 7996 | defer case_scope.unstack(); | | |
| 7997 | | | |
| 7998 | if (dbg_var_name != .empty) { | | |
| 7999 | try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst); | | |
| 8000 | } | | |
| 8001 | if (dbg_var_tag_name != .empty) { | | |
| 8002 | try case_scope.addDbgVar(.dbg_var_val, dbg_var_tag_name, dbg_var_tag_inst); | | |
| 8003 | } | | |
| 8004 | const target_expr_node = case.ast.target_expr; | | |
| 8005 | const case_result = try fullBodyExpr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node, .allow_branch_hint); | | |
| 8006 | try checkUsed(parent_gz, &case_scope.base, sub_scope); | | |
| 8007 | if (!parent_gz.refIsNoReturn(case_result)) { | | |
| 8008 | _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); | | |
| 8009 | } | 7979 | } |
| 8010 | | 7980 | |
| 8011 | const case_slice = case_scope.instructionsSlice(); | 7981 | // We allow prongs with error items which are not inside the error set |
| 8012 | const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block}; | 7982 | // being switched on if their body is `=> comptime unreachable,`. |
| 8013 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts); | 7983 | const is_comptime_unreach = comptime_unreach: { |
| 8014 | try payloads.ensureUnusedCapacity(gpa, body_len); | 7984 | if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false; |
| 8015 | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ | 7985 | const comptime_node = tree.nodeData(target_expr_node).node; |
| | 7986 | break :comptime_unreach tree.nodeTag(comptime_node) == .unreachable_literal; |
| | 7987 | }; |
| | 7988 | |
| | 7989 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = .{ |
| 8016 | .body_len = @intCast(body_len), | 7990 | .body_len = @intCast(body_len), |
| 8017 | .capture = capture, | 7991 | .capture = capture, |
| 8018 | .is_inline = case.inline_token != null, | 7992 | .is_inline = case.inline_token != null, |
| 8019 | .has_tag_capture = has_tag_capture, | 7993 | .has_tag_capture = has_tag_capture, |
| 8020 | }); | 7994 | .is_comptime_unreach = is_comptime_unreach, |
| 8021 | appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts); | 7995 | }; |
| | 7996 | |
| | 7997 | if (is_multi_case) { |
| | 7998 | payloads.items[multi_prong_body_table + multi_case_index] = body_start; |
| | 7999 | payloads.items[multi_prong_infos_start + multi_case_index] = @bitCast(prong_info); |
| | 8000 | multi_case_index += 1; |
| | 8001 | } else { |
| | 8002 | // prong body start is implicit, it's right behind our only item. |
| | 8003 | payloads.items[scalar_prong_infos_start + scalar_case_index] = @bitCast(prong_info); |
| | 8004 | scalar_case_index += 1; |
| | 8005 | } |
| 8022 | } | 8006 | } |
| 8023 | } | 8007 | } |
| | 8008 | assert(scalar_case_index + multi_case_index + @intFromBool(has_else) + @intFromBool(under_is_bare) == case_nodes.len); |
| | 8009 | assert(multi_items_infos_start + multi_item_offset == bodies_start); |
| 8024 | | 8010 | |
| 8025 | if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) { | 8011 | if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) { |
| 8026 | try astgen.appendErrorTok(label_token, "unused switch label", .{}); | 8012 | try astgen.appendErrorTok(label_token, "unused switch label", .{}); |
| ... | @@ -8029,84 +8015,108 @@ fn switchExpr( | ... | @@ -8029,84 +8015,108 @@ fn switchExpr( |
| 8029 | // Now that the item expressions are generated we can add this. | 8015 | // Now that the item expressions are generated we can add this. |
| 8030 | try parent_gz.instructions.append(gpa, switch_block); | 8016 | try parent_gz.instructions.append(gpa, switch_block); |
| 8031 | | 8017 | |
| | 8018 | // We've collected all of the data we need! Now we just have to finalize it |
| | 8019 | // by copying our bodies from `payloads` to `extra`, this time in the order |
| | 8020 | // expected by ZIR consumers. |
| | 8021 | |
| 8032 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + | 8022 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + |
| 8033 | @intFromBool(multi_cases_len != 0) + | 8023 | @intFromBool(multi_cases_len > 0) + // multi_cases_len |
| 8034 | @intFromBool(any_has_tag_capture) + | 8024 | @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder |
| 8035 | payloads.items.len - scratch_top); | 8025 | @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder |
| 8036 | | 8026 | @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset |
| 8037 | const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ | 8027 | @intFromBool(needs_non_err_handling) + // non_err_info |
| 8038 | .operand = raw_operand, | 8028 | @intFromBool(has_else) + // else_info |
| 8039 | .bits = Zir.Inst.SwitchBlock.Bits{ | 8029 | @intFromBool(has_under) + // under_prong_info or under_index |
| 8040 | .has_multi_cases = multi_cases_len != 0, | 8030 | payloads.items.len - body_table_end); // item infos and bodies |
| 8041 | .special_prongs = special_prongs, | 8031 | |
| 8042 | .any_has_tag_capture = any_has_tag_capture, | 8032 | // singular pieces of data |
| 8043 | .any_non_inline_capture = any_non_inline_capture, | 8033 | const zir_payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ |
| | 8034 | .raw_operand = raw_operand, |
| | 8035 | .bits = .{ |
| | 8036 | .has_multi_cases = multi_cases_len > 0, |
| | 8037 | .any_ranges = any_ranges, |
| | 8038 | .has_else = has_else, |
| | 8039 | .has_under = has_under, |
| | 8040 | .under_is_bare = under_is_bare, |
| 8044 | .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, | 8041 | .has_continue = switch_full.label_token != null and block_scope.label.?.used_for_continue, |
| | 8042 | .any_maybe_runtime_capture = any_maybe_runtime_capture, |
| | 8043 | .payload_capture_inst_is_placeholder = payload_capture_inst_is_placeholder, |
| | 8044 | .tag_capture_inst_is_placeholder = tag_capture_inst_is_placeholder, |
| 8045 | .scalar_cases_len = @intCast(scalar_cases_len), | 8045 | .scalar_cases_len = @intCast(scalar_cases_len), |
| 8046 | }, | 8046 | }, |
| 8047 | }); | 8047 | }); |
| | 8048 | astgen.instructions.items(.data)[@intFromEnum(switch_block)].pl_node.payload_index = zir_payload_index; |
| 8048 | | 8049 | |
| 8049 | if (multi_cases_len != 0) { | 8050 | if (multi_cases_len > 0) astgen.extra.appendAssumeCapacity(multi_cases_len); |
| 8050 | astgen.extra.appendAssumeCapacity(multi_cases_len); | 8051 | if (payload_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(payload_capture_inst)); |
| | 8052 | if (tag_capture_inst_is_placeholder) astgen.extra.appendAssumeCapacity(@intFromEnum(tag_capture_inst)); |
| | 8053 | if (needs_non_err_handling) { |
| | 8054 | const catch_or_if_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node); |
| | 8055 | astgen.extra.appendAssumeCapacity(@bitCast(@intFromEnum(catch_or_if_src_node_offset))); |
| | 8056 | astgen.extra.appendAssumeCapacity(@bitCast(non_err_info)); |
| 8051 | } | 8057 | } |
| | 8058 | if (has_else) astgen.extra.appendAssumeCapacity(@bitCast(else_info)); |
| | 8059 | if (has_under) astgen.extra.appendAssumeCapacity(under_extra); |
| 8052 | | 8060 | |
| 8053 | if (any_has_tag_capture) { | 8061 | const extra_payloads_start = astgen.extra.items.len; |
| 8054 | astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst)); | | |
| 8055 | } | | |
| 8056 | | 8062 | |
| 8057 | const zir_datas = astgen.instructions.items(.data); | 8063 | // body lens |
| 8058 | zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; | 8064 | astgen.extra.appendSliceAssumeCapacity(payloads.items[body_table_end..bodies_start]); |
| 8059 | | 8065 | |
| | 8066 | // bodies |
| | 8067 | if (needs_non_err_handling) { |
| | 8068 | const body = payloads.items[non_err_prong_body_start..][0..non_err_info.body_len]; |
| | 8069 | astgen.extra.appendSliceAssumeCapacity(body); |
| | 8070 | } |
| 8060 | if (has_else) { | 8071 | if (has_else) { |
| 8061 | const start_index = payloads.items[else_case_index]; | 8072 | const body = payloads.items[else_prong_body_start..][0..else_info.body_len]; |
| 8062 | var end_index = start_index + 1; | 8073 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8063 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]); | 8074 | } |
| 8064 | end_index += prong_info.body_len; | 8075 | if (under_is_bare) { |
| 8065 | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); | 8076 | const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra); |
| 8066 | } | 8077 | const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len]; |
| 8067 | if (has_under) { | 8078 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8068 | const start_index = payloads.items[under_case_index]; | 8079 | } |
| 8069 | var body_len_index = start_index; | 8080 | for (0..scalar_cases_len) |scalar_i| { |
| 8070 | var end_index = start_index; | 8081 | const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]); |
| 8071 | switch (underscore_additional_items) { | 8082 | const item_body_start = payloads.items[scalar_body_table + scalar_i]; |
| 8072 | .none => { | 8083 | const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0]; |
| 8073 | end_index += 1; | 8084 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]); |
| 8074 | }, | 8085 | const prong_body_start = item_body_start + item_body.len; |
| 8075 | .one => { | 8086 | const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; |
| 8076 | body_len_index += 1; | 8087 | astgen.extra.appendSliceAssumeCapacity(prong_body); |
| 8077 | end_index += 2; | 8088 | astgen.extra.appendSliceAssumeCapacity(item_body); |
| 8078 | }, | 8089 | } |
| 8079 | .many => { | 8090 | var multi_item_i: usize = 0; |
| 8080 | body_len_index += 2; | 8091 | for (0..multi_cases_len) |multi_i| { |
| 8081 | const items_len = payloads.items[start_index]; | 8092 | const prong_body_start = payloads.items[multi_prong_body_table + multi_i]; |
| 8082 | const ranges_len = payloads.items[start_index + 1]; | 8093 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]); |
| 8083 | end_index += 3 + items_len + 2 * ranges_len; | 8094 | const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; |
| 8084 | }, | 8095 | astgen.extra.appendSliceAssumeCapacity(prong_body); |
| 8085 | } | 8096 | |
| 8086 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); | 8097 | const items_len = payloads.items[multi_case_items_lens_start + multi_i]; |
| 8087 | end_index += prong_info.body_len; | 8098 | const ranges_len = if (any_ranges) ranges_len: { |
| 8088 | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); | 8099 | break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i]; |
| 8089 | } | 8100 | } else 0; |
| 8090 | for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| { | 8101 | // The table entries and body lens are already in the correct order so we |
| 8091 | var body_len_index = start_index; | 8102 | // don't have to differentiate between items and ranges here. |
| 8092 | var end_index = start_index; | 8103 | for (0..items_len + 2 * ranges_len) |_| { |
| 8093 | const table_index = scalar_case_table + i; | 8104 | const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]); |
| 8094 | if (table_index < multi_case_table) { | 8105 | if (item_info.bodyLen()) |body_len| { |
| 8095 | body_len_index += 1; | 8106 | const body_start = payloads.items[multi_item_body_table + multi_item_i]; |
| 8096 | end_index += 2; | 8107 | const body = payloads.items[body_start..][0..body_len]; |
| 8097 | } else { | 8108 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8098 | body_len_index += 2; | 8109 | } |
| 8099 | const items_len = payloads.items[start_index]; | 8110 | multi_item_i += 1; |
| 8100 | const ranges_len = payloads.items[start_index + 1]; | | |
| 8101 | end_index += 3 + items_len + 2 * ranges_len; | | |
| 8102 | } | 8111 | } |
| 8103 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); | | |
| 8104 | end_index += prong_info.body_len; | | |
| 8105 | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); | | |
| 8106 | } | 8112 | } |
| 8107 | | 8113 | |
| | 8114 | // Make sure we didn't forget anything... |
| | 8115 | assert(multi_item_i == total_items_len + 2 * total_ranges_len - scalar_cases_len); |
| | 8116 | assert(astgen.extra.items.len - extra_payloads_start == payloads.items.len - body_table_end); |
| | 8117 | |
| 8108 | if (need_result_rvalue) { | 8118 | if (need_result_rvalue) { |
| 8109 | return rvalue(parent_gz, ri, switch_block.toRef(), node); | 8119 | return rvalue(parent_gz, ri, switch_block.toRef(), switch_node); |
| 8110 | } else { | 8120 | } else { |
| 8111 | return switch_block.toRef(); | 8121 | return switch_block.toRef(); |
| 8112 | } | 8122 | } |
| ... | @@ -13786,6 +13796,19 @@ fn scanContainer( | ... | @@ -13786,6 +13796,19 @@ fn scanContainer( |
| 13786 | return error.AnalysisFail; | 13796 | return error.AnalysisFail; |
| 13787 | } | 13797 | } |
| 13788 | | 13798 | |
| | 13799 | fn appendPlaceholder(astgen: *AstGen) Allocator.Error!Zir.Inst.Index { |
| | 13800 | const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); |
| | 13801 | try astgen.instructions.append(astgen.gpa, .{ |
| | 13802 | .tag = .extended, |
| | 13803 | .data = .{ .extended = .{ |
| | 13804 | .opcode = .value_placeholder, |
| | 13805 | .small = undefined, |
| | 13806 | .operand = undefined, |
| | 13807 | } }, |
| | 13808 | }); |
| | 13809 | return inst; |
| | 13810 | } |
| | 13811 | |
| 13789 | /// Assumes capacity for body has already been added. Needed capacity taking into | 13812 | /// Assumes capacity for body has already been added. Needed capacity taking into |
| 13790 | /// account fixups can be found with `countBodyLenAfterFixups`. | 13813 | /// account fixups can be found with `countBodyLenAfterFixups`. |
| 13791 | fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void { | 13814 | fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void { |