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 {
25192519 root,
25202520 /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`.
25212521 @"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.
25232523 /// rhs is the body node.
25242524 test_decl,
25252525 /// lhs is the index into extra_data.
lib/std/zig/parse.zig+8-2
......@@ -500,10 +500,16 @@ const Parser = struct {
500500 }
501501 }
502502
503 /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block
503 /// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
504504 fn expectTestDecl(p: *Parser) !Node.Index {
505505 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 };
507513 const block_node = try p.parseBlock();
508514 if (block_node == 0) return p.fail(.expected_block);
509515 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
151151 .test_decl => {
152152 const test_token = main_tokens[decl];
153153 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) {
155156 try renderToken(ais, tree, test_token + 1, .space);
156157 }
157158 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 {
105105 };
106106 defer astgen.deinit(gpa);
107107
108 // String table indexes 0 and 1 are reserved for special meaning.
109 try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0 });
108 // String table indexes 0, 1, 2 are reserved for special meaning.
109 try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0, 0 });
110110
111111 // We expect at least as many ZIR instructions and extra data items
112112 // as AST nodes.
......@@ -3736,13 +3736,78 @@ fn testDecl(
37363736 };
37373737 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;
37393745 const test_name: u32 = blk: {
3740 const main_tokens = tree.nodes.items(.main_token);
3741 const token_tags = tree.tokens.items(.tag);
3742 const test_token = main_tokens[node];
3743 const str_lit_token = test_token + 1;
3744 if (token_tags[str_lit_token] == .string_literal) {
3745 break :blk try astgen.testNameString(str_lit_token);
3746 if (test_name_token_tag == .string_literal) {
3747 break :blk try astgen.testNameString(test_name_token);
3748 } else if (test_name_token_tag == .identifier) {
3749 const ident_name_raw = tree.tokenSlice(test_name_token);
3750
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;
37463811 }
37473812 // String table index 1 has a special meaning here of test decl with no name.
37483813 break :blk 1;
......@@ -3804,9 +3869,15 @@ fn testDecl(
38043869 const line_delta = decl_block.decl_line - gz.decl_line;
38053870 wip_members.appendToDecl(line_delta);
38063871 }
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);
38083876 wip_members.appendToDecl(block_inst);
3809 wip_members.appendToDecl(0); // no doc comments on test decls
3877 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
38103881}
38113882
38123883fn structDeclInner(
src/Module.zig+6
......@@ -4170,6 +4170,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
41704170 const line_off = zir.extra[decl_sub_index + 4];
41714171 const line = iter.parent_decl.relativeToLine(line_off);
41724172 const decl_name_index = zir.extra[decl_sub_index + 5];
4173 const decl_doccomment_index = zir.extra[decl_sub_index + 7];
41734174 const decl_index = zir.extra[decl_sub_index + 6];
41744175 const decl_block_inst_data = zir.instructions.items(.data)[decl_index].pl_node;
41754176 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
41934194 iter.unnamed_test_index += 1;
41944195 break :name try std.fmt.allocPrintZ(gpa, "test_{d}", .{i});
41954196 },
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 },
41964202 else => name: {
41974203 const raw_name = zir.nullTerminatedString(decl_name_index);
41984204 if (raw_name.len == 0) {
src/Zir.zig+2-1
......@@ -2579,10 +2579,11 @@ pub const Inst = struct {
25792579 /// - 0 means comptime or usingnamespace decl.
25802580 /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
25812581 /// - 1 means test decl with no name.
2582 /// - 2 means that the test is a decltest, doc_comment gives the name of the identifier
25822583 /// - if there is a 0 byte at the position `name` indexes, it indicates
25832584 /// this is a test decl, and the name starts at `name+1`.
25842585 /// value: Index,
2585 /// doc_comment: u32, // 0 if no doc comment
2586 /// doc_comment: u32, 0 if no doc comment, if this is a decltest, doc_comment references the decl name in the string table
25862587 /// align: Ref, // if corresponding bit is set
25872588 /// link_section_or_address_space: { // if corresponding bit is set.
25882589 /// link_section: Ref,
src/print_zir.zig+7-3
......@@ -1443,20 +1443,24 @@ const Writer = struct {
14431443 } else if (decl_name_index == 1) {
14441444 try stream.writeByteNTimes(' ', self.indent);
14451445 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) });
14461449 } else {
14471450 const raw_decl_name = self.code.nullTerminatedString(decl_name_index);
14481451 const decl_name = if (raw_decl_name.len == 0)
14491452 self.code.nullTerminatedString(decl_name_index + 1)
14501453 else
14511454 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 "";
14531456 const export_str = if (is_exported) "export " else "";
14541457
14551458 try self.writeDocComment(stream, doc_comment_index);
14561459
14571460 try stream.writeByteNTimes(' ', self.indent);
1458 try stream.print("[{d}] {s}{s}{s}{}", .{
1459 sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name),
1461 const endquote_if_test: []const u8 = if (raw_decl_name.len == 0) "\"" else "";
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,
14601464 });
14611465 if (align_inst != .none) {
14621466 try stream.writeAll(" align(");
test/behavior.zig+5
......@@ -49,6 +49,11 @@ test {
4949 _ = @import("behavior/type.zig");
5050 _ = @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
5257 if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) {
5358 // Tests that pass (partly) for stage1, llvm backend, C backend, wasm backend.
5459 _ = @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}