authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-11-22 14:10:52+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-11-23 22:16:31+02:00
log8eea73fb922c1c7fab4b8bf1717588464691a66e
tree6b80dd1b1d2e1993e05e6d3c19cbc5ba4d424c5e
parent4cea15f12bdaf1603d60cf80e767dd8f2bb48b26

add tests for tuple declarations


14 files changed, 260 insertions(+), 30 deletions(-)

lib/std/zig/parse.zig+1-1
...@@ -884,7 +884,7 @@ const Parser = struct {...@@ -884,7 +884,7 @@ const Parser = struct {
884884
885 var align_expr: Node.Index = 0;885 var align_expr: Node.Index = 0;
886 var type_expr: Node.Index = 0;886 var type_expr: Node.Index = 0;
887 if (p.eatToken(.colon) != null or tuple_like) |_| {887 if (p.eatToken(.colon) != null or tuple_like) {
888 type_expr = try p.expectTypeExpr();888 type_expr = try p.expectTypeExpr();
889 align_expr = try p.parseByteAlign();889 align_expr = try p.parseByteAlign();
890 }890 }
lib/std/zig/render.zig+19
...@@ -1189,6 +1189,16 @@ fn renderContainerField(...@@ -1189,6 +1189,16 @@ fn renderContainerField(
1189 try renderToken(ais, tree, t, .space); // comptime1189 try renderToken(ais, tree, t, .space); // comptime
1190 }1190 }
1191 if (field.ast.type_expr == 0 and field.ast.value_expr == 0) {1191 if (field.ast.type_expr == 0 and field.ast.value_expr == 0) {
1192 if (field.ast.align_expr != 0) {
1193 try renderIdentifier(ais, tree, field.ast.main_token, .space, .eagerly_unquote); // name
1194 const lparen_token = tree.firstToken(field.ast.align_expr) - 1;
1195 const align_kw = lparen_token - 1;
1196 const rparen_token = tree.lastToken(field.ast.align_expr) + 1;
1197 try renderToken(ais, tree, align_kw, .none); // align
1198 try renderToken(ais, tree, lparen_token, .none); // (
1199 try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment
1200 return renderToken(ais, tree, rparen_token, .space); // )
1201 }
1192 return renderIdentifierComma(ais, tree, field.ast.main_token, space, .eagerly_unquote); // name1202 return renderIdentifierComma(ais, tree, field.ast.main_token, space, .eagerly_unquote); // name
1193 }1203 }
1194 if (field.ast.type_expr != 0 and field.ast.value_expr == 0) {1204 if (field.ast.type_expr != 0 and field.ast.value_expr == 0) {
...@@ -1211,6 +1221,15 @@ fn renderContainerField(...@@ -1211,6 +1221,15 @@ fn renderContainerField(
1211 }1221 }
1212 if (field.ast.type_expr == 0 and field.ast.value_expr != 0) {1222 if (field.ast.type_expr == 0 and field.ast.value_expr != 0) {
1213 try renderIdentifier(ais, tree, field.ast.main_token, .space, .eagerly_unquote); // name1223 try renderIdentifier(ais, tree, field.ast.main_token, .space, .eagerly_unquote); // name
1224 if (field.ast.align_expr != 0) {
1225 const lparen_token = tree.firstToken(field.ast.align_expr) - 1;
1226 const align_kw = lparen_token - 1;
1227 const rparen_token = tree.lastToken(field.ast.align_expr) + 1;
1228 try renderToken(ais, tree, align_kw, .none); // align
1229 try renderToken(ais, tree, lparen_token, .none); // (
1230 try renderExpression(gpa, ais, tree, field.ast.align_expr, .none); // alignment
1231 try renderToken(ais, tree, rparen_token, .space); // )
1232 }
1214 try renderToken(ais, tree, field.ast.main_token + 1, .space); // =1233 try renderToken(ais, tree, field.ast.main_token + 1, .space); // =
1215 return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value1234 return renderExpressionComma(gpa, ais, tree, field.ast.value_expr, space); // value
1216 }1235 }
src/AstGen.zig+3-2
...@@ -4854,8 +4854,9 @@ fn containerDecl(...@@ -4854,8 +4854,9 @@ fn containerDecl(
4854 },4854 },
4855 );4855 );
4856 }4856 }
4857 // Alignment expressions in enums are caught by the parser.4857 if (member.ast.align_expr != 0) {
4858 assert(member.ast.align_expr == 0);4858 return astgen.failNode(member.ast.align_expr, "enum fields cannot be aligned", .{});
4859 }
48594860
4860 const name_token = member.ast.main_token;4861 const name_token = member.ast.main_token;
4861 if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {4862 if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {
src/Sema.zig+41-8
...@@ -11834,6 +11834,12 @@ fn analyzeTupleCat(...@@ -11834,6 +11834,12 @@ fn analyzeTupleCat(
11834 if (dest_fields == 0) {11834 if (dest_fields == 0) {
11835 return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value));11835 return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value));
11836 }11836 }
11837 if (lhs_len == 0) {
11838 return rhs;
11839 }
11840 if (rhs_len == 0) {
11841 return lhs;
11842 }
11837 const final_len = try sema.usizeCast(block, rhs_src, dest_fields);11843 const final_len = try sema.usizeCast(block, rhs_src, dest_fields);
1183811844
11839 const types = try sema.arena.alloc(Type, final_len);11845 const types = try sema.arena.alloc(Type, final_len);
...@@ -11880,13 +11886,13 @@ fn analyzeTupleCat(...@@ -11880,13 +11886,13 @@ fn analyzeTupleCat(
11880 var i: u32 = 0;11886 var i: u32 = 0;
11881 while (i < lhs_len) : (i += 1) {11887 while (i < lhs_len) : (i += 1) {
11882 const operand_src = lhs_src; // TODO better source location11888 const operand_src = lhs_src; // TODO better source location
11883 element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, @intCast(u32, i), lhs_ty);11889 element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, i, lhs_ty);
11884 }11890 }
11885 i = 0;11891 i = 0;
11886 while (i < rhs_len) : (i += 1) {11892 while (i < rhs_len) : (i += 1) {
11887 const operand_src = rhs_src; // TODO better source location11893 const operand_src = rhs_src; // TODO better source location
11888 element_refs[i + lhs_len] =11894 element_refs[i + lhs_len] =
11889 try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, i), rhs_ty);11895 try sema.tupleFieldValByIndex(block, operand_src, rhs, i, rhs_ty);
11890 }11896 }
1189111897
11892 return block.addAggregateInit(tuple_ty, element_refs);11898 return block.addAggregateInit(tuple_ty, element_refs);
...@@ -18502,8 +18508,12 @@ fn reifyStruct(...@@ -18502,8 +18508,12 @@ fn reifyStruct(
18502 }18508 }
18503 const abi_align = @intCast(u29, (try alignment_val.getUnsignedIntAdvanced(target, sema)).?);18509 const abi_align = @intCast(u29, (try alignment_val.getUnsignedIntAdvanced(target, sema)).?);
1850418510
18505 if (layout == .Packed and abi_align != 0) {18511 if (layout == .Packed) {
18506 return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});18512 if (abi_align != 0) return sema.fail(block, src, "alignment in a packed struct field must be set to 0", .{});
18513 if (is_comptime_val.toBool()) return sema.fail(block, src, "packed struct fields cannot be marked comptime", .{});
18514 }
18515 if (layout == .Extern and is_comptime_val.toBool()) {
18516 return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{});
18507 }18517 }
1850818518
18509 const field_name = try name_val.toAllocatedBytes(18519 const field_name = try name_val.toAllocatedBytes(
...@@ -18512,6 +18522,25 @@ fn reifyStruct(...@@ -18512,6 +18522,25 @@ fn reifyStruct(
18512 mod,18522 mod,
18513 );18523 );
1851418524
18525 if (is_tuple) {
18526 const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch {
18527 return sema.fail(
18528 block,
18529 src,
18530 "tuple cannot have non-numeric field '{s}'",
18531 .{field_name},
18532 );
18533 };
18534
18535 if (field_index >= fields_len) {
18536 return sema.fail(
18537 block,
18538 src,
18539 "tuple field {} exceeds tuple field count",
18540 .{field_index},
18541 );
18542 }
18543 }
18515 const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);18544 const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
18516 if (gop.found_existing) {18545 if (gop.found_existing) {
18517 // TODO: better source location18546 // TODO: better source location
...@@ -18525,6 +18554,9 @@ fn reifyStruct(...@@ -18525,6 +18554,9 @@ fn reifyStruct(
18525 opt_val;18554 opt_val;
18526 break :blk try payload_val.copy(new_decl_arena_allocator);18555 break :blk try payload_val.copy(new_decl_arena_allocator);
18527 } else Value.initTag(.unreachable_value);18556 } else Value.initTag(.unreachable_value);
18557 if (is_comptime_val.toBool() and default_val.tag() == .unreachable_value) {
18558 return sema.fail(block, src, "comptime field without default initialization value", .{});
18559 }
1852818560
18529 var buffer: Value.ToTypeBuffer = undefined;18561 var buffer: Value.ToTypeBuffer = undefined;
18530 gop.value_ptr.* = .{18562 gop.value_ptr.* = .{
...@@ -27094,7 +27126,7 @@ fn coerceTupleToStruct(...@@ -27094,7 +27126,7 @@ fn coerceTupleToStruct(
2709427126
27095 const inst_ty = sema.typeOf(inst);27127 const inst_ty = sema.typeOf(inst);
27096 var runtime_src: ?LazySrcLoc = null;27128 var runtime_src: ?LazySrcLoc = null;
27097 const field_count = struct_ty.structFieldCount();27129 const field_count = inst_ty.structFieldCount();
27098 var field_i: u32 = 0;27130 var field_i: u32 = 0;
27099 while (field_i < field_count) : (field_i += 1) {27131 while (field_i < field_count) : (field_i += 1) {
27100 const field_src = inst_src; // TODO better source location27132 const field_src = inst_src; // TODO better source location
...@@ -27176,15 +27208,16 @@ fn coerceTupleToTuple(...@@ -27176,15 +27208,16 @@ fn coerceTupleToTuple(
27176 inst: Air.Inst.Ref,27208 inst: Air.Inst.Ref,
27177 inst_src: LazySrcLoc,27209 inst_src: LazySrcLoc,
27178) !Air.Inst.Ref {27210) !Air.Inst.Ref {
27179 const field_count = tuple_ty.structFieldCount();27211 const dest_field_count = tuple_ty.structFieldCount();
27180 const field_vals = try sema.arena.alloc(Value, field_count);27212 const field_vals = try sema.arena.alloc(Value, dest_field_count);
27181 const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len);27213 const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len);
27182 mem.set(Air.Inst.Ref, field_refs, .none);27214 mem.set(Air.Inst.Ref, field_refs, .none);
2718327215
27184 const inst_ty = sema.typeOf(inst);27216 const inst_ty = sema.typeOf(inst);
27217 const inst_field_count = inst_ty.structFieldCount();
27185 var runtime_src: ?LazySrcLoc = null;27218 var runtime_src: ?LazySrcLoc = null;
27186 var field_i: u32 = 0;27219 var field_i: u32 = 0;
27187 while (field_i < field_count) : (field_i += 1) {27220 while (field_i < inst_field_count) : (field_i += 1) {
27188 const field_src = inst_src; // TODO better source location27221 const field_src = inst_src; // TODO better source location
27189 const field_name = if (inst_ty.castTag(.anon_struct)) |payload|27222 const field_name = if (inst_ty.castTag(.anon_struct)) |payload|
27190 payload.data.names[field_i]27223 payload.data.names[field_i]
src/arch/x86_64/abi.zig+1
...@@ -552,6 +552,7 @@ test "C_C_D" {...@@ -552,6 +552,7 @@ test "C_C_D" {
552 .layout = .Extern,552 .layout = .Extern,
553 .status = .fully_resolved,553 .status = .fully_resolved,
554 .known_non_opv = true,554 .known_non_opv = true,
555 .is_tuple = false,
555 };556 };
556 var C_C_D = Type.Payload.Struct{ .data = &C_C_D_struct };557 var C_C_D = Type.Payload.Struct{ .data = &C_C_D_struct };
557558
src/type.zig+6-2
...@@ -804,7 +804,7 @@ pub const Type = extern union {...@@ -804,7 +804,7 @@ pub const Type = extern union {
804 return a_struct_obj == b_struct_obj;804 return a_struct_obj == b_struct_obj;
805 },805 },
806 .tuple, .empty_struct_literal => {806 .tuple, .empty_struct_literal => {
807 if (!b.isTuple()) return false;807 if (!b.isSimpleTuple()) return false;
808808
809 const a_tuple = a.tupleFields();809 const a_tuple = a.tupleFields();
810 const b_tuple = b.tupleFields();810 const b_tuple = b.tupleFields();
...@@ -5572,7 +5572,11 @@ pub const Type = extern union {...@@ -5572,7 +5572,11 @@ pub const Type = extern union {
55725572
5573 pub fn structFieldCount(ty: Type) usize {5573 pub fn structFieldCount(ty: Type) usize {
5574 switch (ty.tag()) {5574 switch (ty.tag()) {
5575 .@"struct" => return ty.castTag(.@"struct").?.data.fields.count(),5575 .@"struct" => {
5576 const struct_obj = ty.castTag(.@"struct").?.data;
5577 assert(struct_obj.haveFieldTypes());
5578 return struct_obj.fields.count();
5579 },
5576 .empty_struct, .empty_struct_literal => return 0,5580 .empty_struct, .empty_struct_literal => return 0,
5577 .tuple => return ty.castTag(.tuple).?.data.types.len,5581 .tuple => return ty.castTag(.tuple).?.data.types.len,
5578 .anon_struct => return ty.castTag(.anon_struct).?.data.types.len,5582 .anon_struct => return ty.castTag(.anon_struct).?.data.types.len,
test/behavior.zig+1
...@@ -200,6 +200,7 @@ test {...@@ -200,6 +200,7 @@ test {
200 _ = @import("behavior/packed_struct_explicit_backing_int.zig");200 _ = @import("behavior/packed_struct_explicit_backing_int.zig");
201 _ = @import("behavior/empty_union.zig");201 _ = @import("behavior/empty_union.zig");
202 _ = @import("behavior/inline_switch.zig");202 _ = @import("behavior/inline_switch.zig");
203 _ = @import("behavior/tuple_declarations.zig");
203 _ = @import("behavior/bugs/12723.zig");204 _ = @import("behavior/bugs/12723.zig");
204 _ = @import("behavior/bugs/12776.zig");205 _ = @import("behavior/bugs/12776.zig");
205 }206 }
test/behavior/tuple_declarations.zig created+79
...@@ -0,0 +1,79 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const testing = std.testing;
4const expect = testing.expect;
5const expectEqualStrings = testing.expectEqualStrings;
6
7test "tuple declaration type info" {
8 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
9 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
10 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
11
12 {
13 const T = struct { comptime u32 align(2) = 1, []const u8 };
14 const info = @typeInfo(T).Struct;
15
16 try expect(info.layout == .Auto);
17 try expect(info.backing_integer == null);
18 try expect(info.fields.len == 2);
19 try expect(info.decls.len == 0);
20 try expect(info.is_tuple);
21
22 try expectEqualStrings(info.fields[0].name, "0");
23 try expect(info.fields[0].field_type == u32);
24 try expect(@ptrCast(*const u32, @alignCast(@alignOf(u32), info.fields[0].default_value)).* == 1);
25 try expect(info.fields[0].is_comptime);
26 try expect(info.fields[0].alignment == 2);
27
28 try expectEqualStrings(info.fields[1].name, "1");
29 try expect(info.fields[1].field_type == []const u8);
30 try expect(info.fields[1].default_value == null);
31 try expect(!info.fields[1].is_comptime);
32 try expect(info.fields[1].alignment == @alignOf([]const u8));
33 }
34 {
35 const T = packed struct(u32) { u1, u30, u1 };
36 const info = @typeInfo(T).Struct;
37
38 try expect(std.mem.endsWith(u8, @typeName(T), "test.tuple declaration type info.T"));
39
40 try expect(info.layout == .Packed);
41 try expect(info.backing_integer == u32);
42 try expect(info.fields.len == 3);
43 try expect(info.decls.len == 0);
44 try expect(info.is_tuple);
45
46 try expectEqualStrings(info.fields[0].name, "0");
47 try expect(info.fields[0].field_type == u1);
48
49 try expectEqualStrings(info.fields[1].name, "1");
50 try expect(info.fields[1].field_type == u30);
51
52 try expectEqualStrings(info.fields[2].name, "2");
53 try expect(info.fields[2].field_type == u1);
54 }
55}
56
57test "Tuple declaration usage" {
58 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
59 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
60 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
61
62 const T = struct { u32, []const u8 };
63 var t: T = .{ 1, "foo" };
64 try expect(t[0] == 1);
65 try expectEqualStrings(t[1], "foo");
66
67 var mul = t ** 3;
68 try expect(@TypeOf(mul) != T);
69 try expect(mul.len == 6);
70 try expect(mul[2] == 1);
71 try expectEqualStrings(mul[3], "foo");
72
73 var t2: T = .{ 2, "bar" };
74 var cat = t ++ t2;
75 try expect(@TypeOf(cat) != T);
76 try expect(cat.len == 4);
77 try expect(cat[2] == 2);
78 try expectEqualStrings(cat[3], "bar");
79}
test/cases/compile_errors/alignment_of_enum_field_specified.zig+1-1
...@@ -11,4 +11,4 @@ export fn entry1() void {...@@ -11,4 +11,4 @@ export fn entry1() void {
11// backend=stage211// backend=stage2
12// target=native12// target=native
13//13//
14// :3:7: error: expected ',' after field14// :3:13: error: enum fields cannot be aligned
test/cases/compile_errors/reify_struct.zig created+80
...@@ -0,0 +1,80 @@
1comptime {
2 @Type(.{ .Struct = .{
3 .layout = .Auto,
4 .fields = &.{.{
5 .name = "foo",
6 .field_type = u32,
7 .default_value = null,
8 .is_comptime = false,
9 .alignment = 4,
10 }},
11 .decls = &.{},
12 .is_tuple = true,
13 } });
14}
15comptime {
16 @Type(.{ .Struct = .{
17 .layout = .Auto,
18 .fields = &.{.{
19 .name = "3",
20 .field_type = u32,
21 .default_value = null,
22 .is_comptime = false,
23 .alignment = 4,
24 }},
25 .decls = &.{},
26 .is_tuple = true,
27 } });
28}
29comptime {
30 @Type(.{ .Struct = .{
31 .layout = .Auto,
32 .fields = &.{.{
33 .name = "0",
34 .field_type = u32,
35 .default_value = null,
36 .is_comptime = true,
37 .alignment = 4,
38 }},
39 .decls = &.{},
40 .is_tuple = true,
41 } });
42}
43comptime {
44 @Type(.{ .Struct = .{
45 .layout = .Extern,
46 .fields = &.{.{
47 .name = "0",
48 .field_type = u32,
49 .default_value = null,
50 .is_comptime = true,
51 .alignment = 4,
52 }},
53 .decls = &.{},
54 .is_tuple = true,
55 } });
56}
57comptime {
58 @Type(.{ .Struct = .{
59 .layout = .Packed,
60 .fields = &.{.{
61 .name = "0",
62 .field_type = u32,
63 .default_value = null,
64 .is_comptime = true,
65 .alignment = 4,
66 }},
67 .decls = &.{},
68 .is_tuple = true,
69 } });
70}
71
72// error
73// backend=stage2
74// target=native
75//
76// :2:5: error: tuple cannot have non-numeric field 'foo'
77// :16:5: error: tuple field 3 exceeds tuple field count
78// :30:5: error: comptime field without default initialization value
79// :44:5: error: extern struct fields cannot be marked comptime
80// :58:5: error: alignment in a packed struct field must be set to 0
test/cases/compile_errors/struct_field_missing_type.zig deleted-13
...@@ -1,13 +0,0 @@
1const Letter = struct {
2 A,
3};
4export fn entry() void {
5 var a = Letter { .A = {} };
6 _ = a;
7}
8
9// error
10// backend=stage2
11// target=native
12//
13// :2:5: error: struct field missing type
test/cases/compile_errors/tuple_declarations.zig created+25
...@@ -0,0 +1,25 @@
1const E = enum {
2 *u32,
3};
4const U = union {
5 *u32,
6};
7const S = struct {
8 a: u32,
9 *u32,
10};
11const T = struct {
12 u32,
13 []const u8,
14
15 const a = 1;
16};
17
18// error
19// backend=stage2
20// target=native
21//
22// :2:5: error: enum field missing name
23// :5:5: error: union field missing name
24// :8:5: error: tuple field has a name
25// :15:5: error: tuple declarations cannot contain declarations
test/compile_errors.zig+2-2
...@@ -213,7 +213,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -213,7 +213,7 @@ pub fn addCases(ctx: *TestContext) !void {
213 case.backend = .stage2;213 case.backend = .stage2;
214214
215 case.addSourceFile("b.zig",215 case.addSourceFile("b.zig",
216 \\bad216 \\+
217 );217 );
218218
219 case.addError(219 case.addError(
...@@ -221,7 +221,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -221,7 +221,7 @@ pub fn addCases(ctx: *TestContext) !void {
221 \\ _ = (@sizeOf(@import("b.zig")));221 \\ _ = (@sizeOf(@import("b.zig")));
222 \\}222 \\}
223 , &[_][]const u8{223 , &[_][]const u8{
224 ":1:1: error: struct field missing type",224 ":1:1: error: expected type expression, found '+'",
225 });225 });
226 }226 }
227227
test/stage2/cbe.zig+1-1
...@@ -670,7 +670,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -670,7 +670,7 @@ pub fn addCases(ctx: *TestContext) !void {
670 \\ _ = E1.a;670 \\ _ = E1.a;
671 \\}671 \\}
672 , &.{672 , &.{
673 ":3:7: error: expected ',' after field",673 ":3:13: error: enum fields cannot be aligned",
674 });674 });
675675
676 // Redundant non-exhaustive enum mark.676 // Redundant non-exhaustive enum mark.