Commit aa3db7cc15

mlugg <mlugg@mlugg.co.uk>
2025-03-12 04:00:45
Sema: correctly handle empty by-ref initializers
Resolves: #23210
1 parent ea57fb5
Changed files (2)
src
test
behavior
src/Sema.zig
@@ -20310,11 +20310,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);
         }
test/behavior/array.zig
@@ -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(.{}));
+}