Commit 8eea73fb92

Veikka Tuominen <git@vexu.eu>
2022-11-22 13:10:52
add tests for tuple declarations
1 parent 4cea15f
lib/std/zig/parse.zig
@@ -884,7 +884,7 @@ const Parser = struct {
 
         var align_expr: Node.Index = 0;
         var type_expr: Node.Index = 0;
-        if (p.eatToken(.colon) != null or tuple_like) |_| {
+        if (p.eatToken(.colon) != null or tuple_like) {
             type_expr = try p.expectTypeExpr();
             align_expr = try p.parseByteAlign();
         }
lib/std/zig/render.zig
@@ -1189,6 +1189,16 @@ fn renderContainerField(
         try renderToken(ais, tree, t, .space); // comptime
     }
     if (field.ast.type_expr == 0 and field.ast.value_expr == 0) {
+        if (field.ast.align_expr != 0) {
+            try renderIdentifier(ais, tree, field.ast.main_token, .space, .eagerly_unquote); // name
+            const lparen_token = tree.firstToken(field.ast.align_expr) - 1;
+            const align_kw = lparen_token - 1;
+            const rparen_token = tree.lastToken(field.ast.align_expr) + 1;
+            try renderToken(ais, tree, align_kw, .none); // align
+            try renderToken(ais, tree, lparen_token, .none); // (
+            try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment
+            return renderToken(ais, tree, rparen_token, .space); // )
+        }
         return renderIdentifierComma(ais, tree, field.ast.main_token, space, .eagerly_unquote); // name
     }
     if (field.ast.type_expr != 0 and field.ast.value_expr == 0) {
@@ -1211,6 +1221,15 @@ fn renderContainerField(
     }
     if (field.ast.type_expr == 0 and field.ast.value_expr != 0) {
         try renderIdentifier(ais, tree, field.ast.main_token, .space, .eagerly_unquote); // name
+        if (field.ast.align_expr != 0) {
+            const lparen_token = tree.firstToken(field.ast.align_expr) - 1;
+            const align_kw = lparen_token - 1;
+            const rparen_token = tree.lastToken(field.ast.align_expr) + 1;
+            try renderToken(ais, tree, align_kw, .none); // align
+            try renderToken(ais, tree, lparen_token, .none); // (
+            try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment
+            try renderToken(ais, tree, rparen_token, .space); // )
+        }
         try renderToken(ais, tree, field.ast.main_token + 1, .space); // =
         return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value
     }
src/arch/x86_64/abi.zig
@@ -552,6 +552,7 @@ test "C_C_D" {
         .layout = .Extern,
         .status = .fully_resolved,
         .known_non_opv = true,
+        .is_tuple = false,
     };
     var C_C_D = Type.Payload.Struct{ .data = &C_C_D_struct };
 
src/AstGen.zig
@@ -4854,8 +4854,9 @@ fn containerDecl(
                             },
                         );
                     }
-                    // Alignment expressions in enums are caught by the parser.
-                    assert(member.ast.align_expr == 0);
+                    if (member.ast.align_expr != 0) {
+                        return astgen.failNode(member.ast.align_expr, "enum fields cannot be aligned", .{});
+                    }
 
                     const name_token = member.ast.main_token;
                     if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {
src/Sema.zig
@@ -11834,6 +11834,12 @@ fn analyzeTupleCat(
     if (dest_fields == 0) {
         return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value));
     }
+    if (lhs_len == 0) {
+        return rhs;
+    }
+    if (rhs_len == 0) {
+        return lhs;
+    }
     const final_len = try sema.usizeCast(block, rhs_src, dest_fields);
 
     const types = try sema.arena.alloc(Type, final_len);
@@ -11880,13 +11886,13 @@ fn analyzeTupleCat(
     var i: u32 = 0;
     while (i < lhs_len) : (i += 1) {
         const operand_src = lhs_src; // TODO better source location
-        element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, @intCast(u32, i), lhs_ty);
+        element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, i, lhs_ty);
     }
     i = 0;
     while (i < rhs_len) : (i += 1) {
         const operand_src = rhs_src; // TODO better source location
         element_refs[i + lhs_len] =
-            try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, i), rhs_ty);
+            try sema.tupleFieldValByIndex(block, operand_src, rhs, i, rhs_ty);
     }
 
     return block.addAggregateInit(tuple_ty, element_refs);
