aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/std/zig/Ast.zig41
-rw-r--r--lib/std/zig/parse.zig220
-rw-r--r--lib/std/zig/parser_test.zig28
-rw-r--r--lib/std/zig/render.zig96
-rw-r--r--src/AstGen.zig69
-rw-r--r--src/Module.zig7
-rw-r--r--src/Sema.zig318
-rw-r--r--src/Zir.zig5
-rw-r--r--src/arch/x86_64/abi.zig1
-rw-r--r--src/codegen/c.zig2
-rw-r--r--src/codegen/llvm.zig10
-rw-r--r--src/print_zir.zig19
-rw-r--r--src/type.zig20
-rw-r--r--src/value.zig4
-rw-r--r--test/behavior.zig1
-rw-r--r--test/behavior/tuple_declarations.zig79
-rw-r--r--test/cases/compile_errors/alignment_of_enum_field_specified.zig2
-rw-r--r--test/cases/compile_errors/reify_struct.zig80
-rw-r--r--test/cases/compile_errors/struct_field_missing_type.zig13
-rw-r--r--test/cases/compile_errors/tuple_declarations.zig25
-rw-r--r--test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig2
-rw-r--r--test/compile_errors.zig4
-rw-r--r--test/stage2/cbe.zig2
23 files changed, 676 insertions, 372 deletions
diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig
index 5dd0cdd5af..0966978ab8 100644
--- a/lib/std/zig/Ast.zig
+++ b/lib/std/zig/Ast.zig
@@ -559,6 +559,7 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex {
.container_field,
=> {
const name_token = main_tokens[n];
+ if (token_tags[name_token + 1] != .colon) return name_token - end_offset;
if (name_token > 0 and token_tags[name_token - 1] == .keyword_comptime) {
end_offset += 1;
}
@@ -1320,33 +1321,39 @@ pub fn containerField(tree: Ast, node: Node.Index) full.ContainerField {
assert(tree.nodes.items(.tag)[node] == .container_field);
const data = tree.nodes.items(.data)[node];
const extra = tree.extraData(data.rhs, Node.ContainerField);
+ const main_token = tree.nodes.items(.main_token)[node];
return tree.fullContainerField(.{
- .name_token = tree.nodes.items(.main_token)[node],
+ .main_token = main_token,
.type_expr = data.lhs,
.value_expr = extra.value_expr,
.align_expr = extra.align_expr,
+ .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}
pub fn containerFieldInit(tree: Ast, node: Node.Index) full.ContainerField {
assert(tree.nodes.items(.tag)[node] == .container_field_init);
const data = tree.nodes.items(.data)[node];
+ const main_token = tree.nodes.items(.main_token)[node];
return tree.fullContainerField(.{
- .name_token = tree.nodes.items(.main_token)[node],
+ .main_token = main_token,
.type_expr = data.lhs,
.value_expr = data.rhs,
.align_expr = 0,
+ .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}
pub fn containerFieldAlign(tree: Ast, node: Node.Index) full.ContainerField {
assert(tree.nodes.items(.tag)[node] == .container_field_align);
const data = tree.nodes.items(.data)[node];
+ const main_token = tree.nodes.items(.main_token)[node];
return tree.fullContainerField(.{
- .name_token = tree.nodes.items(.main_token)[node],
+ .main_token = main_token,
.type_expr = data.lhs,
.value_expr = 0,
.align_expr = data.rhs,
+ .tuple_like = tree.tokens.items(.tag)[main_token + 1] != .colon,
});
}
@@ -1944,10 +1951,14 @@ fn fullContainerField(tree: Ast, info: full.ContainerField.Components) full.Cont
.ast = info,
.comptime_token = null,
};
- // comptime name: type = init,
- // ^
- if (info.name_token > 0 and token_tags[info.name_token - 1] == .keyword_comptime) {
- result.comptime_token = info.name_token - 1;
+ if (token_tags[info.main_token] == .keyword_comptime) {
+ // comptime type = init,
+ // ^
+ result.comptime_token = info.main_token;
+ } else if (info.main_token > 0 and token_tags[info.main_token - 1] == .keyword_comptime) {
+ // comptime name: type = init,
+ // ^
+ result.comptime_token = info.main_token - 1;
}
return result;
}
@@ -2256,14 +2267,26 @@ pub const full = struct {
ast: Components,
pub const Components = struct {
- name_token: TokenIndex,
+ main_token: TokenIndex,
type_expr: Node.Index,
value_expr: Node.Index,
align_expr: Node.Index,
+ tuple_like: bool,
};
pub fn firstToken(cf: ContainerField) TokenIndex {
- return cf.comptime_token orelse cf.ast.name_token;
+ return cf.comptime_token orelse cf.ast.main_token;
+ }
+
+ pub fn convertToNonTupleLike(cf: *ContainerField, nodes: NodeList.Slice) void {
+ if (!cf.ast.tuple_like) return;
+ if (cf.ast.type_expr == 0) return;
+ if (nodes.items(.tag)[cf.ast.type_expr] != .identifier) return;
+
+ const ident = nodes.items(.main_token)[cf.ast.type_expr];
+ cf.ast.tuple_like = false;
+ cf.ast.main_token = ident;
+ cf.ast.type_expr = 0;
}
};
diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig
index 1be074fc27..0226ec2e1d 100644
--- a/lib/std/zig/parse.zig
+++ b/lib/std/zig/parse.zig
@@ -272,53 +272,6 @@ const Parser = struct {
trailing = false;
},
.keyword_comptime => switch (p.token_tags[p.tok_i + 1]) {
- .identifier => {
- p.tok_i += 1;
- const identifier = p.tok_i;
- defer last_field = identifier;
- const container_field = try p.expectContainerFieldRecoverable();
- if (container_field != 0) {
- switch (field_state) {
- .none => field_state = .seen,
- .err, .seen => {},
- .end => |node| {
- try p.warnMsg(.{
- .tag = .decl_between_fields,
- .token = p.nodes.items(.main_token)[node],
- });
- try p.warnMsg(.{
- .tag = .previous_field,
- .is_note = true,
- .token = last_field,
- });
- try p.warnMsg(.{
- .tag = .next_field,
- .is_note = true,
- .token = identifier,
- });
- // Continue parsing; error will be reported later.
- field_state = .err;
- },
- }
- try p.scratch.append(p.gpa, container_field);
- switch (p.token_tags[p.tok_i]) {
- .comma => {
- p.tok_i += 1;
- trailing = true;
- continue;
- },
- .r_brace, .eof => {
- trailing = false;
- break;
- },
- else => {},
- }
- // There is not allowed to be a decl after a field with no comma.
- // Report error but recover parser.
- try p.warn(.expected_comma_after_field);
- p.findNextContainerMember();
- }
- },
.l_brace => {
if (doc_comment) |some| {
try p.warnMsg(.{ .tag = .test_doc_comment, .token = some });
@@ -349,53 +302,15 @@ const Parser = struct {
},
else => {
p.tok_i += 1;
- try p.warn(.expected_block_or_field);
- },
- },
- .keyword_pub => {
- p.tok_i += 1;
- const top_level_decl = try p.expectTopLevelDeclRecoverable();
- if (top_level_decl != 0) {
- if (field_state == .seen) {
- field_state = .{ .end = top_level_decl };
- }
- try p.scratch.append(p.gpa, top_level_decl);
- }
- trailing = p.token_tags[p.tok_i - 1] == .semicolon;
- },
- .keyword_usingnamespace => {
- const node = try p.expectUsingNamespaceRecoverable();
- if (node != 0) {
- if (field_state == .seen) {
- field_state = .{ .end = node };
- }
- try p.scratch.append(p.gpa, node);
- }
- trailing = p.token_tags[p.tok_i - 1] == .semicolon;
- },
- .keyword_const,
- .keyword_var,
- .keyword_threadlocal,
- .keyword_export,
- .keyword_extern,
- .keyword_inline,
- .keyword_noinline,
- .keyword_fn,
- => {
- const top_level_decl = try p.expectTopLevelDeclRecoverable();
- if (top_level_decl != 0) {
- if (field_state == .seen) {
- field_state = .{ .end = top_level_decl };
- }
- try p.scratch.append(p.gpa, top_level_decl);
- }
- trailing = p.token_tags[p.tok_i - 1] == .semicolon;
- },
- .identifier => {
- const identifier = p.tok_i;
- defer last_field = identifier;
- const container_field = try p.expectContainerFieldRecoverable();
- if (container_field != 0) {
+ const identifier = p.tok_i;
+ defer last_field = identifier;
+ const container_field = p.expectContainerField() catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.ParseError => {
+ p.findNextContainerMember();
+ continue;
+ },
+ };
switch (field_state) {
.none => field_state = .seen,
.err, .seen => {},
@@ -435,7 +350,46 @@ const Parser = struct {
// Report error but recover parser.
try p.warn(.expected_comma_after_field);
p.findNextContainerMember();
+ },
+ },
+ .keyword_pub => {
+ p.tok_i += 1;
+ const top_level_decl = try p.expectTopLevelDeclRecoverable();
+ if (top_level_decl != 0) {
+ if (field_state == .seen) {
+ field_state = .{ .end = top_level_decl };
+ }
+ try p.scratch.append(p.gpa, top_level_decl);
+ }
+ trailing = p.token_tags[p.tok_i - 1] == .semicolon;
+ },
+ .keyword_usingnamespace => {
+ const node = try p.expectUsingNamespaceRecoverable();
+ if (node != 0) {
+ if (field_state == .seen) {
+ field_state = .{ .end = node };
+ }
+ try p.scratch.append(p.gpa, node);
+ }
+ trailing = p.token_tags[p.tok_i - 1] == .semicolon;
+ },
+ .keyword_const,
+ .keyword_var,
+ .keyword_threadlocal,
+ .keyword_export,
+ .keyword_extern,
+ .keyword_inline,
+ .keyword_noinline,
+ .keyword_fn,
+ => {
+ const top_level_decl = try p.expectTopLevelDeclRecoverable();
+ if (top_level_decl != 0) {
+ if (field_state == .seen) {
+ field_state = .{ .end = top_level_decl };
+ }
+ try p.scratch.append(p.gpa, top_level_decl);
}
+ trailing = p.token_tags[p.tok_i - 1] == .semicolon;
},
.eof, .r_brace => {
if (doc_comment) |tok| {
@@ -451,11 +405,57 @@ const Parser = struct {
error.OutOfMemory => return error.OutOfMemory,
error.ParseError => false,
};
- if (!c_container) {
- try p.warn(.expected_container_members);
- // This was likely not supposed to end yet; try to find the next declaration.
- p.findNextContainerMember();
+ if (c_container) continue;
+
+ const identifier = p.tok_i;
+ defer last_field = identifier;
+ const container_field = p.expectContainerField() catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.ParseError => {
+ p.findNextContainerMember();
+ continue;
+ },
+ };
+ switch (field_state) {
+ .none => field_state = .seen,
+ .err, .seen => {},
+ .end => |node| {
+ try p.warnMsg(.{
+ .tag = .decl_between_fields,
+ .token = p.nodes.items(.main_token)[node],
+ });
+ try p.warnMsg(.{
+ .tag = .previous_field,
+ .is_note = true,
+ .token = last_field,
+ });
+ try p.warnMsg(.{
+ .tag = .next_field,
+ .is_note = true,
+ .token = identifier,
+ });
+ // Continue parsing; error will be reported later.
+ field_state = .err;
+ },
}
+ try p.scratch.append(p.gpa, container_field);
+ switch (p.token_tags[p.tok_i]) {
+ .comma => {
+ p.tok_i += 1;
+ trailing = true;
+ continue;
+ },
+ .r_brace, .eof => {
+ trailing = false;
+ break;
+ },
+ else => {},
+ }
+ // There is not allowed to be a decl after a field with no comma.
+ // Report error but recover parser.
+ try p.warn(.expected_comma_after_field);
+ p.findNextContainerMember();
+ continue;
},
}
}
@@ -875,12 +875,16 @@ const Parser = struct {
/// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
fn expectContainerField(p: *Parser) !Node.Index {
+ var main_token = p.tok_i;
_ = p.eatToken(.keyword_comptime);
- const name_token = p.assertToken(.identifier);
+ const tuple_like = p.token_tags[p.tok_i] != .identifier or p.token_tags[p.tok_i + 1] != .colon;
+ if (!tuple_like) {
+ main_token = p.assertToken(.identifier);
+ }
var align_expr: Node.Index = 0;
var type_expr: Node.Index = 0;
- if (p.eatToken(.colon)) |_| {
+ if (p.eatToken(.colon) != null or tuple_like) {
type_expr = try p.expectTypeExpr();
align_expr = try p.parseByteAlign();
}
@@ -890,7 +894,7 @@ const Parser = struct {
if (align_expr == 0) {
return p.addNode(.{
.tag = .container_field_init,
- .main_token = name_token,
+ .main_token = main_token,
.data = .{
.lhs = type_expr,
.rhs = value_expr,
@@ -899,7 +903,7 @@ const Parser = struct {
} else if (value_expr == 0) {
return p.addNode(.{
.tag = .container_field_align,
- .main_token = name_token,
+ .main_token = main_token,
.data = .{
.lhs = type_expr,
.rhs = align_expr,
@@ -908,7 +912,7 @@ const Parser = struct {
} else {
return p.addNode(.{
.tag = .container_field,
- .main_token = name_token,
+ .main_token = main_token,
.data = .{
.lhs = type_expr,
.rhs = try p.addExtra(Node.ContainerField{
@@ -920,16 +924,6 @@ const Parser = struct {
}
}
- fn expectContainerFieldRecoverable(p: *Parser) error{OutOfMemory}!Node.Index {
- return p.expectContainerField() catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.ParseError => {
- p.findNextContainerMember();
- return null_node;
- },
- };
- }
-
/// Statement
/// <- KEYWORD_comptime? VarDecl
/// / KEYWORD_comptime BlockExprStatement
diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig
index bf1da35d37..e554c51f70 100644
--- a/lib/std/zig/parser_test.zig
+++ b/lib/std/zig/parser_test.zig
@@ -1,3 +1,15 @@
+test "zig fmt: tuple struct" {
+ try testCanonical(
+ \\const T = struct {
+ \\ comptime u32,
+ \\ *u32 = 1,
+ \\ // needs to be wrapped in parentheses to not be parsed as a function decl
+ \\ (fn () void) align(1),
+ \\};
+ \\
+ );
+}
+
test "zig fmt: preserves clobbers in inline asm with stray comma" {
try testCanonical(
\\fn foo() void {
@@ -265,14 +277,6 @@ test "zig fmt: decl between fields" {
});
}
-test "zig fmt: eof after missing comma" {
- try testError(
- \\foo()
- , &[_]Error{
- .expected_comma_after_field,
- });
-}
-
test "zig fmt: errdefer with payload" {
try testCanonical(
\\pub fn main() anyerror!void {
@@ -5732,8 +5736,8 @@ test "recovery: missing semicolon" {
test "recovery: invalid container members" {
try testError(
\\usingnamespace;
- \\foo+
- \\bar@,
+ \\@foo()+
+ \\@bar()@,
\\while (a == 2) { test "" {}}
\\test "" {
\\ a & b
@@ -5741,7 +5745,7 @@ test "recovery: invalid container members" {
, &[_]Error{
.expected_expr,
.expected_comma_after_field,
- .expected_container_members,
+ .expected_type_expr,
.expected_semi_after_stmt,
});
}
@@ -5820,7 +5824,7 @@ test "recovery: invalid comptime" {
try testError(
\\comptime
, &[_]Error{
- .expected_block_or_field,
+ .expected_type_expr,
});
}
diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig
index 665c31a0af..160ec13f0c 100644
--- a/lib/std/zig/render.zig
+++ b/lib/std/zig/render.zig
@@ -40,14 +40,34 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: Ast) Error!void {
/// Render all members in the given slice, keeping empty lines where appropriate
fn renderMembers(gpa: Allocator, ais: *Ais, tree: Ast, members: []const Ast.Node.Index) Error!void {
if (members.len == 0) return;
- try renderMember(gpa, ais, tree, members[0], .newline);
+ var is_tuple = true;
+ for (members) |member| {
+ const tuple_like = switch (tree.nodes.items(.tag)[member]) {
+ .container_field_init => tree.containerFieldInit(member).ast.tuple_like,
+ .container_field_align => tree.containerFieldAlign(member).ast.tuple_like,
+ .container_field => tree.containerField(member).ast.tuple_like,
+ else => continue,
+ };
+ if (!tuple_like) {
+ is_tuple = false;
+ break;
+ }
+ }
+ try renderMember(gpa, ais, tree, members[0], is_tuple, .newline);
for (members[1..]) |member| {
try renderExtraNewline(ais, tree, member);
- try renderMember(gpa, ais, tree, member, .newline);
+ try renderMember(gpa, ais, tree, member, is_tuple, .newline);
}
}
-fn renderMember(gpa: Allocator, ais: *Ais, tree: Ast, decl: Ast.Node.Index, space: Space) Error!void {
+fn renderMember(
+ gpa: Allocator,
+ ais: *Ais,
+ tree: Ast,
+ decl: Ast.Node.Index,
+ is_tuple: bool,
+ space: Space,
+) Error!void {
const token_tags = tree.tokens.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const datas = tree.nodes.items(.data);
@@ -161,9 +181,9 @@ fn renderMember(gpa: Allocator, ais: *Ais, tree: Ast, decl: Ast.Node.Index, spac
try renderExpression(gpa, ais, tree, datas[decl].rhs, space);
},
- .container_field_init => return renderContainerField(gpa, ais, tree, tree.containerFieldInit(decl), space),
- .container_field_align => return renderContainerField(gpa, ais, tree, tree.containerFieldAlign(decl), space),
- .container_field => return renderContainerField(gpa, ais, tree, tree.containerField(decl), space),
+ .container_field_init => return renderContainerField(gpa, ais, tree, tree.containerFieldInit(decl), is_tuple, space),
+ .container_field_align => return renderContainerField(gpa, ais, tree, tree.containerFieldAlign(decl), is_tuple, space),
+ .container_field => return renderContainerField(gpa, ais, tree, tree.containerField(decl), is_tuple, space),
.@"comptime" => return renderExpression(gpa, ais, tree, decl, space),
.root => unreachable,
@@ -1158,18 +1178,34 @@ fn renderContainerField(
gpa: Allocator,
ais: *Ais,
tree: Ast,
- field: Ast.full.ContainerField,
+ field_param: Ast.full.ContainerField,
+ is_tuple: bool,
space: Space,
) Error!void {
+ var field = field_param;
+ if (!is_tuple) field.convertToNonTupleLike(tree.nodes);
+
if (field.comptime_token) |t| {
try renderToken(ais, tree, t, .space); // comptime
}
if (field.ast.type_expr == 0 and field.ast.value_expr == 0) {
- return renderIdentifierComma(ais, tree, field.ast.name_token, space, .eagerly_unquote); // name
+ 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) {
- try renderIdentifier(ais, tree, field.ast.name_token, .none, .eagerly_unquote); // name
- try renderToken(ais, tree, field.ast.name_token + 1, .space); // :
+ if (!field.ast.tuple_like) {
+ try renderIdentifier(ais, tree, field.ast.main_token, .none, .eagerly_unquote); // name
+ try renderToken(ais, tree, field.ast.main_token + 1, .space); // :
+ }
if (field.ast.align_expr != 0) {
try renderExpression(gpa, ais, tree, field.ast.type_expr, .space); // type
@@ -1184,13 +1220,23 @@ fn renderContainerField(
}
}
if (field.ast.type_expr == 0 and field.ast.value_expr != 0) {
- try renderIdentifier(ais, tree, field.ast.name_token, .space, .eagerly_unquote); // name
- try renderToken(ais, tree, field.ast.name_token + 1, .space); // =
+ 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
}
-
- try renderIdentifier(ais, tree, field.ast.name_token, .none, .eagerly_unquote); // name
- try renderToken(ais, tree, field.ast.name_token + 1, .space); // :
+ if (!field.ast.tuple_like) {
+ try renderIdentifier(ais, tree, field.ast.main_token, .none, .eagerly_unquote); // name
+ try renderToken(ais, tree, field.ast.main_token + 1, .space); // :
+ }
try renderExpression(gpa, ais, tree, field.ast.type_expr, .space); // type
if (field.ast.align_expr != 0) {
@@ -1901,6 +1947,20 @@ fn renderContainerDecl(
try renderToken(ais, tree, layout_token, .space);
}
+ var is_tuple = token_tags[container_decl.ast.main_token] == .keyword_struct;
+ if (is_tuple) for (container_decl.ast.members) |member| {
+ const tuple_like = switch (tree.nodes.items(.tag)[member]) {
+ .container_field_init => tree.containerFieldInit(member).ast.tuple_like,
+ .container_field_align => tree.containerFieldAlign(member).ast.tuple_like,
+ .container_field => tree.containerField(member).ast.tuple_like,
+ else => continue,
+ };
+ if (!tuple_like) {
+ is_tuple = false;
+ break;
+ }
+ };
+
var lbrace: Ast.TokenIndex = undefined;
if (container_decl.ast.enum_token) |enum_token| {
try renderToken(ais, tree, container_decl.ast.main_token, .none); // union
@@ -1967,7 +2027,7 @@ fn renderContainerDecl(
// Print all the declarations on the same line.
try renderToken(ais, tree, lbrace, .space); // lbrace
for (container_decl.ast.members) |member| {
- try renderMember(gpa, ais, tree, member, .space);
+ try renderMember(gpa, ais, tree, member, is_tuple, .space);
}
return renderToken(ais, tree, rbrace, space); // rbrace
}
@@ -1985,9 +2045,9 @@ fn renderContainerDecl(
.container_field_init,
.container_field_align,
.container_field,
- => try renderMember(gpa, ais, tree, member, .comma),
+ => try renderMember(gpa, ais, tree, member, is_tuple, .comma),
- else => try renderMember(gpa, ais, tree, member, .newline),
+ else => try renderMember(gpa, ais, tree, member, is_tuple, .newline),
}
}
ais.popIndent();
diff --git a/src/AstGen.zig b/src/AstGen.zig
index db80a2f350..58727099eb 100644
--- a/src/AstGen.zig
+++ b/src/AstGen.zig
@@ -4386,6 +4386,7 @@ fn structDeclInner(
.backing_int_body_len = 0,
.known_non_opv = false,
.known_comptime_only = false,
+ .is_tuple = false,
});
return indexToRef(decl_inst);
}
@@ -4467,22 +4468,53 @@ fn structDeclInner(
// No defer needed here because it is handled by `wip_members.deinit()` above.
const bodies_start = astgen.scratch.items.len;
+ var is_tuple = false;
+ const node_tags = tree.nodes.items(.tag);
+ for (container_decl.ast.members) |member_node| {
+ switch (node_tags[member_node]) {
+ .container_field_init => is_tuple = tree.containerFieldInit(member_node).ast.tuple_like,
+ .container_field_align => is_tuple = tree.containerFieldAlign(member_node).ast.tuple_like,
+ .container_field => is_tuple = tree.containerField(member_node).ast.tuple_like,
+ else => continue,
+ }
+ if (is_tuple) break;
+ }
+ if (is_tuple) for (container_decl.ast.members) |member_node| {
+ switch (node_tags[member_node]) {
+ .container_field_init,
+ .container_field_align,
+ .container_field,
+ .@"comptime",
+ => continue,
+ else => {
+ return astgen.failNode(member_node, "tuple declarations cannot contain declarations", .{});
+ },
+ }
+ };
+
var known_non_opv = false;
var known_comptime_only = false;
for (container_decl.ast.members) |member_node| {
- const member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
+ var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
.decl => continue,
.field => |field| field,
};
- const field_name = try astgen.identAsString(member.ast.name_token);
- wip_members.appendToField(field_name);
+ if (!is_tuple) {
+ member.convertToNonTupleLike(astgen.tree.nodes);
+ assert(!member.ast.tuple_like);
+
+ const field_name = try astgen.identAsString(member.ast.main_token);
+ wip_members.appendToField(field_name);
+ } else if (!member.ast.tuple_like) {
+ return astgen.failTok(member.ast.main_token, "tuple field has a name", .{});
+ }
const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
wip_members.appendToField(doc_comment_index);
if (member.ast.type_expr == 0) {
- return astgen.failTok(member.ast.name_token, "struct field missing type", .{});
+ return astgen.failTok(member.ast.main_token, "struct field missing type", .{});
}
const field_type = try typeExpr(&block_scope, &namespace.base, member.ast.type_expr);
@@ -4562,6 +4594,7 @@ fn structDeclInner(
.backing_int_body_len = @intCast(u32, backing_int_body_len),
.known_non_opv = known_non_opv,
.known_comptime_only = known_comptime_only,
+ .is_tuple = is_tuple,
});
wip_members.finishBits(bits_per_field);
@@ -4640,15 +4673,19 @@ fn unionDeclInner(
defer wip_members.deinit();
for (members) |member_node| {
- const member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
+ var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
.decl => continue,
.field => |field| field,
};
+ member.convertToNonTupleLike(astgen.tree.nodes);
+ if (member.ast.tuple_like) {
+ return astgen.failTok(member.ast.main_token, "union field missing name", .{});
+ }
if (member.comptime_token) |comptime_token| {
return astgen.failTok(comptime_token, "union fields cannot be marked comptime", .{});
}
- const field_name = try astgen.identAsString(member.ast.name_token);
+ const field_name = try astgen.identAsString(member.ast.main_token);
wip_members.appendToField(field_name);
const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
@@ -4787,7 +4824,7 @@ fn containerDecl(
var nonexhaustive_node: Ast.Node.Index = 0;
var nonfinal_nonexhaustive = false;
for (container_decl.ast.members) |member_node| {
- const member = switch (node_tags[member_node]) {
+ var member = switch (node_tags[member_node]) {
.container_field_init => tree.containerFieldInit(member_node),
.container_field_align => tree.containerFieldAlign(member_node),
.container_field => tree.containerField(member_node),
@@ -4796,6 +4833,10 @@ fn containerDecl(
continue;
},
};
+ member.convertToNonTupleLike(astgen.tree.nodes);
+ if (member.ast.tuple_like) {
+ return astgen.failTok(member.ast.main_token, "enum field missing name", .{});
+ }
if (member.comptime_token) |comptime_token| {
return astgen.failTok(comptime_token, "enum fields cannot be marked comptime", .{});
}
@@ -4813,10 +4854,11 @@ 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.name_token;
+ const name_token = member.ast.main_token;
if (mem.eql(u8, tree.tokenSlice(name_token), "_")) {
if (nonexhaustive_node != 0) {
return astgen.failNodeNotes(
@@ -4915,15 +4957,16 @@ fn containerDecl(
for (container_decl.ast.members) |member_node| {
if (member_node == counts.nonexhaustive_node)
continue;
- const member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
+ var member = switch (try containerMember(&block_scope, &namespace.base, &wip_members, member_node)) {
.decl => continue,
.field => |field| field,
};
+ member.convertToNonTupleLike(astgen.tree.nodes);
assert(member.comptime_token == null);
assert(member.ast.type_expr == 0);
assert(member.ast.align_expr == 0);
- const field_name = try astgen.identAsString(member.ast.name_token);
+ const field_name = try astgen.identAsString(member.ast.main_token);
wip_members.appendToField(field_name);
const doc_comment_index = try astgen.docCommentAsString(member.firstToken());
@@ -11786,6 +11829,7 @@ const GenZir = struct {
layout: std.builtin.Type.ContainerLayout,
known_non_opv: bool,
known_comptime_only: bool,
+ is_tuple: bool,
}) !void {
const astgen = gz.astgen;
const gpa = astgen.gpa;
@@ -11820,6 +11864,7 @@ const GenZir = struct {
.has_backing_int = args.backing_int_ref != .none,
.known_non_opv = args.known_non_opv,
.known_comptime_only = args.known_comptime_only,
+ .is_tuple = args.is_tuple,
.name_strategy = gz.anon_name_strategy,
.layout = args.layout,
}),
diff --git a/src/Module.zig b/src/Module.zig
index d598993c3f..bb03616dff 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -938,6 +938,7 @@ pub const Struct = struct {
known_non_opv: bool,
requires_comptime: PropertyBoolean = .unknown,
have_field_inits: bool = false,
+ is_tuple: bool,
pub const Fields = std.StringArrayHashMapUnmanaged(Field);
@@ -4458,6 +4459,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
.layout = .Auto,
.status = .none,
.known_non_opv = undefined,
+ .is_tuple = undefined, // set below
.namespace = .{
.parent = null,
.ty = struct_ty,
@@ -4489,6 +4491,9 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
assert(file.zir_loaded);
const main_struct_inst = Zir.main_struct_inst;
struct_obj.zir_index = main_struct_inst;
+ const extended = file.zir.instructions.items(.data)[main_struct_inst].extended;
+ const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
+ struct_obj.is_tuple = small.is_tuple;
var sema_arena = std.heap.ArenaAllocator.init(gpa);
defer sema_arena.deinit();
@@ -6138,7 +6143,7 @@ fn queryFieldSrc(
.name => .{
.file_scope = file_scope,
.parent_decl_node = 0,
- .lazy = .{ .token_abs = field.ast.name_token },
+ .lazy = .{ .token_abs = field.ast.main_token },
},
.type => .{
.file_scope = file_scope,
diff --git a/src/Sema.zig b/src/Sema.zig
index 0a28d2d7d0..ad78d81800 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -2519,6 +2519,7 @@ fn zirStructDecl(
.layout = small.layout,
.status = .none,
.known_non_opv = undefined,
+ .is_tuple = small.is_tuple,
.namespace = .{
.parent = block.namespace,
.ty = struct_ty,
@@ -4291,13 +4292,12 @@ fn zirValidateArrayInit(
if (instrs.len != array_len) switch (array_ty.zigTypeTag()) {
.Struct => {
- const struct_obj = array_ty.castTag(.tuple).?.data;
var root_msg: ?*Module.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
- for (struct_obj.values) |default_val, i| {
- if (i < instrs.len) continue;
-
+ var i = instrs.len;
+ while (i < array_len) : (i += 1) {
+ const default_val = array_ty.structFieldDefaultValue(i);
if (default_val.tag() == .unreachable_value) {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
@@ -7230,7 +7230,7 @@ fn instantiateGenericCall(
}
fn resolveTupleLazyValues(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void {
- if (!ty.isTuple()) return;
+ if (!ty.isSimpleTuple()) return;
const tuple = ty.tupleFields();
for (tuple.values) |field_val, i| {
try sema.resolveTupleLazyValues(block, src, tuple.types[i]);
@@ -7295,7 +7295,7 @@ fn zirElemTypeIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
const indexable_ty = try sema.resolveType(block, .unneeded, bin.lhs);
assert(indexable_ty.isIndexable()); // validated by a previous instruction
if (indexable_ty.zigTypeTag() == .Struct) {
- const elem_type = indexable_ty.tupleFields().types[@enumToInt(bin.rhs)];
+ const elem_type = indexable_ty.structFieldType(@enumToInt(bin.rhs));
return sema.addType(elem_type);
} else {
const elem_type = indexable_ty.elemType2();
@@ -11827,13 +11827,19 @@ fn analyzeTupleCat(
const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node };
const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node };
- const lhs_tuple = lhs_ty.tupleFields();
- const rhs_tuple = rhs_ty.tupleFields();
- const dest_fields = lhs_tuple.types.len + rhs_tuple.types.len;
+ const lhs_len = lhs_ty.structFieldCount();
+ const rhs_len = rhs_ty.structFieldCount();
+ const dest_fields = lhs_len + rhs_len;
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);
@@ -11841,20 +11847,23 @@ fn analyzeTupleCat(
const opt_runtime_src = rs: {
var runtime_src: ?LazySrcLoc = null;
- for (lhs_tuple.types) |ty, i| {
- types[i] = ty;
- values[i] = lhs_tuple.values[i];
+ var i: u32 = 0;
+ while (i < lhs_len) : (i += 1) {
+ types[i] = lhs_ty.structFieldType(i);
+ const default_val = lhs_ty.structFieldDefaultValue(i);
+ values[i] = default_val;
const operand_src = lhs_src; // TODO better source location
- if (values[i].tag() == .unreachable_value) {
+ if (default_val.tag() == .unreachable_value) {
runtime_src = operand_src;
}
}
- const offset = lhs_tuple.types.len;
- for (rhs_tuple.types) |ty, i| {
- types[i + offset] = ty;
- values[i + offset] = rhs_tuple.values[i];
+ i = 0;
+ while (i < rhs_len) : (i += 1) {
+ types[i + lhs_len] = rhs_ty.structFieldType(i);
+ const default_val = rhs_ty.structFieldDefaultValue(i);
+ values[i + lhs_len] = default_val;
const operand_src = rhs_src; // TODO better source location
- if (rhs_tuple.values[i].tag() == .unreachable_value) {
+ if (default_val.tag() == .unreachable_value) {
runtime_src = operand_src;
}
}
@@ -11874,15 +11883,16 @@ fn analyzeTupleCat(
try sema.requireRuntimeBlock(block, src, runtime_src);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len);
- for (lhs_tuple.types) |_, i| {
+ 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);
}
- const offset = lhs_tuple.types.len;
- for (rhs_tuple.types) |_, i| {
+ i = 0;
+ while (i < rhs_len) : (i += 1) {
const operand_src = rhs_src; // TODO better source location
- element_refs[i + offset] =
- try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, i), rhs_ty);
+ element_refs[i + lhs_len] =
+ try sema.tupleFieldValByIndex(block, operand_src, rhs, i, rhs_ty);
}
return block.addAggregateInit(tuple_ty, element_refs);
@@ -12107,12 +12117,11 @@ fn analyzeTupleMul(
factor: u64,
) CompileError!Air.Inst.Ref {
const operand_ty = sema.typeOf(operand);
- const operand_tuple = operand_ty.tupleFields();
const src = LazySrcLoc.nodeOffset(src_node);
const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node };
const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node };
- const tuple_len = operand_tuple.types.len;
+ const tuple_len = operand_ty.structFieldCount();
const final_len_u64 = std.math.mul(u64, tuple_len, factor) catch
return sema.fail(block, rhs_src, "operation results in overflow", .{});
@@ -12126,18 +12135,19 @@ fn analyzeTupleMul(
const opt_runtime_src = rs: {
var runtime_src: ?LazySrcLoc = null;
- for (operand_tuple.types) |ty, i| {
- types[i] = ty;
- values[i] = operand_tuple.values[i];
+ var i: u32 = 0;
+ while (i < tuple_len) : (i += 1) {
+ types[i] = operand_ty.structFieldType(i);
+ values[i] = operand_ty.structFieldDefaultValue(i);
const operand_src = lhs_src; // TODO better source location
if (values[i].tag() == .unreachable_value) {
runtime_src = operand_src;
}
}
- var i: usize = 1;
+ i = 0;
while (i < factor) : (i += 1) {
- mem.copy(Type, types[tuple_len * i ..], operand_tuple.types);
- mem.copy(Value, values[tuple_len * i ..], operand_tuple.values);
+ mem.copy(Type, types[tuple_len * i ..], types[0..tuple_len]);
+ mem.copy(Value, values[tuple_len * i ..], values[0..tuple_len]);
}
break :rs runtime_src;
};
@@ -12155,11 +12165,12 @@ fn analyzeTupleMul(
try sema.requireRuntimeBlock(block, src, runtime_src);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len);
- for (operand_tuple.types) |_, i| {
+ var i: u32 = 0;
+ while (i < tuple_len) : (i += 1) {
const operand_src = lhs_src; // TODO better source location
element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, operand, @intCast(u32, i), operand_ty);
}
- var i: usize = 1;
+ i = 1;
while (i < factor) : (i += 1) {
mem.copy(Air.Inst.Ref, element_refs[tuple_len * i ..], element_refs[0..tuple_len]);
}
@@ -15593,7 +15604,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
const layout = struct_ty.containerLayout();
const struct_field_vals = fv: {
- if (struct_ty.isTupleOrAnonStruct()) {
+ if (struct_ty.isSimpleTupleOrAnonStruct()) {
const tuple = struct_ty.tupleFields();
const field_types = tuple.types;
const struct_field_vals = try fields_anon_decl.arena().alloc(Value, field_types.len);
@@ -17063,10 +17074,12 @@ fn finishStructInit(
}
}
} else if (struct_ty.isTuple()) {
- const struct_obj = struct_ty.castTag(.tuple).?.data;
- for (struct_obj.values) |default_val, i| {
+ var i: u32 = 0;
+ const len = struct_ty.structFieldCount();
+ while (i < len) : (i += 1) {
if (field_inits[i] != .none) continue;
+ const default_val = struct_ty.structFieldDefaultValue(i);
if (default_val.tag() == .unreachable_value) {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
@@ -17075,7 +17088,7 @@ fn finishStructInit(
root_msg = try sema.errMsg(block, init_src, template, .{i});
}
} else {
- field_inits[i] = try sema.addConstant(struct_obj.types[i], default_val);
+ field_inits[i] = try sema.addConstant(struct_ty.structFieldType(i), default_val);
}
}
} else {
@@ -17297,7 +17310,7 @@ fn zirArrayInit(
const resolved_arg = try sema.resolveInst(arg);
const arg_src = src; // TODO better source location
const elem_ty = if (array_ty.zigTypeTag() == .Struct)
- array_ty.tupleFields().types[i]
+ array_ty.structFieldType(i)
else
array_ty.elemType2();
resolved_args[i] = try sema.coerce(block, elem_ty, resolved_arg, arg_src);
@@ -17337,12 +17350,11 @@ fn zirArrayInit(
const alloc = try block.addTy(.alloc, alloc_ty);
if (array_ty.isTuple()) {
- const types = array_ty.tupleFields().types;
for (resolved_args) |arg, i| {
const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
.mutable = true,
.@"addrspace" = target_util.defaultAddressSpace(target, .local),
- .pointee_type = types[i],
+ .pointee_type = array_ty.structFieldType(i),
});
const elem_ptr_ty_ref = try sema.addType(elem_ptr_ty);
@@ -18015,10 +18027,7 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
return sema.fail(block, src, "non-packed struct does not support backing integer type", .{});
}
- return if (is_tuple_val.toBool())
- try sema.reifyTuple(block, src, fields_val)
- else
- try sema.reifyStruct(block, inst, src, layout, backing_int_val, fields_val, name_strategy);
+ return try sema.reifyStruct(block, inst, src, layout, backing_int_val, fields_val, name_strategy, is_tuple_val.toBool());
},
.Enum => {
const struct_val: []const Value = union_val.val.castTag(.aggregate).?.data;
@@ -18432,84 +18441,6 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in
}
}
-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(sema.mod));
- 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.mod, sema.arena, i);
- const field_struct_val = elem_val.castTag(.aggregate).?.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,
- sema.mod,
- );
-
- 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|
- sema.mod.declPtr(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,
@@ -18519,6 +18450,7 @@ fn reifyStruct(
backing_int_val: Value,
fields_val: Value,
name_strategy: Zir.Inst.NameStrategy,
+ is_tuple: bool,
) CompileError!Air.Inst.Ref {
var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
errdefer new_decl_arena.deinit();
@@ -18542,6 +18474,7 @@ fn reifyStruct(
.layout = layout,
.status = .have_field_types,
.known_non_opv = false,
+ .is_tuple = is_tuple,
.namespace = .{
.parent = block.namespace,
.ty = struct_ty,
@@ -18575,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(
@@ -18585,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
@@ -18598,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.* = .{
@@ -23177,6 +23136,7 @@ fn structFieldVal(
},
.@"struct" => {
const struct_obj = struct_ty.castTag(.@"struct").?.data;
+ if (struct_obj.is_tuple) return sema.tupleFieldVal(block, src, struct_byval, field_name, field_name_src, struct_ty);
const field_index_usize = struct_obj.fields.getIndex(field_name) orelse
return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name);
@@ -23249,11 +23209,10 @@ fn tupleFieldValByIndex(
field_index: u32,
tuple_ty: Type,
) CompileError!Air.Inst.Ref {
- const tuple = tuple_ty.tupleFields();
- const field_ty = tuple.types[field_index];
+ const field_ty = tuple_ty.structFieldType(field_index);
- if (tuple.values[field_index].tag() != .unreachable_value) {
- return sema.addConstant(field_ty, tuple.values[field_index]);
+ if (tuple_ty.structFieldValueComptime(field_index)) |default_value| {
+ return sema.addConstant(field_ty, default_value);
}
if (try sema.resolveMaybeUndefVal(tuple_byval)) |tuple_val| {
@@ -23601,19 +23560,20 @@ fn tupleFieldPtr(
) CompileError!Air.Inst.Ref {
const tuple_ptr_ty = sema.typeOf(tuple_ptr);
const tuple_ty = tuple_ptr_ty.childType();
- const tuple_fields = tuple_ty.tupleFields();
+ _ = try sema.resolveTypeFields(tuple_ty);
+ const field_count = tuple_ty.structFieldCount();
- if (tuple_fields.types.len == 0) {
+ if (field_count == 0) {
return sema.fail(block, tuple_ptr_src, "indexing into empty tuple is not allowed", .{});
}
- if (field_index >= tuple_fields.types.len) {
+ if (field_index >= field_count) {
return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{
- field_index, tuple_fields.types.len,
+ field_index, field_count,
});
}
- const field_ty = tuple_fields.types[field_index];
+ const field_ty = tuple_ty.structFieldType(field_index);
const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, .{
.pointee_type = field_ty,
.mutable = tuple_ptr_ty.ptrIsMutable(),
@@ -23656,24 +23616,23 @@ fn tupleField(
field_index_src: LazySrcLoc,
field_index: u32,
) CompileError!Air.Inst.Ref {
- const tuple_ty = sema.typeOf(tuple);
- const tuple_fields = tuple_ty.tupleFields();
+ const tuple_ty = try sema.resolveTypeFields(sema.typeOf(tuple));
+ const field_count = tuple_ty.structFieldCount();
- if (tuple_fields.types.len == 0) {
+ if (field_count == 0) {
return sema.fail(block, tuple_src, "indexing into empty tuple is not allowed", .{});
}
- if (field_index >= tuple_fields.types.len) {
+ if (field_index >= field_count) {
return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{
- field_index, tuple_fields.types.len,
+ field_index, field_count,
});
}
- const field_ty = tuple_fields.types[field_index];
- const field_val = tuple_fields.values[field_index];
+ const field_ty = tuple_ty.structFieldType(field_index);
- if (field_val.tag() != .unreachable_value) {
- return sema.addConstant(field_ty, field_val); // comptime field
+ if (tuple_ty.structFieldValueComptime(field_index)) |default_value| {
+ return sema.addConstant(field_ty, default_value); // comptime field
}
if (try sema.resolveMaybeUndefVal(tuple)) |tuple_val| {
@@ -24223,7 +24182,10 @@ fn coerceExtra(
inst_ty.childType().isAnonStruct() and
sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result))
{
- return sema.coerceAnonStructToStructPtrs(block, dest_ty, dest_ty_src, inst, inst_src);
+ return sema.coerceAnonStructToStructPtrs(block, dest_ty, dest_ty_src, inst, inst_src) catch |err| switch (err) {
+ error.NotCoercible => break :pointer,
+ else => |e| return e,
+ };
}
},
.Array => {
@@ -24253,7 +24215,7 @@ fn coerceExtra(
// empty tuple to zero-length slice
// note that this allows coercing to a mutable slice.
- if (inst_child_ty.tupleFields().types.len == 0) {
+ if (inst_child_ty.structFieldCount() == 0) {
const slice_val = try Value.Tag.slice.create(sema.arena, .{
.ptr = Value.undef,
.len = Value.zero,
@@ -24536,12 +24498,15 @@ fn coerceExtra(
},
else => {},
},
- .Struct => {
+ .Struct => blk: {
if (inst == .empty_struct) {
return sema.structInitEmpty(block, dest_ty, dest_ty_src, inst_src);
}
if (inst_ty.isTupleOrAnonStruct()) {
- return sema.coerceTupleToStruct(block, dest_ty, inst, inst_src);
+ return sema.coerceTupleToStruct(block, dest_ty, inst, inst_src) catch |err| switch (err) {
+ error.NotCoercible => break :blk,
+ else => |e| return e,
+ };
}
},
else => {},
@@ -25563,9 +25528,9 @@ fn storePtr2(
// fields.
const operand_ty = sema.typeOf(uncasted_operand);
if (operand_ty.isTuple() and elem_ty.zigTypeTag() == .Array) {
- const tuple = operand_ty.tupleFields();
- for (tuple.types) |_, i_usize| {
- const i = @intCast(u32, i_usize);
+ const field_count = operand_ty.structFieldCount();
+ var i: u32 = 0;
+ while (i < field_count) : (i += 1) {
const elem_src = operand_src; // TODO better source location
const elem = try sema.tupleField(block, operand_src, uncasted_operand, elem_src, i);
const elem_index = try sema.addIntUnsigned(Type.usize, i);
@@ -26657,7 +26622,7 @@ fn checkPtrAttributes(sema: *Sema, dest_ty: Type, inst_ty: Type, in_memory_resul
const inst_info = inst_ty.ptrInfo().data;
const len0 = (inst_info.pointee_type.zigTypeTag() == .Array and (inst_info.pointee_type.arrayLenIncludingSentinel() == 0 or
(inst_info.pointee_type.arrayLen() == 0 and dest_info.sentinel == null and dest_info.size != .C and dest_info.size != .Many))) or
- (inst_info.pointee_type.isTuple() and inst_info.pointee_type.tupleFields().types.len == 0);
+ (inst_info.pointee_type.isTuple() and inst_info.pointee_type.structFieldCount() == 0);
const ok_cv_qualifiers =
((inst_info.mutable or !dest_info.mutable) or len0) and
@@ -27142,18 +27107,18 @@ fn coerceTupleToStruct(
mem.set(Air.Inst.Ref, field_refs, .none);
const inst_ty = sema.typeOf(inst);
- const tuple = inst_ty.tupleFields();
var runtime_src: ?LazySrcLoc = null;
- for (tuple.types) |_, i_usize| {
- const i = @intCast(u32, i_usize);
+ 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
const field_name = if (inst_ty.castTag(.anon_struct)) |payload|
- payload.data.names[i]
+ payload.data.names[field_i]
else
- try std.fmt.allocPrint(sema.arena, "{d}", .{i});
+ try std.fmt.allocPrint(sema.arena, "{d}", .{field_i});
const field_index = try sema.structFieldIndex(block, struct_ty, field_name, field_src);
const field = fields.values()[field_index];
- const elem_ref = try sema.tupleField(block, inst_src, inst, field_src, i);
+ const elem_ref = try sema.tupleField(block, inst_src, inst, field_src, field_i);
const coerced = try sema.coerce(block, field.ty, elem_ref, field_src);
field_refs[field_index] = coerced;
if (field.is_comptime) {
@@ -27162,7 +27127,7 @@ fn coerceTupleToStruct(
};
if (!init_val.eql(field.default_val, field.ty, sema.mod)) {
- return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, i);
+ return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, field_i);
}
}
if (runtime_src == null) {
@@ -27225,21 +27190,23 @@ 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 tuple = inst_ty.tupleFields();
+ const inst_field_count = inst_ty.structFieldCount();
+ if (inst_field_count > dest_field_count) return error.NotCoercible;
+
var runtime_src: ?LazySrcLoc = null;
- for (tuple.types) |_, i_usize| {
- const i = @intCast(u32, i_usize);
+ var field_i: u32 = 0;
+ 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[i]
+ payload.data.names[field_i]
else
- try std.fmt.allocPrint(sema.arena, "{d}", .{i});
+ try std.fmt.allocPrint(sema.arena, "{d}", .{field_i});
if (mem.eql(u8, field_name, "len")) {
return sema.fail(block, field_src, "cannot assign to 'len' field of tuple", .{});
@@ -27247,9 +27214,9 @@ fn coerceTupleToTuple(
const field_index = try sema.tupleFieldIndex(block, tuple_ty, field_name, field_src);
- const field_ty = tuple_ty.structFieldType(i);
- const default_val = tuple_ty.structFieldDefaultValue(i);
- const elem_ref = try sema.tupleField(block, inst_src, inst, field_src, i);
+ const field_ty = tuple_ty.structFieldType(field_i);
+ const default_val = tuple_ty.structFieldDefaultValue(field_i);
+ const elem_ref = try sema.tupleField(block, inst_src, inst, field_src, field_i);
const coerced = try sema.coerce(block, field_ty, elem_ref, field_src);
field_refs[field_index] = coerced;
if (default_val.tag() != .unreachable_value) {
@@ -27258,7 +27225,7 @@ fn coerceTupleToTuple(
};
if (!init_val.eql(default_val, field_ty, sema.mod)) {
- return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, i);
+ return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, field_i);
}
}
if (runtime_src == null) {
@@ -29641,6 +29608,7 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
block_scope.params.deinit(gpa);
}
+ struct_obj.fields = .{};
try struct_obj.fields.ensureTotalCapacity(decl_arena_allocator, fields_len);
const Field = struct {
@@ -29675,8 +29643,11 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
const has_type_body = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
- const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
- extra_index += 1;
+ var field_name_zir: ?[:0]const u8 = null;
+ if (!small.is_tuple) {
+ field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+ extra_index += 1;
+ }
extra_index += 1; // doc_comment
fields[field_i] = .{};
@@ -29689,7 +29660,10 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
extra_index += 1;
// This string needs to outlive the ZIR code.
- const field_name = try decl_arena_allocator.dupe(u8, field_name_zir);
+ const field_name = if (field_name_zir) |some|
+ try decl_arena_allocator.dupe(u8, some)
+ else
+ try std.fmt.allocPrint(decl_arena_allocator, "{d}", .{field_i});
const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
if (gop.found_existing) {
diff --git a/src/Zir.zig b/src/Zir.zig
index c30642d6c5..c7e1d22d22 100644
--- a/src/Zir.zig
+++ b/src/Zir.zig
@@ -3166,7 +3166,7 @@ pub const Inst = struct {
/// 0b0X00: whether corresponding field is comptime
/// 0bX000: whether corresponding field has a type expression
/// 9. fields: { // for every fields_len
- /// field_name: u32,
+ /// field_name: u32, // if !is_tuple
/// doc_comment: u32, // 0 if no doc comment
/// field_type: Ref, // if corresponding bit is not set. none means anytype.
/// field_type_body_len: u32, // if corresponding bit is set
@@ -3186,9 +3186,10 @@ pub const Inst = struct {
has_backing_int: bool,
known_non_opv: bool,
known_comptime_only: bool,
+ is_tuple: bool,
name_strategy: NameStrategy,
layout: std.builtin.Type.ContainerLayout,
- _: u6 = undefined,
+ _: u5 = undefined,
};
};
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/codegen/c.zig b/src/codegen/c.zig
index 9b3ed334c0..91e9b5f939 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1796,7 +1796,7 @@ pub const DeclGen = struct {
},
.Struct, .Union => |tag| if (tag == .Struct and t.containerLayout() == .Packed)
try dg.renderType(w, t.castTag(.@"struct").?.data.backing_int_ty, kind)
- else if (t.isTupleOrAnonStruct()) {
+ else if (t.isSimpleTupleOrAnonStruct()) {
const ExpectedContents = struct { types: [8]Type, values: [8]Value };
var stack align(@alignOf(ExpectedContents)) =
std.heap.stackFallback(@sizeOf(ExpectedContents), dg.gpa);
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index b3ea892566..a78b201a0a 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1956,7 +1956,7 @@ pub const Object = struct {
break :blk fwd_decl;
};
- if (ty.isTupleOrAnonStruct()) {
+ if (ty.isSimpleTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
@@ -2890,7 +2890,7 @@ pub const DeclGen = struct {
// reference, we need to copy it here.
gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
- if (t.isTupleOrAnonStruct()) {
+ if (t.isSimpleTupleOrAnonStruct()) {
const tuple = t.tupleFields();
const llvm_struct_ty = dg.context.structCreateNamed("");
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
@@ -3584,7 +3584,7 @@ pub const DeclGen = struct {
const field_vals = tv.val.castTag(.aggregate).?.data;
const gpa = dg.gpa;
- if (tv.ty.isTupleOrAnonStruct()) {
+ if (tv.ty.isSimpleTupleOrAnonStruct()) {
const tuple = tv.ty.tupleFields();
var llvm_fields: std.ArrayListUnmanaged(*llvm.Value) = .{};
defer llvm_fields.deinit(gpa);
@@ -10210,7 +10210,7 @@ fn llvmFieldIndex(
var offset: u64 = 0;
var big_align: u32 = 0;
- if (ty.isTupleOrAnonStruct()) {
+ if (ty.isSimpleTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
var llvm_field_index: c_uint = 0;
for (tuple.types) |field_ty, i| {
@@ -10773,7 +10773,7 @@ fn isByRef(ty: Type) bool {
.Struct => {
// Packed structs are represented to LLVM as integers.
if (ty.containerLayout() == .Packed) return false;
- if (ty.isTupleOrAnonStruct()) {
+ if (ty.isSimpleTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
var count: usize = 0;
for (tuple.values) |field_val, i| {
diff --git a/src/print_zir.zig b/src/print_zir.zig
index d434abd439..9135c22010 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -1262,6 +1262,7 @@ const Writer = struct {
try self.writeFlag(stream, "known_non_opv, ", small.known_non_opv);
try self.writeFlag(stream, "known_comptime_only, ", small.known_comptime_only);
+ try self.writeFlag(stream, "tuple, ", small.is_tuple);
try stream.print("{s}, ", .{@tagName(small.name_strategy)});
@@ -1335,8 +1336,11 @@ const Writer = struct {
const has_type_body = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
- const field_name = self.code.extra[extra_index];
- extra_index += 1;
+ var field_name: u32 = 0;
+ if (!small.is_tuple) {
+ field_name = self.code.extra[extra_index];
+ extra_index += 1;
+ }
const doc_comment_index = self.code.extra[extra_index];
extra_index += 1;
@@ -1370,13 +1374,16 @@ const Writer = struct {
try stream.writeAll("{\n");
self.indent += 2;
- for (fields) |field| {
- const field_name = self.code.nullTerminatedString(field.name);
-
+ for (fields) |field, i| {
try self.writeDocComment(stream, field.doc_comment_index);
try stream.writeByteNTimes(' ', self.indent);
try self.writeFlag(stream, "comptime ", field.is_comptime);
- try stream.print("{}: ", .{std.zig.fmtId(field_name)});
+ if (field.name != 0) {
+ const field_name = self.code.nullTerminatedString(field.name);
+ try stream.print("{}: ", .{std.zig.fmtId(field_name)});
+ } else {
+ try stream.print("@\"{d}\": ", .{i});
+ }
if (field.field_type != .none) {
try self.writeInstRef(stream, field.field_type);
}
diff --git a/src/type.zig b/src/type.zig
index 6afee8bc73..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();
@@ -4494,6 +4494,7 @@ pub const Type = extern union {
.mut_slice,
.tuple,
.empty_struct_literal,
+ .@"struct",
=> return null,
.pointer => return self.castTag(.pointer).?.data.sentinel,
@@ -6178,6 +6179,7 @@ pub const Type = extern union {
pub fn isTuple(ty: Type) bool {
return switch (ty.tag()) {
.tuple, .empty_struct_literal => true,
+ .@"struct" => ty.castTag(.@"struct").?.data.is_tuple,
else => false,
};
}
@@ -6192,10 +6194,26 @@ pub const Type = extern union {
pub fn isTupleOrAnonStruct(ty: Type) bool {
return switch (ty.tag()) {
.tuple, .empty_struct_literal, .anon_struct => true,
+ .@"struct" => ty.castTag(.@"struct").?.data.is_tuple,
+ else => false,
+ };
+ }
+
+ pub fn isSimpleTuple(ty: Type) bool {
+ return switch (ty.tag()) {
+ .tuple, .empty_struct_literal => true,
+ else => false,
+ };
+ }
+
+ pub fn isSimpleTupleOrAnonStruct(ty: Type) bool {
+ return switch (ty.tag()) {
+ .tuple, .empty_struct_literal, .anon_struct => true,
else => false,
};
}
+ // Only allowed for simple tuple types
pub fn tupleFields(ty: Type) Payload.Tuple.Data {
return switch (ty.tag()) {
.tuple => ty.castTag(.tuple).?.data,
diff --git a/src/value.zig b/src/value.zig
index 43c31905ca..94769b4da7 100644
--- a/src/value.zig
+++ b/src/value.zig
@@ -2209,7 +2209,7 @@ pub const Value = extern union {
const b_field_vals = b.castTag(.aggregate).?.data;
assert(a_field_vals.len == b_field_vals.len);
- if (ty.isTupleOrAnonStruct()) {
+ if (ty.isSimpleTupleOrAnonStruct()) {
const types = ty.tupleFields().types;
assert(types.len == a_field_vals.len);
for (types) |field_ty, i| {
@@ -3004,7 +3004,7 @@ pub const Value = extern union {
.the_only_possible_value => return ty.onePossibleValue().?,
.empty_struct_value => {
- if (ty.isTupleOrAnonStruct()) {
+ if (ty.isSimpleTupleOrAnonStruct()) {
const tuple = ty.tupleFields();
return tuple.values[index];
}
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/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig b/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig
index 4e9bdfa2e5..284d3c0d0d 100644
--- a/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig
+++ b/test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig
@@ -7,4 +7,4 @@ export fn entry() void {
// backend=stage2
// target=native
//
-// :3:11: error: index '0' out of bounds of tuple '@TypeOf(.{})'
+// :3:11: error: expected type '@TypeOf(.{})', found 'tuple{comptime comptime_int = 1, comptime comptime_int = 2, comptime comptime_int = 3}'
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.