stage2: improve @typeName

* make it always return a fully qualified name. stage1 is inconsistent
   about this.
 * AstGen: fix anon_name_strategy to correctly be `func` when anon type
   creation happens in the operand of the return expression.
 * Sema: implement type names for the "function" naming strategy.
 * Put "enum", "union", "opaque", or "struct" in place of "anon" when
   creating respective anonymous Decl names.
 * std.testing: add `expectStringStartsWith`. Didn't end up using it
   after all.

Also this enables the real test runner for stage2 LLVM backend (sans
wasm32) since it works now.
This commit is contained in:
Andrew Kelley 2022-03-18 00:12:22 -07:00
parent 69d78bdae4
commit f3f5a5d05b
11 changed files with 371 additions and 89 deletions

View File

@ -9702,8 +9702,9 @@ test "integer truncation" {
<p>
This function returns the string representation of a type, as
an array. It is equivalent to a string literal of the type name.
The returned type name is fully qualified with the parent namespace included
as part of the type name with a series of dots.
</p>
{#header_close#}
{#header_open|@TypeOf#}

View File

@ -23,7 +23,9 @@ fn processArgs() void {
}
pub fn main() void {
if (builtin.zig_backend != .stage1) {
if (builtin.zig_backend != .stage1 and
(builtin.zig_backend != .stage2_llvm or builtin.cpu.arch == .wasm32))
{
return main2() catch @panic("test failure");
}
if (builtin.zig_backend == .stage1) processArgs();

View File

@ -445,6 +445,26 @@ pub fn expectEqualStrings(expected: []const u8, actual: []const u8) !void {
}
}
pub fn expectStringStartsWith(actual: []const u8, expected_starts_with: []const u8) !void {
if (std.mem.startsWith(u8, actual, expected_starts_with))
return;
const shortened_actual = if (actual.len >= expected_starts_with.len)
actual[0..expected_starts_with.len]
else
actual;
print("\n====== expected to start with: =========\n", .{});
printWithVisibleNewlines(expected_starts_with);
print("\n====== instead ended with: ===========\n", .{});
printWithVisibleNewlines(shortened_actual);
print("\n========= full output: ==============\n", .{});
printWithVisibleNewlines(actual);
print("\n======================================\n", .{});
return error.TestExpectedStartsWith;
}
pub fn expectStringEndsWith(actual: []const u8, expected_ends_with: []const u8) !void {
if (std.mem.endsWith(u8, actual, expected_ends_with))
return;

View File

@ -6293,7 +6293,10 @@ fn ret(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Inst.Ref
} else .{
.ty = try gz.addNodeExtended(.ret_type, node),
};
const prev_anon_name_strategy = gz.anon_name_strategy;
gz.anon_name_strategy = .func;
const operand = try reachableExpr(gz, scope, rl, operand_node, node);
gz.anon_name_strategy = prev_anon_name_strategy;
switch (nodeMayEvalToError(tree, operand_node)) {
.never => {

View File

@ -1767,7 +1767,7 @@ fn zirStructDecl(
const struct_obj = try new_decl_arena_allocator.create(Module.Struct);
const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj);
const struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty);
const type_name = try sema.createTypeName(block, small.name_strategy);
const type_name = try sema.createTypeName(block, small.name_strategy, "struct");
const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = struct_val,
@ -1796,28 +1796,54 @@ fn zirStructDecl(
return sema.analyzeDeclVal(block, src, new_decl);
}
fn createTypeName(sema: *Sema, block: *Block, name_strategy: Zir.Inst.NameStrategy) ![:0]u8 {
fn createTypeName(
sema: *Sema,
block: *Block,
name_strategy: Zir.Inst.NameStrategy,
anon_prefix: []const u8,
) ![:0]u8 {
switch (name_strategy) {
.anon => {
// It would be neat to have "struct:line:column" but this name has
// to survive incremental updates, where it may have been shifted down
// or up to a different line, but unchanged, and thus not unnecessarily
// semantically analyzed.
// This name is also used as the key in the parent namespace so it cannot be
// renamed.
const name_index = sema.mod.getNextAnonNameIndex();
return std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{
block.src_decl.name, name_index,
return std.fmt.allocPrintZ(sema.gpa, "{s}__{s}_{d}", .{
block.src_decl.name, anon_prefix, name_index,
});
},
.parent => return sema.gpa.dupeZ(u8, mem.sliceTo(block.src_decl.name, 0)),
.func => {
const name_index = sema.mod.getNextAnonNameIndex();
const name = try std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{
block.src_decl.name, name_index,
});
log.warn("TODO: handle NameStrategy.func correctly instead of using anon name '{s}'", .{
name,
});
return name;
const fn_info = sema.code.getFnInfo(sema.func.?.zir_body_inst);
const zir_tags = sema.code.instructions.items(.tag);
var buf = std.ArrayList(u8).init(sema.gpa);
defer buf.deinit();
try buf.appendSlice(mem.sliceTo(block.src_decl.name, 0));
try buf.appendSlice("(");
var arg_i: usize = 0;
for (fn_info.param_body) |zir_inst| switch (zir_tags[zir_inst]) {
.param, .param_comptime, .param_anytype, .param_anytype_comptime => {
const arg = sema.inst_map.get(zir_inst).?;
// The comptime call code in analyzeCall already did this, so we're
// just repeating it here and it's guaranteed to work.
const arg_val = sema.resolveConstMaybeUndefVal(block, .unneeded, arg) catch unreachable;
if (arg_i != 0) try buf.appendSlice(",");
try buf.writer().print("{}", .{arg_val});
arg_i += 1;
continue;
},
else => continue,
};
try buf.appendSlice(")");
return buf.toOwnedSliceSentinel(0);
},
}
}
@ -1877,7 +1903,7 @@ fn zirEnumDecl(
};
const enum_ty = Type.initPayload(&enum_ty_payload.base);
const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty);
const type_name = try sema.createTypeName(block, small.name_strategy);
const type_name = try sema.createTypeName(block, small.name_strategy, "enum");
const new_decl = try mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = enum_val,
@ -2088,7 +2114,7 @@ fn zirUnionDecl(
};
const union_ty = Type.initPayload(&union_payload.base);
const union_val = try Value.Tag.ty.create(new_decl_arena_allocator, union_ty);
const type_name = try sema.createTypeName(block, small.name_strategy);
const type_name = try sema.createTypeName(block, small.name_strategy, "union");
const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = union_val,
@ -2156,7 +2182,7 @@ fn zirOpaqueDecl(
};
const opaque_ty = Type.initPayload(&opaque_ty_payload.base);
const opaque_val = try Value.Tag.ty.create(new_decl_arena_allocator, opaque_ty);
const type_name = try sema.createTypeName(block, small.name_strategy);
const type_name = try sema.createTypeName(block, small.name_strategy, "opaque");
const new_decl = try mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = opaque_val,
@ -2204,7 +2230,7 @@ fn zirErrorSetDecl(
const error_set = try new_decl_arena_allocator.create(Module.ErrorSet);
const error_set_ty = try Type.Tag.error_set.create(new_decl_arena_allocator, error_set);
const error_set_val = try Value.Tag.ty.create(new_decl_arena_allocator, error_set_ty);
const type_name = try sema.createTypeName(block, name_strategy);
const type_name = try sema.createTypeName(block, name_strategy, "error");
const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = error_set_val,
@ -12697,7 +12723,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
};
const enum_ty = Type.initPayload(&enum_ty_payload.base);
const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty);
const type_name = try sema.createTypeName(block, .anon);
const type_name = try sema.createTypeName(block, .anon, "enum");
const new_decl = try mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = enum_val,
@ -12707,7 +12733,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
enum_obj.* = .{
.owner_decl = new_decl,
.tag_ty = Type.initTag(.@"null"),
.tag_ty = Type.@"null",
.tag_ty_inferred = true,
.fields = .{},
.values = .{},
@ -12785,7 +12811,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
};
const opaque_ty = Type.initPayload(&opaque_ty_payload.base);
const opaque_val = try Value.Tag.ty.create(new_decl_arena_allocator, opaque_ty);
const type_name = try sema.createTypeName(block, .anon);
const type_name = try sema.createTypeName(block, .anon, "opaque");
const new_decl = try mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = opaque_val,
@ -12836,7 +12862,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I
};
const union_ty = Type.initPayload(&union_payload.base);
const new_union_val = try Value.Tag.ty.create(new_decl_arena_allocator, union_ty);
const type_name = try sema.createTypeName(block, .anon);
const type_name = try sema.createTypeName(block, .anon, "union");
const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = new_union_val,
@ -13001,7 +13027,7 @@ fn reifyStruct(
const struct_obj = try new_decl_arena_allocator.create(Module.Struct);
const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj);
const new_struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty);
const type_name = try sema.createTypeName(block, .anon);
const type_name = try sema.createTypeName(block, .anon, "struct");
const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{
.ty = Type.type,
.val = new_struct_val,

View File

@ -1793,7 +1793,7 @@ pub const Type = extern union {
},
.error_set_inferred => {
const func = ty.castTag(.error_set_inferred).?.data.func;
return writer.print("(inferred error set of {s})", .{func.owner_decl.name});
return writer.print("@typeInfo(@typeInfo(@TypeOf({s})).Fn.return_type.?).ErrorUnion.error_set", .{func.owner_decl.name});
},
.error_set_merged => {
const names = ty.castTag(.error_set_merged).?.data.keys();
@ -1836,6 +1836,21 @@ pub const Type = extern union {
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
.generic_poison => unreachable,
.var_args_param => unreachable,
.bound_fn => unreachable,
// TODO get rid of these Type.Tag values.
.atomic_order => unreachable,
.atomic_rmw_op => unreachable,
.calling_convention => unreachable,
.address_space => unreachable,
.float_mode => unreachable,
.reduce_op => unreachable,
.call_options => unreachable,
.prefetch_options => unreachable,
.export_options => unreachable,
.extern_options => unreachable,
.type_info => unreachable,
.u1,
.u8,
@ -1873,39 +1888,44 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.var_args_param,
.bound_fn,
=> return maybeDupe(@tagName(t), ally, is_arena),
.enum_literal => return maybeDupe("@Type(.EnumLiteral)", ally, is_arena),
.@"null" => return maybeDupe("@Type(.Null)", ally, is_arena),
.@"undefined" => return maybeDupe("@Type(.Undefined)", ally, is_arena),
.enum_literal => return maybeDupe("@TypeOf(.enum_literal)", ally, is_arena),
.@"null" => return maybeDupe("@TypeOf(null)", ally, is_arena),
.@"undefined" => return maybeDupe("@TypeOf(undefined)", ally, is_arena),
.empty_struct_literal => return maybeDupe("@TypeOf(.{})", ally, is_arena),
.empty_struct, .empty_struct_literal => return maybeDupe("struct {}", ally, is_arena),
.empty_struct => {
const namespace = ty.castTag(.empty_struct).?.data;
var buffer = std.ArrayList(u8).init(ally);
defer buffer.deinit();
try namespace.renderFullyQualifiedName("", buffer.writer());
return buffer.toOwnedSliceSentinel(0);
},
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(struct_obj.owner_decl.name, 0));
return try struct_obj.owner_decl.getFullyQualifiedName(ally);
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Payload.Union).?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(union_obj.owner_decl.name, 0));
return try union_obj.owner_decl.getFullyQualifiedName(ally);
},
.enum_full, .enum_nonexhaustive => {
const enum_full = ty.cast(Payload.EnumFull).?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(enum_full.owner_decl.name, 0));
return try enum_full.owner_decl.getFullyQualifiedName(ally);
},
.enum_simple => {
const enum_simple = ty.castTag(.enum_simple).?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(enum_simple.owner_decl.name, 0));
return try enum_simple.owner_decl.getFullyQualifiedName(ally);
},
.enum_numbered => {
const enum_numbered = ty.castTag(.enum_numbered).?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(enum_numbered.owner_decl.name, 0));
return try enum_numbered.owner_decl.getFullyQualifiedName(ally);
},
.@"opaque" => {
const opaque_obj = ty.cast(Payload.Opaque).?.data;
return try ally.dupeZ(u8, std.mem.sliceTo(opaque_obj.owner_decl.name, 0));
return try opaque_obj.owner_decl.getFullyQualifiedName(ally);
},
.anyerror_void_error_union => return maybeDupe("anyerror!void", ally, is_arena),
@ -1919,21 +1939,79 @@ pub const Type = extern union {
.manyptr_u8 => return maybeDupe("[*]u8", ally, is_arena),
.manyptr_const_u8 => return maybeDupe("[*]const u8", ally, is_arena),
.manyptr_const_u8_sentinel_0 => return maybeDupe("[*:0]const u8", ally, is_arena),
.atomic_order => return maybeDupe("AtomicOrder", ally, is_arena),
.atomic_rmw_op => return maybeDupe("AtomicRmwOp", ally, is_arena),
.calling_convention => return maybeDupe("CallingConvention", ally, is_arena),
.address_space => return maybeDupe("AddressSpace", ally, is_arena),
.float_mode => return maybeDupe("FloatMode", ally, is_arena),
.reduce_op => return maybeDupe("ReduceOp", ally, is_arena),
.call_options => return maybeDupe("CallOptions", ally, is_arena),
.prefetch_options => return maybeDupe("PrefetchOptions", ally, is_arena),
.export_options => return maybeDupe("ExportOptions", ally, is_arena),
.extern_options => return maybeDupe("ExternOptions", ally, is_arena),
.type_info => return maybeDupe("Type", ally, is_arena),
.error_set_inferred => {
const func = ty.castTag(.error_set_inferred).?.data.func;
var buf = std.ArrayList(u8).init(ally);
defer buf.deinit();
try buf.appendSlice("@typeInfo(@typeInfo(@TypeOf(");
try func.owner_decl.renderFullyQualifiedName(buf.writer());
try buf.appendSlice(")).Fn.return_type.?).ErrorUnion.error_set");
return try buf.toOwnedSliceSentinel(0);
},
.function => {
const fn_info = ty.fnInfo();
var buf = std.ArrayList(u8).init(ally);
defer buf.deinit();
try buf.appendSlice("fn(");
for (fn_info.param_types) |param_type, i| {
if (i != 0) try buf.appendSlice(", ");
const param_name = try param_type.nameAllocAdvanced(ally, is_arena);
defer if (!is_arena) ally.free(param_name);
try buf.appendSlice(param_name);
}
if (fn_info.is_var_args) {
if (fn_info.param_types.len != 0) {
try buf.appendSlice(", ");
}
try buf.appendSlice("...");
}
try buf.appendSlice(") ");
if (fn_info.cc != .Unspecified) {
try buf.appendSlice("callconv(.");
try buf.appendSlice(@tagName(fn_info.cc));
try buf.appendSlice(") ");
}
if (fn_info.alignment != 0) {
try buf.writer().print("align({d}) ", .{fn_info.alignment});
}
{
const ret_ty_name = try fn_info.return_type.nameAllocAdvanced(ally, is_arena);
defer if (!is_arena) ally.free(ret_ty_name);
try buf.appendSlice(ret_ty_name);
}
return try buf.toOwnedSliceSentinel(0);
},
.error_union => {
const error_union = ty.castTag(.error_union).?.data;
var buf = std.ArrayList(u8).init(ally);
defer buf.deinit();
{
const err_set_ty_name = try error_union.error_set.nameAllocAdvanced(ally, is_arena);
defer if (!is_arena) ally.free(err_set_ty_name);
try buf.appendSlice(err_set_ty_name);
}
try buf.appendSlice("!");
{
const payload_ty_name = try error_union.payload.nameAllocAdvanced(ally, is_arena);
defer if (!is_arena) ally.free(payload_ty_name);
try buf.appendSlice(payload_ty_name);
}
return try buf.toOwnedSliceSentinel(0);
},
else => {
// TODO this is wasteful and also an incorrect implementation of `@typeName`
var buf = std.ArrayList(u8).init(ally);
defer buf.deinit();
try buf.writer().print("{}", .{ty});
return try buf.toOwnedSliceSentinel(0);
},

View File

@ -120,6 +120,7 @@ test {
_ = @import("behavior/tuple.zig");
_ = @import("behavior/type.zig");
_ = @import("behavior/type_info.zig");
_ = @import("behavior/typename.zig");
_ = @import("behavior/undefined.zig");
_ = @import("behavior/underscore.zig");
_ = @import("behavior/union.zig");
@ -179,7 +180,6 @@ test {
_ = @import("behavior/bugs/7027.zig");
_ = @import("behavior/select.zig");
_ = @import("behavior/struct_contains_slice_of_itself.zig");
_ = @import("behavior/typename.zig");
}
}
}

