stage2: add decltests

This commit is contained in:
Jacob G-W 2022-01-27 15:23:28 -05:00 committed by Veikka Tuominen
parent 0b7347fd18
commit 3bbe6a28e0
9 changed files with 119 additions and 18 deletions

View File

@ -2519,7 +2519,7 @@ pub const Node = struct {
root,
/// `usingnamespace lhs;`. rhs unused. main_token is `usingnamespace`.
@"usingnamespace",
/// lhs is test name token (must be string literal), if any.
/// lhs is test name token (must be string literal or identifier), if any.
/// rhs is the body node.
test_decl,
/// lhs is the index into extra_data.

View File

@ -500,10 +500,16 @@ const Parser = struct {
}
}
/// TestDecl <- KEYWORD_test STRINGLITERALSINGLE? Block
/// TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
fn expectTestDecl(p: *Parser) !Node.Index {
const test_token = p.assertToken(.keyword_test);
const name_token = p.eatToken(.string_literal);
const name_token = switch (p.token_tags[p.nextToken()]) {
.string_literal, .identifier => p.tok_i - 1,
else => blk: {
p.tok_i -= 1;
break :blk null;
},
};
const block_node = try p.parseBlock();
if (block_node == 0) return p.fail(.expected_block);
return p.addNode(.{

View File

@ -151,7 +151,8 @@ fn renderMember(gpa: Allocator, ais: *Ais, tree: Ast, decl: Ast.Node.Index, spac
.test_decl => {
const test_token = main_tokens[decl];
try renderToken(ais, tree, test_token, .space);
if (token_tags[test_token + 1] == .string_literal) {
const test_name_tag = token_tags[test_token + 1];
if (test_name_tag == .string_literal or test_name_tag == .identifier) {
try renderToken(ais, tree, test_token + 1, .space);
}
try renderExpression(gpa, ais, tree, datas[decl].rhs, space);

View File

@ -105,8 +105,8 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
};
defer astgen.deinit(gpa);
// String table indexes 0 and 1 are reserved for special meaning.
try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0 });
// String table indexes 0, 1, 2 are reserved for special meaning.
try astgen.string_bytes.appendSlice(gpa, &[_]u8{ 0, 0, 0 });
// We expect at least as many ZIR instructions and extra data items
// as AST nodes.
@ -3736,13 +3736,78 @@ fn testDecl(
};
defer decl_block.unstack();
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
const test_token = main_tokens[node];
const test_name_token = test_token + 1;
const test_name_token_tag = token_tags[test_name_token];
const is_decltest = test_name_token_tag == .identifier;
const test_name: u32 = blk: {
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
const test_token = main_tokens[node];
const str_lit_token = test_token + 1;
if (token_tags[str_lit_token] == .string_literal) {
break :blk try astgen.testNameString(str_lit_token);
if (test_name_token_tag == .string_literal) {
break :blk try astgen.testNameString(test_name_token);
} else if (test_name_token_tag == .identifier) {
const ident_name_raw = tree.tokenSlice(test_name_token);
if (mem.eql(u8, ident_name_raw, "_")) return astgen.failTok(test_name_token, "'_' used as an identifier without @\"_\" syntax", .{});
// if not @"" syntax, just use raw token slice
if (ident_name_raw[0] != '@') {
if (primitives.get(ident_name_raw)) |_| return astgen.failTok(test_name_token, "cannot test a primitive", .{});
if (ident_name_raw.len >= 2) integer: {
const first_c = ident_name_raw[0];
if (first_c == 'i' or first_c == 'u') {
_ = switch (first_c == 'i') {
true => .signed,
false => .unsigned,
};
_ = parseBitCount(ident_name_raw[1..]) catch |err| switch (err) {
error.Overflow => return astgen.failTok(
test_name_token,
"primitive integer type '{s}' exceeds maximum bit width of 65535",
.{ident_name_raw},
),
error.InvalidCharacter => break :integer,
};
return astgen.failTok(test_name_token, "cannot test a primitive", .{});
}
}
}
// Local variables, including function parameters.
const name_str_index = try astgen.identAsString(test_name_token);
var s = scope;
var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already
var num_namespaces_out: u32 = 0;
var capturing_namespace: ?*Scope.Namespace = null;
while (true) switch (s.tag) {
.local_val, .local_ptr => unreachable, // a test cannot be in a local scope
.gen_zir => s = s.cast(GenZir).?.parent,
.defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent,
.namespace => {
const ns = s.cast(Scope.Namespace).?;
if (ns.decls.get(name_str_index)) |i| {
if (found_already) |f| {
return astgen.failTokNotes(test_name_token, "ambiguous reference", .{}, &.{
try astgen.errNoteNode(f, "declared here", .{}),
try astgen.errNoteNode(i, "also declared here", .{}),
});
}
// We found a match but must continue looking for ambiguous references to decls.
found_already = i;
}
num_namespaces_out += 1;
capturing_namespace = ns;
s = ns.parent;
},
.top => break,
};
if (found_already == null) {
const ident_name = try astgen.identifierTokenString(test_name_token);
return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name});
}
break :blk name_str_index;
}
// String table index 1 has a special meaning here of test decl with no name.
break :blk 1;
@ -3804,9 +3869,15 @@ fn testDecl(
const line_delta = decl_block.decl_line - gz.decl_line;
wip_members.appendToDecl(line_delta);
}
wip_members.appendToDecl(test_name);
if (is_decltest)
wip_members.appendToDecl(2) // 2 here means that it is a decltest, look at doc comment for name
else
wip_members.appendToDecl(test_name);
wip_members.appendToDecl(block_inst);
wip_members.appendToDecl(0); // no doc comments on test decls
if (is_decltest)
wip_members.appendToDecl(test_name) // the doc comment on a decltest represents it's name
else
wip_members.appendToDecl(0); // no doc comments on test decls
}
fn structDeclInner(

View File

@ -4170,6 +4170,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
const line_off = zir.extra[decl_sub_index + 4];
const line = iter.parent_decl.relativeToLine(line_off);
const decl_name_index = zir.extra[decl_sub_index + 5];
const decl_doccomment_index = zir.extra[decl_sub_index + 7];
const decl_index = zir.extra[decl_sub_index + 6];
const decl_block_inst_data = zir.instructions.items(.data)[decl_index].pl_node;
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
iter.unnamed_test_index += 1;
break :name try std.fmt.allocPrintZ(gpa, "test_{d}", .{i});
},
2 => name: {
is_named_test = true;
const test_name = zir.nullTerminatedString(decl_doccomment_index);
break :name try std.fmt.allocPrintZ(gpa, "decltest.{s}", .{test_name});
},
else => name: {
const raw_name = zir.nullTerminatedString(decl_name_index);
if (raw_name.len == 0) {

View File

@ -2579,10 +2579,11 @@ pub const Inst = struct {
/// - 0 means comptime or usingnamespace decl.
/// - if name == 0 `is_exported` determines which one: 0=comptime,1=usingnamespace
/// - 1 means test decl with no name.
/// - 2 means that the test is a decltest, doc_comment gives the name of the identifier
/// - if there is a 0 byte at the position `name` indexes, it indicates
/// this is a test decl, and the name starts at `name+1`.
/// value: Index,
/// doc_comment: u32, // 0 if no doc comment
/// doc_comment: u32, 0 if no doc comment, if this is a decltest, doc_comment references the decl name in the string table
/// align: Ref, // if corresponding bit is set
/// link_section_or_address_space: { // if corresponding bit is set.
/// link_section: Ref,

View File

@ -1443,20 +1443,24 @@ const Writer = struct {
} else if (decl_name_index == 1) {
try stream.writeByteNTimes(' ', self.indent);
try stream.writeAll("test");
} else if (decl_name_index == 2) {
try stream.writeByteNTimes(' ', self.indent);
try stream.print("[{d}] decltest {s}", .{ sub_index, self.code.nullTerminatedString(doc_comment_index) });
} else {
const raw_decl_name = self.code.nullTerminatedString(decl_name_index);
const decl_name = if (raw_decl_name.len == 0)
self.code.nullTerminatedString(decl_name_index + 1)
else
raw_decl_name;
const test_str = if (raw_decl_name.len == 0) "test " else "";
const test_str = if (raw_decl_name.len == 0) "test \"" else "";
const export_str = if (is_exported) "export " else "";
try self.writeDocComment(stream, doc_comment_index);
try stream.writeByteNTimes(' ', self.indent);
try stream.print("[{d}] {s}{s}{s}{}", .{
sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name),
const endquote_if_test: []const u8 = if (raw_decl_name.len == 0) "\"" else "";
try stream.print("[{d}] {s}{s}{s}{}{s}", .{
sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name), endquote_if_test,
});
if (align_inst != .none) {
try stream.writeAll(" align(");

View File

@ -49,6 +49,11 @@ test {
_ = @import("behavior/type.zig");
_ = @import("behavior/var_args.zig");
// tests that don't pass for stage1
if (builtin.zig_backend != .stage1) {
_ = @import("behavior/decltest.zig");
}
if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) {
// Tests that pass (partly) for stage1, llvm backend, C backend, wasm backend.
_ = @import("behavior/bitcast.zig");

View File

@ -0,0 +1,7 @@
pub fn the_add_function(a: u32, b: u32) u32 {
return a + b;
}
test the_add_function {
if (the_add_function(1, 2) != 3) unreachable;
}