| ... | ... | @@ -115,7 +115,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void { |
| 115 | 115 | Zir.Inst.Call.Flags, |
| 116 | 116 | Zir.Inst.BuiltinCall.Flags, |
| 117 | 117 | Zir.Inst.SwitchBlock.Bits, |
| 118 | | Zir.Inst.SwitchBlockErrUnion.Bits, |
| 119 | 118 | Zir.Inst.FuncFancy.Bits, |
| 120 | 119 | Zir.Inst.Param.Type, |
| 121 | 120 | Zir.Inst.Func.RetTy, |
| ... | ... | @@ -858,11 +857,11 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE |
| 858 | 857 | no_switch_on_err: { |
| 859 | 858 | const error_token = if_full.error_token orelse break :no_switch_on_err; |
| 860 | 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; |
| 862 | | if (full_switch.label_token != null) break :no_switch_on_err; |
| 863 | | if (tree.nodeTag(full_switch.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; |
| 865 | | return switchExprErrUnion(gz, scope, ri.br(), node, .@"if"); |
| 860 | const switch_full = tree.fullSwitch(else_node) orelse break :no_switch_on_err; |
| 861 | if (switch_full.label_token != null) break :no_switch_on_err; // handled in `ifExpr` |
| 862 | if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; |
| 863 | if (!try astgen.tokenIdentEql(error_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; |
| 864 | return switchExpr(gz, scope, ri.br(), node, switch_full, .{ .@"if" = if_full }); |
| 866 | 865 | } |
| 867 | 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 | 1023 | null; |
| 1025 | 1024 | no_switch_on_err: { |
| 1026 | 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; |
| 1028 | | if (full_switch.label_token != null) break :no_switch_on_err; |
| 1029 | | if (tree.nodeTag(full_switch.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; |
| 1031 | | return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch"); |
| 1026 | const switch_full = tree.fullSwitch(tree.nodeData(node).node_and_node[1]) orelse break :no_switch_on_err; |
| 1027 | if (switch_full.label_token != null) break :no_switch_on_err; // handled in `orelseCatchExpr` |
| 1028 | if (tree.nodeTag(switch_full.ast.condition) != .identifier) break :no_switch_on_err; |
| 1029 | if (!try astgen.tokenIdentEql(capture_token, tree.nodeMainToken(switch_full.ast.condition))) break :no_switch_on_err; |
| 1030 | return switchExpr(gz, scope, ri.br(), node, switch_full, .@"catch"); |
| 1032 | 1031 | } |
| 1033 | 1032 | switch (ri.rl) { |
| 1034 | 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 | 1107 | .error_set_decl => return errorSetDecl(gz, ri, node), |
| 1109 | 1108 | .array_access => return arrayAccess(gz, scope, ri, node), |
| 1110 | 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 | 1112 | .@"nosuspend" => return nosuspendExpr(gz, scope, ri, node), |
| 1114 | 1113 | .@"suspend" => return suspendExpr(gz, scope, node), |
| ... | ... | @@ -3134,14 +3133,7 @@ fn deferStmt( |
| 3134 | 3133 | } |
| 3135 | 3134 | const remapped_err_code: Zir.Inst.Index = @enumFromInt(gz.astgen.instructions.len); |
| 3136 | 3135 | opt_remapped_err_code = remapped_err_code.toOptional(); |
| 3137 | | try gz.astgen.instructions.append(gz.astgen.gpa, .{ |
| 3138 | | .tag = .extended, |
| 3139 | | .data = .{ .extended = .{ |
| 3140 | | .opcode = .value_placeholder, |
| 3141 | | .small = undefined, |
| 3142 | | .operand = undefined, |
| 3143 | | } }, |
| 3144 | | }); |
| 3136 | _ = try gz.astgen.appendPlaceholder(); |
| 3145 | 3137 | const remapped_err_code_ref = remapped_err_code.toRef(); |
| 3146 | 3138 | local_val_scope = .{ |
| 3147 | 3139 | .parent = &defer_gen.base, |
| ... | ... | @@ -6115,7 +6107,30 @@ fn orelseCatchExpr( |
| 6115 | 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 | 6134 | if (!else_scope.endsWithNoReturn()) { |
| 6120 | 6135 | // As our last action before the break, "pop" the error trace if needed |
| 6121 | 6136 | if (do_err_trace) |
| ... | ... | @@ -6468,7 +6483,26 @@ fn ifExpr( |
| 6468 | 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 | 6506 | if (!else_scope.endsWithNoReturn()) { |
| 6473 | 6507 | // As our last action before the break, "pop" the error trace if needed |
| 6474 | 6508 | if (do_err_trace) |
| ... | ... | @@ -7117,568 +7151,156 @@ fn forExpr( |
| 7117 | 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 | 7184 | parent_gz: *GenZir, |
| 7122 | 7185 | scope: *Scope, |
| 7123 | 7186 | ri: ResultInfo, |
| 7124 | | catch_or_if_node: Ast.Node.Index, |
| 7125 | | node_ty: enum { @"catch", @"if" }, |
| 7187 | node: Ast.Node.Index, |
| 7188 | switch_full: Ast.full.Switch, |
| 7189 | non_err: SwitchNonErr, |
| 7126 | 7190 | ) InnerError!Zir.Inst.Ref { |
| 7127 | 7191 | const astgen = parent_gz.astgen; |
| 7128 | 7192 | const gpa = astgen.gpa; |
| 7129 | 7193 | const tree = astgen.tree; |
| 7130 | 7194 | |
| 7131 | | const if_full = switch (node_ty) { |
| 7132 | | .@"catch" => undefined, |
| 7133 | | .@"if" => tree.fullIf(catch_or_if_node).?, |
| 7134 | | }; |
| 7135 | | |
| 7136 | | const switch_node, const operand_node, const error_payload = switch (node_ty) { |
| 7195 | const switch_node, const operand_node, const err_token = switch (non_err) { |
| 7196 | .none, .peer_break_target => .{ |
| 7197 | node, |
| 7198 | switch_full.ast.condition, |
| 7199 | undefined, |
| 7200 | }, |
| 7137 | 7201 | .@"catch" => .{ |
| 7138 | | tree.nodeData(catch_or_if_node).node_and_node[1], |
| 7139 | | tree.nodeData(catch_or_if_node).node_and_node[0], |
| 7140 | | tree.nodeMainToken(catch_or_if_node) + 2, |
| 7202 | tree.nodeData(node).node_and_node[1], |
| 7203 | tree.nodeData(node).node_and_node[0], |
| 7204 | tree.nodeMainToken(node) + 2, |
| 7141 | 7205 | }, |
| 7142 | | .@"if" => .{ |
| 7206 | .@"if" => |if_full| .{ |
| 7143 | 7207 | if_full.ast.else_expr.unwrap().?, |
| 7144 | 7208 | if_full.ast.cond_expr, |
| 7145 | 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; |
| 7151 | | const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node); |
| 7221 | const need_rl = astgen.nodes_need_rl.contains(node); |
| 7152 | 7222 | const block_ri: ResultInfo = if (need_rl) ri else .{ |
| 7153 | 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 | 7225 | .inferred_ptr => .none, |
| 7156 | 7226 | else => ri.rl, |
| 7157 | 7227 | }, |
| 7158 | 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 | 7231 | // We need to call `rvalue` to write through to the pointer only if we had a |
| 7167 | 7232 | // result pointer and aren't forwarding it. |
| 7168 | 7233 | const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?; |
| 7169 | 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) { |
| 7180 | | const case_src = case.ast.arrow_token - 1; |
| 7181 | | if (else_src) |src| { |
| 7182 | | return astgen.failTokNotes( |
| 7183 | | case_src, |
| 7184 | | "multiple else prongs in switch expression", |
| 7185 | | .{}, |
| 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 | const catch_or_if_node = if (needs_non_err_handling) node else undefined; |
| 7237 | const do_err_trace = needs_non_err_handling and astgen.fn_block != null; |
| 7238 | const non_err_is_ref: bool = switch (non_err) { |
| 7239 | .none, .peer_break_target => undefined, |
| 7240 | .@"catch" => ri.rl == .ref or ri.rl == .ref_coerced_ty, |
| 7241 | .@"if" => |if_full| if_full.payload_token != null and |
| 7242 | tree.tokenTag(if_full.payload_token.?) == .asterisk, |
| 7236 | 7243 | }; |
| 7237 | 7244 | |
| 7238 | | astgen.advanceSourceCursorToNode(operand_node); |
| 7239 | | const operand_lc: LineColumn = .{ astgen.source_line - parent_gz.decl_line, astgen.source_column }; |
| 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}); |
| 7245 | if (switch_full.label_token) |label_token| { |
| 7246 | try astgen.checkLabelRedefinition(scope, label_token); |
| 7384 | 7247 | } |
| 7385 | 7248 | |
| 7386 | | const err_name = blk: { |
| 7387 | | const err_str = tree.tokenSlice(error_payload); |
| 7249 | const err_capture_name: Zir.NullTerminatedString = if (needs_non_err_handling) blk: { |
| 7250 | const err_str = tree.tokenSlice(err_token); |
| 7388 | 7251 | if (mem.eql(u8, err_str, "_")) { |
| 7389 | 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); |
| 7393 | | try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture); |
| 7394 | | |
| 7255 | const err_name = try astgen.identAsString(err_token); |
| 7256 | try astgen.detectLocalShadowing(scope, err_name, err_token, err_str, .capture); |
| 7395 | 7257 | break :blk err_name; |
| 7396 | | }; |
| 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 | | } |
| 7258 | } else undefined; |
| 7648 | 7259 | |
| 7649 | 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, |
| 7651 | | // and bail out with a compile error if there are multiple special prongs present. |
| 7261 | // for the following variables, make note of the special prong AST node indices, |
| 7262 | // and bail out with a compile error if there are incompatible special prongs present. |
| 7652 | 7263 | var any_payload_is_ref = false; |
| 7264 | var any_has_payload_capture = false; |
| 7653 | 7265 | var any_has_tag_capture = false; |
| 7654 | | var any_non_inline_capture = false; |
| 7266 | var any_maybe_runtime_capture = false; |
| 7655 | 7267 | var scalar_cases_len: u32 = 0; |
| 7656 | 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 | 7271 | var else_case_node: Ast.Node.OptionalIndex = .none; |
| 7659 | 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 | 7274 | var underscore_node: Ast.Node.OptionalIndex = .none; |
| 7662 | 7275 | var underscore_src: ?Ast.TokenIndex = null; |
| 7663 | | var underscore_additional_items: Zir.SpecialProngs.AdditionalItems = .none; |
| 7276 | var under_is_bare = false; |
| 7664 | 7277 | for (case_nodes) |case_node| { |
| 7665 | 7278 | const case = tree.fullSwitchCase(case_node).?; |
| 7666 | 7279 | if (case.payload_token) |payload_token| { |
| 7667 | 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 | 7283 | any_payload_is_ref = true; |
| 7669 | 7284 | break :blk payload_token + 1; |
| 7670 | 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 | 7298 | if (tree.tokenTag(ident + 1) == .comma) { |
| 7672 | 7299 | any_has_tag_capture = true; |
| 7673 | | } |
| 7674 | 7300 | |
| 7675 | | // If the first capture is ignored, then there is no runtime-known |
| 7676 | | // capture, as the tag capture must be for an inline prong. |
| 7677 | | // This check isn't perfect, because for things like enums, the |
| 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; |
| 7301 | if (case.inline_token == null) { |
| 7302 | any_maybe_runtime_capture = true; |
| 7303 | } |
| 7682 | 7304 | } |
| 7683 | 7305 | } |
| 7684 | 7306 | |
| ... | ... | @@ -7690,13 +7312,7 @@ fn switchExpr( |
| 7690 | 7312 | case_src, |
| 7691 | 7313 | "multiple else prongs in switch expression", |
| 7692 | 7314 | .{}, |
| 7693 | | &[_]u32{ |
| 7694 | | try astgen.errNoteTok( |
| 7695 | | src, |
| 7696 | | "previous else prong here", |
| 7697 | | .{}, |
| 7698 | | ), |
| 7699 | | }, |
| 7315 | &.{try astgen.errNoteTok(src, "previous else prong here", .{})}, |
| 7700 | 7316 | ); |
| 7701 | 7317 | } |
| 7702 | 7318 | else_case_node = case_node.toOptional(); |
| ... | ... | @@ -7704,156 +7320,492 @@ fn switchExpr( |
| 7704 | 7320 | continue; |
| 7705 | 7321 | } |
| 7706 | 7322 | |
| 7707 | | // Check for '_' prong. |
| 7323 | // Check for '_' prong and ranges. |
| 7708 | 7324 | var case_has_underscore = false; |
| 7325 | var case_has_ranges = false; |
| 7709 | 7326 | for (case.ast.values) |val| { |
| 7710 | 7327 | switch (tree.nodeTag(val)) { |
| 7711 | | .identifier => if (mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_")) { |
| 7712 | | const val_src = tree.nodeMainToken(val); |
| 7713 | | if (underscore_src) |src| { |
| 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; |
| 7328 | .switch_range => { |
| 7329 | total_ranges_len += 1; |
| 7330 | case_has_ranges = true; |
| 7740 | 7331 | }, |
| 7741 | 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 | 7378 | scalar_cases_len += 1; |
| 7749 | | } else { |
| 7379 | } else if (case_len >= 1) { |
| 7750 | 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( |
| 7758 | | else_src != null, |
| 7759 | | underscore_src != null, |
| 7760 | | underscore_additional_items, |
| 7761 | | ); |
| 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 }; |
| 7384 | const has_else = else_src != null; |
| 7385 | const has_under = underscore_src != null; |
| 7386 | if (under_is_bare) assert(has_under); // make sure that the former implies the latter |
| 7387 | if (is_err_switch) assert(!has_under); // should have failed by now |
| 7388 | const any_ranges = total_ranges_len > 0; |
| 7772 | 7389 | |
| 7773 | | // If this switch is labeled, it may have `continue`s targeting it, and thus we need the operand type |
| 7774 | | // to provide a result type. |
| 7775 | | const raw_operand_ty_ref = if (switch_full.label_token != null) t: { |
| 7776 | | break :t try parent_gz.addUnNode(.typeof, raw_operand, operand_node); |
| 7777 | | } else undefined; |
| 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 |
| 7390 | // This contains all of the body lengths (already in the correct order) and |
| 7391 | // the bodies they belong to that go into the `extra` array later, except the |
| 7392 | // first item_table_end slots are a table that indexes the item bodies (and |
| 7393 | // also indirectly the prong bodies, as they are always trailing after their |
| 7394 | // item bodies). |
| 7782 | 7395 | const payloads = &astgen.scratch; |
| 7783 | 7396 | const scratch_top = astgen.scratch.items.len; |
| 7784 | | const case_table_start = scratch_top; |
| 7785 | | const else_case_index = if (has_else) case_table_start else undefined; |
| 7786 | | const under_case_index = if (has_under) case_table_start + @intFromBool(has_else) else undefined; |
| 7787 | | const scalar_case_table = case_table_start + @intFromBool(has_else) + @intFromBool(has_under); |
| 7788 | | const multi_case_table = scalar_case_table + scalar_cases_len; |
| 7789 | | const case_table_end = multi_case_table + multi_cases_len; |
| 7790 | | try astgen.scratch.resize(gpa, case_table_end); |
| 7397 | var payloads_end = scratch_top; |
| 7398 | |
| 7399 | // Since range item body pairs are always contiguous we don't technically |
| 7400 | // have to keep track of the position of the second body. However handling |
| 7401 | // all of the several indices and offsets is complicated enough as it is, |
| 7402 | // so for the sake of keeping this function a little bit more simple we do |
| 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 | 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 | 7440 | var block_scope = parent_gz.makeSubBlock(scope); |
| 7794 | 7441 | // block_scope not used for collecting instructions |
| 7795 | 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 | 7459 | try emitDbgStmtForceCurrentIndex(parent_gz, operand_lc); |
| 7800 | 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; |
| 7802 | | const switch_block = try parent_gz.makeBlockInst(switch_tag, node); |
| 7461 | const switch_tag: Zir.Inst.Tag = switch (non_err) { |
| 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 | 7541 | if (switch_full.label_token) |label_token| { |
| 7805 | 7542 | block_scope.label = .{ .token = label_token }; |
| 7806 | | block_scope.break_target = switch_block; |
| 7807 | 7543 | block_scope.continue_target = .{ .switch_continue = switch_block }; |
| 7808 | 7544 | block_scope.continue_result_info = .{ |
| 7809 | 7545 | .rl = if (any_payload_is_ref) |
| 7810 | | .{ .ref_coerced_ty = raw_operand_ty_ref } |
| 7546 | .{ .ref_coerced_ty = switch_block.toRef() } |
| 7811 | 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. |
| 7819 | | var case_scope = parent_gz.makeSubBlock(&block_scope.base); |
| 7820 | | case_scope.instructions_top = GenZir.unstacked_top; |
| 7579 | var prong_body_extra_insts_buf: [3]Zir.Inst.Index = undefined; |
| 7580 | const prong_body_extra_insts: []const Zir.Inst.Index = extra_insts: { |
| 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 |
| 7823 | | const tag_inst = if (any_has_tag_capture) tag_inst: { |
| 7824 | | const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len); |
| 7825 | | try astgen.instructions.append(astgen.gpa, .{ |
| 7826 | | .tag = .extended, |
| 7827 | | .data = .{ .extended = .{ |
| 7828 | | .opcode = .value_placeholder, |
| 7829 | | .small = undefined, |
| 7830 | | .operand = undefined, |
| 7831 | | } }, |
| 7832 | | }); |
| 7833 | | break :tag_inst inst; |
| 7834 | | } else undefined; |
| 7588 | const switch_operand, const catch_or_if_operand = if (needs_non_err_handling) |
| 7589 | .{ switch_block.toRef(), raw_operand } |
| 7590 | else |
| 7591 | .{ raw_operand, undefined }; |
| 7592 | |
| 7593 | // We re-use this same scope for all case items and contents. |
| 7594 | var scratch_scope = parent_gz.makeSubBlock(&block_scope.base); |
| 7595 | scratch_scope.instructions_top = GenZir.unstacked_top; |
| 7596 | |
| 7597 | // We have to take care of the non-error body first if there is one. |
| 7598 | non_err_body: { |
| 7599 | if (!needs_non_err_handling) break :non_err_body; |
| 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 | 7686 | // In this pass we generate all the item and prong expressions. |
| 7837 | 7687 | var multi_case_index: u32 = 0; |
| 7838 | 7688 | var scalar_case_index: u32 = 0; |
| 7689 | var multi_item_offset: usize = 0; |
| 7839 | 7690 | for (case_nodes) |case_node| { |
| 7840 | 7691 | const case = tree.fullSwitchCase(case_node).?; |
| 7841 | 7692 | |
| 7842 | | const is_multi_case = case.ast.values.len > 1 or |
| 7843 | | (case.ast.values.len == 1 and tree.nodeTag(case.ast.values[0]) == .switch_range); |
| 7693 | const case_has_under = case_node.toOptional() == under_case_node; |
| 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; |
| 7846 | | var dbg_var_inst: Zir.Inst.Ref = undefined; |
| 7777 | // Capture and prong body |
| 7778 | |
| 7779 | var dbg_var_payload_name: Zir.NullTerminatedString = .empty; |
| 7780 | var dbg_var_payload_inst: Zir.Inst.Ref = undefined; |
| 7847 | 7781 | var dbg_var_tag_name: Zir.NullTerminatedString = .empty; |
| 7848 | 7782 | var dbg_var_tag_inst: Zir.Inst.Ref = undefined; |
| 7849 | 7783 | var has_tag_capture = false; |
| 7850 | | var capture_val_scope: Scope.LocalVal = undefined; |
| 7851 | | var tag_scope: Scope.LocalVal = undefined; |
| 7784 | var err_capture_scope: Scope.LocalVal = undefined; |
| 7785 | var payload_capture_scope: Scope.LocalVal = undefined; |
| 7786 | var tag_capture_scope: Scope.LocalVal = undefined; |
| 7852 | 7787 | |
| 7853 | 7788 | var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none; |
| 7854 | 7789 | |
| 7855 | | const sub_scope = blk: { |
| 7856 | | const payload_token = case.payload_token orelse break :blk &case_scope.base; |
| 7790 | // Check all captures and make them available to the prong body. |
| 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 | 7809 | const capture_is_ref = tree.tokenTag(payload_token) == .asterisk; |
| 7858 | 7810 | const ident = payload_token + @intFromBool(capture_is_ref); |
| 7859 | 7811 | |
| ... | ... | @@ -7867,36 +7819,38 @@ fn switchExpr( |
| 7867 | 7819 | return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{}); |
| 7868 | 7820 | } |
| 7869 | 7821 | capture = .none; |
| 7870 | | payload_sub_scope = &case_scope.base; |
| 7822 | payload_sub_scope = switch_scope; |
| 7871 | 7823 | } else { |
| 7872 | 7824 | const capture_name = try astgen.identAsString(ident); |
| 7873 | | try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture); |
| 7874 | | capture_val_scope = .{ |
| 7875 | | .parent = &case_scope.base, |
| 7876 | | .gen_zir = &case_scope, |
| 7825 | try astgen.detectLocalShadowing(&scratch_scope.base, capture_name, ident, ident_slice, .capture); |
| 7826 | payload_capture_scope = .{ |
| 7827 | .parent = switch_scope, |
| 7828 | .gen_zir = &scratch_scope, |
| 7877 | 7829 | .name = capture_name, |
| 7878 | | .inst = switch_block.toRef(), |
| 7830 | .inst = payload_capture_inst.toRef(), |
| 7879 | 7831 | .token_src = ident, |
| 7880 | 7832 | .id_cat = .capture, |
| 7881 | 7833 | }; |
| 7882 | | dbg_var_name = capture_name; |
| 7883 | | dbg_var_inst = switch_block.toRef(); |
| 7884 | | payload_sub_scope = &capture_val_scope.base; |
| 7834 | dbg_var_payload_name = payload_capture_scope.name; |
| 7835 | dbg_var_payload_inst = payload_capture_scope.inst; |
| 7836 | payload_sub_scope = &payload_capture_scope.base; |
| 7885 | 7837 | } |
| 7886 | 7838 | |
| 7887 | | const tag_token = if (tree.tokenTag(ident + 1) == .comma) |
| 7888 | | ident + 2 |
| 7889 | | else if (capture == .none) { |
| 7890 | | // discarding the capture is only valid iff the tag is captured |
| 7839 | if (is_err_switch and capture == .by_ref) { |
| 7840 | return astgen.failTok(ident, "error set cannot be captured by reference", .{}); |
| 7841 | } |
| 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 | 7847 | // whether the tag capture is discarded is handled below |
| 7892 | 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 | 7851 | const tag_slice = tree.tokenSlice(tag_token); |
| 7896 | 7852 | if (mem.eql(u8, tag_slice, "_")) { |
| 7897 | 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 | 7855 | const tag_name = try astgen.identAsString(tag_token); |
| 7902 | 7856 | try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture"); |
| ... | ... | @@ -7904,123 +7858,155 @@ fn switchExpr( |
| 7904 | 7858 | assert(any_has_tag_capture); |
| 7905 | 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 | 7866 | .parent = payload_sub_scope, |
| 7909 | | .gen_zir = &case_scope, |
| 7867 | .gen_zir = &scratch_scope, |
| 7910 | 7868 | .name = tag_name, |
| 7911 | | .inst = tag_inst.toRef(), |
| 7869 | .inst = tag_capture_inst.toRef(), |
| 7912 | 7870 | .token_src = tag_token, |
| 7913 | 7871 | .id_cat = .@"switch tag capture", |
| 7914 | 7872 | }; |
| 7915 | | dbg_var_tag_name = tag_name; |
| 7916 | | dbg_var_tag_inst = tag_inst.toRef(); |
| 7917 | | break :blk &tag_scope.base; |
| 7873 | dbg_var_tag_name = tag_capture_scope.name; |
| 7874 | dbg_var_tag_inst = tag_capture_scope.inst; |
| 7875 | break :scope &tag_capture_scope.base; |
| 7918 | 7876 | }; |
| 7919 | 7877 | |
| 7920 | | const header_index: u32 = @intCast(payloads.items.len); |
| 7921 | | const body_len_index = if (is_multi_case) blk: { |
| 7922 | | if (case_node.toOptional() == underscore_case_node) { |
| 7923 | | payloads.items[under_case_index] = header_index; |
| 7924 | | if (special_prongs.hasOneAdditionalItem()) { |
| 7925 | | try payloads.resize(gpa, header_index + 2); // item, body_len |
| 7926 | | const maybe_item_node = case.ast.values[0]; |
| 7927 | | const item_node = if (maybe_item_node.toOptional() == underscore_node) |
| 7928 | | case.ast.values[1] |
| 7929 | | else |
| 7930 | | maybe_item_node; |
| 7931 | | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); |
| 7932 | | payloads.items[header_index] = @intFromEnum(item_inst); |
| 7933 | | break :blk header_index + 1; |
| 7934 | | } |
| 7878 | if (capture != .none) assert(any_has_payload_capture); |
| 7879 | if (is_err_switch) { |
| 7880 | assert(!any_payload_is_ref); // should have failed by now |
| 7881 | assert(!any_has_tag_capture); // should have failed by now |
| 7882 | } |
| 7883 | |
| 7884 | prong_body: { |
| 7885 | scratch_scope.instructions_top = parent_gz.instructions.items.len; |
| 7886 | defer scratch_scope.unstack(); |
| 7887 | |
| 7888 | if (dbg_var_payload_name != .empty) { |
| 7889 | try scratch_scope.addDbgVar(.dbg_var_val, dbg_var_payload_name, dbg_var_payload_inst); |
| 7890 | } |
| 7891 | if (dbg_var_tag_name != .empty) { |
| 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 | 7903 | } else { |
| 7936 | | payloads.items[multi_case_table + multi_case_index] = header_index; |
| 7937 | | multi_case_index += 1; |
| 7904 | try checkUsed(parent_gz, &scratch_scope.base, prong_body_scope); |
| 7938 | 7905 | } |
| 7939 | | try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len |
| 7940 | | |
| 7941 | | // items |
| 7942 | | var items_len: u32 = 0; |
| 7943 | | for (case.ast.values) |item_node| { |
| 7944 | | if (item_node.toOptional() == underscore_node or |
| 7945 | | tree.nodeTag(item_node) == .switch_range) |
| 7946 | | { |
| 7947 | | continue; |
| 7906 | if (!scratch_scope.endsWithNoReturn()) { |
| 7907 | // As our last action before the break, "pop" the error trace if needed |
| 7908 | if (do_err_trace) { |
| 7909 | try restoreErrRetIndex( |
| 7910 | &scratch_scope, |
| 7911 | .{ .block = switch_block }, |
| 7912 | block_scope.break_result_info, |
| 7913 | target_expr_node, |
| 7914 | case_result, |
| 7915 | ); |
| 7948 | 7916 | } |
| 7949 | | items_len += 1; |
| 7950 | | |
| 7951 | | const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node, .switch_item); |
| 7952 | | try payloads.append(gpa, @intFromEnum(item_inst)); |
| 7917 | _ = try scratch_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node); |
| 7953 | 7918 | } |
| 7954 | 7919 | |
| 7955 | | // ranges |
| 7956 | | var ranges_len: u32 = 0; |
| 7957 | | for (case.ast.values) |range| { |
| 7958 | | if (tree.nodeTag(range) != .switch_range) { |
| 7959 | | continue; |
| 7960 | | } |
| 7961 | | ranges_len += 1; |
| 7920 | const body_slice = scratch_scope.instructionsSlice(); |
| 7921 | const body_start: u32 = @intCast(payloads.items.len); |
| 7922 | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body_slice, prong_body_extra_insts); |
| 7923 | try payloads.ensureUnusedCapacity(gpa, body_len); |
| 7924 | astgen.appendBodyWithFixupsExtraRefsArrayList(payloads, body_slice, prong_body_extra_insts); |
| 7925 | |
| 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; |
| 7964 | | const first = try comptimeExpr(parent_gz, scope, item_ri, first_node, .switch_item); |
| 7965 | | const last = try comptimeExpr(parent_gz, scope, item_ri, last_node, .switch_item); |
| 7966 | | try payloads.appendSlice(gpa, &[_]u32{ |
| 7967 | | @intFromEnum(first), @intFromEnum(last), |
| 7968 | | }); |
| 7951 | else_info = .{ |
| 7952 | .body_len = @intCast(body_len), |
| 7953 | .capture = capture, |
| 7954 | .is_inline = case.inline_token != null, |
| 7955 | .has_tag_capture = has_tag_capture, |
| 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; |
| 7972 | | payloads.items[header_index + 1] = ranges_len; |
| 7973 | | break :blk header_index + 2; |
| 7974 | | } else if (case_node.toOptional() == else_case_node) blk: { |
| 7975 | | payloads.items[else_case_index] = header_index; |
| 7976 | | try payloads.resize(gpa, header_index + 1); // body_len |
| 7977 | | break :blk header_index; |
| 7978 | | } else if (case_node.toOptional() == underscore_case_node) blk: { |
| 7979 | | assert(!special_prongs.hasAdditionalItems()); |
| 7980 | | payloads.items[under_case_index] = header_index; |
| 7981 | | try payloads.resize(gpa, header_index + 1); // body_len |
| 7982 | | break :blk header_index; |
| 7983 | | } else blk: { |
| 7984 | | payloads.items[scalar_case_table + scalar_case_index] = header_index; |
| 7985 | | scalar_case_index += 1; |
| 7986 | | try payloads.resize(gpa, header_index + 2); // item, body_len |
| 7987 | | const item_node = case.ast.values[0]; |
| 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); |
| 7962 | if (case_has_under) { |
| 7963 | // We're either writing under_prong_info or under_index here. |
| 7964 | if (under_is_bare) { |
| 7965 | assert(case.ast.values.len == 1); // only `_` |
| 7966 | const bare_under_info: Zir.Inst.SwitchBlock.ProngInfo.BareUnder = .{ |
| 7967 | .body_len = @intCast(body_len), |
| 7968 | .capture = capture, |
| 7969 | .has_tag_capture = has_tag_capture, |
| 7970 | }; |
| 7971 | under_extra = @bitCast(bare_under_info); |
| 7972 | bare_under_prong_body_start = body_start; |
| 7973 | break :prong_body; |
| 7974 | } else if (is_multi_case) { |
| 7975 | under_extra = scalar_cases_len + multi_case_index; |
| 7976 | } else { |
| 7977 | under_extra = scalar_case_index; |
| 7978 | } |
| 8009 | 7979 | } |
| 8010 | 7980 | |
| 8011 | | const case_slice = case_scope.instructionsSlice(); |
| 8012 | | const extra_insts: []const Zir.Inst.Index = if (has_tag_capture) &.{ switch_block, tag_inst } else &.{switch_block}; |
| 8013 | | const body_len = astgen.countBodyLenAfterFixupsExtraRefs(case_slice, extra_insts); |
| 8014 | | try payloads.ensureUnusedCapacity(gpa, body_len); |
| 8015 | | payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{ |
| 7981 | // We allow prongs with error items which are not inside the error set |
| 7982 | // being switched on if their body is `=> comptime unreachable,`. |
| 7983 | const is_comptime_unreach = comptime_unreach: { |
| 7984 | if (tree.nodeTag(target_expr_node) != .@"comptime") break :comptime_unreach false; |
| 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 | 7990 | .body_len = @intCast(body_len), |
| 8017 | 7991 | .capture = capture, |
| 8018 | 7992 | .is_inline = case.inline_token != null, |
| 8019 | 7993 | .has_tag_capture = has_tag_capture, |
| 8020 | | }); |
| 8021 | | appendBodyWithFixupsExtraRefsArrayList(astgen, payloads, case_slice, extra_insts); |
| 7994 | .is_comptime_unreach = is_comptime_unreach, |
| 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 | 8011 | if (switch_full.label_token) |label_token| if (!block_scope.label.?.used) { |
| 8026 | 8012 | try astgen.appendErrorTok(label_token, "unused switch label", .{}); |
| ... | ... | @@ -8029,84 +8015,108 @@ fn switchExpr( |
| 8029 | 8015 | // Now that the item expressions are generated we can add this. |
| 8030 | 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 | 8022 | try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).@"struct".fields.len + |
| 8033 | | @intFromBool(multi_cases_len != 0) + |
| 8034 | | @intFromBool(any_has_tag_capture) + |
| 8035 | | payloads.items.len - scratch_top); |
| 8036 | | |
| 8037 | | const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ |
| 8038 | | .operand = raw_operand, |
| 8039 | | .bits = Zir.Inst.SwitchBlock.Bits{ |
| 8040 | | .has_multi_cases = multi_cases_len != 0, |
| 8041 | | .special_prongs = special_prongs, |
| 8042 | | .any_has_tag_capture = any_has_tag_capture, |
| 8043 | | .any_non_inline_capture = any_non_inline_capture, |
| 8023 | @intFromBool(multi_cases_len > 0) + // multi_cases_len |
| 8024 | @intFromBool(payload_capture_inst_is_placeholder) + // payload_capture_placeholder |
| 8025 | @intFromBool(tag_capture_inst_is_placeholder) + // tag_capture_placeholder |
| 8026 | @intFromBool(needs_non_err_handling) + // catch_or_if_src_node_offset |
| 8027 | @intFromBool(needs_non_err_handling) + // non_err_info |
| 8028 | @intFromBool(has_else) + // else_info |
| 8029 | @intFromBool(has_under) + // under_prong_info or under_index |
| 8030 | payloads.items.len - body_table_end); // item infos and bodies |
| 8031 | |
| 8032 | // singular pieces of data |
| 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 | 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 | 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 | | astgen.extra.appendAssumeCapacity(multi_cases_len); |
| 8050 | if (multi_cases_len > 0) 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) { |
| 8054 | | astgen.extra.appendAssumeCapacity(@intFromEnum(tag_inst)); |
| 8055 | | } |
| 8061 | const extra_payloads_start = astgen.extra.items.len; |
| 8056 | 8062 | |
| 8057 | | const zir_datas = astgen.instructions.items(.data); |
| 8058 | | zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index; |
| 8063 | // body lens |
| 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 | 8071 | if (has_else) { |
| 8061 | | const start_index = payloads.items[else_case_index]; |
| 8062 | | var end_index = start_index + 1; |
| 8063 | | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[start_index]); |
| 8064 | | end_index += prong_info.body_len; |
| 8065 | | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); |
| 8066 | | } |
| 8067 | | if (has_under) { |
| 8068 | | const start_index = payloads.items[under_case_index]; |
| 8069 | | var body_len_index = start_index; |
| 8070 | | var end_index = start_index; |
| 8071 | | switch (underscore_additional_items) { |
| 8072 | | .none => { |
| 8073 | | end_index += 1; |
| 8074 | | }, |
| 8075 | | .one => { |
| 8076 | | body_len_index += 1; |
| 8077 | | end_index += 2; |
| 8078 | | }, |
| 8079 | | .many => { |
| 8080 | | body_len_index += 2; |
| 8081 | | const items_len = payloads.items[start_index]; |
| 8082 | | const ranges_len = payloads.items[start_index + 1]; |
| 8083 | | end_index += 3 + items_len + 2 * ranges_len; |
| 8084 | | }, |
| 8085 | | } |
| 8086 | | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]); |
| 8087 | | end_index += prong_info.body_len; |
| 8088 | | astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]); |
| 8089 | | } |
| 8090 | | for (payloads.items[scalar_case_table..case_table_end], 0..) |start_index, i| { |
| 8091 | | var body_len_index = start_index; |
| 8092 | | var end_index = start_index; |
| 8093 | | const table_index = scalar_case_table + i; |
| 8094 | | if (table_index < multi_case_table) { |
| 8095 | | body_len_index += 1; |
| 8096 | | end_index += 2; |
| 8097 | | } else { |
| 8098 | | body_len_index += 2; |
| 8099 | | const items_len = payloads.items[start_index]; |
| 8100 | | const ranges_len = payloads.items[start_index + 1]; |
| 8101 | | end_index += 3 + items_len + 2 * ranges_len; |
| 8072 | const body = payloads.items[else_prong_body_start..][0..else_info.body_len]; |
| 8073 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8074 | } |
| 8075 | if (under_is_bare) { |
| 8076 | const under_prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(under_extra); |
| 8077 | const body = payloads.items[bare_under_prong_body_start..][0..under_prong_info.body_len]; |
| 8078 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8079 | } |
| 8080 | for (0..scalar_cases_len) |scalar_i| { |
| 8081 | const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[scalar_item_infos_start + scalar_i]); |
| 8082 | const item_body_start = payloads.items[scalar_body_table + scalar_i]; |
| 8083 | const item_body = payloads.items[item_body_start..][0 .. item_info.bodyLen() orelse 0]; |
| 8084 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[scalar_prong_infos_start + scalar_i]); |
| 8085 | const prong_body_start = item_body_start + item_body.len; |
| 8086 | const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; |
| 8087 | astgen.extra.appendSliceAssumeCapacity(prong_body); |
| 8088 | astgen.extra.appendSliceAssumeCapacity(item_body); |
| 8089 | } |
| 8090 | var multi_item_i: usize = 0; |
| 8091 | for (0..multi_cases_len) |multi_i| { |
| 8092 | const prong_body_start = payloads.items[multi_prong_body_table + multi_i]; |
| 8093 | const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[multi_prong_infos_start + multi_i]); |
| 8094 | const prong_body = payloads.items[prong_body_start..][0..prong_info.body_len]; |
| 8095 | astgen.extra.appendSliceAssumeCapacity(prong_body); |
| 8096 | |
| 8097 | const items_len = payloads.items[multi_case_items_lens_start + multi_i]; |
| 8098 | const ranges_len = if (any_ranges) ranges_len: { |
| 8099 | break :ranges_len payloads.items[multi_case_ranges_lens_start + multi_i]; |
| 8100 | } else 0; |
| 8101 | // The table entries and body lens are already in the correct order so we |
| 8102 | // don't have to differentiate between items and ranges here. |
| 8103 | for (0..items_len + 2 * ranges_len) |_| { |
| 8104 | const item_info: Zir.Inst.SwitchBlock.ItemInfo = @bitCast(payloads.items[multi_items_infos_start + multi_item_i]); |
| 8105 | if (item_info.bodyLen()) |body_len| { |
| 8106 | const body_start = payloads.items[multi_item_body_table + multi_item_i]; |
| 8107 | const body = payloads.items[body_start..][0..body_len]; |
| 8108 | astgen.extra.appendSliceAssumeCapacity(body); |
| 8109 | } |
| 8110 | multi_item_i += 1; |
| 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 | 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 | 8120 | } else { |
| 8111 | 8121 | return switch_block.toRef(); |
| 8112 | 8122 | } |
| ... | ... | @@ -13786,6 +13796,19 @@ fn scanContainer( |
| 13786 | 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 | 13812 | /// Assumes capacity for body has already been added. Needed capacity taking into |
| 13790 | 13813 | /// account fixups can be found with `countBodyLenAfterFixups`. |
| 13791 | 13814 | fn appendBodyWithFixups(astgen: *AstGen, body: []const Zir.Inst.Index) void { |