From 8eea73fb922c1c7fab4b8bf1717588464691a66e Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Tue, 22 Nov 2022 14:10:52 +0200 Subject: [PATCH] add tests for tuple declarations --- lib/std/zig/parse.zig | 2 +- lib/std/zig/render.zig | 19 +++++ src/AstGen.zig | 5 +- src/Sema.zig | 49 ++++++++++-- src/arch/x86_64/abi.zig | 1 + src/type.zig | 8 +- test/behavior.zig | 1 + test/behavior/tuple_declarations.zig | 79 ++++++++++++++++++ .../alignment_of_enum_field_specified.zig | 2 +- test/cases/compile_errors/reify_struct.zig | 80 +++++++++++++++++++ .../struct_field_missing_type.zig | 13 --- .../compile_errors/tuple_declarations.zig | 25 ++++++ test/compile_errors.zig | 4 +- test/stage2/cbe.zig | 2 +- 14 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 test/behavior/tuple_declarations.zig create mode 100644 test/cases/compile_errors/reify_struct.zig delete mode 100644 test/cases/compile_errors/struct_field_missing_type.zig create mode 100644 test/cases/compile_errors/tuple_declarations.zig diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index e0f0ebacbb..0226ec2e1d 100644 --- a/lib/std/zig/parse.zig +++ b/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(); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 2c9ed8632f..160ec13f0c 100644 --- a/lib/std/zig/render.zig +++ b/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 } diff --git a/src/AstGen.zig b/src/AstGen.zig index ddcdcf26a1..58727099eb 100644 --- a/src/AstGen.zig +++ b/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), "_")) { diff --git a/src/Sema.zig b/src/Sema.zig index b8d7b9f2b5..7265207e9b 100644 --- a/src/Sema.zig +++ b/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] diff --git a/src/arch/x86_64/abi.zig b/src/arch/x86_64/abi.zig index b0b9848de0..aa53da8169 100644 --- a/src/arch/x86_64/abi.zig +++ b/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 }; diff --git a/src/type.zig b/src/type.zig index 66be76ff31..21cfdf9d73 100644 --- a/src/type.zig +++ b/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, diff --git a/test/behavior.zig b/test/behavior.zig index 208e5bee29..c45c819762 100644 --- a/test/behavior.zig +++ b/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"); } diff --git a/test/behavior/tuple_declarations.zig b/test/behavior/tuple_declarations.zig new file mode 100644 index 0000000000..7beab1ca8f --- /dev/null +++ b/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"); +} diff --git a/test/cases/compile_errors/alignment_of_enum_field_specified.zig b/test/cases/compile_errors/alignment_of_enum_field_specified.zig index 2ff42f7a65..14ffa6f499 100644 --- a/test/cases/compile_errors/alignment_of_enum_field_specified.zig +++ b/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 diff --git a/test/cases/compile_errors/reify_struct.zig b/test/cases/compile_errors/reify_struct.zig new file mode 100644 index 0000000000..1c6001ced6 --- /dev/null +++ b/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 diff --git a/test/cases/compile_errors/struct_field_missing_type.zig b/test/cases/compile_errors/struct_field_missing_type.zig deleted file mode 100644 index 0e9151a5eb..0000000000 --- a/test/cases/compile_errors/struct_field_missing_type.zig +++ /dev/null @@ -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 diff --git a/test/cases/compile_errors/tuple_declarations.zig b/test/cases/compile_errors/tuple_declarations.zig new file mode 100644 index 0000000000..ec72561816 --- /dev/null +++ b/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 diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5454124df8..801d0464ea 100644 --- a/test/compile_errors.zig +++ b/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 '+'", }); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 68e618c4a9..c29dafb248 100644 --- a/test/stage2/cbe.zig +++ b/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.