@@ -18502,8 +18508,12 @@ fn reifyStruct(
         }
         const abi_align = @intCast(u29, (try alignment_val.getUnsignedIntAdvanced(target, sema)).?);
 
-        if (layout == .Packed and abi_align != 0) {
-            return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});
+        if (layout == .Packed) {
+            if (abi_align != 0) return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});
+            if (is_comptime_val.toBool()) return sema.fail(block, src, "packed struct fields cannot be marked comptime", .{});
+        }
+        if (layout == .Extern and is_comptime_val.toBool()) {
+            return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{});
         }
 
         const field_name = try name_val.toAllocatedBytes(
@@ -18512,6 +18522,25 @@ fn reifyStruct(
             mod,
         );
 
+        if (is_tuple) {
+            const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch {
+                return sema.fail(
+                    block,
+                    src,
+                    "tuple cannot have non-numeric field '{s}'",
+                    .{field_name},
+                );
+            };
+
+            if (field_index >= fields_len) {
+                return sema.fail(
+                    block,
+                    src,
+                    "tuple field {} exceeds tuple field count",
+                    .{field_index},
+                );
+            }
+        }
         const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
         if (gop.found_existing) {
             // TODO: better source location
@@ -18525,6 +18554,9 @@ fn reifyStruct(
                 opt_val;
             break :blk try payload_val.copy(new_decl_arena_allocator);
         } else Value.initTag(.unreachable_value);
+        if (is_comptime_val.toBool() and default_val.tag() == .unreachable_value) {
+            return sema.fail(block, src, "comptime field without default initialization value", .{});
+        }
 
         var buffer: Value.ToTypeBuffer = undefined;
         gop.value_ptr.* = .{
@@ -27094,7 +27126,7 @@ fn coerceTupleToStruct(
 
     const inst_ty = sema.typeOf(inst);
     var runtime_src: ?LazySrcLoc = null;
-    const field_count = struct_ty.structFieldCount();
+    const field_count = inst_ty.structFieldCount();
     var field_i: u32 = 0;
     while (field_i < field_count) : (field_i += 1) {
         const field_src = inst_src; // TODO better source location
@@ -27176,15 +27208,16 @@ fn coerceTupleToTuple(
     inst: Air.Inst.Ref,
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
-    const field_count = tuple_ty.structFieldCount();
-    const field_vals = try sema.arena.alloc(Value, field_count);
+    const dest_field_count = tuple_ty.structFieldCount();
+    const field_vals = try sema.arena.alloc(Value, dest_field_count);
     const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len);
     mem.set(Air.Inst.Ref, field_refs, .none);
 
     const inst_ty = sema.typeOf(inst);
+    const inst_field_count = inst_ty.structFieldCount();
     var runtime_src: ?LazySrcLoc = null;
     var field_i: u32 = 0;
-    while (field_i < field_count) : (field_i += 1) {
+    while (field_i < inst_field_count) : (field_i += 1) {
         const field_src = inst_src; // TODO better source location
         const field_name = if (inst_ty.castTag(.anon_struct)) |payload|
             payload.data.names[field_i]
src/type.zig
@@ -804,7 +804,7 @@ pub const Type = extern union {
                 return a_struct_obj == b_struct_obj;
             },
             .tuple, .empty_struct_literal => {
-                if (!b.isTuple()) return false;
+                if (!b.isSimpleTuple()) return false;
 
                 const a_tuple = a.tupleFields();
                 const b_tuple = b.tupleFields();
@@ -5572,7 +5572,11 @@ pub const Type = extern union {
 
     pub fn structFieldCount(ty: Type) usize {
         switch (ty.tag()) {
-            .@"struct" => return ty.castTag(.@"struct").?.data.fields.count(),
+            .@"struct" => {
+                const struct_obj = ty.castTag(.@"struct").?.data;
+                assert(struct_obj.haveFieldTypes());
+                return struct_obj.fields.count();
+            },
             .empty_struct, .empty_struct_literal => return 0,
             .tuple => return ty.castTag(.tuple).?.data.types.len,
             .anon_struct => return ty.castTag(.anon_struct).?.data.types.len,
test/behavior/tuple_declarations.zig
@@ -0,0 +1,79 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const testing = std.testing;
+const expect = testing.expect;
+const expectEqualStrings = testing.expectEqualStrings;
+
+test "tuple declaration type info" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+
+    {
+        const T = struct { comptime u32 align(2) = 1, []const u8 };
+        const info = @typeInfo(T).Struct;
+
+        try expect(info.layout == .Auto);
+        try expect(info.backing_integer == null);
+        try expect(info.fields.len == 2);
+        try expect(info.decls.len == 0);
+        try expect(info.is_tuple);
+
+        try expectEqualStrings(info.fields[0].name, "0");
+        try expect(info.fields[0].field_type == u32);
+        try expect(@ptrCast(*const u32, @alignCast(@alignOf(u32), info.fields[0].default_value)).* == 1);
+        try expect(info.fields[0].is_comptime);
+        try expect(info.fields[0].alignment == 2);
+
+        try expectEqualStrings(info.fields[1].name, "1");
+        try expect(info.fields[1].field_type == []const u8);
+        try expect(info.fields[1].default_value == null);
+        try expect(!info.fields[1].is_comptime);
+        try expect(info.fields[1].alignment == @alignOf([]const u8));
+    }
+    {
+        const T = packed struct(u32) { u1, u30, u1 };
+        const info = @typeInfo(T).Struct;
+
+        try expect(std.mem.endsWith(u8, @typeName(T), "test.tuple declaration type info.T"));
+
+        try expect(info.layout == .Packed);
+        try expect(info.backing_integer == u32);
+        try expect(info.fields.len == 3);
+        try expect(info.decls.len == 0);
+        try expect(info.is_tuple);
+
+        try expectEqualStrings(info.fields[0].name, "0");
+        try expect(info.fields[0].field_type == u1);
+
+        try expectEqualStrings(info.fields[1].name, "1");
+        try expect(info.fields[1].field_type == u30);
+
+        try expectEqualStrings(info.fields[2].name, "2");
+        try expect(info.fields[2].field_type == u1);
+    }
+}
+
+test "Tuple declaration usage" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+
+    const T = struct { u32, []const u8 };
+    var t: T = .{ 1, "foo" };
+    try expect(t[0] == 1);
+    try expectEqualStrings(t[1], "foo");
+
+    var mul = t ** 3;
+    try expect(@TypeOf(mul) != T);
+    try expect(mul.len == 6);
+    try expect(mul[2] == 1);
+    try expectEqualStrings(mul[3], "foo");
+
+    var t2: T = .{ 2, "bar" };
+    var cat = t ++ t2;
+    try expect(@TypeOf(cat) != T);
+    try expect(cat.len == 4);
+    try expect(cat[2] == 2);
+    try expectEqualStrings(cat[3], "bar");
+}
test/cases/compile_errors/alignment_of_enum_field_specified.zig
@@ -11,4 +11,4 @@ export fn entry1() void {
 // backend=stage2
 // target=native
 //
-// :3:7: error: expected ',' after field
+// :3:13: error: enum fields cannot be aligned
test/cases/compile_errors/reify_struct.zig
@@ -0,0 +1,80 @@
+comptime {
+    @Type(.{ .Struct = .{
+        .layout = .Auto,
+        .fields = &.{.{
+            .name = "foo",
+            .field_type = u32,
+            .default_value = null,
+            .is_comptime = false,
+            .alignment = 4,
+        }},
+        .decls = &.{},
+        .is_tuple = true,
+    } });
+}
+comptime {
+    @Type(.{ .Struct = .{
+        .layout = .Auto,
+        .fields = &.{.{
+            .name = "3",
+            .field_type = u32,
+            .default_value = null,
+            .is_comptime = false,
+            .alignment = 4,
+        }},
+        .decls = &.{},
+        .is_tuple = true,
+    } });
+}
+comptime {
+    @Type(.{ .Struct = .{
+        .layout = .Auto,
+        .fields = &.{.{
+            .name = "0",
+            .field_type = u32,
+            .default_value = null,
+            .is_comptime = true,
+            .alignment = 4,
+        }},
+        .decls = &.{},
+        .is_tuple = true,
+    } });
+}
+comptime {
+    @Type(.{ .Struct = .{
+        .layout = .Extern,
+        .fields = &.{.{
+            .name = "0",
+            .field_type = u32,
+            .default_value = null,
+            .is_comptime = true,
+            .alignment = 4,
+        }},
+        .decls = &.{},
+        .is_tuple = true,
+    } });
+}
+comptime {
+    @Type(.{ .Struct = .{
+        .layout = .Packed,
+        .fields = &.{.{
+            .name = "0",
+            .field_type = u32,
+            .default_value = null,
+            .is_comptime = true,
+            .alignment = 4,
+        }},
+        .decls = &.{},
+        .is_tuple = true,
+    } });
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :2:5: error: tuple cannot have non-numeric field 'foo'
+// :16:5: error: tuple field 3 exceeds tuple field count
+// :30:5: error: comptime field without default initialization value
+// :44:5: error: extern struct fields cannot be marked comptime
+// :58:5: error: alignment in a packed struct field must be set to 0
test/cases/compile_errors/struct_field_missing_type.zig
@@ -1,13 +0,0 @@
-const Letter = struct {
-    A,
-};
-export fn entry() void {
-    var a = Letter { .A = {} };
-    _ = a;
-}
-
-// error
-// backend=stage2
-// target=native
-//
-// :2:5: error: struct field missing type
test/cases/compile_errors/tuple_declarations.zig
@@ -0,0 +1,25 @@
+const E = enum {
+    *u32,
+};
+const U = union {
+    *u32,
+};
+const S = struct {
+    a: u32,
+    *u32,
+};
+const T = struct {
+    u32,
+    []const u8,
+
+    const a = 1;
+};
+
+// error
+// backend=stage2
+// target=native
+//
+// :2:5: error: enum field missing name
+// :5:5: error: union field missing name
+// :8:5: error: tuple field has a name
+// :15:5: error: tuple declarations cannot contain declarations
test/stage2/cbe.zig
@@ -670,7 +670,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    _ = E1.a;
             \\}
         , &.{
-            ":3:7: error: expected ',' after field",
+            ":3:13: error: enum fields cannot be aligned",
         });
 
         // Redundant non-exhaustive enum mark.
test/behavior.zig
@@ -200,6 +200,7 @@ test {
         _ = @import("behavior/packed_struct_explicit_backing_int.zig");
         _ = @import("behavior/empty_union.zig");
         _ = @import("behavior/inline_switch.zig");
+        _ = @import("behavior/tuple_declarations.zig");
         _ = @import("behavior/bugs/12723.zig");
         _ = @import("behavior/bugs/12776.zig");
     }
test/compile_errors.zig
@@ -213,7 +213,7 @@ pub fn addCases(ctx: *TestContext) !void {
         case.backend = .stage2;
 
         case.addSourceFile("b.zig",
-            \\bad
+            \\+
         );
 
         case.addError(
@@ -221,7 +221,7 @@ pub fn addCases(ctx: *TestContext) !void {
             \\    _ = (@sizeOf(@import("b.zig")));
             \\}
         , &[_][]const u8{
-            ":1:1: error: struct field missing type",
+            ":1:1: error: expected type expression, found '+'",
         });
     }