Commit f3f5a5d05b

Andrew Kelley <andrew@ziglang.org>
2022-03-18 08:12:22
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.
1 parent 69d78bd
doc/langref.html.in
@@ -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#}
lib/std/special/test_runner.zig
@@ -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();
lib/std/testing.zig
@@ -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;
src/AstGen.zig
@@ -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 => {
src/Sema.zig
@@ -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,
src/type.zig
@@ -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);
             },
test/behavior/bugs/3779.zig
@@ -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");
test/behavior/basic.zig
@@ -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;
test/behavior/typename.zig
@@ -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" {
+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;
+    }
+
+    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 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)");
+    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 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)",
+    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" {
+    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/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)");
+    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 expectEqualSlices(u8, @typeName(TypeFromFn2(u8, bool)), "behavior.typename.TypeFromFn2(u8,bool)");
+    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]);
+}
test/behavior/undefined.zig
@@ -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)"));
 }
test/behavior.zig
@@ -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");
             }
         }
     }