commit d6386772daff1cd3932cc1f177be38b6c90c2e3b (tree)
parent 1ea73060bbed6a3153e123c224f3eee67f0373d5
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sun, 31 May 2026 09:40:52 +0200
Merge pull request 'Sema: simplify switch capture logic' (#35207) from justusk/zig:switch-improve-captures into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35207
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
Diffstat:
4 files changed, 665 insertions(+), 509 deletions(-)
diff --git a/src/Sema.zig b/src/Sema.zig
@@ -2415,6 +2415,32 @@ fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: T
});
}
+fn failWithInvalidSwitchTagCapture(sema: *Sema, block: *Block, tag_capture_src: LazySrcLoc, operand_ty: Type) CompileError {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+
+ if (operand_ty.zigTypeTag(zcu) == .@"union") {
+ assert(operand_ty.containerLayout(zcu) == .@"packed");
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of packed union", .{});
+ errdefer msg.destroy(sema.gpa);
+ try sema.addDeclaredHereNote(msg, operand_ty);
+ if (operand_ty.srcLocOrNull(zcu)) |ty_src| {
+ try sema.errNote(ty_src, msg, "consider using a tagged union", .{});
+ }
+ break :msg msg;
+ });
+ }
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
+ operand_ty.fmt(pt),
+ });
+ errdefer msg.destroy(sema.gpa);
+ try sema.addDeclaredHereNote(msg, operand_ty);
+ break :msg msg;
+ });
+}
+
fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
const pt = sema.pt;
const msg = msg: {
@@ -10022,7 +10048,7 @@ fn analyzeSwitchBlock(
const case_vals = validated_switch.case_vals;
- const index, const body, const capture, const has_tag_capture, const is_inline, const is_special = find_prong: {
+ const case_idx, const body, const capture, const has_tag_capture = find_prong: {
var case_val_idx: usize = 0;
var case_it = zir_switch.iterateCases();
var extra_index = zir_switch.end;
@@ -10045,12 +10071,12 @@ fn analyzeSwitchBlock(
}
continue;
}
- break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture, prong_info.is_inline, false };
+ break :find_prong .{ case.index, prong_body, prong_info.capture, prong_info.has_tag_capture };
}
if (has_else) {
// This *has* to be checked after iterating all regular cases because
// we allow simple noreturn else prongs when switching on error sets!
- break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture, else_case.is_inline, true };
+ break :find_prong .{ else_case.index, else_case.body, else_case.capture, else_case.has_tag_capture };
}
unreachable; // malformed validated switch
};
@@ -10061,58 +10087,33 @@ fn analyzeSwitchBlock(
if (!(err_set and
try sema.maybeErrorUnwrap(&case_block, body, cond_ref, operand_src, true)))
{
- // Set up captures manually to avoid special cases in the main logic.
- const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
+ const payload_inst = if (capture != .none) inst: {
const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
const payload_ref: Air.Inst.Ref = payload_ref: {
- const item_val: Value = item_val: {
+ const captured_opv: Value = captured_opv: {
if (!tagged_union_originally) {
- break :item_val item_opv;
+ break :captured_opv item_opv;
}
if (maybe_operand_opv) |operand_opv| {
- break :item_val .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
+ break :captured_opv .fromInterned(zcu.intern_pool.indexToKey(operand_opv.toIntern()).un.val);
}
assert(zir_switch.any_maybe_runtime_capture); // there's a payload capture
- const operand_val, const operand_ref = switch (operand) {
- .simple => unreachable,
- .loop => |l| load_operand: {
- const loaded = try sema.analyzeLoad(block, src, l.operand_alloc, src);
- if (l.operand_is_ref) {
- const by_val = try sema.analyzeLoad(block, src, loaded, src);
- break :load_operand .{ by_val, loaded };
- } else {
- break :load_operand .{ loaded, .none };
- }
- },
- };
- const prong_kind: SwitchProngKind = kind: {
- if (is_inline) break :kind .{ .inline_ref = .fromValue(item_opv) };
- if (is_special) break :kind .special;
- break :kind .{ .item_refs = &.{.fromValue(item_opv)} };
- };
- break :payload_ref try sema.analyzeSwitchPayloadCapture(
+ const loaded_operand = try sema.analyzeSwitchOperandLoad(&case_block, operand, operand_src, capture == .by_ref);
+ break :payload_ref try sema.resolveSwitchPayloadCaptureTaggedUnion(
&case_block,
- operand,
- operand_val,
- operand_ref,
- operand_ty,
+ loaded_operand,
operand_src,
- block.src(.{ .switch_capture = .{
- .switch_node_offset = src_node_offset,
- .case_idx = index,
- } }),
+ operand_ty,
+ item_opv,
capture == .by_ref,
- prong_kind,
- validated_switch.else_err_ty,
);
};
break :payload_ref switch (capture) {
- .by_val => .fromValue(item_val),
- .by_ref => try sema.uavRef(item_val),
+ .by_val => .fromValue(captured_opv),
+ .by_ref => try sema.uavRef(captured_opv),
.none => unreachable,
};
};
- assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
break :inst payload_inst;
} else undefined;
@@ -10120,6 +10121,13 @@ fn analyzeSwitchBlock(
const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
+ if (!tagged_union_originally) {
+ const tag_capture_src = block.src(.{ .switch_tag_capture = .{
+ .switch_node_offset = src_node_offset,
+ .case_idx = case_idx,
+ } });
+ return sema.failWithInvalidSwitchTagCapture(block, tag_capture_src, operand_ty);
+ }
sema.inst_map.putAssumeCapacity(tag_inst, .fromValue(item_opv));
break :inst tag_inst;
} else undefined;
@@ -10405,7 +10413,7 @@ fn finishSwitchBr(
} }),
prong_info.capture,
prong_info.has_tag_capture,
- .{ .inline_ref = item_ref },
+ .{ .@"inline" = item_ref },
validated_switch.else_err_ty,
switch_inst,
zir_switch,
@@ -10495,7 +10503,7 @@ fn finishSwitchBr(
} }),
prong_info.capture,
prong_info.has_tag_capture,
- .{ .inline_ref = item_ref },
+ .{ .@"inline" = item_ref },
validated_switch.else_err_ty,
switch_inst,
zir_switch,
@@ -10635,7 +10643,7 @@ fn finishSwitchBr(
} }),
else_case.capture,
else_case.has_tag_capture,
- .{ .inline_ref = item_ref },
+ .{ .@"inline" = item_ref },
validated_switch.else_err_ty,
switch_inst,
zir_switch,
@@ -11075,8 +11083,7 @@ fn validateSwitchBlock(
const has_else = zir_switch.else_case != null;
const has_under = zir_switch.has_under;
- var case_vals: std.ArrayList(Air.Inst.Ref) = .empty;
- try case_vals.ensureUnusedCapacity(arena, zir_switch.item_infos.len);
+ var case_vals: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, zir_switch.item_infos.len);
// Duplicate checking variables later also used for `inline else`.
var seen_enum_fields: []?LazySrcLoc = &.{};
@@ -11482,10 +11489,10 @@ fn resolveSwitchBlock(
// This prong should be unreachable!
return .unreachable_value;
}
- const prong_kind: SwitchProngKind = kind: {
- if (prong_info.is_inline) break :kind .{ .inline_ref = cond_ref };
- if (range_refs.len > 0) break :kind .has_ranges;
- break :kind .{ .item_refs = item_refs };
+ const prong_items: SwitchProngItems = prong_items: {
+ if (prong_info.is_inline) break :prong_items .{ .@"inline" = cond_ref };
+ if (range_refs.len > 0) break :prong_items .has_ranges;
+ break :prong_items .{ .item_refs = item_refs };
};
return sema.resolveSwitchProng(
block,
@@ -11499,7 +11506,7 @@ fn resolveSwitchBlock(
} }),
prong_info.capture,
prong_info.has_tag_capture,
- prong_kind,
+ prong_items,
validated_switch.else_err_ty,
merges,
switch_inst,
@@ -11513,8 +11520,8 @@ fn resolveSwitchBlock(
if ((try sema.compareAll(cond_val, .gte, first_val, item_ty)) and
(try sema.compareAll(cond_val, .lte, last_val, item_ty)))
{
- const prong_kind: SwitchProngKind = if (prong_info.is_inline)
- .{ .inline_ref = cond_ref }
+ const prong_items: SwitchProngItems = if (prong_info.is_inline)
+ .{ .@"inline" = cond_ref }
else
.has_ranges;
return sema.resolveSwitchProng(
@@ -11529,7 +11536,7 @@ fn resolveSwitchBlock(
} }),
prong_info.capture,
prong_info.has_tag_capture,
- prong_kind,
+ prong_items,
validated_switch.else_err_ty,
merges,
switch_inst,
@@ -11548,8 +11555,8 @@ fn resolveSwitchBlock(
if (else_is_named_only and item_ty.enumTagFieldIndex(cond_val, zcu) != null) {
assert(item_ty.isNonexhaustiveEnum(zcu));
- const prong_kind: SwitchProngKind = if (else_case.is_inline)
- .{ .inline_ref = cond_ref }
+ const prong_items: SwitchProngItems = if (else_case.is_inline)
+ .{ .@"inline" = cond_ref }
else
.special;
return sema.resolveSwitchProng(
@@ -11564,7 +11571,7 @@ fn resolveSwitchBlock(
} }),
else_case.capture,
else_case.has_tag_capture,
- prong_kind,
+ prong_items,
validated_switch.else_err_ty,
merges,
switch_inst,
@@ -11588,8 +11595,8 @@ fn resolveSwitchBlock(
return .unreachable_value;
}
}
- const prong_kind: SwitchProngKind = if (is_inline)
- .{ .inline_ref = cond_ref }
+ const prong_items: SwitchProngItems = if (is_inline)
+ .{ .@"inline" = cond_ref }
else
.special;
return sema.resolveSwitchProng(
@@ -11604,7 +11611,7 @@ fn resolveSwitchBlock(
} }),
capture,
has_tag_capture,
- prong_kind,
+ prong_items,
validated_switch.else_err_ty,
merges,
switch_inst,
@@ -11640,14 +11647,59 @@ const SwitchOperand = union(enum) {
},
};
-const SwitchProngKind = union(enum) {
- /// Prefer populating this field over the others, if possible.
- inline_ref: Air.Inst.Ref,
+fn analyzeSwitchOperandLoad(
+ sema: *Sema,
+ block: *Block,
+ operand: SwitchOperand,
+ operand_src: LazySrcLoc,
+ by_ref: bool,
+) CompileError!Air.Inst.Ref {
+ switch (operand) {
+ .simple => |s| {
+ if (by_ref) {
+ assert(s.by_ref != .none);
+ return s.by_ref;
+ } else {
+ return s.by_val;
+ }
+ },
+ .loop => |l| {
+ const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
+ assert(loaded != .none); // there are no captures, so no need to load the switch operand
+ if (by_ref) {
+ assert(l.operand_is_ref);
+ return loaded;
+ }
+ return if (l.operand_is_ref)
+ try sema.analyzeLoad(block, operand_src, loaded, operand_src)
+ else
+ loaded;
+ },
+ }
+}
+
+const SwitchProngItems = union(enum) {
+ @"inline": Air.Inst.Ref,
item_refs: []const Air.Inst.Ref,
has_ranges,
special,
};
+/// A switch capture is comptime-known if it is `inline` and/or it is a by-value
+/// capture of a prong with a single item.
+fn resolveSwitchCaptureFromProngItems(
+ sema: *Sema,
+ prong_items: SwitchProngItems,
+ by_ref: bool,
+) ?Value {
+ const ref: Air.Inst.Ref = switch (prong_items) {
+ .@"inline" => |ref| ref,
+ .item_refs => |refs| if (refs.len == 1 and !by_ref) refs[0] else return null,
+ .has_ranges, .special => return null,
+ };
+ return sema.resolveValue(ref).?;
+}
+
/// Resolve a switch prong which is determined at comptime to have no peers.
/// Sets up captures as needed. Uses `analyzeBodyRuntimeBreak`.
fn resolveSwitchProng(
@@ -11661,7 +11713,7 @@ fn resolveSwitchProng(
capture_src: LazySrcLoc,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
has_tag_capture: bool,
- kind: SwitchProngKind,
+ prong_items: SwitchProngItems,
else_err_ty: ?Type,
merges: *Block.Merges,
switch_inst: Zir.Inst.Index,
@@ -11676,36 +11728,28 @@ fn resolveSwitchProng(
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
- const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
+ const analyzed_captures = try sema.analyzeSwitchCaptures(
+ child_block,
+ operand,
+ operand_src,
+ sema.typeOf(operand.simple.by_val),
+ capture_src,
+ capture,
+ has_tag_capture,
+ prong_items,
+ else_err_ty,
+ );
+
+ const payload_inst = if (capture != .none) inst: {
const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
- const payload_ref = try sema.analyzeSwitchPayloadCapture(
- child_block,
- operand,
- operand.simple.by_val,
- operand.simple.by_ref,
- sema.typeOf(operand.simple.by_val),
- operand_src,
- capture_src,
- capture == .by_ref,
- kind,
- else_err_ty,
- );
- assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
- sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
+ sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
break :inst payload_inst;
} else undefined;
defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
- const tag_ref = try sema.analyzeSwitchTagCapture(
- child_block,
- operand.simple.by_val,
- sema.typeOf(operand.simple.by_val),
- capture_src,
- kind,
- );
- sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
+ sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
break :inst tag_inst;
} else undefined;
defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
@@ -11751,7 +11795,7 @@ fn analyzeSwitchProng(
capture_src: LazySrcLoc,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
has_tag_capture: bool,
- kind: SwitchProngKind,
+ prong_items: SwitchProngItems,
else_err_ty: ?Type,
switch_inst: Zir.Inst.Index,
zir_switch: *const Zir.UnwrappedSwitchBlock,
@@ -11772,77 +11816,28 @@ fn analyzeSwitchProng(
}
}
- const need_load: bool = need_load: {
- if (capture == .none and !has_tag_capture) {
- // No need to load the operand for this prong!
- break :need_load false;
- }
- if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union" and
- operand_ty.containerLayout(zcu) != .@"packed")
- {
- // Non-OPV tagged union payload captures are always runtime-known.
- break :need_load true;
- }
- if (kind == .inline_ref) {
- // `inline_ref` *is* the (comptime-known) capture.
- break :need_load false;
- }
- assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now
- if (capture != .by_ref and
- kind == .item_refs and kind.item_refs.len == 1)
- {
- // Capture is comptime-known because it's the only prong item
- break :need_load false;
- }
- break :need_load true;
- };
-
- const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: {
- if (!need_load) break :load_operand .{ .none, .none };
- switch (operand) {
- .simple => |s| break :load_operand .{ s.by_val, s.by_ref },
- .loop => |l| {
- const loaded = try sema.analyzeLoad(case_block, operand_src, l.operand_alloc, operand_src);
- if (l.operand_is_ref) {
- const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src);
- break :load_operand .{ by_val, loaded };
- } else {
- break :load_operand .{ loaded, .none };
- }
- },
- }
- };
+ const analyzed_captures = try sema.analyzeSwitchCaptures(
+ case_block,
+ operand,
+ operand_src,
+ operand_ty,
+ capture_src,
+ capture,
+ has_tag_capture,
+ prong_items,
+ else_err_ty,
+ );
- const payload_inst: Zir.Inst.Index = if (capture != .none) inst: {
+ const payload_inst = if (capture != .none) inst: {
const payload_inst = zir_switch.payload_capture_placeholder.unwrap() orelse switch_inst;
- const payload_ref = try sema.analyzeSwitchPayloadCapture(
- case_block,
- operand,
- operand_val,
- operand_ptr,
- operand_ty,
- operand_src,
- capture_src,
- capture == .by_ref,
- kind,
- else_err_ty,
- );
- assert(!sema.typeOf(payload_ref).isNoReturn(sema.pt.zcu));
- sema.inst_map.putAssumeCapacity(payload_inst, payload_ref);
+ sema.inst_map.putAssumeCapacity(payload_inst, analyzed_captures.payload_ref);
break :inst payload_inst;
} else undefined;
defer if (capture != .none) assert(sema.inst_map.remove(payload_inst));
const tag_inst: Zir.Inst.Index = if (has_tag_capture) inst: {
const tag_inst = zir_switch.tag_capture_placeholder.unwrap() orelse switch_inst;
- const tag_ref = try sema.analyzeSwitchTagCapture(
- case_block,
- operand_val,
- operand_ty,
- capture_src,
- kind,
- );
- sema.inst_map.putAssumeCapacity(tag_inst, tag_ref);
+ sema.inst_map.putAssumeCapacity(tag_inst, analyzed_captures.tag_ref);
break :inst tag_inst;
} else undefined;
defer if (has_tag_capture) assert(sema.inst_map.remove(tag_inst));
@@ -11853,157 +11848,295 @@ fn analyzeSwitchProng(
return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
}
-fn analyzeSwitchTagCapture(
+fn analyzeSwitchCaptures(
sema: *Sema,
case_block: *Block,
- /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`.
- operand_val: Air.Inst.Ref,
+ operand: SwitchOperand,
+ operand_src: LazySrcLoc,
operand_ty: Type,
capture_src: LazySrcLoc,
- kind: SwitchProngKind,
-) CompileError!Air.Inst.Ref {
+ capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
+ has_tag_capture: bool,
+ prong_items: SwitchProngItems,
+ else_err_ty: ?Type,
+) CompileError!struct {
+ payload_ref: Air.Inst.Ref,
+ tag_ref: Air.Inst.Ref,
+} {
const pt = sema.pt;
const zcu = pt.zcu;
- const tag_capture_src: LazySrcLoc = .{
- .base_node_inst = capture_src.base_node_inst,
- .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
- };
+ if (operand_ty.zigTypeTag(zcu) == .@"union" and
+ operand_ty.containerLayout(zcu) != .@"packed")
+ {
+ if (capture == .none) {
+ const tag_ref: Air.Inst.Ref = tag_ref: {
+ if (!has_tag_capture) break :tag_ref .none;
+ if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |tag_val| {
+ break :tag_ref .fromValue(tag_val);
+ }
+ const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
+ break :tag_ref try sema.unionToTag(case_block, loaded_operand);
+ };
+ return .{ .payload_ref = .none, .tag_ref = tag_ref };
+ }
- if (operand_ty.zigTypeTag(zcu) != .@"union") {
- return sema.fail(case_block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
- operand_ty.fmt(pt),
- });
- }
- if (operand_ty.containerLayout(zcu) == .@"packed") {
- return sema.fail(case_block, tag_capture_src, "cannot capture tag of packed union", .{});
+ // We always have to load the operand for tagged union payload captures
+ // since we can't derive the payload value from the tag (except for OPV
+ // types, for which the load is always basically a noop anyway).
+
+ const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
+
+ if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |tag_val| {
+ const payload_ref = try sema.resolveSwitchPayloadCaptureTaggedUnion(
+ case_block,
+ loaded_operand,
+ operand_src,
+ operand_ty,
+ tag_val,
+ capture == .by_ref,
+ );
+ const tag_ref: Air.Inst.Ref = if (has_tag_capture) .fromValue(tag_val) else .none;
+ return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
+ }
+
+ const payload_ref = try sema.analyzeSwitchPayloadCaptureTaggedUnion(
+ case_block,
+ operand,
+ loaded_operand,
+ operand_src,
+ operand_ty,
+ capture == .by_ref,
+ capture_src,
+ prong_items,
+ );
+
+ const tag_ref: Air.Inst.Ref = tag_ref: {
+ if (!has_tag_capture) break :tag_ref .none;
+ const operand_val = switch (capture) {
+ .none => unreachable, // handled above
+ .by_val => loaded_operand,
+ .by_ref => try sema.analyzeLoad(case_block, operand_src, loaded_operand, operand_src),
+ };
+ break :tag_ref try sema.unionToTag(case_block, operand_val);
+ };
+
+ assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
+ return .{ .payload_ref = payload_ref, .tag_ref = tag_ref };
}
- switch (kind) {
- .has_ranges => unreachable,
- .inline_ref => |ref| return ref,
- .item_refs => |refs| if (refs.len == 1) return refs[0],
- .special => {},
+
+ const payload_ref: Air.Inst.Ref = payload_ref: {
+ if (capture == .none) break :payload_ref .none;
+
+ if (operand_ty.zigTypeTag(zcu) == .error_set) {
+ // Error captures need to have their type narrowed!
+
+ if (capture == .by_ref) {
+ return sema.fail(
+ case_block,
+ capture_src,
+ "error set cannot be captured by reference",
+ .{},
+ );
+ }
+ assert(capture == .by_val);
+
+ if (sema.resolveSwitchCaptureFromProngItems(prong_items, false)) |err_val| {
+ const err_name = err_val.getErrorName(zcu).unwrap().?;
+ break :payload_ref .fromIntern((try pt.intern(.{ .err = .{
+ .ty = (try pt.singleErrorSetType(err_name)).toIntern(),
+ .name = err_name,
+ } })));
+ }
+
+ const loaded_operand = try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, false);
+
+ switch (prong_items) {
+ .@"inline" => unreachable, // handled above
+ .has_ranges => unreachable, // not possible for error set
+ .special => {
+ if (else_err_ty) |err_ty| {
+ break :payload_ref try sema.bitCast(case_block, err_ty, loaded_operand, operand_src, null);
+ } else {
+ try sema.analyzeUnreachable(case_block, operand_src, false);
+ break :payload_ref .unreachable_value;
+ }
+ },
+ .item_refs => |item_refs| {
+ var names: InferredErrorSet.NameMap = .{};
+ try names.ensureUnusedCapacity(sema.arena, item_refs.len);
+ for (item_refs) |item_ref| {
+ const item_val = sema.resolveValue(item_ref).?;
+ names.putAssumeCapacityNoClobber(item_val.getErrorName(zcu).unwrap().?, {});
+ }
+ const narrowed_ty = try pt.errorSetFromUnsortedNames(names.keys());
+ break :payload_ref try sema.bitCast(case_block, narrowed_ty, loaded_operand, operand_src, null);
+ },
+ }
+ }
+
+ // We try to make the capture comptime-known based on `prong_items` first:
+
+ if (sema.resolveSwitchCaptureFromProngItems(prong_items, capture == .by_ref)) |item_val| {
+ break :payload_ref switch (capture) {
+ .none => unreachable, // handled above
+ .by_val => .fromValue(item_val),
+ .by_ref => try sema.uavRef(item_val),
+ };
+ }
+
+ // Otherwise the capture value is just the passed-through value of the
+ // switch condition (which we might have to load first).
+
+ break :payload_ref try sema.analyzeSwitchOperandLoad(case_block, operand, operand_src, capture == .by_ref);
+ };
+
+ if (has_tag_capture) {
+ const tag_capture_src: LazySrcLoc = .{
+ .base_node_inst = capture_src.base_node_inst,
+ .offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
+ };
+ return sema.failWithInvalidSwitchTagCapture(case_block, tag_capture_src, operand_ty);
}
- return sema.unionToTag(case_block, operand_val);
+
+ return .{ .payload_ref = payload_ref, .tag_ref = .none };
}
-fn analyzeSwitchPayloadCapture(
+fn resolveSwitchPayloadCaptureTaggedUnion(
sema: *Sema,
case_block: *Block,
- operand: SwitchOperand,
- /// Always has to be not-`none` if this is a tagged union payload capture.
- /// For non-tagged-union captures, this may be `none` if this is an inline
- /// capture or if `kind.item_refs.len == 1` and capture is by val.
- operand_val: Air.Inst.Ref,
- /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`.
- operand_ptr: Air.Inst.Ref,
+ loaded_operand: Air.Inst.Ref,
+ operand_src: LazySrcLoc,
operand_ty: Type,
+ tag_val: Value,
+ capture_by_ref: bool,
+) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+
+ const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(tag_val, zcu).?);
+ const union_obj = zcu.typeToUnion(operand_ty).?;
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
+ const payload_ref: Air.Inst.Ref = payload_ref: {
+ if (capture_by_ref) {
+ const operand_ptr_info = sema.typeOf(loaded_operand).ptrInfo(zcu);
+ const ptr_field_ty = try pt.ptrType(.{
+ .child = field_ty.toIntern(),
+ .flags = .{
+ .is_const = operand_ptr_info.flags.is_const,
+ .is_volatile = operand_ptr_info.flags.is_volatile,
+ .address_space = operand_ptr_info.flags.address_space,
+ },
+ });
+ break :payload_ref try case_block.addStructFieldPtr(loaded_operand, field_index, ptr_field_ty);
+ }
+ if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |union_val| {
+ const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
+ break :payload_ref .fromIntern(tag_and_val.val);
+ }
+ if (try field_ty.onePossibleValue(pt)) |opv| break :payload_ref .fromValue(opv);
+ break :payload_ref try case_block.addStructFieldVal(loaded_operand, field_index, field_ty);
+ };
+ assert(!sema.typeOf(payload_ref).isNoReturn(zcu));
+ return payload_ref;
+}
+
+fn analyzeSwitchPayloadCaptureTaggedUnion(
+ sema: *Sema,
+ case_block: *Block,
+ operand: SwitchOperand,
+ loaded_operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
- capture_src: LazySrcLoc,
+ operand_ty: Type,
capture_by_ref: bool,
- kind: SwitchProngKind,
- else_err_ty: ?Type,
+ capture_src: LazySrcLoc,
+ prong_items: SwitchProngItems,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
+ const gpa = sema.gpa;
+
+ const item_refs: []const Air.Inst.Ref = switch (prong_items) {
+ .@"inline" => unreachable, // handled above
+ .has_ranges => unreachable, // not possible for tagged union
+ .special => return loaded_operand,
+ .item_refs => |item_refs| item_refs,
+ };
const switch_node_offset = operand_src.offset.node_offset_switch_operand;
- const tagged_union_originally = operand_ty.zigTypeTag(zcu) == .@"union" and
- operand_ty.containerLayout(zcu) != .@"packed";
- const err_set = operand_ty.zigTypeTag(zcu) == .error_set;
+ const union_obj = zcu.typeToUnion(operand_ty).?;
- if (err_set and capture_by_ref) {
- return sema.fail(
- case_block,
- capture_src,
- "error set cannot be captured by reference",
- .{},
- );
- }
+ const first_item_val = sema.resolveValue(item_refs[0]).?;
+ const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
+ const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
- if (kind == .inline_ref) {
- const item_val = sema.resolveValue(kind.inline_ref).?;
- if (tagged_union_originally) {
- const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
- const union_obj = zcu.typeToUnion(operand_ty).?;
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
- if (capture_by_ref) {
- const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu);
- const ptr_field_ty = try pt.ptrType(.{
- .child = field_ty.toIntern(),
- .flags = .{
- .is_const = operand_ptr_info.flags.is_const,
- .is_volatile = operand_ptr_info.flags.is_volatile,
- .address_space = operand_ptr_info.flags.address_space,
- },
- });
- return case_block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
- } else {
- if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |union_val| {
- const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
- return .fromIntern(tag_and_val.val);
- }
- if (try field_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
- return case_block.addStructFieldVal(operand_val, field_index, field_ty);
- }
- } else if (capture_by_ref) {
- return sema.uavRef(item_val);
- } else {
- return kind.inline_ref;
- }
+ const field_indices = try sema.arena.alloc(u32, item_refs.len);
+ for (item_refs, field_indices) |item_ref, *field_idx| {
+ const item_val = sema.resolveValue(item_ref).?;
+ field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
}
- if (kind == .special) {
- if (err_set) {
- if (else_err_ty) |err_ty| {
- return sema.bitCast(case_block, err_ty, operand_val, operand_src, null);
- } else {
- try sema.analyzeUnreachable(case_block, operand_src, false);
- return .unreachable_value;
- }
- }
- if (capture_by_ref) {
- return operand_ptr;
- }
- return operand_val;
- }
+ // Fast path: if all the operands are the same type already, we don't need to hit
+ // PTR! This will also allow us to emit simpler code.
+ const same_types = for (field_indices[1..]) |field_idx| {
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
+ if (!field_ty.eql(first_field_ty, zcu)) break false;
+ } else true;
- if (tagged_union_originally) {
- const case_vals = kind.item_refs;
-
- const union_obj = zcu.typeToUnion(operand_ty).?;
- const first_item_val = sema.resolveValue(case_vals[0]).?;
-
- const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
- const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
+ const capture_ty: Type = capture_ty: {
+ if (same_types) break :capture_ty first_field_ty;
+ // We need values to run PTR on, so make a bunch of undef constants.
+ const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
+ for (dummy_captures, field_indices) |*dummy, field_idx| {
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
+ dummy.* = try pt.undefRef(field_ty);
+ }
- const field_indices = try sema.arena.alloc(u32, case_vals.len);
- for (case_vals, field_indices) |item, *field_idx| {
- const item_val = sema.resolveValue(item).?;
- field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
+ const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
+ for (item_srcs, 0..) |*item_src, item_i| {
+ item_src.* = .{
+ .base_node_inst = capture_src.base_node_inst,
+ .offset = .{ .switch_case_item = .{
+ .switch_node_offset = switch_node_offset,
+ .case_idx = capture_src.offset.switch_capture.case_idx,
+ .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
+ } },
+ };
}
- // Fast path: if all the operands are the same type already, we don't need to hit
- // PTR! This will also allow us to emit simpler code.
- const same_types = for (field_indices[1..]) |field_idx| {
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
- if (!field_ty.eql(first_field_ty, zcu)) break false;
- } else true;
+ break :capture_ty sema.resolvePeerTypes(
+ case_block,
+ capture_src,
+ dummy_captures,
+ .{ .override = item_srcs },
+ ) catch |err| switch (err) {
+ error.AnalysisFail => {
+ const msg = sema.err orelse return error.AnalysisFail;
+ try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
+ return error.AnalysisFail;
+ },
+ else => |e| return e,
+ };
+ };
- const capture_ty: Type = capture_ty: {
- if (same_types) break :capture_ty first_field_ty;
+ // By-reference captures have some further restrictions which make them easier to emit
+ if (capture_by_ref) {
+ const operand_ptr_ty = sema.typeOf(loaded_operand);
+ const capture_ptr_ty = resolve: {
+ // By-ref captures of hetereogeneous types are only allowed if all field
+ // pointer types are peer resolvable to each other.
// We need values to run PTR on, so make a bunch of undef constants.
- const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
- for (dummy_captures, field_indices) |*dummy, field_idx| {
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
- dummy.* = try pt.undefRef(field_ty);
- }
-
- const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
- for (case_srcs, 0..) |*case_src, item_i| {
- case_src.* = .{
+ const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, item_refs.len);
+ for (field_indices, dummy_captures) |field_index, *dummy| {
+ const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
+ dummy.* = try pt.undefRef(field_ptr_ty);
+ }
+ const item_srcs = try sema.arena.alloc(?LazySrcLoc, item_refs.len);
+ for (item_srcs, 0..) |*item_src, item_i| {
+ item_src.* = .{
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
@@ -12013,14 +12146,15 @@ fn analyzeSwitchPayloadCapture(
};
}
- break :capture_ty sema.resolvePeerTypes(
+ break :resolve sema.resolvePeerTypes(
case_block,
capture_src,
dummy_captures,
- .{ .override = case_srcs },
+ .{ .override = item_srcs },
) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return error.AnalysisFail;
+ try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
return error.AnalysisFail;
},
@@ -12028,262 +12162,188 @@ fn analyzeSwitchPayloadCapture(
};
};
- // By-reference captures have some further restrictions which make them easier to emit
- if (capture_by_ref) {
- const operand_ptr_ty = sema.typeOf(operand_ptr);
- const capture_ptr_ty = resolve: {
- // By-ref captures of hetereogeneous types are only allowed if all field
- // pointer types are peer resolvable to each other.
- // We need values to run PTR on, so make a bunch of undef constants.
- const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
- for (field_indices, dummy_captures) |field_index, *dummy| {
- const field_ptr_ty = try operand_ptr_ty.fieldPtrType(field_index, pt);
- dummy.* = try pt.undefRef(field_ptr_ty);
- }
- const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
- for (case_srcs, 0..) |*case_src, item_i| {
- case_src.* = .{
- .base_node_inst = capture_src.base_node_inst,
- .offset = .{ .switch_case_item = .{
- .switch_node_offset = switch_node_offset,
- .case_idx = capture_src.offset.switch_capture.case_idx,
- .item_idx = .{ .kind = .single, .value = @intCast(item_i) },
- } },
- };
- }
-
- break :resolve sema.resolvePeerTypes(
- case_block,
- capture_src,
- dummy_captures,
- .{ .override = case_srcs },
- ) catch |err| switch (err) {
- error.AnalysisFail => {
- const msg = sema.err orelse return error.AnalysisFail;
- try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
- try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
- return error.AnalysisFail;
- },
- else => |e| return e,
- };
- };
-
- if (try sema.resolveDefinedValue(case_block, operand_src, operand_ptr)) |op_ptr_val| {
- if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
- const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
- return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
- }
-
- try sema.requireRuntimeBlock(case_block, operand_src, null);
- return case_block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
- }
-
- if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
-
- if (try sema.resolveDefinedValue(case_block, operand_src, operand_val)) |operand_val_val| {
- if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
- const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
- if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
- const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
- return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
+ if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |op_ptr_val| {
+ if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
+ const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
+ return .fromValue(try pt.getCoerced(field_ptr_val, capture_ptr_ty));
}
try sema.requireRuntimeBlock(case_block, operand_src, null);
+ return case_block.addStructFieldPtr(loaded_operand, first_field_index, capture_ptr_ty);
+ }
- if (same_types) {
- return case_block.addStructFieldVal(operand_val, first_field_index, capture_ty);
- }
+ if (try capture_ty.onePossibleValue(pt)) |opv| return .fromValue(opv);
- // We may have to emit a switch block which coerces the operand to the capture type.
- // If we can, try to avoid that using in-memory coercions.
- const first_non_imc = in_mem: {
- for (field_indices, 0..) |field_idx, i| {
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
- if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
- break :in_mem i;
- }
- }
- // All fields are in-memory coercible to the resolved type!
- // Just take the first field and bitcast the result.
- const uncoerced = try case_block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
- return case_block.addBitCast(capture_ty, uncoerced);
- };
+ if (try sema.resolveDefinedValue(case_block, operand_src, loaded_operand)) |operand_val| {
+ if (operand_val.isUndef(zcu)) return pt.undefRef(capture_ty);
+ const union_val = ip.indexToKey(operand_val.toIntern()).un;
+ if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
+ const uncoerced: Air.Inst.Ref = .fromIntern(union_val.val);
+ return sema.coerce(case_block, capture_ty, uncoerced, operand_src);
+ }
- // By-val capture with heterogeneous types which are not all in-memory coercible to
- // the resolved capture type. We finally have to fall back to the ugly method.
+ try sema.requireRuntimeBlock(case_block, operand_src, null);
- // However, let's first track which operands are in-memory coercible. There may well
- // be several, and we can squash all of these cases into the same switch prong using
- // a simple bitcast. We'll make this the 'else' prong.
+ if (same_types) {
+ return case_block.addStructFieldVal(loaded_operand, first_field_index, capture_ty);
+ }
- var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
- in_mem_coercible.unset(first_non_imc);
- {
- const next = first_non_imc + 1;
- for (field_indices[next..], next..) |field_idx, i| {
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
- if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
- in_mem_coercible.unset(i);
- }
+ // We may have to emit a switch block which coerces the operand to the capture type.
+ // If we can, try to avoid that using in-memory coercions.
+ const first_non_imc = in_mem: {
+ for (field_indices, 0..) |field_idx, i| {
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
+ if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
+ break :in_mem i;
}
}
+ // All fields are in-memory coercible to the resolved type!
+ // Just take the first field and bitcast the result.
+ const uncoerced = try case_block.addStructFieldVal(loaded_operand, first_field_index, first_field_ty);
+ return case_block.addBitCast(capture_ty, uncoerced);
+ };
- const capture_block_inst = try case_block.addInstAsIndex(.{
- .tag = .block,
- .data = .{
- .ty_pl = .{
- .ty = .fromType(capture_ty),
- .payload = undefined, // updated below
- },
- },
- });
-
- const prong_count = field_indices.len - in_mem_coercible.count();
+ // By-val capture with heterogeneous types which are not all in-memory coercible to
+ // the resolved capture type. We finally have to fall back to the ugly method.
- const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
- var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
- defer cases_extra.deinit();
+ // However, let's first track which operands are in-memory coercible. There may well
+ // be several, and we can squash all of these cases into the same switch prong using
+ // a simple bitcast. We'll make this the 'else' prong.
- {
- // All branch hints are `.none`, so just add zero elems.
- comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
- const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
- try cases_extra.appendNTimes(0, need_elems);
+ var in_mem_coercible: std.bit_set.Dynamic = try .initFull(sema.arena, field_indices.len);
+ in_mem_coercible.unset(first_non_imc);
+ {
+ const next = first_non_imc + 1;
+ for (field_indices[next..], next..) |field_idx, i| {
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
+ if (.ok != try sema.coerceInMemoryAllowed(case_block, capture_ty, field_ty, false, zcu.getTarget(), .unneeded, .unneeded, null)) {
+ in_mem_coercible.unset(i);
+ }
}
+ }
- {
- // Non-bitcast cases
- var it = in_mem_coercible.iterator(.{ .kind = .unset });
- while (it.next()) |idx| {
- var coerce_block = case_block.makeSubBlock();
- defer coerce_block.instructions.deinit(sema.gpa);
+ const capture_block_inst = try case_block.addInstAsIndex(.{
+ .tag = .block,
+ .data = .{
+ .ty_pl = .{
+ .ty = .fromType(capture_ty),
+ .payload = undefined, // updated below
+ },
+ },
+ });
- const case_src: LazySrcLoc = .{
- .base_node_inst = capture_src.base_node_inst,
- .offset = .{ .switch_case_item = .{
- .switch_node_offset = switch_node_offset,
- .case_idx = capture_src.offset.switch_capture.case_idx,
- .item_idx = .{ .kind = .single, .value = @intCast(idx) },
- } },
- };
+ const prong_count = field_indices.len - in_mem_coercible.count();
- const field_idx = field_indices[idx];
- const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
- const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
- const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
- _ = try coerce_block.addBr(capture_block_inst, coerced);
+ const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
+ var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_extra);
+ defer cases_extra.deinit(gpa);
- try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
- 1 + // `item`, no ranges
- coerce_block.instructions.items.len);
- cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
- .items_len = 1,
- .ranges_len = 0,
- .body_len = @intCast(coerce_block.instructions.items.len),
- }));
- cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
- cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
- }
- }
- const else_body_len = len: {
- // 'else' prong uses a bitcast
+ {
+ // All branch hints are `.none`, so just add zero elems.
+ comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0);
+ const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
+ try cases_extra.appendNTimes(gpa, 0, need_elems);
+ }
+
+ {
+ // Non-bitcast cases
+ var it = in_mem_coercible.iterator(.{ .kind = .unset });
+ while (it.next()) |idx| {
var coerce_block = case_block.makeSubBlock();
defer coerce_block.instructions.deinit(sema.gpa);
- const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
- const first_imc_field_idx = field_indices[first_imc_item_idx];
- const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
- const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
- const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
- _ = try coerce_block.addBr(capture_block_inst, coerced);
-
- try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
- break :len coerce_block.instructions.items.len;
- };
+ const case_src: LazySrcLoc = .{
+ .base_node_inst = capture_src.base_node_inst,
+ .offset = .{ .switch_case_item = .{
+ .switch_node_offset = switch_node_offset,
+ .case_idx = capture_src.offset.switch_capture.case_idx,
+ .item_idx = .{ .kind = .single, .value = @intCast(idx) },
+ } },
+ };
- try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
- cases_extra.items.len +
- @typeInfo(Air.Block).@"struct".field_names.len +
- 1);
+ const field_idx = field_indices[idx];
+ const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
+ const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, field_idx, field_ty);
+ const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
+ _ = try coerce_block.addBr(capture_block_inst, coerced);
- const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
- try sema.air_instructions.append(sema.gpa, .{
- .tag = .switch_br,
- .data = .{
- .pl_op = .{
- .operand = undefined, // set by switch below
- .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
- .cases_len = @intCast(prong_count),
- .else_body_len = @intCast(else_body_len),
- }),
- },
- },
- });
- sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
-
- // Set up block body
- switch (operand) {
- .simple => |s| {
- const air_datas = sema.air_instructions.items(.data);
- air_datas[switch_br_inst].pl_op.operand = s.cond;
- air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
- sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
- sema.air_extra.appendAssumeCapacity(switch_br_inst);
- },
- .loop => {
- // The block must first extract the tag from the loaded union.
- const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
- try sema.air_instructions.append(sema.gpa, .{
- .tag = .get_union_tag,
- .data = .{ .ty_op = .{
- .ty = .fromIntern(union_obj.enum_tag_type),
- .operand = operand_val,
- } },
- });
- const air_datas = sema.air_instructions.items(.data);
- air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
- air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
- sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
- sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
- sema.air_extra.appendAssumeCapacity(switch_br_inst);
- },
+ try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".field_names.len +
+ 1 + // `item`, no ranges
+ coerce_block.instructions.items.len);
+ cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
+ .items_len = 1,
+ .ranges_len = 0,
+ .body_len = @intCast(coerce_block.instructions.items.len),
+ }));
+ cases_extra.appendAssumeCapacity(@intFromEnum(item_refs[idx])); // item
+ cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
}
-
- return capture_block_inst.toRef();
}
+ const else_body_len = len: {
+ // 'else' prong uses a bitcast
+ var coerce_block = case_block.makeSubBlock();
+ defer coerce_block.instructions.deinit(sema.gpa);
- if (err_set) {
- const case_vals = kind.item_refs;
- if (case_vals.len == 1) {
- const item_val = sema.resolveValue(case_vals[0]).?;
- const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
- return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null);
- }
+ const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
+ const first_imc_field_idx = field_indices[first_imc_item_idx];
+ const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
+ const uncoerced = try coerce_block.addStructFieldVal(loaded_operand, first_imc_field_idx, first_imc_field_ty);
+ const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
+ _ = try coerce_block.addBr(capture_block_inst, coerced);
- var names: InferredErrorSet.NameMap = .{};
- try names.ensureUnusedCapacity(sema.arena, case_vals.len);
- for (case_vals) |err| {
- const err_val = sema.resolveValue(err).?;
- names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
- }
- const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
- return sema.bitCast(case_block, error_ty, operand_val, operand_src, null);
- }
+ try cases_extra.appendSlice(gpa, @ptrCast(coerce_block.instructions.items));
+ break :len coerce_block.instructions.items.len;
+ };
- // In this case the capture value is just the passed-through value of the
- // switch condition. It is comptime-known if there is only one item.
- if (capture_by_ref) {
- return operand_ptr;
- }
- switch (kind) {
- .inline_ref, .special => unreachable,
- .item_refs => |case_vals| {
- // If there's only a single item, the capture is comptime-known!
- if (case_vals.len == 1) return case_vals[0];
+ try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".field_names.len +
+ cases_extra.items.len +
+ @typeInfo(Air.Block).@"struct".field_names.len +
+ 1);
+
+ const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
+ try sema.air_instructions.append(gpa, .{
+ .tag = .switch_br,
+ .data = .{
+ .pl_op = .{
+ .operand = undefined, // set by switch below
+ .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
+ .cases_len = @intCast(prong_count),
+ .else_body_len = @intCast(else_body_len),
+ }),
+ },
+ },
+ });
+ sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
+
+ // Set up block body
+ switch (operand) {
+ .simple => |s| {
+ const air_datas = sema.air_instructions.items(.data);
+ air_datas[switch_br_inst].pl_op.operand = s.cond;
+ air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
+ sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
+ sema.air_extra.appendAssumeCapacity(switch_br_inst);
+ },
+ .loop => {
+ // The block must first extract the tag from the loaded union.
+ const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
+ try sema.air_instructions.append(sema.gpa, .{
+ .tag = .get_union_tag,
+ .data = .{ .ty_op = .{
+ .ty = .fromIntern(union_obj.enum_tag_type),
+ .operand = loaded_operand,
+ } },
+ });
+ const air_datas = sema.air_instructions.items(.data);
+ air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
+ air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload =
+ sema.addExtraAssumeCapacity(Air.Block{ .body_len = 2 });
+ sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
+ sema.air_extra.appendAssumeCapacity(switch_br_inst);
},
- .has_ranges => {},
}
- return operand_val;
+
+ return capture_block_inst.toRef();
}
const ResolvedSwitchItem = struct {
diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig
@@ -1486,3 +1486,36 @@ test "switch on large types" {
try S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
try comptime S.doTheTest(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_1234, 0xFFFF_1234);
}
+
+test "error captures narrow error sets" {
+ const S = struct {
+ fn doTheTest(err: error{ A, B, C, D }) !void {
+ switch (err) {
+ error.A, error.B => |e| comptime assert(@TypeOf(e) == error{ A, B }),
+ else => |e| comptime assert(@TypeOf(e) == error{ C, D }),
+ }
+ switch (err) {
+ inline error.A, error.B => |e| comptime {
+ if (e == error.A)
+ assert(@TypeOf(e) == error{A})
+ else if (e == error.B)
+ assert(@TypeOf(e) == error{B})
+ else
+ unreachable;
+ },
+ inline else => |e| comptime {
+ if (e == error.C)
+ assert(@TypeOf(e) == error{C})
+ else if (e == error.D)
+ assert(@TypeOf(e) == error{D})
+ else
+ unreachable;
+ },
+ }
+ }
+ };
+
+ try S.doTheTest(error.B);
+ try comptime S.doTheTest(error.B);
+ try comptime S.doTheTest(error.C);
+}
diff --git a/test/cases/compile_errors/switch_capture_packed_union_tag.zig b/test/cases/compile_errors/switch_capture_packed_union_tag.zig
@@ -1,15 +0,0 @@
-const P = packed union(u8) {
- a: u8,
- b: i8,
-};
-
-export fn foo(p: P) void {
- switch (p) {
- .{ .a = 123 } => |_, tag| _ = tag,
- else => {},
- }
-}
-
-// error
-//
-// :8:30: error: cannot capture tag of packed union
diff --git a/test/cases/compile_errors/switch_invalid_tag_capture.zig b/test/cases/compile_errors/switch_invalid_tag_capture.zig
@@ -0,0 +1,78 @@
+const P = packed union(u8) {
+ a: u8,
+ b: i8,
+};
+export fn entry1(p: P) void {
+ switch (p) {
+ .{ .a = 123 } => |_, tag| _ = tag,
+ else => {},
+ }
+}
+export fn entry2(p: P) void {
+ label: switch (p) {
+ .{ .a = 123 } => |_, tag| _ = tag,
+ else => continue :label .{ .a = 123 },
+ }
+}
+
+const E = enum(u8) { a, b };
+export fn entry3(e: E) void {
+ switch (e) {
+ .a => |_, tag| _ = tag,
+ else => {},
+ }
+}
+export fn entry4(e: E) void {
+ label: switch (e) {
+ .a => |_, tag| _ = tag,
+ else => continue :label .a,
+ }
+}
+
+const Error = error{ MyError, MyOtherError };
+export fn entry5(ok: bool) void {
+ switch (foo(ok)) {
+ error.MyError => |_, tag| _ = tag,
+ else => {},
+ }
+}
+export fn entry6(ok: bool) void {
+ label: switch (foo(ok)) {
+ error.MyError => |_, tag| _ = tag,
+ else => continue :label error.MyError,
+ }
+}
+fn foo(ok: bool) Error {
+ return if (ok) error.MyError else error.MyOtherError;
+}
+
+export fn entry7() void {
+ switch (@as(u0, 0)) {
+ 0 => |_, tag| _ = tag,
+ }
+}
+export fn entry8() void {
+ label: switch (@as(u0, 0)) {
+ 0 => |_, tag| {
+ _ = tag;
+ continue :label 0;
+ },
+ }
+}
+
+// error
+//
+// :7:30: error: cannot capture tag of packed union
+// :1:18: note: union declared here
+// :1:18: note: consider using a tagged union
+// :13:30: error: cannot capture tag of packed union
+// :1:18: note: union declared here
+// :1:18: note: consider using a tagged union
+// :21:19: error: cannot capture tag of non-union type 'tmp.E'
+// :18:11: note: enum declared here
+// :27:19: error: cannot capture tag of non-union type 'tmp.E'
+// :18:11: note: enum declared here
+// :35:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
+// :41:30: error: cannot capture tag of non-union type 'error{MyError,MyOtherError}'
+// :51:18: error: cannot capture tag of non-union type 'u0'
+// :56:18: error: cannot capture tag of non-union type 'u0'