View File

@ -199,11 +199,18 @@ const OpaqueA = opaque {};
const OpaqueB = opaque {};
test "opaque types" {
try expect(*OpaqueA != *OpaqueB);
if (builtin.zig_backend == .stage1) { // TODO make this pass for stage2
try expect(mem.eql(u8, @typeName(OpaqueA), "OpaqueA"));
try expect(mem.eql(u8, @typeName(OpaqueB), "OpaqueB"));
if (builtin.zig_backend == .stage1) {
// stage1 gets the type names wrong
return error.SkipZigTest;
}
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
try expect(*OpaqueA != *OpaqueB);
try expect(mem.eql(u8, @typeName(OpaqueA), "behavior.basic.OpaqueA"));
try expect(mem.eql(u8, @typeName(OpaqueB), "behavior.basic.OpaqueB"));
}
const global_a: i32 = 1234;

View File

@ -28,9 +28,10 @@ const type_name = @typeName(TestType);
const ptr_type_name: [*:0]const u8 = type_name;
test "@typeName() returns a string literal" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the type wrong
try std.testing.expectEqual(*const [type_name.len:0]u8, @TypeOf(type_name));
try std.testing.expectEqualStrings("TestType", type_name);
try std.testing.expectEqualStrings("TestType", ptr_type_name[0..type_name.len]);
try std.testing.expectEqualStrings("behavior.bugs.3779.TestType", type_name);
try std.testing.expectEqualStrings("behavior.bugs.3779.TestType", ptr_type_name[0..type_name.len]);
}
const actual_contents = @embedFile("3779_file_to_embed.txt");

