From 1ebe3bd01d13b28b3ecd4962f0f70344fe75ba4d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 14 Mar 2022 10:47:35 -0700 Subject: [PATCH] stage2: reify structs and tuples (#11144) Implements `@Type` for structs, anon structs, and tuples. This is another place that would probably benefit from a `.reified_struct` type tag but will defer for later in the interest of getting tests passing first. --- src/Sema.zig | 200 ++++++++++++++++++++++++++++++++++++++--- test/behavior/type.zig | 46 +++++++++- 2 files changed, 235 insertions(+), 11 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 99519cd562..c2fc8ad759 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -12459,7 +12459,6 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const ty = try Type.array(sema.arena, len, sentinel, child_ty); return sema.addType(ty); }, - .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}), .Optional => { const struct_val = union_val.val.castTag(.@"struct").?.data; // TODO use reflection instead of magic numbers here @@ -12515,10 +12514,31 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const ty = try Type.Tag.error_set_merged.create(sema.arena, names); return sema.addType(ty); }, + .Struct => { + // TODO use reflection instead of magic numbers here + const struct_val = union_val.val.castTag(.@"struct").?.data; + // layout: containerlayout, + const layout_val = struct_val[0]; + // fields: []const enumfield, + const fields_val = struct_val[1]; + // decls: []const declaration, + const decls_val = struct_val[2]; + // is_tuple: bool, + const is_tuple_val = struct_val[3]; + + // Decls + if (decls_val.sliceLen() > 0) { + return sema.fail(block, src, "reified structs must have no decls", .{}); + } + + return if (is_tuple_val.toBool()) + try sema.reifyTuple(block, src, fields_val) + else + try sema.reifyStruct(block, inst, src, layout_val, fields_val); + }, .Enum => { const struct_val = union_val.val.castTag(.@"struct").?.data; // TODO use reflection instead of magic numbers here - // error_set: type, // layout: ContainerLayout, const layout_val = struct_val[0]; // tag_type: type, @@ -12537,10 +12557,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I } // Decls - const decls_slice_val = decls_val.castTag(.slice).?.data; - const decls_decl = decls_slice_val.ptr.pointerDecl().?; - try sema.ensureDeclAnalyzed(decls_decl); - if (decls_decl.ty.arrayLen() > 0) { + if (decls_val.sliceLen() > 0) { return sema.fail(block, src, "reified enums must have no decls", .{}); } @@ -12636,10 +12653,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const decls_val = struct_val[0]; // Decls - const decls_slice_val = decls_val.castTag(.slice).?.data; - const decls_decl = decls_slice_val.ptr.pointerDecl().?; - try sema.ensureDeclAnalyzed(decls_decl); - if (decls_decl.ty.arrayLen() > 0) { + if (decls_val.sliceLen() > 0) { return sema.fail(block, src, "reified opaque must have no decls", .{}); } @@ -12684,6 +12698,172 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I } } +fn reifyTuple( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + fields_val: Value, +) CompileError!Air.Inst.Ref { + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen()); + if (fields_len == 0) return sema.addType(Type.initTag(.empty_struct_literal)); + + const types = try sema.arena.alloc(Type, fields_len); + const values = try sema.arena.alloc(Value, fields_len); + + var used_fields: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; + defer used_fields.deinit(sema.gpa); + try used_fields.ensureTotalCapacity(sema.gpa, fields_len); + + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.arena, i); + const field_struct_val = elem_val.castTag(.@"struct").?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + //default_value: ?*const anyopaque, + const default_value_val = field_struct_val[2]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + sema.arena, + ); + + const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch |err| { + return sema.fail( + block, + src, + "tuple cannot have non-numeric field '{s}': {}", + .{ field_name, err }, + ); + }; + + if (field_index >= fields_len) { + return sema.fail( + block, + src, + "tuple field {} exceeds tuple field count", + .{field_index}, + ); + } + + const gop = used_fields.getOrPutAssumeCapacity(field_index); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate tuple field {}", .{field_index}); + } + + const default_val = if (default_value_val.optionalValue()) |opt_val| blk: { + const payload_val = if (opt_val.pointerDecl()) |opt_decl| + opt_decl.val + else + opt_val; + break :blk try payload_val.copy(sema.arena); + } else Value.initTag(.unreachable_value); + + var buffer: Value.ToTypeBuffer = undefined; + types[field_index] = try field_type_val.toType(&buffer).copy(sema.arena); + values[field_index] = default_val; + } + + const ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + return sema.addType(ty); +} + +fn reifyStruct( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + src: LazySrcLoc, + layout_val: Value, + fields_val: Value, +) CompileError!Air.Inst.Ref { + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + const struct_obj = try new_decl_arena_allocator.create(Module.Struct); + const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj); + const new_struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty); + const type_name = try sema.createTypeName(block, .anon); + const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{ + .ty = Type.type, + .val = new_struct_val, + }, type_name); + new_decl.owns_tv = true; + errdefer sema.mod.abortAnonDecl(new_decl); + struct_obj.* = .{ + .owner_decl = new_decl, + .fields = .{}, + .node_offset = src.node_offset, + .zir_index = inst, + .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .status = .have_field_types, + .known_non_opv = undefined, + .namespace = .{ + .parent = block.namespace, + .ty = struct_ty, + .file_scope = block.getFileScope(), + }, + }; + + // Fields + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen()); + try struct_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.arena, i); + const field_struct_val = elem_val.castTag(.@"struct").?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + //default_value: ?*const anyopaque, + const default_value_val = field_struct_val[2]; + // is_comptime: bool, + const is_comptime_val = field_struct_val[3]; + // alignment: comptime_int, + const alignment_val = field_struct_val[4]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + ); + + const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate struct field {s}", .{field_name}); + } + + const default_val = if (default_value_val.optionalValue()) |opt_val| blk: { + const payload_val = if (opt_val.pointerDecl()) |opt_decl| + opt_decl.val + else + opt_val; + break :blk try payload_val.copy(new_decl_arena_allocator); + } else Value.initTag(.unreachable_value); + + var buffer: Value.ToTypeBuffer = undefined; + gop.value_ptr.* = .{ + .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), + .abi_align = try alignment_val.copy(new_decl_arena_allocator), + .default_val = default_val, + .is_comptime = is_comptime_val.toBool(), + .offset = undefined, + }; + } + + try new_decl.finalizeNewArena(&new_decl_arena); + return sema.analyzeDeclVal(block, src, new_decl); +} + fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; diff --git a/test/behavior/type.zig b/test/behavior/type.zig index ff86aa28e8..15b4638488 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -260,7 +260,11 @@ test "Type.ErrorSet" { } test "Type.Struct" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const A = @Type(@typeInfo(struct { x: u8, y: u32 })); const infoA = @typeInfo(A).Struct; @@ -303,6 +307,46 @@ test "Type.Struct" { try testing.expectEqual(@as(u32, 5), @ptrCast(*const u32, infoC.fields[1].default_value.?).*); try testing.expectEqual(@as(usize, 0), infoC.decls.len); try testing.expectEqual(@as(bool, false), infoC.is_tuple); + + // anon structs + const D = @Type(@typeInfo(@TypeOf(.{ .x = 3, .y = 5 }))); + const infoD = @typeInfo(D).Struct; + try testing.expectEqual(Type.ContainerLayout.Auto, infoD.layout); + try testing.expectEqualSlices(u8, "x", infoD.fields[0].name); + try testing.expectEqual(comptime_int, infoD.fields[0].field_type); + try testing.expectEqual(@as(comptime_int, 3), @ptrCast(*const comptime_int, infoD.fields[0].default_value.?).*); + try testing.expectEqualSlices(u8, "y", infoD.fields[1].name); + try testing.expectEqual(comptime_int, infoD.fields[1].field_type); + try testing.expectEqual(@as(comptime_int, 5), @ptrCast(*const comptime_int, infoD.fields[1].default_value.?).*); + try testing.expectEqual(@as(usize, 0), infoD.decls.len); + try testing.expectEqual(@as(bool, false), infoD.is_tuple); + + // tuples + const E = @Type(@typeInfo(@TypeOf(.{ 1, 2 }))); + const infoE = @typeInfo(E).Struct; + try testing.expectEqual(Type.ContainerLayout.Auto, infoE.layout); + try testing.expectEqualSlices(u8, "0", infoE.fields[0].name); + try testing.expectEqual(comptime_int, infoE.fields[0].field_type); + try testing.expectEqual(@as(comptime_int, 1), @ptrCast(*const comptime_int, infoE.fields[0].default_value.?).*); + try testing.expectEqualSlices(u8, "1", infoE.fields[1].name); + try testing.expectEqual(comptime_int, infoE.fields[1].field_type); + try testing.expectEqual(@as(comptime_int, 2), @ptrCast(*const comptime_int, infoE.fields[1].default_value.?).*); + try testing.expectEqual(@as(usize, 0), infoE.decls.len); + try testing.expectEqual(@as(bool, true), infoE.is_tuple); + + // empty struct + const F = @Type(@typeInfo(struct {})); + const infoF = @typeInfo(F).Struct; + try testing.expectEqual(Type.ContainerLayout.Auto, infoF.layout); + try testing.expect(infoF.fields.len == 0); + try testing.expectEqual(@as(bool, false), infoF.is_tuple); + + // empty tuple + const G = @Type(@typeInfo(@TypeOf(.{}))); + const infoG = @typeInfo(G).Struct; + try testing.expectEqual(Type.ContainerLayout.Auto, infoG.layout); + try testing.expect(infoG.fields.len == 0); + try testing.expectEqual(@as(bool, true), infoG.is_tuple); } test "Type.Enum" {