authorgravatar for justus@klausecker.deJustus Klausecker <justus@klausecker.de> 2026-01-12 19:59:47+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-01-13 06:03:07+01:00
log044ba3e0b020c317e8934d0d8271a512a9deeff8
tree1e15f54d75b54ace831b96066d90f8683a01208d
parent4fcf6507f9f96d1f341819dab89ded74cd3c7f7d

Sema: fix single-range switch prong capture (for real this time)

e2338edb47 didn't *quite* do it, the call sites of all switch prong related functions now have to do their part too and be a little more precise about what kind of prong they're currently analyzing. Also removes some unused/unnecessary stuff.

3 files changed, 70 insertions(+), 74 deletions(-)

lib/std/zig/Zir.zig-2
......@@ -3438,8 +3438,6 @@ pub const Inst = struct {
34383438 return if (item_info.kind == .body_len) item_info.data else null;
34393439 }
34403440 };
3441
3442 pub const Kind = enum { default, ref, err_union };
34433441 };
34443442
34453443 pub const ArrayInitRefTy = struct {
src/Sema.zig+63-71
......@@ -10747,8 +10747,6 @@ fn analyzeSwitchBlock(
1074710747 const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
1074810748
1074910749 const has_else = zir_switch.else_case != null;
10750 const has_under = zir_switch.has_under;
10751
1075210750 const else_case = validated_switch.else_case;
1075310751
1075410752 const operand: SwitchOperand, const operand_ty: Type, const maybe_operand_opv: ?Value, const item_ty: Type = operand: {
......@@ -10813,11 +10811,6 @@ fn analyzeSwitchBlock(
1081310811 .loop => |l| l.init_cond,
1081410812 };
1081510813
10816 // We treat `else` and `_` the same, except if both are present.
10817 const else_is_named_only = has_else and has_under;
10818 const catch_all_case: CatchAllSwitchCase =
10819 if (has_under) .under else if (has_else) .@"else" else .none;
10820
1082110814 resolve_at_comptime: {
1082210815 // always runtime; evaluation in comptime scope uses `simple`
1082310816 if (operand == .loop) break :resolve_at_comptime;
......@@ -10834,8 +10827,6 @@ fn analyzeSwitchBlock(
1083410827 cur_operand,
1083510828 raw_operand_ty,
1083610829 cur_cond_val,
10837 catch_all_case,
10838 else_is_named_only,
1083910830 merges,
1084010831 switch_inst,
1084110832 zir_switch,
......@@ -10958,6 +10949,11 @@ fn analyzeSwitchBlock(
1095810949 }
1095910950 },
1096010951 };
10952 const prong_kind: SwitchProngKind = kind: {
10953 if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
10954 if (is_special) break :kind .special;
10955 break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
10956 };
1096110957 break :payload_ref try sema.analyzeSwitchPayloadCapture(
1096210958 &case_block,
1096310959 operand,
......@@ -10970,8 +10966,7 @@ fn analyzeSwitchBlock(
1097010966 .case_idx = index,
1097110967 } }),
1097210968 capture == .by_ref,
10973 if (is_special) .special else .{ .item_refs = &.{.fromValue(item_opv)} },
10974 if (is_inline) .fromValue(item_opv) else .none,
10969 prong_kind,
1097510970 validated_switch.else_err_ty,
1097610971 );
1097710972 },
......@@ -11094,8 +11089,6 @@ fn finishSwitchBr(
1109411089 const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(switch_inst) - 1);
1109511090
1109611091 const else_is_named_only = has_else and has_under;
11097 const catch_all_case: CatchAllSwitchCase =
11098 if (has_under) .under else if (has_else) .@"else" else .none;
1109911092
1110011093 const item_ty = switch (operand_ty.zigTypeTag(zcu)) {
1110111094 .@"union" => operand_ty.unionTagType(zcu).?,
......@@ -11216,8 +11209,7 @@ fn finishSwitchBr(
1121611209 } }),
1121711210 prong_info.capture,
1121811211 prong_info.has_tag_capture,
11219 item_ref,
11220 .{ .item_refs = &.{item_ref} },
11212 .{ .inline_ref = item_ref },
1122111213 validated_switch.else_err_ty,
1122211214 switch_inst,
1122311215 zir_switch,
......@@ -11308,8 +11300,7 @@ fn finishSwitchBr(
1130811300 } }),
1130911301 prong_info.capture,
1131011302 prong_info.has_tag_capture,
11311 item_ref,
11312 .has_ranges,
11303 .{ .inline_ref = item_ref },
1131311304 validated_switch.else_err_ty,
1131411305 switch_inst,
1131511306 zir_switch,
......@@ -11333,6 +11324,9 @@ fn finishSwitchBr(
1133311324 if (prong_info.is_inline) continue; // handled above
1133411325
1133511326 if (is_under_prong) {
11327 // We will handle this later. If there are any named items specified
11328 // along with the `_`, we don't have to actually emit any AIR for them
11329 // as they will be 'absorbed' by the `_` (the catch-all prong) anyway.
1133611330 under_prong = .{
1133711331 .index = case.index,
1133811332 .body = prong_body,
......@@ -11359,8 +11353,7 @@ fn finishSwitchBr(
1135911353 } }),
1136011354 prong_info.capture,
1136111355 prong_info.has_tag_capture,
11362 .none,
11363 .{ .item_refs = item_refs },
11356 if (range_refs.len > 0) .has_ranges else .{ .item_refs = item_refs },
1136411357 validated_switch.else_err_ty,
1136511358 switch_inst,
1136611359 zir_switch,
......@@ -11385,7 +11378,7 @@ fn finishSwitchBr(
1138511378 }
1138611379
1138711380 const catch_all_extra: []const u32 = catch_all_extra: {
11388 if (catch_all_case == .none and !case_block.wantSafety()) {
11381 if (!has_else and !has_under and !case_block.wantSafety()) {
1138911382 try branch_hints.append(gpa, .none);
1139011383 break :catch_all_extra &.{};
1139111384 }
......@@ -11445,8 +11438,7 @@ fn finishSwitchBr(
1144511438 } }),
1144611439 else_case.capture,
1144711440 else_case.has_tag_capture,
11448 item_ref,
11449 .special,
11441 .{ .inline_ref = item_ref },
1145011442 validated_switch.else_err_ty,
1145111443 switch_inst,
1145211444 zir_switch,
......@@ -11473,7 +11465,7 @@ fn finishSwitchBr(
1147311465 case_block.error_return_trace_index = child_block.error_return_trace_index;
1147411466
1147511467 if (zcu.backendSupportsFeature(.is_named_enum_value) and
11476 catch_all_case != .none and block.wantSafety() and
11468 (has_else or has_under) and block.wantSafety() and
1147711469 item_ty.zigTypeTag(zcu) == .@"enum" and
1147811470 (!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
1147911471 {
......@@ -11508,7 +11500,6 @@ fn finishSwitchBr(
1150811500 } }),
1150911501 else_case.capture,
1151011502 else_case.has_tag_capture,
11511 .none,
1151211503 .special,
1151311504 validated_switch.else_err_ty,
1151411505 switch_inst,
......@@ -11548,10 +11539,12 @@ fn finishSwitchBr(
1154811539 }
1154911540
1155011541 const analyze_catch_all_body = analyze_body: {
11551 switch (catch_all_case) {
11552 .none => break :analyze_body false, // we still may want a safety check!
11553 .under => break :analyze_body true, // can't be a union anyway
11554 .@"else" => if (else_case.is_inline) break :analyze_body false,
11542 if (has_under) {
11543 break :analyze_body true; // can't be a union or an error set, never inlined
11544 } else if (has_else) {
11545 if (else_case.is_inline) break :analyze_body false; // already handled above
11546 } else {
11547 break :analyze_body false; // we still may want a safety check!
1155511548 }
1155611549 if (union_originally) {
1155711550 const union_obj = zcu.typeToUnion(operand_ty).?;
......@@ -11574,11 +11567,10 @@ fn finishSwitchBr(
1157411567
1157511568 const catch_all_hint = hint: {
1157611569 if (analyze_catch_all_body) {
11577 const index, const body, const capture, const has_tag_capture = switch (catch_all_case) {
11578 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture },
11579 .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture },
11580 .none => unreachable,
11581 };
11570 const index, const body, const capture, const has_tag_capture = if (under_prong) |under|
11571 .{ under.index, under.body, under.capture, under.has_tag_capture }
11572 else
11573 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
1158211574 break :hint try sema.analyzeSwitchProng(
1158311575 &case_block,
1158411576 operand,
......@@ -11591,7 +11583,6 @@ fn finishSwitchBr(
1159111583 } }),
1159211584 capture,
1159311585 has_tag_capture,
11594 .none,
1159511586 .special,
1159611587 validated_switch.else_err_ty,
1159711588 switch_inst,
......@@ -12072,9 +12063,9 @@ fn validateSwitchBlock(
1207212063 const msg = try sema.errMsg(
1207312064 operand_src,
1207412065 "ranges not allowed when switching on type '{f}'",
12075 .{operand_ty.fmt(sema.pt)},
12066 .{operand_ty.fmt(pt)},
1207612067 );
12077 errdefer msg.destroy(sema.gpa);
12068 errdefer msg.destroy(gpa);
1207812069 try sema.errNote(
1207912070 range_src,
1208012071 msg,
......@@ -12309,8 +12300,6 @@ fn resolveSwitchBlock(
1230912300 operand: SwitchOperand,
1231012301 raw_operand_ty: Type,
1231112302 maybe_lazy_cond_val: Value,
12312 catch_all_case: CatchAllSwitchCase,
12313 else_is_named_only: bool,
1231412303 merges: *Block.Merges,
1231512304 switch_inst: Zir.Inst.Index,
1231612305 zir_switch: *const Zir.UnwrappedSwitchBlock,
......@@ -12377,6 +12366,11 @@ fn resolveSwitchBlock(
1237712366 // This prong should be unreachable!
1237812367 return .unreachable_value;
1237912368 }
12369 const prong_kind: SwitchProngKind = kind: {
12370 if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };
12371 if (range_refs.len > 0) break :kind .has_ranges;
12372 break :kind .{ .item_refs = item_refs };
12373 };
1238012374 return sema.resolveSwitchProng(
1238112375 block,
1238212376 child_block,
......@@ -12389,8 +12383,7 @@ fn resolveSwitchBlock(
1238912383 } }),
1239012384 prong_info.capture,
1239112385 prong_info.has_tag_capture,
12392 if (prong_info.is_inline) cond_ref else .none,
12393 .{ .item_refs = item_refs },
12386 prong_kind,
1239412387 validated_switch.else_err_ty,
1239512388 merges,
1239612389 switch_inst,
......@@ -12404,6 +12397,10 @@ fn resolveSwitchBlock(
1240412397 if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
1240512398 (try sema.compareAll(cond_val, .lte, last_val, item_ty)))
1240612399 {
12400 const prong_kind: SwitchProngKind = if (prong_info.is_inline)
12401 .{ .inline_ref = cond_ref }
12402 else
12403 .has_ranges;
1240712404 return sema.resolveSwitchProng(
1240812405 block,
1240912406 child_block,
......@@ -12416,8 +12413,7 @@ fn resolveSwitchBlock(
1241612413 } }),
1241712414 prong_info.capture,
1241812415 prong_info.has_tag_capture,
12419 if (prong_info.is_inline) cond_ref else .none,
12420 .has_ranges,
12416 prong_kind,
1242112417 validated_switch.else_err_ty,
1242212418 merges,
1242312419 switch_inst,
......@@ -12428,11 +12424,16 @@ fn resolveSwitchBlock(
1242812424 }
1242912425
1243012426 const else_case = validated_switch.else_case;
12427 const else_is_named_only = zir_switch.else_case != null and under_prong != null;
1243112428
1243212429 // named-only prong
1243312430
1243412431 if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
1243512432 assert(item_ty.isNonexhaustiveEnum(zcu));
12433 const prong_kind: SwitchProngKind = if (else_case.is_inline)
12434 .{ .inline_ref = cond_ref }
12435 else
12436 .special;
1243612437 return sema.resolveSwitchProng(
1243712438 block,
1243812439 child_block,
......@@ -12445,8 +12446,7 @@ fn resolveSwitchBlock(
1244512446 } }),
1244612447 else_case.capture,
1244712448 else_case.has_tag_capture,
12448 if (else_case.is_inline) cond_ref else .none,
12449 .special,
12449 prong_kind,
1245012450 validated_switch.else_err_ty,
1245112451 merges,
1245212452 switch_inst,
......@@ -12456,11 +12456,10 @@ fn resolveSwitchBlock(
1245612456
1245712457 // catch-all prong
1245812458
12459 const index, const body, const capture, const has_tag_capture, const is_inline = switch (catch_all_case) {
12460 .@"else" => .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline },
12461 .under => .{ under_prong.?.index, under_prong.?.body, under_prong.?.capture, under_prong.?.has_tag_capture, false },
12462 .none => unreachable,
12463 };
12459 const index, const body, const capture, const has_tag_capture, const is_inline = if (under_prong) |under|
12460 .{ under.index, under.body, under.capture, under.has_tag_capture, false }
12461 else
12462 .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline };
1246412463 if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_ref);
1246512464 if (union_originally) {
1246612465 for (validated_switch.seen_enum_fields, 0..) |maybe_seen, field_i| {
......@@ -12471,6 +12470,10 @@ fn resolveSwitchBlock(
1247112470 return .unreachable_value;
1247212471 }
1247312472 }
12473 const prong_kind: SwitchProngKind = if (is_inline)
12474 .{ .inline_ref = cond_ref }
12475 else
12476 .special;
1247412477 return sema.resolveSwitchProng(
1247512478 block,
1247612479 child_block,
......@@ -12483,8 +12486,7 @@ fn resolveSwitchBlock(
1248312486 } }),
1248412487 capture,
1248512488 has_tag_capture,
12486 if (is_inline) cond_ref else .none,
12487 .special,
12489 prong_kind,
1248812490 validated_switch.else_err_ty,
1248912491 merges,
1249012492 switch_inst,
......@@ -12520,9 +12522,9 @@ const SwitchOperand = union(enum) {
1252012522 },
1252112523};
1252212524
12523const CatchAllSwitchCase = enum { none, @"else", under };
12524
1252512525const SwitchProngKind = union(enum) {
12526 /// Prefer populating this field over the others, if possible.
12527 inline_ref: Air.Inst.Ref,
1252612528 item_refs: []const Air.Inst.Ref,
1252712529 has_ranges,
1252812530 special,
......@@ -12541,7 +12543,6 @@ fn resolveSwitchProng(
1254112543 capture_src: LazySrcLoc,
1254212544 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1254312545 has_tag_capture: bool,
12544 inline_case_capture: Air.Inst.Ref,
1254512546 kind: SwitchProngKind,
1254612547 else_err_ty: ?Type,
1254712548 merges: *Block.Merges,
......@@ -12569,7 +12570,6 @@ fn resolveSwitchProng(
1256912570 capture_src,
1257012571 capture == .by_ref,
1257112572 kind,
12572 inline_case_capture,
1257312573 else_err_ty,
1257412574 );
1257512575 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
......@@ -12585,7 +12585,6 @@ fn resolveSwitchProng(
1258512585 operand.simple.by_val,
1258612586 sema.typeOf(operand.simple.by_val),
1258712587 capture_src,
12588 inline_case_capture,
1258912588 kind,
1259012589 );
1259112590 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
......@@ -12637,7 +12636,6 @@ fn analyzeSwitchProng(
1263712636 capture_src: LazySrcLoc,
1263812637 capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
1263912638 has_tag_capture: bool,
12640 inline_case_capture: Air.Inst.Ref,
1264112639 kind: SwitchProngKind,
1264212640 else_err_ty: ?Type,
1264312641 switch_inst: Zir.Inst.Index,
......@@ -12664,7 +12662,7 @@ fn analyzeSwitchProng(
1266412662 // No need to load the operand for this prong!
1266512663 break :load_operand .{ undefined, undefined };
1266612664 }
12667 if (inline_case_capture != .none and
12665 if (kind == .inline_ref and
1266812666 !(capture != .none and operand_ty.zigTypeTag(zcu) == .@"union"))
1266912667 {
1267012668 // We only need to load the operand if there's a union payload capture
......@@ -12698,7 +12696,6 @@ fn analyzeSwitchProng(
1269812696 capture_src,
1269912697 capture == .by_ref,
1270012698 kind,
12701 inline_case_capture,
1270212699 else_err_ty,
1270312700 );
1270412701 assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
......@@ -12714,7 +12711,6 @@ fn analyzeSwitchProng(
1271412711 operand_val,
1271512712 operand_ty,
1271612713 capture_src,
12717 inline_case_capture,
1271812714 kind,
1271912715 );
1272012716 sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
......@@ -12735,7 +12731,6 @@ fn analyzeSwitchTagCapture(
1273512731 operand_val: Air.Inst.Ref,
1273612732 operand_ty: Type,
1273712733 capture_src: LazySrcLoc,
12738 inline_case_capture: Air.Inst.Ref,
1273912734 kind: SwitchProngKind,
1274012735) CompileError!Air.Inst.Ref {
1274112736 const pt = sema.pt;
......@@ -12751,12 +12746,11 @@ fn analyzeSwitchTagCapture(
1275112746 operand_ty.fmt(pt),
1275212747 });
1275312748 }
12754 if (inline_case_capture != .none) {
12755 return inline_case_capture; // this already is the tag, it's what we're switching on!
12756 }
1275712749 switch (kind) {
12758 .has_ranges, .special => {},
12750 .has_ranges => unreachable,
12751 .inline_ref => |ref| return ref,
1275912752 .item_refs => |refs| if (refs.len == 1) return refs[0],
12753 .special => {},
1276012754 }
1276112755 const tag_ty = operand_ty.unionTagType(zcu).?;
1276212756 return sema.unionToTag(case_block, tag_ty, operand_val, tag_capture_src);
......@@ -12775,8 +12769,6 @@ fn analyzeSwitchPayloadCapture(
1277512769 capture_src: LazySrcLoc,
1277612770 capture_by_ref: bool,
1277712771 kind: SwitchProngKind,
12778 /// If this is not `.none`, this is an inline capture.
12779 inline_case_capture: Air.Inst.Ref,
1278012772 else_err_ty: ?Type,
1278112773) CompileError!Air.Inst.Ref {
1278212774 const pt = sema.pt;
......@@ -12785,8 +12777,8 @@ fn analyzeSwitchPayloadCapture(
1278512777
1278612778 const switch_node_offset = operand_src.offset.node_offset_switch_operand;
1278712779
12788 if (inline_case_capture != .none) {
12789 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, inline_case_capture, undefined) catch unreachable;
12780 if (kind == .inline_ref) {
12781 const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, kind.inline_ref, undefined) catch unreachable;
1279012782 if (operand_ty.zigTypeTag(zcu) == .@"union") {
1279112783 const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
1279212784 const union_obj = zcu.typeToUnion(operand_ty).?;
......@@ -12812,7 +12804,7 @@ fn analyzeSwitchPayloadCapture(
1281212804 } else if (capture_by_ref) {
1281312805 return sema.uavRef(item_val.toIntern());
1281412806 } else {
12815 return inline_case_capture;
12807 return kind.inline_ref;
1281612808 }
1281712809 }
1281812810
......@@ -13155,7 +13147,7 @@ fn analyzeSwitchPayloadCapture(
1315513147 return operand_ptr;
1315613148 }
1315713149 switch (kind) {
13158 .special => unreachable,
13150 .inline_ref, .special => unreachable,
1315913151 .item_refs => |case_vals| {
1316013152 // If there's only a single item, the capture is comptime-known!
1316113153 if (case_vals.len == 1) return case_vals[0];
test/behavior/switch.zig+7-1
......@@ -1308,7 +1308,7 @@ test "single-item prong in switch on enum has comptime-known capture" {
13081308 try comptime E.doTheTest(.a);
13091309}
13101310
1311test "single-range switch prong capture" {
1311test "single range switch prong capture" {
13121312 const S = struct {
13131313 fn doTheTest(x: u8) !void {
13141314 switch (x) {
......@@ -1317,6 +1317,12 @@ test "single-range switch prong capture" {
13171317 },
13181318 else => return error.TestFailed,
13191319 }
1320 switch (x) {
1321 1...5, 6 => |val| {
1322 try expect(val == 2);
1323 },
1324 else => return error.TestFailed,
1325 }
13201326 }
13211327 };
13221328 try S.doTheTest(2);