authorgravatar for jacoblevgw@gmail.comJacob G-W <jacoblevgw@gmail.com> 2022-01-27 15:23:28-05:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-02-13 14:42:20+02:00
log3bbe6a28e069b03c8a9185dd14129517453e26d2
treeb868356b27a7be324cbc1625df8b6bd13c3cc9d9
parent0b7347fd18eee7dd829cd9aaed3683123d84859b

stage2: add decltests


9 files changed, 119 insertions(+), 18 deletions(-)

lib/std/zig/Ast.zig+1-1
...@@ -2519,7 +2519,7 @@ pub const Node = struct {...@@ -2519,7 +2519,7 @@ pub const Node = struct {
2519 root,2519 root,
2520 /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`.2520 /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`.
2521 @"usingnamespace",2521 @"usingnamespace",
2522 /// lhs is test name token (must be string literal), if any.2522 /// lhs is test name token (must be string literal or identifier), if any.
2523 /// rhs is the body node.2523 /// rhs is the body node.
2524 test_decl,2524 test_decl,
2525 /// lhs is the index into extra_data.2525 /// lhs is the index into extra_data.
lib/std/zig/parse.zig+8-2
...@@ -500,10 +500,16 @@ const Parser = struct {...@@ -500,10 +500,16 @@ const Parser = struct {
500 }500 }
501 }501 }
502502
503 /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block503 /// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
504 fn expectTestDecl(p: *Parser) !Node.Index {504 fn expectTestDecl(p: *Parser) !Node.Index {
505 const test_token = p.assertToken(.keyword_test);505 const test_token = p.assertToken(.keyword_test);
506 const name_token = p.eatToken(.string_literal);506 const name_token = switch (p.token_tags[p.nextToken()]) {
507 .string_literal, .identifier => p.tok_i - 1,
508 else => blk: {
509 p.tok_i -= 1;
510 break :blk null;
511 },
512 };
507 const block_node = try p.parseBlock();513 const block_node = try p.parseBlock();
508 if (block_node == 0) return p.fail(.expected_block);514 if (block_node == 0) return p.fail(.expected_block);
509 return p.addNode(.{515 return p.addNode(.{
lib/std/zig/render.zig+2-1
...@@ -151,7 +151,8 @@ fn renderMember(gpa: Allocator, ais: *Ais, tree: Ast, decl: Ast.Node.Index, spac...@@ -151,7 +151,8 @@ fn renderMember(gpa: Allocator, ais: *Ais, tree: Ast, decl: Ast.Node.Index, spac
151 .test_decl => {151 .test_decl => {
152 const test_token = main_tokens[decl];152 const test_token = main_tokens[decl];
153 try renderToken(ais, tree, test_token, .space);153 try renderToken(ais, tree, test_token, .space);
154 if (token_tags[test_token + 1] == .string_literal) {154 const test_name_tag = token_tags[test_token + 1];
155 if (test_name_tag == .string_literal or test_name_tag == .identifier) {
155 try renderToken(ais, tree, test_token + 1, .space);156 try renderToken(ais, tree, test_token + 1, .space);
156 }157 }
157 try renderExpression(gpa, ais, tree, datas[decl].rhs, space);158 try renderExpression(gpa, ais, tree, datas[decl].rhs, space);
src/AstGen.zig+81-10
...@@ -105,8 +105,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {...@@ -105,8 +105,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
105 };105 };
106 defer astgen.deinit(gpa);106 defer astgen.deinit(gpa);
107107
108 // String table indexes 0 and 1 are reserved for special meaning.108 // String table indexes 0, 1, 2 are reserved for special meaning.
109 try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0 });109 try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0, 0 });
110110
111 // We expect at least as many ZIR instructions and extra data items111 // We expect at least as many ZIR instructions and extra data items
112 // as AST nodes.112 // as AST nodes.
...@@ -3736,13 +3736,78 @@ fn testDecl(...@@ -3736,13 +3736,78 @@ fn testDecl(
3736 };3736 };
3737 defer decl_block.unstack();3737 defer decl_block.unstack();
37383738
3739 const main_tokens = tree.nodes.items(.main_token);
3740 const token_tags = tree.tokens.items(.tag);
3741 const test_token = main_tokens[node];
3742 const test_name_token = test_token + 1;
3743 const test_name_token_tag = token_tags[test_name_token];
3744 const is_decltest = test_name_token_tag == .identifier;
3739 const test_name: u32 = blk: {3745 const test_name: u32 = blk: {
3740 const main_tokens = tree.nodes.items(.main_token);3746 if (test_name_token_tag == .string_literal) {
3741 const token_tags = tree.tokens.items(.tag);3747 break :blk try astgen.testNameString(test_name_token);
3742 const test_token = main_tokens[node];3748 } else if (test_name_token_tag == .identifier) {
3743 const str_lit_token = test_token + 1;3749 const ident_name_raw = tree.tokenSlice(test_name_token);
3744 if (token_tags[str_lit_token] == .string_literal) {3750
3745 break :blk try astgen.testNameString(str_lit_token);3751 if (mem.eql(u8, ident_name_raw, "_")) return astgen.failTok(test_name_token, "'_' used as an identifier without @\"_\" syntax", .{});
3752
3753 // if not @"" syntax, just use raw token slice
3754 if (ident_name_raw[0] != '@') {
3755 if (primitives.get(ident_name_raw)) |_| return astgen.failTok(test_name_token, "cannot test a primitive", .{});
3756
3757 if (ident_name_raw.len >= 2) integer: {
3758 const first_c = ident_name_raw[0];
3759 if (first_c == 'i' or first_c == 'u') {
3760 _ = switch (first_c == 'i') {
3761 true => .signed,
3762 false => .unsigned,
3763 };
3764 _ = parseBitCount(ident_name_raw[1..]) catch |err| switch (err) {
3765 error.Overflow => return astgen.failTok(
3766 test_name_token,
3767 "primitive integer type '{s}' exceeds maximum bit width of 65535",
3768 .{ident_name_raw},
3769 ),
3770 error.InvalidCharacter => break :integer,
3771 };
3772 return astgen.failTok(test_name_token, "cannot test a primitive", .{});
3773 }
3774 }
3775 }
3776
3777 // Local variables, including function parameters.
3778 const name_str_index = try astgen.identAsString(test_name_token);
3779 var s = scope;
3780 var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
3781 var num_namespaces_out: u32 = 0;
3782 var capturing_namespace: ?*Scope.Namespace = null;
3783 while (true) switch (s.tag) {
3784 .local_val, .local_ptr => unreachable, // a test cannot be in a local scope
3785 .gen_zir => s = s.cast(GenZir).?.parent,
3786 .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
3787 .namespace => {
3788 const ns = s.cast(Scope.Namespace).?;
3789 if (ns.decls.get(name_str_index)) |i| {
3790 if (found_already) |f| {
3791 return astgen.failTokNotes(test_name_token, "ambiguous reference", .{}, &.{
3792 try astgen.errNoteNode(f, "declared here", .{}),
3793 try astgen.errNoteNode(i, "also declared here", .{}),
3794 });
3795 }
3796 // We found a match but must continue looking for ambiguous references to decls.
3797 found_already = i;
3798 }
3799 num_namespaces_out += 1;
3800 capturing_namespace = ns;
3801 s = ns.parent;
3802 },
3803 .top => break,
3804 };
3805 if (found_already == null) {
3806 const ident_name = try astgen.identifierTokenString(test_name_token);
3807 return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name});
3808 }
3809
3810 break :blk name_str_index;
3746 }3811 }
3747 // String table index 1 has a special meaning here of test decl with no name.3812 // String table index 1 has a special meaning here of test decl with no name.
3748 break :blk 1;3813 break :blk 1;
...@@ -3804,9 +3869,15 @@ fn testDecl(...@@ -3804,9 +3869,15 @@ fn testDecl(
3804 const line_delta = decl_block.decl_line - gz.decl_line;3869 const line_delta = decl_block.decl_line - gz.decl_line;
3805 wip_members.appendToDecl(line_delta);3870 wip_members.appendToDecl(line_delta);
3806 }3871 }
3807 wip_members.appendToDecl(test_name);3872 if (is_decltest)
3873 wip_members.appendToDecl(2) // 2 here means that it is a decltest, look at doc comment for name
3874 else
3875 wip_members.appendToDecl(test_name);
3808 wip_members.appendToDecl(block_inst);3876 wip_members.appendToDecl(block_inst);
3809 wip_members.appendToDecl(0); // no doc comments on test decls3877 if (is_decltest)
3878 wip_members.appendToDecl(test_name) // the doc comment on a decltest represents it's name
3879 else
3880 wip_members.appendToDecl(0); // no doc comments on test decls
3810}3881}
38113882
3812fn structDeclInner(3883fn structDeclInner(
src/Module.zig+6
...@@ -4170,6 +4170,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi...@@ -4170,6 +4170,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
4170 const line_off = zir.extra[decl_sub_index + 4];4170 const line_off = zir.extra[decl_sub_index + 4];
4171 const line = iter.parent_decl.relativeToLine(line_off);4171 const line = iter.parent_decl.relativeToLine(line_off);
4172 const decl_name_index = zir.extra[decl_sub_index + 5];4172 const decl_name_index = zir.extra[decl_sub_index + 5];
4173 const decl_doccomment_index = zir.extra[decl_sub_index + 7];
4173 const decl_index = zir.extra[decl_sub_index + 6];4174 const decl_index = zir.extra[decl_sub_index + 6];
4174 const decl_block_inst_data = zir.instructions.items(.data)[decl_index].pl_node;4175 const decl_block_inst_data = zir.instructions.items(.data)[decl_index].pl_node;
4175 const decl_node = iter.parent_decl.relativeToNodeIndex(decl_block_inst_data.src_node);4176 const decl_node = iter.parent_decl.relativeToNodeIndex(decl_block_inst_data.src_node);
...@@ -4193,6 +4194,11 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi...@@ -4193,6 +4194,11 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
4193 iter.unnamed_test_index += 1;4194 iter.unnamed_test_index += 1;
4194 break :name try std.fmt.allocPrintZ(gpa, "test_{d}", .{i});4195 break :name try std.fmt.allocPrintZ(gpa, "test_{d}", .{i});
4195 },4196 },
4197 2 => name: {
4198 is_named_test = true;
4199 const test_name = zir.nullTerminatedString(decl_doccomment_index);
4200 break :name try std.fmt.allocPrintZ(gpa, "decltest.{s}", .{test_name});
4201 },
4196 else => name: {4202 else => name: {
4197 const raw_name = zir.nullTerminatedString(decl_name_index);4203 const raw_name = zir.nullTerminatedString(decl_name_index);
4198 if (raw_name.len == 0) {4204 if (raw_name.len == 0) {
src/Zir.zig+2-1
...@@ -2579,10 +2579,11 @@ pub const Inst = struct {...@@ -2579,10 +2579,11 @@ pub const Inst = struct {
2579 /// - 0 means comptime or usingnamespace decl.2579 /// - 0 means comptime or usingnamespace decl.
2580 /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace2580 /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
2581 /// - 1 means test decl with no name.2581 /// - 1 means test decl with no name.
2582 /// - 2 means that the test is a decltest, doc_comment gives the name of the identifier
2582 /// - if there is a 0 byte at the position `name` indexes, it indicates2583 /// - if there is a 0 byte at the position `name` indexes, it indicates
2583 /// this is a test decl, and the name starts at `name+1`.2584 /// this is a test decl, and the name starts at `name+1`.
2584 /// value: Index,2585 /// value: Index,
2585 /// doc_comment: u32, // 0 if no doc comment2586 /// doc_comment: u32, 0 if no doc comment, if this is a decltest, doc_comment references the decl name in the string table
2586 /// align: Ref, // if corresponding bit is set2587 /// align: Ref, // if corresponding bit is set
2587 /// link_section_or_address_space: { // if corresponding bit is set.2588 /// link_section_or_address_space: { // if corresponding bit is set.
2588 /// link_section: Ref,2589 /// link_section: Ref,
src/print_zir.zig+7-3
...@@ -1443,20 +1443,24 @@ const Writer = struct {...@@ -1443,20 +1443,24 @@ const Writer = struct {
1443 } else if (decl_name_index == 1) {1443 } else if (decl_name_index == 1) {
1444 try stream.writeByteNTimes(' ', self.indent);1444 try stream.writeByteNTimes(' ', self.indent);
1445 try stream.writeAll("test");1445 try stream.writeAll("test");
1446 } else if (decl_name_index == 2) {
1447 try stream.writeByteNTimes(' ', self.indent);
1448 try stream.print("[{d}] decltest {s}", .{ sub_index, self.code.nullTerminatedString(doc_comment_index) });
1446 } else {1449 } else {
1447 const raw_decl_name = self.code.nullTerminatedString(decl_name_index);1450 const raw_decl_name = self.code.nullTerminatedString(decl_name_index);
1448 const decl_name = if (raw_decl_name.len == 0)1451 const decl_name = if (raw_decl_name.len == 0)
1449 self.code.nullTerminatedString(decl_name_index + 1)1452 self.code.nullTerminatedString(decl_name_index + 1)
1450 else1453 else
1451 raw_decl_name;1454 raw_decl_name;
1452 const test_str = if (raw_decl_name.len == 0) "test " else "";1455 const test_str = if (raw_decl_name.len == 0) "test \"" else "";
1453 const export_str = if (is_exported) "export " else "";1456 const export_str = if (is_exported) "export " else "";
14541457
1455 try self.writeDocComment(stream, doc_comment_index);1458 try self.writeDocComment(stream, doc_comment_index);
14561459
1457 try stream.writeByteNTimes(' ', self.indent);1460 try stream.writeByteNTimes(' ', self.indent);
1458 try stream.print("[{d}] {s}{s}{s}{}", .{1461 const endquote_if_test: []const u8 = if (raw_decl_name.len == 0) "\"" else "";
1459 sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name),1462 try stream.print("[{d}] {s}{s}{s}{}{s}", .{
1463 sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name), endquote_if_test,
1460 });1464 });
1461 if (align_inst != .none) {1465 if (align_inst != .none) {
1462 try stream.writeAll(" align(");1466 try stream.writeAll(" align(");
test/behavior.zig+5
...@@ -49,6 +49,11 @@ test {...@@ -49,6 +49,11 @@ test {
49 _ = @import("behavior/type.zig");49 _ = @import("behavior/type.zig");
50 _ = @import("behavior/var_args.zig");50 _ = @import("behavior/var_args.zig");
5151
52 // tests that don't pass for stage1
53 if (builtin.zig_backend != .stage1) {
54 _ = @import("behavior/decltest.zig");
55 }
56
52 if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) {57 if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) {
53 // Tests that pass (partly) for stage1, llvm backend, C backend, wasm backend.58 // Tests that pass (partly) for stage1, llvm backend, C backend, wasm backend.
54 _ = @import("behavior/bitcast.zig");59 _ = @import("behavior/bitcast.zig");
test/behavior/decltest.zig created+7
...@@ -0,0 +1,7 @@
1pub fn the_add_function(a: u32, b: u32) u32 {
2 return a + b;
3}
4
5test the_add_function {
6 if (the_add_function(1, 2) != 3) unreachable;
7}