aboutsummaryrefslogtreecommitdiff
path: root/src/AstGen.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/AstGen.zig')
-rw-r--r--src/AstGen.zig562
1 files changed, 556 insertions, 6 deletions
diff --git a/src/AstGen.zig b/src/AstGen.zig
index 3941f5a378..6b93f9567c 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -93,6 +93,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
Zir.Inst.Call.Flags,
Zir.Inst.BuiltinCall.Flags,
Zir.Inst.SwitchBlock.Bits,
+ Zir.Inst.SwitchBlockErrUnion.Bits,
Zir.Inst.FuncFancy.Bits,
=> @bitCast(@field(extra, field.name)),
@@ -838,7 +839,18 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
.if_simple,
.@"if",
- => return ifExpr(gz, scope, ri.br(), node, tree.fullIf(node).?),
+ => {
+ const if_full = tree.fullIf(node).?;
+ if (if_full.error_token) |error_token| {
+ const tag = node_tags[if_full.ast.else_expr];
+ if ((tag == .@"switch" or tag == .switch_comma) and
+ std.mem.eql(u8, tree.tokenSlice(error_token), tree.tokenSlice(error_token + 4)))
+ {
+ return switchExprErrUnion(gz, scope, ri.br(), node, .@"if");
+ }
+ }
+ return ifExpr(gz, scope, ri.br(), node, if_full);
+ },
.while_simple,
.while_cont,
@@ -1014,10 +1026,16 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
},
.@"catch" => {
const catch_token = main_tokens[node];
- const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe)
- catch_token + 2
- else
- null;
+ const payload_token: ?Ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) blk: {
+ if (token_tags.len > catch_token + 6 and
+ token_tags[catch_token + 4] == .keyword_switch)
+ {
+ if (std.mem.eql(u8, tree.tokenSlice(catch_token + 2), tree.tokenSlice(catch_token + 6))) {
+ return switchExprErrUnion(gz, scope, ri.br(), node, .@"catch");
+ }
+ }
+ break :blk catch_token + 2;
+ } else null;
switch (ri.rl) {
.ref, .ref_coerced_ty => return orelseCatchExpr(
gz,
@@ -2556,7 +2574,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.vector_type,
.indexable_ptr_len,
.anyframe_type,
- .as,
.as_node,
.as_shift_operand,
.bit_and,
@@ -2641,6 +2658,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.import,
.switch_block,
.switch_block_ref,
+ .switch_block_err_union,
.union_init,
.field_type_ref,
.error_set_decl,
@@ -6858,6 +6876,538 @@ fn forExpr(
return result;
}
+fn switchExprErrUnion(
+ parent_gz: *GenZir,
+ scope: *Scope,
+ ri: ResultInfo,
+ catch_or_if_node: Ast.Node.Index,
+ node_ty: enum { @"catch", @"if" },
+) InnerError!Zir.Inst.Ref {
+ const astgen = parent_gz.astgen;
+ const gpa = astgen.gpa;
+ const tree = astgen.tree;
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+ const token_tags = tree.tokens.items(.tag);
+
+ const if_full = switch (node_ty) {
+ .@"catch" => undefined,
+ .@"if" => tree.fullIf(catch_or_if_node).?,
+ };
+
+ const switch_node, const operand_node, const error_payload = switch (node_ty) {
+ .@"catch" => .{
+ node_datas[catch_or_if_node].rhs,
+ node_datas[catch_or_if_node].lhs,
+ main_tokens[catch_or_if_node] + 2,
+ },
+ .@"if" => .{
+ if_full.ast.else_expr,
+ if_full.ast.cond_expr,
+ if_full.error_token.?,
+ },
+ };
+ assert(node_tags[switch_node] == .@"switch" or node_tags[switch_node] == .switch_comma);
+
+ const extra = tree.extraData(node_datas[switch_node].rhs, Ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+
+ const need_rl = astgen.nodes_need_rl.contains(catch_or_if_node);
+ const block_ri: ResultInfo = if (need_rl) ri else .{
+ .rl = switch (ri.rl) {
+ .ptr => .{ .ty = (try ri.rl.resultType(parent_gz, catch_or_if_node)).? },
+ .inferred_ptr => .none,
+ else => ri.rl,
+ },
+ .ctx = ri.ctx,
+ };
+
+ const payload_is_ref = node_ty == .@"if" and
+ if_full.payload_token != null and token_tags[if_full.payload_token.?] == .asterisk;
+
+ // We need to call `rvalue` to write through to the pointer only if we had a
+ // result pointer and aren't forwarding it.
+ const LocTag = @typeInfo(ResultInfo.Loc).Union.tag_type.?;
+ const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
+ var scalar_cases_len: u32 = 0;
+ var multi_cases_len: u32 = 0;
+ var inline_cases_len: u32 = 0;
+ var has_else = false;
+ var else_node: Ast.Node.Index = 0;
+ var else_src: ?Ast.TokenIndex = null;
+ for (case_nodes) |case_node| {
+ const case = tree.fullSwitchCase(case_node).?;
+
+ if (case.ast.values.len == 0) {
+ const case_src = case.ast.arrow_token - 1;
+ if (else_src) |src| {
+ return astgen.failTokNotes(
+ case_src,
+ "multiple else prongs in switch expression",
+ .{},
+ &[_]u32{
+ try astgen.errNoteTok(
+ src,
+ "previous else prong here",
+ .{},
+ ),
+ },
+ );
+ }
+ has_else = true;
+ else_node = case_node;
+ else_src = case_src;
+ continue;
+ } else if (case.ast.values.len == 1 and
+ node_tags[case.ast.values[0]] == .identifier and
+ mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
+ {
+ const case_src = case.ast.arrow_token - 1;
+ return astgen.failTokNotes(
+ case_src,
+ "'_' prong is not allowed when switching on errors",
+ .{},
+ &[_]u32{
+ try astgen.errNoteTok(
+ case_src,
+ "consider using 'else'",
+ .{},
+ ),
+ },
+ );
+ }
+
+ for (case.ast.values) |val| {
+ if (node_tags[val] == .string_literal)
+ return astgen.failNode(val, "cannot switch on strings", .{});
+ }
+
+ if (case.ast.values.len == 1 and node_tags[case.ast.values[0]] != .switch_range) {
+ scalar_cases_len += 1;
+ } else {
+ multi_cases_len += 1;
+ }
+ if (case.inline_token != null) {
+ inline_cases_len += 1;
+ }
+ }
+
+ const operand_ri: ResultInfo = .{
+ .rl = if (payload_is_ref) .ref else .none,
+ .ctx = .error_handling_expr,
+ };
+
+ astgen.advanceSourceCursorToNode(operand_node);
+ const operand_lc = LineColumn{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
+
+ const raw_operand = try reachableExpr(parent_gz, scope, operand_ri, operand_node, switch_node);
+ const item_ri: ResultInfo = .{ .rl = .none };
+
+ // This contains the data that goes into the `extra` array for the SwitchBlockErrUnion, except
+ // the first cases_nodes.len slots are a table that indexes payloads later in the array,
+ // with the non-error and else case indices coming first, then scalar_cases_len indexes, then
+ // multi_cases_len indexes
+ const payloads = &astgen.scratch;
+ const scratch_top = astgen.scratch.items.len;
+ const case_table_start = scratch_top;
+ const scalar_case_table = case_table_start + 1 + @intFromBool(has_else);
+ const multi_case_table = scalar_case_table + scalar_cases_len;
+ const case_table_end = multi_case_table + multi_cases_len;
+
+ try astgen.scratch.resize(gpa, case_table_end);
+ defer astgen.scratch.items.len = scratch_top;
+
+ var block_scope = parent_gz.makeSubBlock(scope);
+ // block_scope not used for collecting instructions
+ block_scope.instructions_top = GenZir.unstacked_top;
+ block_scope.setBreakResultInfo(block_ri);
+
+ // Sema expects a dbg_stmt immediately before switch_block_err_union
+ try emitDbgStmt(parent_gz, operand_lc);
+ // This gets added to the parent block later, after the item expressions.
+ const switch_block = try parent_gz.makeBlockInst(.switch_block_err_union, switch_node);
+
+ // We re-use this same scope for all cases, including the special prong, if any.
+ var case_scope = parent_gz.makeSubBlock(&block_scope.base);
+ case_scope.instructions_top = GenZir.unstacked_top;
+
+ {
+ const body_len_index: u32 = @intCast(payloads.items.len);
+ payloads.items[case_table_start] = body_len_index;
+ try payloads.resize(gpa, body_len_index + 1); // body_len
+
+ case_scope.instructions_top = parent_gz.instructions.items.len;
+ defer case_scope.unstack();
+
+ try case_scope.addDbgBlockBegin();
+
+ const unwrap_payload_tag: Zir.Inst.Tag = if (payload_is_ref)
+ .err_union_payload_unsafe_ptr
+ else
+ .err_union_payload_unsafe;
+
+ const unwrapped_payload = try case_scope.addUnNode(
+ unwrap_payload_tag,
+ raw_operand,
+ catch_or_if_node,
+ );
+
+ switch (node_ty) {
+ .@"catch" => {
+ const case_result = switch (ri.rl) {
+ .ref, .ref_coerced_ty => unwrapped_payload,
+ else => try rvalue(
+ &case_scope,
+ block_scope.break_result_info,
+ unwrapped_payload,
+ catch_or_if_node,
+ ),
+ };
+ try case_scope.addDbgBlockEnd();
+ _ = try case_scope.addBreakWithSrcNode(
+ .@"break",
+ switch_block,
+ case_result,
+ catch_or_if_node,
+ );
+ },
+ .@"if" => {
+ var payload_val_scope: Scope.LocalVal = undefined;
+
+ try case_scope.addDbgBlockBegin();
+ const then_node = if_full.ast.then_expr;
+ const then_sub_scope = s: {
+ assert(if_full.error_token != null);
+ if (if_full.payload_token) |payload_token| {
+ const token_name_index = payload_token + @intFromBool(payload_is_ref);
+ const ident_name = try astgen.identAsString(token_name_index);
+ const token_name_str = tree.tokenSlice(token_name_index);
+ if (mem.eql(u8, "_", token_name_str))
+ break :s &case_scope.base;
+ try astgen.detectLocalShadowing(
+ &case_scope.base,
+ ident_name,
+ token_name_index,
+ token_name_str,
+ .capture,
+ );
+ payload_val_scope = .{
+ .parent = &case_scope.base,
+ .gen_zir = &case_scope,
+ .name = ident_name,
+ .inst = unwrapped_payload,
+ .token_src = payload_token,
+ .id_cat = .capture,
+ };
+ try case_scope.addDbgVar(.dbg_var_val, ident_name, unwrapped_payload);
+ break :s &payload_val_scope.base;
+ } else {
+ _ = try case_scope.addUnNode(
+ .ensure_err_union_payload_void,
+ raw_operand,
+ catch_or_if_node,
+ );
+ break :s &case_scope.base;
+ }
+ };
+ const then_result = try expr(
+ &case_scope,
+ then_sub_scope,
+ block_scope.break_result_info,
+ then_node,
+ );
+ try checkUsed(parent_gz, &case_scope.base, then_sub_scope);
+ if (!case_scope.endsWithNoReturn()) {
+ try case_scope.addDbgBlockEnd();
+ _ = try case_scope.addBreakWithSrcNode(
+ .@"break",
+ switch_block,
+ then_result,
+ then_node,
+ );
+ }
+ },
+ }
+
+ const case_slice = case_scope.instructionsSlice();
+ // Since we use the switch_block_err_union instruction itself to refer
+ // to the capture, which will not be added to the child block, we need
+ // to handle ref_table manually.
+ const refs_len = refs: {
+ var n: usize = 0;
+ var check_inst = switch_block;
+ while (astgen.ref_table.get(check_inst)) |ref_inst| {
+ n += 1;
+ check_inst = ref_inst;
+ }
+ break :refs n;
+ };
+ const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice);
+ try payloads.ensureUnusedCapacity(gpa, body_len);
+ const capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = switch (node_ty) {
+ .@"catch" => .none,
+ .@"if" => if (if_full.payload_token == null)
+ .none
+ else if (payload_is_ref)
+ .by_ref
+ else
+ .by_val,
+ };
+ payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
+ .body_len = @intCast(body_len),
+ .capture = capture,
+ .is_inline = false,
+ .has_tag_capture = false,
+ });
+ if (astgen.ref_table.fetchRemove(switch_block)) |kv| {
+ appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
+ }
+ appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
+ }
+
+ const err_name = blk: {
+ const err_str = tree.tokenSlice(error_payload);
+ if (mem.eql(u8, err_str, "_")) {
+ return astgen.failTok(error_payload, "discard of error capture; omit it instead", .{});
+ }
+ const err_name = try astgen.identAsString(error_payload);
+ try astgen.detectLocalShadowing(scope, err_name, error_payload, err_str, .capture);
+
+ break :blk err_name;
+ };
+
+ // allocate a shared dummy instruction for the error capture
+ const err_inst = err_inst: {
+ const inst: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
+ try astgen.instructions.append(astgen.gpa, .{
+ .tag = .extended,
+ .data = .{ .extended = .{
+ .opcode = .value_placeholder,
+ .small = undefined,
+ .operand = undefined,
+ } },
+ });
+ break :err_inst inst;
+ };
+
+ // In this pass we generate all the item and prong expressions for error cases.
+ var multi_case_index: u32 = 0;
+ var scalar_case_index: u32 = 0;
+ var any_uses_err_capture = false;
+ for (case_nodes) |case_node| {
+ const case = tree.fullSwitchCase(case_node).?;
+
+ const is_multi_case = case.ast.values.len > 1 or
+ (case.ast.values.len == 1 and node_tags[case.ast.values[0]] == .switch_range);
+
+ var dbg_var_name: Zir.NullTerminatedString = .empty;
+ var dbg_var_inst: Zir.Inst.Ref = undefined;
+ var err_scope: Scope.LocalVal = undefined;
+ var capture_scope: Scope.LocalVal = undefined;
+
+ const sub_scope = blk: {
+ err_scope = .{
+ .parent = &case_scope.base,
+ .gen_zir = &case_scope,
+ .name = err_name,
+ .inst = err_inst.toRef(),
+ .token_src = error_payload,
+ .id_cat = .capture,
+ };
+
+ const capture_token = case.payload_token orelse break :blk &err_scope.base;
+ assert(token_tags[capture_token] == .identifier);
+
+ const capture_slice = tree.tokenSlice(capture_token);
+ if (mem.eql(u8, capture_slice, "_")) {
+ return astgen.failTok(capture_token, "discard of error capture; omit it instead", .{});
+ }
+ const tag_name = try astgen.identAsString(capture_token);
+ try astgen.detectLocalShadowing(&case_scope.base, tag_name, capture_token, capture_slice, .capture);
+
+ capture_scope = .{
+ .parent = &case_scope.base,
+ .gen_zir = &case_scope,
+ .name = tag_name,
+ .inst = switch_block.toRef(),
+ .token_src = capture_token,
+ .id_cat = .capture,
+ };
+ dbg_var_name = tag_name;
+ dbg_var_inst = switch_block.toRef();
+
+ err_scope.parent = &capture_scope.base;
+
+ break :blk &err_scope.base;
+ };
+
+ const header_index: u32 = @intCast(payloads.items.len);
+ const body_len_index = if (is_multi_case) blk: {
+ payloads.items[multi_case_table + multi_case_index] = header_index;
+ multi_case_index += 1;
+ try payloads.resize(gpa, header_index + 3); // items_len, ranges_len, body_len
+
+ // items
+ var items_len: u32 = 0;
+ for (case.ast.values) |item_node| {
+ if (node_tags[item_node] == .switch_range) continue;
+ items_len += 1;
+
+ const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node);
+ try payloads.append(gpa, @intFromEnum(item_inst));
+ }
+
+ // ranges
+ var ranges_len: u32 = 0;
+ for (case.ast.values) |range| {
+ if (node_tags[range] != .switch_range) continue;
+ ranges_len += 1;
+
+ const first = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].lhs);
+ const last = try comptimeExpr(parent_gz, scope, item_ri, node_datas[range].rhs);
+ try payloads.appendSlice(gpa, &[_]u32{
+ @intFromEnum(first), @intFromEnum(last),
+ });
+ }
+
+ payloads.items[header_index] = items_len;
+ payloads.items[header_index + 1] = ranges_len;
+ break :blk header_index + 2;
+ } else if (case_node == else_node) blk: {
+ payloads.items[case_table_start + 1] = header_index;
+ try payloads.resize(gpa, header_index + 1); // body_len
+ break :blk header_index;
+ } else blk: {
+ payloads.items[scalar_case_table + scalar_case_index] = header_index;
+ scalar_case_index += 1;
+ try payloads.resize(gpa, header_index + 2); // item, body_len
+ const item_node = case.ast.values[0];
+ const item_inst = try comptimeExpr(parent_gz, scope, item_ri, item_node);
+ payloads.items[header_index] = @intFromEnum(item_inst);
+ break :blk header_index + 1;
+ };
+
+ {
+ // temporarily stack case_scope on parent_gz
+ case_scope.instructions_top = parent_gz.instructions.items.len;
+ defer case_scope.unstack();
+
+ try case_scope.addDbgBlockBegin();
+ if (dbg_var_name != .empty) {
+ try case_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
+ }
+ const target_expr_node = case.ast.target_expr;
+ const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_info, target_expr_node);
+ // check capture_scope, not err_scope to avoid false positive unused error capture
+ try checkUsed(parent_gz, &case_scope.base, err_scope.parent);
+ const uses_err = err_scope.used != 0 or err_scope.discarded != 0;
+ if (uses_err) {
+ try case_scope.addDbgVar(.dbg_var_val, err_name, err_inst.toRef());
+ any_uses_err_capture = true;
+ }
+ try case_scope.addDbgBlockEnd();
+ if (!parent_gz.refIsNoReturn(case_result)) {
+ _ = try case_scope.addBreakWithSrcNode(.@"break", switch_block, case_result, target_expr_node);
+ }
+
+ const case_slice = case_scope.instructionsSlice();
+ // Since we use the switch_block_err_union instruction itself to refer
+ // to the capture, which will not be added to the child block, we need
+ // to handle ref_table manually.
+ const refs_len = refs: {
+ var n: usize = 0;
+ var check_inst = switch_block;
+ while (astgen.ref_table.get(check_inst)) |ref_inst| {
+ n += 1;
+ check_inst = ref_inst;
+ }
+ if (uses_err) {
+ check_inst = err_inst;
+ while (astgen.ref_table.get(check_inst)) |ref_inst| {
+ n += 1;
+ check_inst = ref_inst;
+ }
+ }
+ break :refs n;
+ };
+ const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice);
+ try payloads.ensureUnusedCapacity(gpa, body_len);
+ payloads.items[body_len_index] = @bitCast(Zir.Inst.SwitchBlock.ProngInfo{
+ .body_len = @intCast(body_len),
+ .capture = if (case.payload_token != null) .by_val else .none,
+ .is_inline = case.inline_token != null,
+ .has_tag_capture = false,
+ });
+ if (astgen.ref_table.fetchRemove(switch_block)) |kv| {
+ appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
+ }
+ if (uses_err) {
+ if (astgen.ref_table.fetchRemove(err_inst)) |kv| {
+ appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
+ }
+ }
+ appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
+ }
+ }
+ // Now that the item expressions are generated we can add this.
+ try parent_gz.instructions.append(gpa, switch_block);
+
+ try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlockErrUnion).Struct.fields.len +
+ @intFromBool(multi_cases_len != 0) +
+ payloads.items.len - case_table_end +
+ (case_table_end - case_table_start) * @typeInfo(Zir.Inst.As).Struct.fields.len);
+
+ const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlockErrUnion{
+ .operand = raw_operand,
+ .bits = Zir.Inst.SwitchBlockErrUnion.Bits{
+ .has_multi_cases = multi_cases_len != 0,
+ .has_else = has_else,
+ .scalar_cases_len = @intCast(scalar_cases_len),
+ .any_uses_err_capture = any_uses_err_capture,
+ .payload_is_ref = payload_is_ref,
+ },
+ .main_src_node_offset = parent_gz.nodeIndexToRelative(catch_or_if_node),
+ });
+
+ if (multi_cases_len != 0) {
+ astgen.extra.appendAssumeCapacity(multi_cases_len);
+ }
+
+ if (any_uses_err_capture) {
+ astgen.extra.appendAssumeCapacity(@intFromEnum(err_inst));
+ }
+
+ const zir_datas = astgen.instructions.items(.data);
+ zir_datas[@intFromEnum(switch_block)].pl_node.payload_index = payload_index;
+
+ for (payloads.items[case_table_start..case_table_end], 0..) |start_index, i| {
+ var body_len_index = start_index;
+ var end_index = start_index;
+ const table_index = case_table_start + i;
+ if (table_index < scalar_case_table) {
+ end_index += 1;
+ } else if (table_index < multi_case_table) {
+ body_len_index += 1;
+ end_index += 2;
+ } else {
+ body_len_index += 2;
+ const items_len = payloads.items[start_index];
+ const ranges_len = payloads.items[start_index + 1];
+ end_index += 3 + items_len + 2 * ranges_len;
+ }
+ const prong_info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(payloads.items[body_len_index]);
+ end_index += prong_info.body_len;
+ astgen.extra.appendSliceAssumeCapacity(payloads.items[start_index..end_index]);
+ }
+
+ if (need_result_rvalue) {
+ return rvalue(parent_gz, ri, switch_block.toRef(), switch_node);
+ } else {
+ return switch_block.toRef();
+ }
+}
+
fn switchExpr(
parent_gz: *GenZir,
scope: *Scope,