diff options
| -rw-r--r-- | lib/std/zig/Ast.zig | 41 | ||||
| -rw-r--r-- | lib/std/zig/parse.zig | 220 | ||||
| -rw-r--r-- | lib/std/zig/parser_test.zig | 28 | ||||
| -rw-r--r-- | lib/std/zig/render.zig | 96 | ||||
| -rw-r--r-- | src/AstGen.zig | 69 | ||||
| -rw-r--r-- | src/Module.zig | 7 | ||||
| -rw-r--r-- | src/Sema.zig | 318 | ||||
| -rw-r--r-- | src/Zir.zig | 5 | ||||
| -rw-r--r-- | src/arch/x86_64/abi.zig | 1 | ||||
| -rw-r--r-- | src/codegen/c.zig | 2 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 10 | ||||
| -rw-r--r-- | src/print_zir.zig | 19 | ||||
| -rw-r--r-- | src/type.zig | 20 | ||||
| -rw-r--r-- | src/value.zig | 4 | ||||
| -rw-r--r-- | test/behavior.zig | 1 | ||||
| -rw-r--r-- | test/behavior/tuple_declarations.zig | 79 | ||||
| -rw-r--r-- | test/cases/compile_errors/alignment_of_enum_field_specified.zig | 2 | ||||
| -rw-r--r-- | test/cases/compile_errors/reify_struct.zig | 80 | ||||
| -rw-r--r-- | test/cases/compile_errors/struct_field_missing_type.zig | 13 | ||||
| -rw-r--r-- | test/cases/compile_errors/tuple_declarations.zig | 25 | ||||
| -rw-r--r-- | test/cases/compile_errors/type_mismatch_with_tuple_concatenation.zig | 2 | ||||
| -rw-r--r-- | test/compile_errors.zig | 4 | ||||
| -rw-r--r-- | test/stage2/cbe.zig | 2 |
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. |
