Commit 9821a0c6f0

Andrew Kelley <andrew@ziglang.org>
2022-03-30 05:11:48
Sema: fix generic instantiations of return types with nested captures
* In semaStructFields and semaUnionFields we return error.GenericPoison if one of the field types ends up being generic poison. - This requires handling function calls and function types taking this into account when calling `typeRequiresComptime` on the return type. * Unrelated: I noticed using Valgrind that struct reification did not populate the `known_opv` field. After fixing it, the behavior tests run Valgrind-clean. * ZIR: use `@ptrCast` to cast between slices instead of exploiting the fact that stage1 incorrectly allows `@bitCast` between slices. - A future enhancement will make Zig support `@ptrCast` to directly cast between slices.
1 parent e39c863
Changed files (3)
src
test
behavior
src/Sema.zig
@@ -4764,12 +4764,20 @@ fn analyzeCall(
 
     const gpa = sema.gpa;
 
-    var is_comptime_call = block.is_comptime or modifier == .compile_time or
-        try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type);
+    var is_generic_call = func_ty_info.is_generic;
+    var is_comptime_call = block.is_comptime or modifier == .compile_time;
+    if (!is_comptime_call) {
+        if (sema.typeRequiresComptime(block, func_src, func_ty_info.return_type)) |ct| {
+            is_comptime_call = ct;
+        } else |err| switch (err) {
+            error.GenericPoison => is_generic_call = true,
+            else => |e| return e,
+        }
+    }
     var is_inline_call = is_comptime_call or modifier == .always_inline or
         func_ty_info.cc == .Inline;
 
-    if (!is_inline_call and func_ty_info.is_generic) {
+    if (!is_inline_call and is_generic_call) {
         if (sema.instantiateGenericCall(
             block,
             func,
@@ -6410,10 +6418,20 @@ fn funcCommon(
             }
         }
 
-        is_generic = is_generic or
-            try sema.typeRequiresComptime(block, ret_ty_src, bare_return_type);
+        const ret_poison = if (!is_generic) rp: {
+            if (sema.typeRequiresComptime(block, ret_ty_src, bare_return_type)) |ret_comptime| {
+                is_generic = ret_comptime;
+                break :rp bare_return_type.tag() == .generic_poison;
+            } else |err| switch (err) {
+                error.GenericPoison => {
+                    is_generic = true;
+                    break :rp true;
+                },
+                else => |e| return e,
+            }
+        } else bare_return_type.tag() == .generic_poison;
 
-        const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison)
+        const return_type = if (!inferred_error_set or ret_poison)
             bare_return_type
         else blk: {
             const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode);
@@ -13570,7 +13588,7 @@ fn reifyStruct(
         .zir_index = inst,
         .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout),
         .status = .have_field_types,
-        .known_non_opv = undefined,
+        .known_non_opv = false,
         .namespace = .{
             .parent = block.namespace,
             .ty = struct_ty,
@@ -21666,6 +21684,10 @@ fn semaStructFields(
         // TODO emit compile errors for invalid field types
         // such as arrays and pointers inside packed structs.
 
+        if (field_ty.tag() == .generic_poison) {
+            return error.GenericPoison;
+        }
+
         const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
         assert(!gop.found_existing);
         gop.value_ptr.* = .{
@@ -21913,6 +21935,10 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
             // But only resolve the source location if we need to emit a compile error.
             try sema.resolveType(&block_scope, src, field_type_ref);
 
+        if (field_ty.tag() == .generic_poison) {
+            return error.GenericPoison;
+        }
+
         const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
         assert(!gop.found_existing);
         gop.value_ptr.* = .{
src/Zir.zig
@@ -63,7 +63,7 @@ pub const ExtraIndex = enum(u32) {
 /// Returns the requested data, as well as the new index which is at the start of the
 /// trailers for the object.
 pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, end: usize } {
-    const fields = std.meta.fields(T);
+    const fields = @typeInfo(T).Struct.fields;
     var i: usize = index;
     var result: T = undefined;
     inline for (fields) |field| {
@@ -94,7 +94,8 @@ pub fn nullTerminatedString(code: Zir, index: usize) [:0]const u8 {
 
 pub fn refSlice(code: Zir, start: usize, len: usize) []Inst.Ref {
     const raw_slice = code.extra[start..][0..len];
-    return @bitCast([]Inst.Ref, raw_slice);
+    // TODO we should be able to directly `@ptrCast` the slice to the other slice type.
+    return @ptrCast([*]Inst.Ref, raw_slice.ptr)[0..len];
 }
 
 pub fn hasCompileErrors(code: Zir) bool {
test/behavior/generics.zig
@@ -290,3 +290,19 @@ test "generic function with void and comptime parameter" {
     var s: S = .{ .x = 1234 };
     try namespace.foo({}, &s, u8);
 }
+
+test "anonymous struct return type referencing comptime parameter" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
+    const S = struct {
+        pub fn extraData(comptime T: type, index: usize) struct { data: T, end: usize } {
+            return .{
+                .data = 1234,
+                .end = index,
+            };
+        }
+    };
+    const s = S.extraData(i32, 5678);
+    try expect(s.data == 1234);
+    try expect(s.end == 5678);
+}