From 9821a0c6f0caf3df4cc6000c000d9cc38baf27d8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 29 Mar 2022 20:11:48 -0700 Subject: [PATCH] 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. --- src/Sema.zig | 40 +++++++++++++++++++++++++++++++------- src/Zir.zig | 5 +++-- test/behavior/generics.zig | 16 +++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 09f99c5751ffd14b09681dcf450582877a32c1ff..e1e77f4c112e005e70f4e30476b21aee354d8775 100644 --- a/src/Sema.zig +++ b/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.* = .{ diff --git a/src/Zir.zig b/src/Zir.zig index d895281141bdb7f8ede1b6b5557e663d4b23fcd6..9474627675a409546258918fa52fc491762e4acf 100644 --- a/src/Zir.zig +++ b/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 { diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index 867cf940b86f757f5cf9fdea9534dc0dfbc26c43..08f1e5bad95c49dd6a8e6fc020c0f0cd324cc0ee 100644 --- a/test/behavior/generics.zig +++ b/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); +} -- 2.54.0