| author | |
| committer | |
| log | 3bbe6a28e069b03c8a9185dd14129517453e26d2 |
| tree | b868356b27a7be324cbc1625df8b6bd13c3cc9d9 |
| parent | 0b7347fd18eee7dd829cd9aaed3683123d84859b |
9 files changed, 119 insertions(+), 18 deletions(-)
lib/std/zig/Ast.zig+1-1| ... | ... | @@ -2519,7 +2519,7 @@ pub const Node = struct { |
| 2519 | 2519 | root, |
| 2520 | 2520 | /// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`. |
| 2521 | 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 | 2523 | /// rhs is the body node. |
| 2524 | 2524 | test_decl, |
| 2525 | 2525 | /// lhs is the index into extra_data. |
lib/std/zig/parse.zig+8-2| ... | ... | @@ -500,10 +500,16 @@ const Parser = struct { |
| 500 | 500 | } |
| 501 | 501 | } |
| 502 | 502 | |
| 503 | /// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block | |
| 503 | /// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block | |
| 504 | 504 | fn expectTestDecl(p: *Parser) !Node.Index { |
| 505 | 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 | 513 | const block_node = try p.parseBlock(); |
| 508 | 514 | if (block_node == 0) return p.fail(.expected_block); |
| 509 | 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 | 151 | .test_decl => { |
| 152 | 152 | const test_token = main_tokens[decl]; |
| 153 | 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 | 156 | try renderToken(ais, tree, test_token + 1, .space); |
| 156 | 157 | } |
| 157 | 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 | 105 | }; |
| 106 | 106 | defer astgen.deinit(gpa); |
| 107 | 107 | |
| 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 }); | |
| 110 | 110 | |
| 111 | 111 | // We expect at least as many ZIR instructions and extra data items |
| 112 | 112 | // as AST nodes. |
| ... | ... | @@ -3736,13 +3736,78 @@ fn testDecl( |
| 3736 | 3736 | }; |
| 3737 | 3737 | defer decl_block.unstack(); |
| 3738 | 3738 | |
| 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 | 3745 | 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; | |
| 3746 | 3811 | } |
| 3747 | 3812 | // String table index 1 has a special meaning here of test decl with no name. |
| 3748 | 3813 | break :blk 1; |
| ... | ... | @@ -3804,9 +3869,15 @@ fn testDecl( |
| 3804 | 3869 | const line_delta = decl_block.decl_line - gz.decl_line; |
| 3805 | 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 | 3876 | 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 | |
| 3810 | 3881 | } |
| 3811 | 3882 | |
| 3812 | 3883 | fn structDeclInner( |
src/Module.zig+6| ... | ... | @@ -4170,6 +4170,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi |
| 4170 | 4170 | const line_off = zir.extra[decl_sub_index + 4]; |
| 4171 | 4171 | const line = iter.parent_decl.relativeToLine(line_off); |
| 4172 | 4172 | const decl_name_index = zir.extra[decl_sub_index + 5]; |
| 4173 | const decl_doccomment_index = zir.extra[decl_sub_index + 7]; | |
| 4173 | 4174 | const decl_index = zir.extra[decl_sub_index + 6]; |
| 4174 | 4175 | const decl_block_inst_data = zir.instructions.items(.data)[decl_index].pl_node; |
| 4175 | 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 | 4194 | iter.unnamed_test_index += 1; |
| 4194 | 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 | 4202 | else => name: { |
| 4197 | 4203 | const raw_name = zir.nullTerminatedString(decl_name_index); |
| 4198 | 4204 | if (raw_name.len == 0) { |
src/Zir.zig+2-1| ... | ... | @@ -2579,10 +2579,11 @@ pub const Inst = struct { |
| 2579 | 2579 | /// - 0 means comptime or usingnamespace decl. |
| 2580 | 2580 | /// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace |
| 2581 | 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 | 2583 | /// - if there is a 0 byte at the position `name` indexes, it indicates |
| 2583 | 2584 | /// this is a test decl, and the name starts at `name+1`. |
| 2584 | 2585 | /// 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 | |
| 2586 | 2587 | /// align: Ref, // if corresponding bit is set |
| 2587 | 2588 | /// link_section_or_address_space: { // if corresponding bit is set. |
| 2588 | 2589 | /// link_section: Ref, |
src/print_zir.zig+7-3| ... | ... | @@ -1443,20 +1443,24 @@ const Writer = struct { |
| 1443 | 1443 | } else if (decl_name_index == 1) { |
| 1444 | 1444 | try stream.writeByteNTimes(' ', self.indent); |
| 1445 | 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 | 1449 | } else { |
| 1447 | 1450 | const raw_decl_name = self.code.nullTerminatedString(decl_name_index); |
| 1448 | 1451 | const decl_name = if (raw_decl_name.len == 0) |
| 1449 | 1452 | self.code.nullTerminatedString(decl_name_index + 1) |
| 1450 | 1453 | else |
| 1451 | 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 | 1456 | const export_str = if (is_exported) "export " else ""; |
| 1454 | 1457 | |
| 1455 | 1458 | try self.writeDocComment(stream, doc_comment_index); |
| 1456 | 1459 | |
| 1457 | 1460 | 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, | |
| 1460 | 1464 | }); |
| 1461 | 1465 | if (align_inst != .none) { |
| 1462 | 1466 | try stream.writeAll(" align("); |
test/behavior.zig+5| ... | ... | @@ -49,6 +49,11 @@ test { |
| 49 | 49 | _ = @import("behavior/type.zig"); |
| 50 | 50 | _ = @import("behavior/var_args.zig"); |
| 51 | 51 | |
| 52 | // tests that don't pass for stage1 | |
| 53 | if (builtin.zig_backend != .stage1) { | |
| 54 | _ = @import("behavior/decltest.zig"); | |
| 55 | } | |
| 56 | ||
| 52 | 57 | if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) { |
| 53 | 58 | // Tests that pass (partly) for stage1, llvm backend, C backend, wasm backend. |
| 54 | 59 | _ = @import("behavior/bitcast.zig"); |
test/behavior/decltest.zig created+7| ... | ... | @@ -0,0 +1,7 @@ |
| 1 | pub fn the_add_function(a: u32, b: u32) u32 { | |
| 2 | return a + b; | |
| 3 | } | |
| 4 | ||
| 5 | test the_add_function { | |
| 6 | if (the_add_function(1, 2) != 3) unreachable; | |
| 7 | } |