Sema: correctly handle empty by-ref initializers

Resolves: #23210
This commit is contained in:
mlugg 2025-03-12 03:00:45 +00:00 committed by Alex Rønne Petersen
parent f954950485
commit 6c690a966a
No known key found for this signature in database
2 changed files with 72 additions and 1 deletions

View File

@ -20290,11 +20290,41 @@ fn zirStructInitEmptyResult(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
// Generic poison means this is an untyped anonymous empty struct/array init
const ty_operand = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse return .empty_tuple;
const ty_operand = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse {
if (is_byref) {
return sema.uavRef(.empty_tuple);
} else {
return .empty_tuple;
}
};
const init_ty = if (is_byref) ty: {
const ptr_ty = ty_operand.optEuBaseType(zcu);
assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
switch (ptr_ty.ptrSize(zcu)) {
// Use a zero-length array for a slice or many-ptr result
.slice, .many => break :ty try pt.arrayType(.{
.len = 0,
.child = ptr_ty.childType(zcu).toIntern(),
.sentinel = if (ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
}),
// Just use the child type for a single-pointer or C-pointer result
.one, .c => {
const child = ptr_ty.childType(zcu);
if (child.toIntern() == .anyopaque_type) {
// ...unless that child is anyopaque, in which case this is equivalent to an untyped init.
// `.{}` is an empty tuple.
if (is_byref) {
return sema.uavRef(.empty_tuple);
} else {
return .empty_tuple;
}
}
break :ty child;
},
}
if (!ptr_ty.isSlice(zcu)) {
break :ty ptr_ty.childType(zcu);
}

View File

@ -1094,3 +1094,44 @@ test "@splat zero-length array" {
try S.doTheTest(?*anyopaque, null);
try comptime S.doTheTest(?*anyopaque, null);
}
test "initialize slice with reference to empty array initializer" {
const a: []const u8 = &.{};
comptime assert(a.len == 0);
}
test "initialize many-pointer with reference to empty array initializer" {
const a: [*]const u8 = &.{};
_ = a; // nothing meaningful to test; points to zero bits
}
test "initialize sentinel-terminated slice with reference to empty array initializer" {
const a: [:0]const u8 = &.{};
comptime assert(a.len == 0);
comptime assert(a[0] == 0);
}
test "initialize sentinel-terminated many-pointer with reference to empty array initializer" {
const a: [*:0]const u8 = &.{};
comptime assert(a[0] == 0);
}
test "pass pointer to empty array initializer to anytype parameter" {
const S = struct {
fn TypeOf(x: anytype) type {
return @TypeOf(x);
}
};
comptime assert(S.TypeOf(&.{}) == @TypeOf(&.{}));
}
test "initialize pointer to anyopaque with reference to empty array initializer" {
const ptr: *const anyopaque = &.{};
// The above acts like an untyped initializer, since the `.{}` has no result type.
// So, `ptr` points in memory to an empty tuple (`@TypeOf(.{})`).
const casted: *const @TypeOf(.{}) = @alignCast(@ptrCast(ptr));
const loaded = casted.*;
// `val` should be a `@TypeOf(.{})`, as expected.
// We can't check the value, but it's zero-bit, so the type matching is good enough.
comptime assert(@TypeOf(loaded) == @TypeOf(.{}));
}