View File

@ -1,6 +1,8 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const expectEqualSlices = std.testing.expectEqualSlices;
const expectEqualStrings = std.testing.expectEqualStrings;
const expectStringStartsWith = std.testing.expectStringStartsWith;
// Most tests here can be comptime but use runtime so that a stacktrace
// can show failure location.
@ -9,50 +11,124 @@ const expectEqualSlices = std.testing.expectEqualSlices;
// root file. Running a test against this file as root will result in
// failures.
// CAUTION: this test is source-location sensitive.
test "anon fn param - source-location sensitive" {
// https://github.com/ziglang/zig/issues/9339
try expectEqualSlices(u8, @typeName(TypeFromFn(struct {})), "behavior.typename.TypeFromFn(behavior.typename.struct:15:52)");
try expectEqualSlices(u8, @typeName(TypeFromFn(union { unused: u8 })), "behavior.typename.TypeFromFn(behavior.typename.union:16:52)");
try expectEqualSlices(u8, @typeName(TypeFromFn(enum { unused })), "behavior.typename.TypeFromFn(behavior.typename.enum:17:52)");
test "anon fn param" {
if (builtin.zig_backend == .stage1) {
// stage1 uses line/column for the names but we're moving away from that for
// incremental compilation purposes.
return error.SkipZigTest;
}
try expectEqualSlices(
u8,
@typeName(TypeFromFn3(struct {}, union { unused: u8 }, enum { unused })),
"behavior.typename.TypeFromFn3(behavior.typename.struct:21:31,behavior.typename.union:21:42,behavior.typename.enum:21:64)",
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
// https://github.com/ziglang/zig/issues/9339
try expectEqualStringsIgnoreDigits(
"behavior.typename.TypeFromFn(behavior.typename.test.anon fn param__struct_0)",
@typeName(TypeFromFn(struct {})),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.TypeFromFn(behavior.typename.test.anon fn param__union_0)",
@typeName(TypeFromFn(union { unused: u8 })),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.TypeFromFn(behavior.typename.test.anon fn param__enum_0)",
@typeName(TypeFromFn(enum { unused })),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.TypeFromFnB(behavior.typename.test.anon fn param__struct_0,behavior.typename.test.anon fn param__union_0,behavior.typename.test.anon fn param__enum_0)",
@typeName(TypeFromFnB(struct {}, union { unused: u8 }, enum { unused })),
);
}
// CAUTION: this test is source-location sensitive.
test "anon field init" {
if (builtin.zig_backend == .stage1) {
// stage1 uses line/column for the names but we're moving away from that for
// incremental compilation purposes.
return error.SkipZigTest;
}
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const Foo = .{
.T1 = struct {},
.T2 = union { unused: u8 },
.T3 = enum { unused },
};
try expectEqualSlices(u8, @typeName(Foo.T1), "behavior.typename.struct:29:15");
try expectEqualSlices(u8, @typeName(Foo.T2), "behavior.typename.union:30:15");
try expectEqualSlices(u8, @typeName(Foo.T3), "behavior.typename.enum:31:15");
try expectEqualStringsIgnoreDigits(
"behavior.typename.test.anon field init__struct_0",
@typeName(Foo.T1),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.test.anon field init__union_0",
@typeName(Foo.T2),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.test.anon field init__enum_0",
@typeName(Foo.T3),
);
}
test "basic" {
try expectEqualSlices(u8, @typeName(i64), "i64");
try expectEqualSlices(u8, @typeName(*usize), "*usize");
try expectEqualSlices(u8, @typeName([]u8), "[]u8");
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
try expectEqualStrings(@typeName(i64), "i64");
try expectEqualStrings(@typeName(*usize), "*usize");
try expectEqualStrings(@typeName([]u8), "[]u8");
}
test "top level decl" {
try expectEqualSlices(u8, @typeName(A_Struct), "A_Struct");
try expectEqualSlices(u8, @typeName(A_Union), "A_Union");
try expectEqualSlices(u8, @typeName(A_Enum), "A_Enum");
if (builtin.zig_backend == .stage1) {
// stage1 fails to return fully qualified namespaces.
return error.SkipZigTest;
}
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
try expectEqualStrings(
"behavior.typename.A_Struct",
@typeName(A_Struct),
);
try expectEqualStrings(
"behavior.typename.A_Union",
@typeName(A_Union),
);
try expectEqualStrings(
"behavior.typename.A_Enum",
@typeName(A_Enum),
);
// regular fn, without error
try expectEqualSlices(u8, @typeName(@TypeOf(regular)), "fn() void");
try expectEqualStrings(
"fn() void",
@typeName(@TypeOf(regular)),
);
// regular fn inside struct, with error
try expectEqualSlices(u8, @typeName(@TypeOf(B.doTest)), "fn() @typeInfo(@typeInfo(@TypeOf(behavior.typename.B.doTest)).Fn.return_type.?).ErrorUnion.error_set!void");
try expectEqualStrings(
"fn() @typeInfo(@typeInfo(@TypeOf(behavior.typename.B.doTest)).Fn.return_type.?).ErrorUnion.error_set!void",
@typeName(@TypeOf(B.doTest)),
);
// generic fn
try expectEqualSlices(u8, @typeName(@TypeOf(TypeFromFn)), "fn(type) anytype");
try expectEqualStrings(
"fn(type) type",
@typeName(@TypeOf(TypeFromFn)),
);
}
const A_Struct = struct {};
@ -66,6 +142,17 @@ const A_Enum = enum {
fn regular() void {}
test "fn body decl" {
if (builtin.zig_backend == .stage1) {
// stage1 fails to return fully qualified namespaces.
return error.SkipZigTest;
}
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
try B.doTest();
}
@ -79,20 +166,50 @@ const B = struct {
unused,
};
try expectEqualSlices(u8, @typeName(B_Struct), "B_Struct");
try expectEqualSlices(u8, @typeName(B_Union), "B_Union");
try expectEqualSlices(u8, @typeName(B_Enum), "B_Enum");
try expectEqualStringsIgnoreDigits(
"behavior.typename.B.doTest__struct_0",
@typeName(B_Struct),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.B.doTest__union_0",
@typeName(B_Union),
);
try expectEqualStringsIgnoreDigits(
"behavior.typename.B.doTest__enum_0",
@typeName(B_Enum),
);
}
};
test "fn param" {
// https://github.com/ziglang/zig/issues/675
try expectEqualSlices(u8, @typeName(TypeFromFn(u8)), "behavior.typename.TypeFromFn(u8)");
try expectEqualSlices(u8, @typeName(TypeFromFn(A_Struct)), "behavior.typename.TypeFromFn(behavior.typename.A_Struct)");
try expectEqualSlices(u8, @typeName(TypeFromFn(A_Union)), "behavior.typename.TypeFromFn(behavior.typename.A_Union)");
try expectEqualSlices(u8, @typeName(TypeFromFn(A_Enum)), "behavior.typename.TypeFromFn(behavior.typename.A_Enum)");
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
try expectEqualSlices(u8, @typeName(TypeFromFn2(u8, bool)), "behavior.typename.TypeFromFn2(u8,bool)");
// https://github.com/ziglang/zig/issues/675
try expectEqualStrings(
"behavior.typename.TypeFromFn(u8)",
@typeName(TypeFromFn(u8)),
);
try expectEqualStrings(
"behavior.typename.TypeFromFn(behavior.typename.A_Struct)",
@typeName(TypeFromFn(A_Struct)),
);
try expectEqualStrings(
"behavior.typename.TypeFromFn(behavior.typename.A_Union)",
@typeName(TypeFromFn(A_Union)),
);
try expectEqualStrings(
"behavior.typename.TypeFromFn(behavior.typename.A_Enum)",
@typeName(TypeFromFn(A_Enum)),
);
try expectEqualStrings(
"behavior.typename.TypeFromFn2(u8,bool)",
@typeName(TypeFromFn2(u8, bool)),
);
}
fn TypeFromFn(comptime T: type) type {
@ -106,9 +223,32 @@ fn TypeFromFn2(comptime T1: type, comptime T2: type) type {
return struct {};
}
fn TypeFromFn3(comptime T1: type, comptime T2: type, comptime T3: type) type {
fn TypeFromFnB(comptime T1: type, comptime T2: type, comptime T3: type) type {
_ = T1;
_ = T2;
_ = T3;
return struct {};
}
/// Replaces integers in `actual` with '0' before doing the test.
pub fn expectEqualStringsIgnoreDigits(expected: []const u8, actual: []const u8) !void {
var actual_buf: [1024]u8 = undefined;
var actual_i: usize = 0;
var last_digit = false;
for (actual) |byte| {
switch (byte) {
'0'...'9' => {
if (last_digit) continue;
last_digit = true;
actual_buf[actual_i] = '0';
actual_i += 1;
},
else => {
last_digit = false;
actual_buf[actual_i] = byte;
actual_i += 1;
},
}
}
return expectEqualStrings(expected, actual_buf[0..actual_i]);
}

View File

@ -77,8 +77,12 @@ test "assign undefined to struct with method" {
}
test "type name of undefined" {
if (builtin.zig_backend == .stage1) {
// stage1 gets the type name wrong
return error.SkipZigTest;
}
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
const x = undefined;
try expect(mem.eql(u8, @typeName(@TypeOf(x)), "@Type(.Undefined)"));
try expect(mem.eql(u8, @typeName(@TypeOf(x)), "@TypeOf(undefined)"));
}