diff --git a/src/Sema.zig b/src/Sema.zig index 2736abbd2a..18dd0cd3e0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10924,50 +10924,51 @@ const SwitchProngAnalysis = struct { // By-reference captures have some further restrictions which make them easier to emit if (capture_byref) { const operand_ptr_info = operand_ptr_ty.ptrInfo(mod); - const capture_ptr_ty = try sema.ptrType(.{ - .child = capture_ty.toIntern(), - .flags = .{ - // TODO: alignment! - .is_const = operand_ptr_info.flags.is_const, - .is_volatile = operand_ptr_info.flags.is_volatile, - .address_space = operand_ptr_info.flags.address_space, - }, - }); - - // By-ref captures of hetereogeneous types are only allowed if each field - // pointer type is in-memory coercible to the capture pointer type. - if (!same_types) { - for (field_indices, 0..) |field_idx, i| { + 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_idx, *dummy| { const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_idx]); const field_ptr_ty = try sema.ptrType(.{ .child = field_ty.toIntern(), .flags = .{ - // TODO: alignment! .is_const = operand_ptr_info.flags.is_const, .is_volatile = operand_ptr_info.flags.is_volatile, .address_space = operand_ptr_info.flags.address_space, + .alignment = union_obj.fieldAlign(ip, field_idx), }, }); - if (.ok != try sema.coerceInMemoryAllowed(block, capture_ptr_ty, field_ptr_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) { + dummy.* = try mod.undefRef(field_ptr_ty); + } + const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len); + @memset(case_srcs, .unneeded); + + break :resolve sema.resolvePeerTypes(block, .unneeded, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) { + error.NeededSourceLocation => { + // This must be a multi-prong so this must be a `multi_capture` src const multi_idx = raw_capture_src.multi_capture; const src_decl_ptr = sema.mod.declPtr(block.src_decl); + for (case_srcs, 0..) |*case_src, i| { + const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(i) } }; + case_src.* = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); + } const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); - const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(i) } }; - const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none); - const msg = msg: { - const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{}); - errdefer msg.destroy(sema.gpa); - try sema.errNote(block, case_src, msg, "pointer type child '{}' cannot cast into resolved pointer type child '{}'", .{ - field_ty.fmt(sema.mod), - capture_ty.fmt(sema.mod), - }); - try sema.errNote(block, capture_src, msg, "this coercion is only possible when capturing by value", .{}); - break :msg msg; + _ = sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err1| switch (err1) { + error.AnalysisFail => { + const msg = sema.err orelse return error.AnalysisFail; + try sema.errNote(block, capture_src, msg, "this coercion is only possible when capturing by value", .{}); + try sema.reparentOwnedErrorMsg(block, capture_src, msg, "capture group with incompatible types", .{}); + return error.AnalysisFail; + }, + else => |e| return e, }; - return sema.failWithOwnedErrorMsg(block, msg); - } - } - } + unreachable; + }, + else => |e| return e, + }; + }; if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| { if (op_ptr_val.isUndef(mod)) return mod.undefRef(capture_ptr_ty); diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 458c3530b8..a0adbb818f 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -410,8 +410,8 @@ test "switch on integer with else capturing expr" { var x: i32 = 5; _ = &x; switch (x + 10) { - 14 => @panic("fail"), - 16 => @panic("fail"), + 14 => return error.TestFailed, + 16 => return error.TestFailed, else => |e| try expect(e == 15), } } @@ -522,7 +522,7 @@ test "switch with null and T peer types and inferred result location type" { else => null, }) |v| { _ = v; - @panic("fail"); + return error.TestFailed; } } }; @@ -548,12 +548,12 @@ test "switch prongs with cases with identical payload types" { fn doTheSwitch1(u: Union) !void { switch (u) { .A, .C => |e| { - try expect(@TypeOf(e) == usize); + comptime assert(@TypeOf(e) == usize); try expect(e == 8); }, .B => |e| { _ = e; - @panic("fail"); + return error.TestFailed; }, } } @@ -561,10 +561,10 @@ test "switch prongs with cases with identical payload types" { switch (u) { .A, .C => |e| { _ = e; - @panic("fail"); + return error.TestFailed; }, .B => |e| { - try expect(@TypeOf(e) == isize); + comptime assert(@TypeOf(e) == isize); try expect(e == -8); }, } @@ -574,6 +574,69 @@ test "switch prongs with cases with identical payload types" { try comptime S.doTheTest(); } +test "switch prong pointer capture alignment" { + const U = union(enum) { + a: u8 align(8), + b: u8 align(4), + c: u8, + }; + + const S = struct { + fn doTheTest() !void { + const u = U{ .a = 1 }; + switch (u) { + .a => |*a| comptime assert(@TypeOf(a) == *align(8) const u8), + .b, .c => |*p| { + _ = p; + return error.TestFailed; + }, + } + + switch (u) { + .a, .b => |*p| comptime assert(@TypeOf(p) == *align(4) const u8), + .c => |*p| { + _ = p; + return error.TestFailed; + }, + } + + switch (u) { + .a, .c => |*p| comptime assert(@TypeOf(p) == *const u8), + .b => |*p| { + _ = p; + return error.TestFailed; + }, + } + } + + fn doTheTest2() !void { + const un1 = U{ .b = 1 }; + switch (un1) { + .b => |*b| comptime assert(@TypeOf(b) == *align(4) const u8), + .a, .c => |*p| { + _ = p; + return error.TestFailed; + }, + } + + const un2 = U{ .c = 1 }; + switch (un2) { + .c => |*c| comptime assert(@TypeOf(c) == *const u8), + .a, .b => |*p| { + _ = p; + return error.TestFailed; + }, + } + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); + + try S.doTheTest2(); + try comptime S.doTheTest2(); +} + test "switch on pointer type" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO diff --git a/test/cases/compile_errors/switch_capture_incompatible_types.zig b/test/cases/compile_errors/switch_capture_incompatible_types.zig index 967307441b..5fbee3b347 100644 --- a/test/cases/compile_errors/switch_capture_incompatible_types.zig +++ b/test/cases/compile_errors/switch_capture_incompatible_types.zig @@ -23,5 +23,7 @@ export fn g() void { // :5:10: note: type 'u32' here // :5:14: note: type '*u8' here // :13:20: error: capture group with incompatible types -// :13:14: note: pointer type child 'u32' cannot cast into resolved pointer type child 'u64' +// :13:20: note: incompatible types: '*u64' and '*u32' +// :13:10: note: type '*u64' here +// :13:14: note: type '*u32' here // :13:20: note: this coercion is only possible when capturing by value