Commit 3bbe6a28e0
Changed files (9)
lib
std
test
behavior
lib/std/zig/Ast.zig
@@ -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.
lib/std/zig/parse.zig
@@ -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(.{
lib/std/zig/render.zig
@@ -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);
src/AstGen.zig
@@ -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(
src/Module.zig
@@ -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) {
src/print_zir.zig
@@ -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(");
src/Zir.zig
@@ -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,
test/behavior/decltest.zig
@@ -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;
+}
test/behavior.zig
@@ -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");