add tests for tuple declarations

This commit is contained in:
Veikka Tuominen 2022-11-22 14:10:52 +02:00
parent 4cea15f12b
commit 8eea73fb92
14 changed files with 260 additions and 30 deletions

View File

@ -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();
}

View File

@ -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
}

View File

@ -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), "_")) {

View File

@ -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]

View File

@ -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 };

View File

@ -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,

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 '+'",
});
}

View File

@ -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.