Commit 0b5f3c2ef9

Tadeo Kondrak <me@tadeo.ca>
2021-01-11 19:19:24
Replace @TagType uses, mostly with std.meta.Tag
1 parent 1637d8a
doc/langref.html.in
@@ -2909,15 +2909,15 @@ test "enum variant switch" {
     expect(mem.eql(u8, what_is_it, "this is a number"));
 }
 
-// @TagType can be used to access the integer tag type of an enum.
+// @typeInfo can be used to access the integer tag type of an enum.
 const Small = enum {
     one,
     two,
     three,
     four,
 };
-test "@TagType" {
-    expect(@TagType(Small) == u2);
+test "std.meta.Tag" {
+    expect(@typeInfo(Small).Enum.tag_type == u2);
 }
 
 // @typeInfo tells us the field count and the fields names:
lib/std/c/ast.zig
@@ -110,7 +110,7 @@ pub const Error = union(enum) {
 
     pub const ExpectedToken = struct {
         token: TokenIndex,
-        expected_id: @TagType(Token.Id),
+        expected_id: std.meta.Tag(Token.Id),
 
         pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void {
             const found_token = tree.tokens.at(self.token);
lib/std/c/parse.zig
@@ -26,7 +26,7 @@ pub const Options = struct {
         None,
 
         /// Some warnings are errors
-        Some: []@TagType(ast.Error),
+        Some: []std.meta.Tag(ast.Error),
 
         /// All warnings are errors
         All,
@@ -1363,7 +1363,7 @@ const Parser = struct {
         return &node.base;
     }
 
-    fn eatToken(parser: *Parser, id: @TagType(Token.Id)) ?TokenIndex {
+    fn eatToken(parser: *Parser, id: std.meta.Tag(Token.Id)) ?TokenIndex {
         while (true) {
             switch ((parser.it.next() orelse return null).id) {
                 .LineComment, .MultiLineComment, .Nl => continue,
@@ -1377,7 +1377,7 @@ const Parser = struct {
         }
     }
 
-    fn expectToken(parser: *Parser, id: @TagType(Token.Id)) Error!TokenIndex {
+    fn expectToken(parser: *Parser, id: std.meta.Tag(Token.Id)) Error!TokenIndex {
         while (true) {
             switch ((parser.it.next() orelse return error.ParseError).id) {
                 .LineComment, .MultiLineComment, .Nl => continue,
lib/std/c/tokenizer.zig
@@ -131,7 +131,7 @@ pub const Token = struct {
         Keyword_error,
         Keyword_pragma,
 
-        pub fn symbol(id: @TagType(Id)) []const u8 {
+        pub fn symbol(id: std.meta.TagType(Id)) []const u8 {
             return switch (id) {
                 .Invalid => "Invalid",
                 .Eof => "Eof",
@@ -347,7 +347,7 @@ pub const Token = struct {
 pub const Tokenizer = struct {
     buffer: []const u8,
     index: usize = 0,
-    prev_tok_id: @TagType(Token.Id) = .Invalid,
+    prev_tok_id: std.meta.TagType(Token.Id) = .Invalid,
     pp_directive: bool = false,
 
     pub fn next(self: *Tokenizer) Token {
lib/std/hash/auto_hash.zig
@@ -239,7 +239,7 @@ fn testHashDeepRecursive(key: anytype) u64 {
 
 test "typeContainsSlice" {
     comptime {
-        testing.expect(!typeContainsSlice(@TagType(std.builtin.TypeInfo)));
+        testing.expect(!typeContainsSlice(meta.Tag(std.builtin.TypeInfo)));
 
         testing.expect(typeContainsSlice([]const u8));
         testing.expect(!typeContainsSlice(u8));
lib/std/meta/trailer_flags.zig
@@ -146,7 +146,7 @@ test "TrailerFlags" {
         b: bool,
         c: u64,
     });
-    testing.expectEqual(u2, @TagType(Flags.FieldEnum));
+    testing.expectEqual(u2, meta.Tag(Flags.FieldEnum));
 
     var flags = Flags.init(.{
         .b = true,
lib/std/zig/parser_test.zig
@@ -3822,7 +3822,7 @@ fn testCanonical(source: []const u8) !void {
     return testTransform(source, source);
 }
 
-const Error = @TagType(std.zig.ast.Error);
+const Error = std.meta.Tag(std.zig.ast.Error);
 
 fn testError(source: []const u8, expected_errors: []const Error) !void {
     const tree = try std.zig.parse(std.testing.allocator, source);
lib/std/builtin.zig
@@ -175,7 +175,7 @@ pub const SourceLocation = struct {
     column: u32,
 };
 
-pub const TypeId = @TagType(TypeInfo);
+pub const TypeId = std.meta.Tag(TypeInfo);
 
 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
lib/std/json.zig
@@ -246,7 +246,7 @@ pub const StreamingParser = struct {
         // Only call this function to generate array/object final state.
         pub fn fromInt(x: anytype) State {
             debug.assert(x == 0 or x == 1);
-            const T = @TagType(State);
+            const T = std.meta.Tag(State);
             return @intToEnum(State, @intCast(T, x));
         }
     };
@@ -1782,7 +1782,7 @@ test "parseFree descends into tagged union" {
     };
     // use a string with unicode escape so we know result can't be a reference to global constant
     const r = try parse(T, &TokenStream.init("\"with\\u0105unicode\""), options);
-    testing.expectEqual(@TagType(T).string, @as(@TagType(T), r));
+    testing.expectEqual(std.meta.Tag(T).string, @as(std.meta.Tag(T), r));
     testing.expectEqualSlices(u8, "withąunicode", r.string);
     testing.expectEqual(@as(usize, 0), fail_alloc.deallocations);
     parseFree(T, r, options);
lib/std/meta.zig
@@ -606,7 +606,7 @@ pub const TagType = Tag;
 pub fn Tag(comptime T: type) type {
     return switch (@typeInfo(T)) {
         .Enum => |info| info.tag_type,
-        .Union => |info| if (info.tag_type) |TheTag| TheTag else null,
+        .Union => |info| info.tag_type orelse @compileError(@typeName(T) ++ " has no tag type"),
         else => @compileError("expected enum or union type, found '" ++ @typeName(T) ++ "'"),
     };
 }
@@ -626,9 +626,9 @@ test "std.meta.Tag" {
 }
 
 ///Returns the active tag of a tagged union
-pub fn activeTag(u: anytype) @TagType(@TypeOf(u)) {
+pub fn activeTag(u: anytype) Tag(@TypeOf(u)) {
     const T = @TypeOf(u);
-    return @as(@TagType(T), u);
+    return @as(Tag(T), u);
 }
 
 test "std.meta.activeTag" {
@@ -653,11 +653,11 @@ const TagPayloadType = TagPayload;
 
 ///Given a tagged union type, and an enum, return the type of the union
 /// field corresponding to the enum tag.
-pub fn TagPayload(comptime U: type, tag: @TagType(U)) type {
+pub fn TagPayload(comptime U: type, tag: Tag(U)) type {
     testing.expect(trait.is(.Union)(U));
 
     const info = @typeInfo(U).Union;
-    const tag_info = @typeInfo(@TagType(U)).Enum;
+    const tag_info = @typeInfo(Tag(U)).Enum;
 
     inline for (info.fields) |field_info| {
         if (comptime mem.eql(u8, field_info.name, @tagName(tag)))
lib/std/testing.zig
@@ -119,10 +119,10 @@ pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void {
                 @compileError("Unable to compare untagged union values");
             }
 
-            const TagType = @TagType(@TypeOf(expected));
+            const Tag = std.meta.Tag(@TypeOf(expected));
 
-            const expectedTag = @as(TagType, expected);
-            const actualTag = @as(TagType, actual);
+            const expectedTag = @as(Tag, expected);
+            const actualTag = @as(Tag, actual);
 
             expectEqual(expectedTag, actualTag);
 
src/link/MachO/commands.zig
@@ -140,7 +140,7 @@ pub const LoadCommand = union(enum) {
     }
 
     fn eql(self: LoadCommand, other: LoadCommand) bool {
-        if (@as(@TagType(LoadCommand), self) != @as(@TagType(LoadCommand), other)) return false;
+        if (@as(meta.Tag(LoadCommand), self) != @as(meta.Tag(LoadCommand), other)) return false;
         return switch (self) {
             .DyldInfoOnly => |x| meta.eql(x, other.DyldInfoOnly),
             .Symtab => |x| meta.eql(x, other.Symtab),
src/stage1/analyze.cpp
@@ -3267,7 +3267,7 @@ static Error resolve_union_zero_bits(CodeGen *g, ZigType *union_type) {
 
         tag_type = new_type_table_entry(ZigTypeIdEnum);
         buf_resize(&tag_type->name, 0);
-        buf_appendf(&tag_type->name, "@typeInfo(%s).Enum.tag_type", buf_ptr(&union_type->name));
+        buf_appendf(&tag_type->name, "@typeInfo(%s).Union.tag_type.?", buf_ptr(&union_type->name));
         tag_type->llvm_type = tag_int_type->llvm_type;
         tag_type->llvm_di_type = tag_int_type->llvm_di_type;
         tag_type->abi_size = tag_int_type->abi_size;
src/DepTokenizer.zig
@@ -266,11 +266,11 @@ pub fn next(self: *Tokenizer) ?Token {
     unreachable;
 }
 
-fn errorPosition(comptime id: @TagType(Token), index: usize, bytes: []const u8) Token {
+fn errorPosition(comptime id: std.meta.Tag(Token), index: usize, bytes: []const u8) Token {
     return @unionInit(Token, @tagName(id), .{ .index = index, .bytes = bytes });
 }
 
-fn errorIllegalChar(comptime id: @TagType(Token), index: usize, char: u8) Token {
+fn errorIllegalChar(comptime id: std.meta.Tag(Token), index: usize, char: u8) Token {
     return @unionInit(Token, @tagName(id), .{ .index = index, .char = char });
 }
 
src/test.zig
@@ -750,7 +750,7 @@ pub const TestContext = struct {
 
                     for (actual_errors.list) |actual_error| {
                         for (case_error_list) |case_msg, i| {
-                            const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg;
+                            const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
                             switch (actual_error) {
                                 .src => |actual_msg| {
                                     for (actual_msg.notes) |*note| {
@@ -789,7 +789,7 @@ pub const TestContext = struct {
                     }
                     while (notes_to_check.popOrNull()) |note| {
                         for (case_error_list) |case_msg, i| {
-                            const ex_tag: @TagType(@TypeOf(case_msg)) = case_msg;
+                            const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
                             switch (note.*) {
                                 .src => |actual_msg| {
                                     for (actual_msg.notes) |*sub_note| {
src/translate_c.zig
@@ -3288,7 +3288,7 @@ const ClangFunctionType = union(enum) {
     NoProto: *const clang.FunctionType,
 
     fn getReturnType(self: @This()) clang.QualType {
-        switch (@as(@TagType(@This()), self)) {
+        switch (@as(std.meta.Tag(@This()), self)) {
             .Proto => return self.Proto.getReturnType(),
             .NoProto => return self.NoProto.getReturnType(),
         }
src/type.zig
@@ -110,7 +110,7 @@ pub const Type = extern union {
 
     pub fn tag(self: Type) Tag {
         if (self.tag_if_small_enough < Tag.no_payload_count) {
-            return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough));
+            return @intToEnum(Tag, @intCast(std.meta.Tag(Tag), self.tag_if_small_enough));
         } else {
             return self.ptr_otherwise.tag;
         }
src/value.zig
@@ -223,7 +223,7 @@ pub const Value = extern union {
 
     pub fn tag(self: Value) Tag {
         if (self.tag_if_small_enough < Tag.no_payload_count) {
-            return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough));
+            return @intToEnum(Tag, @intCast(std.meta.Tag(Tag), self.tag_if_small_enough));
         } else {
             return self.ptr_otherwise.tag;
         }
test/stage1/behavior/bugs/1322.zig
@@ -13,7 +13,7 @@ const C = struct {};
 
 test "tagged union with all void fields but a meaningful tag" {
     var a: A = A{ .b = B{ .c = C{} } };
-    std.testing.expect(@as(@TagType(B), a.b) == @TagType(B).c);
+    std.testing.expect(@as(std.meta.Tag(B), a.b) == std.meta.Tag(B).c);
     a = A{ .b = B.None };
-    std.testing.expect(@as(@TagType(B), a.b) == @TagType(B).None);
+    std.testing.expect(@as(std.meta.Tag(B), a.b) == std.meta.Tag(B).None);
 }
test/stage1/behavior/enum.zig
@@ -1,5 +1,6 @@
 const expect = @import("std").testing.expect;
 const mem = @import("std").mem;
+const Tag = @import("std").meta.Tag;
 
 test "extern enum" {
     const S = struct {
@@ -827,12 +828,12 @@ test "set enum tag type" {
     {
         var x = Small.One;
         x = Small.Two;
-        comptime expect(@TagType(Small) == u2);
+        comptime expect(Tag(Small) == u2);
     }
     {
         var x = Small2.One;
         x = Small2.Two;
-        comptime expect(@TagType(Small2) == u2);
+        comptime expect(Tag(Small2) == u2);
     }
 }
 
@@ -905,11 +906,11 @@ fn getC(data: *const BitFieldOfEnums) C {
 }
 
 test "casting enum to its tag type" {
-    testCastEnumToTagType(Small2.Two);
-    comptime testCastEnumToTagType(Small2.Two);
+    testCastEnumTag(Small2.Two);
+    comptime testCastEnumTag(Small2.Two);
 }
 
-fn testCastEnumToTagType(value: Small2) void {
+fn testCastEnumTag(value: Small2) void {
     expect(@enumToInt(value) == 1);
 }
 
@@ -1163,14 +1164,14 @@ test "enum with comptime_int tag type" {
         Two = 2,
         Three = 1,
     };
-    comptime expect(@TagType(Enum) == comptime_int);
+    comptime expect(Tag(Enum) == comptime_int);
 }
 
 test "enum with one member default to u0 tag type" {
     const E0 = enum {
         X,
     };
-    comptime expect(@TagType(E0) == u0);
+    comptime expect(Tag(E0) == u0);
 }
 
 test "tagName on enum literals" {
test/stage1/behavior/type_info.zig
@@ -14,7 +14,7 @@ test "type info: tag type, void info" {
 }
 
 fn testBasic() void {
-    expect(@TagType(TypeInfo) == TypeId);
+    expect(@typeInfo(TypeInfo).Union.tag_type == TypeId);
     const void_info = @typeInfo(void);
     expect(void_info == TypeId.Void);
     expect(void_info.Void == {});
test/stage1/behavior/union.zig
@@ -1,6 +1,7 @@
 const std = @import("std");
 const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
+const Tag = std.meta.Tag;
 
 const Value = union(enum) {
     Int: u64,
@@ -128,7 +129,7 @@ const MultipleChoice = union(enum(u32)) {
 test "simple union(enum(u32))" {
     var x = MultipleChoice.C;
     expect(x == MultipleChoice.C);
-    expect(@enumToInt(@as(@TagType(MultipleChoice), x)) == 60);
+    expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60);
 }
 
 const MultipleChoice2 = union(enum(u32)) {
@@ -144,13 +145,13 @@ const MultipleChoice2 = union(enum(u32)) {
 };
 
 test "union(enum(u32)) with specified and unspecified tag values" {
-    comptime expect(@TagType(@TagType(MultipleChoice2)) == u32);
+    comptime expect(Tag(Tag(MultipleChoice2)) == u32);
     testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
     comptime testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 });
 }
 
 fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) void {
-    expect(@enumToInt(@as(@TagType(MultipleChoice2), x)) == 60);
+    expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60);
     expect(1123 == switch (x) {
         MultipleChoice2.A => 1,
         MultipleChoice2.B => 2,
@@ -204,11 +205,11 @@ test "union field access gives the enum values" {
 }
 
 test "cast union to tag type of union" {
-    testCastUnionToTagType(TheUnion{ .B = 1234 });
-    comptime testCastUnionToTagType(TheUnion{ .B = 1234 });
+    testCastUnionToTag(TheUnion{ .B = 1234 });
+    comptime testCastUnionToTag(TheUnion{ .B = 1234 });
 }
 
-fn testCastUnionToTagType(x: TheUnion) void {
+fn testCastUnionToTag(x: TheUnion) void {
     expect(@as(TheTag, x) == TheTag.B);
 }
 
@@ -298,7 +299,7 @@ const TaggedUnionWithAVoid = union(enum) {
 
 fn testTaggedUnionInit(x: anytype) bool {
     const y = TaggedUnionWithAVoid{ .A = x };
-    return @as(@TagType(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
+    return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A;
 }
 
 pub const UnionEnumNoPayloads = union(enum) {
@@ -309,8 +310,8 @@ pub const UnionEnumNoPayloads = union(enum) {
 test "tagged union with no payloads" {
     const a = UnionEnumNoPayloads{ .B = {} };
     switch (a) {
-        @TagType(UnionEnumNoPayloads).A => @panic("wrong"),
-        @TagType(UnionEnumNoPayloads).B => {},
+        Tag(UnionEnumNoPayloads).A => @panic("wrong"),
+        Tag(UnionEnumNoPayloads).B => {},
     }
 }
 
@@ -325,9 +326,9 @@ test "union with only 1 field casted to its enum type" {
     };
 
     var e = Expr{ .Literal = Literal{ .Bool = true } };
-    const Tag = @TagType(Expr);
-    comptime expect(@TagType(Tag) == u0);
-    var t = @as(Tag, e);
+    const ExprTag = Tag(Expr);
+    comptime expect(Tag(ExprTag) == u0);
+    var t = @as(ExprTag, e);
     expect(t == Expr.Literal);
 }
 
@@ -337,17 +338,17 @@ test "union with only 1 field casted to its enum type which has enum value speci
         Bool: bool,
     };
 
-    const Tag = enum(comptime_int) {
+    const ExprTag = enum(comptime_int) {
         Literal = 33,
     };
 
-    const Expr = union(Tag) {
+    const Expr = union(ExprTag) {
         Literal: Literal,
     };
 
     var e = Expr{ .Literal = Literal{ .Bool = true } };
-    comptime expect(@TagType(Tag) == comptime_int);
-    var t = @as(Tag, e);
+    comptime expect(Tag(ExprTag) == comptime_int);
+    var t = @as(ExprTag, e);
     expect(t == Expr.Literal);
     expect(@enumToInt(t) == 33);
     comptime expect(@enumToInt(t) == 33);
@@ -501,7 +502,7 @@ test "union with one member defaults to u0 tag type" {
     const U0 = union(enum) {
         X: u32,
     };
-    comptime expect(@TagType(@TagType(U0)) == u0);
+    comptime expect(Tag(Tag(U0)) == u0);
 }
 
 test "union with comptime_int tag" {
@@ -510,7 +511,7 @@ test "union with comptime_int tag" {
         Y: u16,
         Z: u8,
     };
-    comptime expect(@TagType(@TagType(Union)) == comptime_int);
+    comptime expect(Tag(Tag(Union)) == comptime_int);
 }
 
 test "extern union doesn't trigger field check at comptime" {
@@ -591,7 +592,7 @@ test "function call result coerces from tagged union to the tag" {
             Two: usize,
         };
 
-        const ArchTag = @TagType(Arch);
+        const ArchTag = Tag(Arch);
 
         fn doTheTest() void {
             var x: ArchTag = getArch1();
@@ -696,8 +697,8 @@ test "cast from pointer to anonymous struct to pointer to union" {
 
 test "method call on an empty union" {
     const S = struct {
-        const MyUnion = union(Tag) {
-            pub const Tag = enum { X1, X2 };
+        const MyUnion = union(MyUnionTag) {
+            pub const MyUnionTag = enum { X1, X2 };
             X1: [0]u8,
             X2: [0]u8,
 
@@ -797,7 +798,7 @@ test "union enum type gets a separate scope" {
         };
 
         fn doTheTest() void {
-            expect(!@hasDecl(@TagType(U), "foo"));
+            expect(!@hasDecl(Tag(U), "foo"));
         }
     };
 
test/compile_errors.zig
@@ -323,7 +323,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         \\    e: E,
         \\};
         \\export fn entry() void {
-        \\    if (@TagType(E) != u8) @compileError("did not infer u8 tag type");
+        \\    if (@typeInfo(E).Enum.tag_type != u8) @compileError("did not infer u8 tag type");
         \\    const s: S = undefined;
         \\}
     , &[_][]const u8{
@@ -2728,7 +2728,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         \\const InvalidToken = struct {};
         \\const ExpectedVarDeclOrFn = struct {};
     , &[_][]const u8{
-        "tmp.zig:4:9: error: expected type '@TagType(Error)', found 'type'",
+        "tmp.zig:4:9: error: expected type '@typeInfo(Error).Union.tag_type.?', found 'type'",
     });
 
     cases.addTest("binary OR operator on error sets",
@@ -7462,24 +7462,12 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         "tmp.zig:4:5: note: declared here",
     });
 
-    cases.add("@TagType when union has no attached enum",
-        \\const Foo = union {
-        \\    A: i32,
-        \\};
-        \\export fn entry() void {
-        \\    const x = @TagType(Foo);
-        \\}
-    , &[_][]const u8{
-        "tmp.zig:5:24: error: union 'Foo' has no tag",
-        "tmp.zig:1:13: note: consider 'union(enum)' here",
-    });
-
     cases.add("non-integer tag type to automatic union enum",
         \\const Foo = union(enum(f32)) {
         \\    A: i32,
         \\};
         \\export fn entry() void {
-        \\    const x = @TagType(Foo);
+        \\    const x = @typeInfo(Foo).Union.tag_type.?;
         \\}
     , &[_][]const u8{
         "tmp.zig:1:24: error: expected integer tag type, found 'f32'",
@@ -7490,7 +7478,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
         \\    A: i32,
         \\};
         \\export fn entry() void {
-        \\    const x = @TagType(Foo);
+        \\    const x = @typeInfo(Foo).Union.tag_type.?;
         \\}
     , &[_][]const u8{
         "tmp.zig:1:19: error: expected enum tag type, found 'u32'",
test/runtime_safety.zig
@@ -74,7 +74,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
             \\pub fn main() void {
             \\    var u: U = undefined;
             \\    @memset(@ptrCast([*]u8, &u), 0x55, @sizeOf(U));
-            \\    var t: @TagType(U) = u;
+            \\    var t: @typeInfo(U).Union.tag_type.? = u;
             \\    var n = @tagName(t);
             \\}
         );
test/tests.zig
@@ -499,7 +499,7 @@ pub fn addPkgTests(
         if (skip_single_threaded and test_target.single_threaded)
             continue;
 
-        const ArchTag = @TagType(builtin.Arch);
+        const ArchTag = std.meta.Tag(builtin.Arch);
         if (test_target.disable_native and
             test_target.target.getOsTag() == std.Target.current.os.tag and
             test_target.target.getCpuArch() == std.Target.current.cpu.arch)
tools/process_headers.zig
@@ -47,7 +47,7 @@ const MultiAbi = union(enum) {
     fn eql(a: MultiAbi, b: MultiAbi) bool {
         if (@enumToInt(a) != @enumToInt(b))
             return false;
-        if (@TagType(MultiAbi)(a) != .specific)
+        if (std.meta.Tag(MultiAbi)(a) != .specific)
             return true;
         return a.specific == b.specific;
     }