authorgravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2023-11-22 14:59:02+11:00
committergravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2024-01-09 14:42:11+11:00
logb784f64a6e4495f65d684e0afb00c4f7a62b950c
treec123b7802176e8ccc9b5fa6571ad0c58d1d4e919
parent41360975669177ba8665a59d8074cee452467fc0

sema: refactor error set switch logic


1 files changed, 321 insertions(+), 260 deletions(-)

src/Sema.zig+321-260
......@@ -11212,16 +11212,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1121211212 var case_vals = try std.ArrayListUnmanaged(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
1121311213 defer case_vals.deinit(gpa);
1121411214
11215 const Special = struct {
11216 body: []const Zir.Inst.Index,
11217 end: usize,
11218 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
11219 is_inline: bool,
11220 has_tag_capture: bool,
11221 };
11222
1122311215 const special_prong = extra.data.bits.specialProng();
11224 const special: Special = switch (special_prong) {
11216 const special: SpecialProng = switch (special_prong) {
1122511217 .none => .{
1122611218 .body = &.{},
1122711219 .end = header_extra_index,
......@@ -11401,150 +11393,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1140111393 );
1140211394 }
1140311395 },
11404 .ErrorSet => {
11405 var extra_index: usize = special.end;
11406 {
11407 var scalar_i: u32 = 0;
11408 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
11409 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
11410 extra_index += 1;
11411 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11412 extra_index += 1 + info.body_len;
11413
11414 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
11415 block,
11416 &seen_errors,
11417 item_ref,
11418 operand_ty,
11419 src_node_offset,
11420 .{ .scalar = scalar_i },
11421 ));
11422 }
11423 }
11424 {
11425 var multi_i: u32 = 0;
11426 while (multi_i < multi_cases_len) : (multi_i += 1) {
11427 const items_len = sema.code.extra[extra_index];
11428 extra_index += 1;
11429 const ranges_len = sema.code.extra[extra_index];
11430 extra_index += 1;
11431 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11432 extra_index += 1;
11433 const items = sema.code.refSlice(extra_index, items_len);
11434 extra_index += items_len + info.body_len;
11435
11436 try case_vals.ensureUnusedCapacity(gpa, items.len);
11437 for (items, 0..) |item_ref, item_i| {
11438 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
11439 block,
11440 &seen_errors,
11441 item_ref,
11442 operand_ty,
11443 src_node_offset,
11444 .{ .multi = .{ .prong = multi_i, .item = @intCast(item_i) } },
11445 ));
11446 }
11447
11448 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
11449 }
11450 }
11451
11452 switch (try sema.resolveInferredErrorSetTy(block, src, operand_ty.toIntern())) {
11453 .anyerror_type => {
11454 if (special_prong != .@"else") {
11455 return sema.fail(
11456 block,
11457 src,
11458 "else prong required when switching on type 'anyerror'",
11459 .{},
11460 );
11461 }
11462 else_error_ty = Type.anyerror;
11463 },
11464 else => |err_set_ty_index| else_validation: {
11465 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
11466 var maybe_msg: ?*Module.ErrorMsg = null;
11467 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
11468
11469 for (error_names.get(ip)) |error_name| {
11470 if (!seen_errors.contains(error_name) and special_prong != .@"else") {
11471 const msg = maybe_msg orelse blk: {
11472 maybe_msg = try sema.errMsg(
11473 block,
11474 src,
11475 "switch must handle all possibilities",
11476 .{},
11477 );
11478 break :blk maybe_msg.?;
11479 };
11480
11481 try sema.errNote(
11482 block,
11483 src,
11484 msg,
11485 "unhandled error value: 'error.{}'",
11486 .{error_name.fmt(ip)},
11487 );
11488 }
11489 }
11490
11491 if (maybe_msg) |msg| {
11492 maybe_msg = null;
11493 try sema.addDeclaredHereNote(msg, operand_ty);
11494 return sema.failWithOwnedErrorMsg(block, msg);
11495 }
11496
11497 if (special_prong == .@"else" and
11498 seen_errors.count() == error_names.len)
11499 {
11500 // In order to enable common patterns for generic code allow simple else bodies
11501 // else => unreachable,
11502 // else => return,
11503 // else => |e| return e,
11504 // even if all the possible errors were already handled.
11505 const tags = sema.code.instructions.items(.tag);
11506 for (special.body) |else_inst| switch (tags[@intFromEnum(else_inst)]) {
11507 .dbg_block_begin,
11508 .dbg_block_end,
11509 .dbg_stmt,
11510 .dbg_var_val,
11511 .ret_type,
11512 .as_node,
11513 .ret_node,
11514 .@"unreachable",
11515 .@"defer",
11516 .defer_err_code,
11517 .err_union_code,
11518 .ret_err_value_code,
11519 .restore_err_ret_index,
11520 .is_non_err,
11521 .ret_is_non_err,
11522 .condbr,
11523 => {},
11524 else => break,
11525 } else break :else_validation;
11526
11527 return sema.fail(
11528 block,
11529 special_prong_src,
11530 "unreachable else prong; all cases already handled",
11531 .{},
11532 );
11533 }
11534
11535 var names: InferredErrorSet.NameMap = .{};
11536 try names.ensureUnusedCapacity(sema.arena, error_names.len);
11537 for (error_names.get(ip)) |error_name| {
11538 if (seen_errors.contains(error_name)) continue;
11539
11540 names.putAssumeCapacityNoClobber(error_name, {});
11541 }
11542 // No need to keep the hash map metadata correct; here we
11543 // extract the (sorted) keys only.
11544 else_error_ty = try mod.errorSetFromUnsortedNames(names.keys());
11545 },
11546 }
11547 },
11396 .ErrorSet => else_error_ty = try validateErrSetSwitch(
11397 sema,
11398 block,
11399 &seen_errors,
11400 &case_vals,
11401 operand_ty,
11402 inst_data,
11403 scalar_cases_len,
11404 multi_cases_len,
11405 .{ .body = special.body, .end = special.end, .src = special_prong_src },
11406 special_prong == .@"else",
11407 ),
1154811408 .Int, .ComptimeInt => {
1154911409 var extra_index: usize = special.end;
1155011410 {
......@@ -11840,114 +11700,19 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1184011700 defer merges.deinit(gpa);
1184111701
1184211702 if (try sema.resolveDefinedValue(&child_block, src, operand)) |operand_val| {
11843 const resolved_operand_val = try sema.resolveLazyValue(operand_val);
11844 var extra_index: usize = special.end;
11845 {
11846 var scalar_i: usize = 0;
11847 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
11848 extra_index += 1;
11849 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11850 extra_index += 1;
11851 const body = sema.code.bodySlice(extra_index, info.body_len);
11852 extra_index += info.body_len;
11853
11854 const item = case_vals.items[scalar_i];
11855 const item_val = sema.resolveConstDefinedValue(&child_block, .unneeded, item, undefined) catch unreachable;
11856 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
11857 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
11858 return spa.resolveProngComptime(
11859 &child_block,
11860 .normal,
11861 body,
11862 info.capture,
11863 .{ .scalar_capture = @intCast(scalar_i) },
11864 &.{item},
11865 if (info.is_inline) operand else .none,
11866 info.has_tag_capture,
11867 merges,
11868 );
11869 }
11870 }
11871 }
11872 {
11873 var multi_i: usize = 0;
11874 var case_val_idx: usize = scalar_cases_len;
11875 while (multi_i < multi_cases_len) : (multi_i += 1) {
11876 const items_len = sema.code.extra[extra_index];
11877 extra_index += 1;
11878 const ranges_len = sema.code.extra[extra_index];
11879 extra_index += 1;
11880 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
11881 extra_index += 1 + items_len;
11882 const body = sema.code.bodySlice(extra_index + 2 * ranges_len, info.body_len);
11883
11884 const items = case_vals.items[case_val_idx..][0..items_len];
11885 case_val_idx += items_len;
11886
11887 for (items) |item| {
11888 // Validation above ensured these will succeed.
11889 const item_val = sema.resolveConstDefinedValue(&child_block, .unneeded, item, undefined) catch unreachable;
11890 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
11891 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
11892 return spa.resolveProngComptime(
11893 &child_block,
11894 .normal,
11895 body,
11896 info.capture,
11897 .{ .multi_capture = @intCast(multi_i) },
11898 items,
11899 if (info.is_inline) operand else .none,
11900 info.has_tag_capture,
11901 merges,
11902 );
11903 }
11904 }
11905
11906 var range_i: usize = 0;
11907 while (range_i < ranges_len) : (range_i += 1) {
11908 const range_items = case_vals.items[case_val_idx..][0..2];
11909 extra_index += 2;
11910 case_val_idx += 2;
11911
11912 // Validation above ensured these will succeed.
11913 const first_val = sema.resolveConstDefinedValue(&child_block, .unneeded, range_items[0], undefined) catch unreachable;
11914 const last_val = sema.resolveConstDefinedValue(&child_block, .unneeded, range_items[1], undefined) catch unreachable;
11915 if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and
11916 (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))
11917 {
11918 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand);
11919 return spa.resolveProngComptime(
11920 &child_block,
11921 .normal,
11922 body,
11923 info.capture,
11924 .{ .multi_capture = @intCast(multi_i) },
11925 undefined, // case_vals may be undefined for ranges
11926 if (info.is_inline) operand else .none,
11927 info.has_tag_capture,
11928 merges,
11929 );
11930 }
11931 }
11932
11933 extra_index += info.body_len;
11934 }
11935 }
11936 if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, special.body, operand);
11937 if (empty_enum) {
11938 return .void_value;
11939 }
11940
11941 return spa.resolveProngComptime(
11703 return resolveSwitchComptime(
11704 sema,
11705 spa,
1194211706 &child_block,
11943 .special,
11944 special.body,
11945 special.capture,
11946 .special_capture,
11947 undefined, // case_vals may be undefined for special prongs
11948 if (special.is_inline) operand else .none,
11949 special.has_tag_capture,
11950 merges,
11707 operand,
11708 operand_val,
11709 operand_ty,
11710 special,
11711 case_vals,
11712 scalar_cases_len,
11713 multi_cases_len,
11714 err_set,
11715 empty_enum,
1195111716 );
1195211717 }
1195311718
......@@ -12593,6 +12358,140 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
1259312358 return sema.analyzeBlockBody(block, src, &child_block, merges);
1259412359}
1259512360
12361const SpecialProng = struct {
12362 body: []const Zir.Inst.Index,
12363 end: usize,
12364 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
12365 is_inline: bool,
12366 has_tag_capture: bool,
12367};
12368
12369fn resolveSwitchComptime(
12370 sema: *Sema,
12371 spa: SwitchProngAnalysis,
12372 child_block: *Block,
12373 cond_operand: Air.Inst.Ref,
12374 operand_val: Value,
12375 operand_ty: Type,
12376 special: SpecialProng,
12377 case_vals: std.ArrayListUnmanaged(Air.Inst.Ref),
12378 scalar_cases_len: u32,
12379 multi_cases_len: u32,
12380 err_set: bool,
12381 empty_enum: bool,
12382) CompileError!Air.Inst.Ref {
12383 const merges = &child_block.label.?.merges;
12384 const resolved_operand_val = try sema.resolveLazyValue(operand_val);
12385 var extra_index: usize = special.end;
12386 {
12387 var scalar_i: usize = 0;
12388 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12389 extra_index += 1;
12390 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12391 extra_index += 1;
12392 const body = sema.code.bodySlice(extra_index, info.body_len);
12393 extra_index += info.body_len;
12394
12395 const item = case_vals.items[scalar_i];
12396 const item_val = sema.resolveConstDefinedValue(child_block, .unneeded, item, undefined) catch unreachable;
12397 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
12398 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
12399 return spa.resolveProngComptime(
12400 child_block,
12401 .normal,
12402 body,
12403 info.capture,
12404 .{ .scalar_capture = @intCast(scalar_i) },
12405 &.{item},
12406 if (info.is_inline) cond_operand else .none,
12407 info.has_tag_capture,
12408 merges,
12409 );
12410 }
12411 }
12412 }
12413 {
12414 var multi_i: usize = 0;
12415 var case_val_idx: usize = scalar_cases_len;
12416 while (multi_i < multi_cases_len) : (multi_i += 1) {
12417 const items_len = sema.code.extra[extra_index];
12418 extra_index += 1;
12419 const ranges_len = sema.code.extra[extra_index];
12420 extra_index += 1;
12421 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12422 extra_index += 1 + items_len;
12423 const body = sema.code.bodySlice(extra_index + 2 * ranges_len, info.body_len);
12424
12425 const items = case_vals.items[case_val_idx..][0..items_len];
12426 case_val_idx += items_len;
12427
12428 for (items) |item| {
12429 // Validation above ensured these will succeed.
12430 const item_val = sema.resolveConstDefinedValue(child_block, .unneeded, item, undefined) catch unreachable;
12431 if (operand_val.eql(item_val, operand_ty, sema.mod)) {
12432 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
12433 return spa.resolveProngComptime(
12434 child_block,
12435 .normal,
12436 body,
12437 info.capture,
12438 .{ .multi_capture = @intCast(multi_i) },
12439 items,
12440 if (info.is_inline) cond_operand else .none,
12441 info.has_tag_capture,
12442 merges,
12443 );
12444 }
12445 }
12446
12447 var range_i: usize = 0;
12448 while (range_i < ranges_len) : (range_i += 1) {
12449 const range_items = case_vals.items[case_val_idx..][0..2];
12450 extra_index += 2;
12451 case_val_idx += 2;
12452
12453 // Validation above ensured these will succeed.
12454 const first_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_items[0], undefined) catch unreachable;
12455 const last_val = sema.resolveConstDefinedValue(child_block, .unneeded, range_items[1], undefined) catch unreachable;
12456 if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and
12457 (try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))
12458 {
12459 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
12460 return spa.resolveProngComptime(
12461 child_block,
12462 .normal,
12463 body,
12464 info.capture,
12465 .{ .multi_capture = @intCast(multi_i) },
12466 undefined, // case_vals may be undefined for ranges
12467 if (info.is_inline) cond_operand else .none,
12468 info.has_tag_capture,
12469 merges,
12470 );
12471 }
12472 }
12473
12474 extra_index += info.body_len;
12475 }
12476 }
12477 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special.body, cond_operand);
12478 if (empty_enum) {
12479 return .void_value;
12480 }
12481
12482 return spa.resolveProngComptime(
12483 child_block,
12484 .special,
12485 special.body,
12486 special.capture,
12487 .special_capture,
12488 undefined, // case_vals may be undefined for special prongs
12489 if (special.is_inline) cond_operand else .none,
12490 special.has_tag_capture,
12491 merges,
12492 );
12493}
12494
1259612495const RangeSetUnhandledIterator = struct {
1259712496 mod: *Module,
1259812497 cur: ?InternPool.Index,
......@@ -12710,6 +12609,168 @@ fn resolveSwitchItemVal(
1271012609 return .{ .ref = new_item, .val = val.toIntern() };
1271112610}
1271212611
12612fn validateErrSetSwitch(
12613 sema: *Sema,
12614 block: *Block,
12615 seen_errors: *SwitchErrorSet,
12616 case_vals: *std.ArrayListUnmanaged(Air.Inst.Ref),
12617 operand_ty: Type,
12618 inst_data: std.meta.FieldType(Zir.Inst.Data, .pl_node),
12619 scalar_cases_len: u32,
12620 multi_cases_len: u32,
12621 else_case: struct { body: []const Zir.Inst.Index, end: usize, src: LazySrcLoc },
12622 has_else: bool,
12623) CompileError!?Type {
12624 const gpa = sema.gpa;
12625 const mod = sema.mod;
12626 const ip = &mod.intern_pool;
12627
12628 const src_node_offset = inst_data.src_node;
12629 const src = inst_data.src();
12630
12631 var extra_index: usize = else_case.end;
12632 {
12633 var scalar_i: u32 = 0;
12634 while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
12635 const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
12636 extra_index += 1;
12637 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12638 extra_index += 1 + info.body_len;
12639
12640 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
12641 block,
12642 seen_errors,
12643 item_ref,
12644 operand_ty,
12645 src_node_offset,
12646 .{ .scalar = scalar_i },
12647 ));
12648 }
12649 }
12650 {
12651 var multi_i: u32 = 0;
12652 while (multi_i < multi_cases_len) : (multi_i += 1) {
12653 const items_len = sema.code.extra[extra_index];
12654 extra_index += 1;
12655 const ranges_len = sema.code.extra[extra_index];
12656 extra_index += 1;
12657 const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
12658 extra_index += 1;
12659 const items = sema.code.refSlice(extra_index, items_len);
12660 extra_index += items_len + info.body_len;
12661
12662 try case_vals.ensureUnusedCapacity(gpa, items.len);
12663 for (items, 0..) |item_ref, item_i| {
12664 case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
12665 block,
12666 seen_errors,
12667 item_ref,
12668 operand_ty,
12669 src_node_offset,
12670 .{ .multi = .{ .prong = multi_i, .item = @intCast(item_i) } },
12671 ));
12672 }
12673
12674 try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
12675 }
12676 }
12677
12678 switch (try sema.resolveInferredErrorSetTy(block, src, operand_ty.toIntern())) {
12679 .anyerror_type => {
12680 if (!has_else) {
12681 return sema.fail(
12682 block,
12683 src,
12684 "else prong required when switching on type 'anyerror'",
12685 .{},
12686 );
12687 }
12688 return Type.anyerror;
12689 },
12690 else => |err_set_ty_index| else_validation: {
12691 const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
12692 var maybe_msg: ?*Module.ErrorMsg = null;
12693 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
12694
12695 for (error_names.get(ip)) |error_name| {
12696 if (!seen_errors.contains(error_name) and !has_else) {
12697 const msg = maybe_msg orelse blk: {
12698 maybe_msg = try sema.errMsg(
12699 block,
12700 src,
12701 "switch must handle all possibilities",
12702 .{},
12703 );
12704 break :blk maybe_msg.?;
12705 };
12706
12707 try sema.errNote(
12708 block,
12709 src,
12710 msg,
12711 "unhandled error value: 'error.{}'",
12712 .{error_name.fmt(ip)},
12713 );
12714 }
12715 }
12716
12717 if (maybe_msg) |msg| {
12718 maybe_msg = null;
12719 try sema.addDeclaredHereNote(msg, operand_ty);
12720 return sema.failWithOwnedErrorMsg(block, msg);
12721 }
12722
12723 if (has_else and seen_errors.count() == error_names.len) {
12724 // In order to enable common patterns for generic code allow simple else bodies
12725 // else => unreachable,
12726 // else => return,
12727 // else => |e| return e,
12728 // even if all the possible errors were already handled.
12729 const tags = sema.code.instructions.items(.tag);
12730 for (else_case.body) |else_inst| switch (tags[@intFromEnum(else_inst)]) {
12731 .dbg_block_begin,
12732 .dbg_block_end,
12733 .dbg_stmt,
12734 .dbg_var_val,
12735 .ret_type,
12736 .as_node,
12737 .ret_node,
12738 .@"unreachable",
12739 .@"defer",
12740 .defer_err_code,
12741 .err_union_code,
12742 .ret_err_value_code,
12743 .restore_err_ret_index,
12744 .is_non_err,
12745 .ret_is_non_err,
12746 .condbr,
12747 => {},
12748 else => break,
12749 } else break :else_validation;
12750
12751 return sema.fail(
12752 block,
12753 else_case.src,
12754 "unreachable else prong; all cases already handled",
12755 .{},
12756 );
12757 }
12758
12759 var names: InferredErrorSet.NameMap = .{};
12760 try names.ensureUnusedCapacity(sema.arena, error_names.len);
12761 for (error_names.get(ip)) |error_name| {
12762 if (seen_errors.contains(error_name)) continue;
12763
12764 names.putAssumeCapacityNoClobber(error_name, {});
12765 }
12766 // No need to keep the hash map metadata correct; here we
12767 // extract the (sorted) keys only.
12768 return try mod.errorSetFromUnsortedNames(names.keys());
12769 },
12770 }
12771 return null;
12772}
12773
1271312774fn validateSwitchRange(
1271412775 sema: *Sema,
1271512776 block: *Block,