From bd8c90175411bcc1a3b6a59ab6416bae844fbaea Mon Sep 17 00:00:00 2001 From: Jonathan M Davis Date: Sat, 20 Apr 2024 19:43:31 -0600 Subject: [PATCH] Add FieldNames, FieldSymbols, and FieldTypes to phobos.sys.traits. FieldNames is roughly equivalent to std.traits' FieldNameTuple, and FieldTypes is roughly equivalent to std.traits' Fields (which used to be FieldTypeTuple). The primary difference implementation-wise is that the phobos.sys.traits versions require that the types that they're given be aggregate types. For some reason, the std.traits versions accept any type and then try to give a result that at least sort of make sense when they're given a type which isn't an aggregate type (even though they really can't, because a type which isn't an aggregate type has no fields, making any choice kind of arbitrary). For types which aren't aggregate types, Fields gives the type back in an AliasSeq, and FieldNameTuple gives AliasSeq!"". Neither makes any sense to me. I assume that it was done so that those traits could be used in generic code and work with any type, but realistically, if you want to do anything sane with them, you need to already have verified that you're dealing with an aggregate type, since it's just going to be error-prone to do stuff like Fields!int and then get AliasSeq!int back as if it had a single field of type int (or FieldNameTuple!int and get an empty string as the name). So, the phobos.sys.traits versions simply require that you give them aggregate types to avoid that entire mess. FieldNames evaluates to the names of the fields as strings. The "Tuple" in the name of the std.traits version is an artifact from when AliasSeqs were called TypeTuples, so I didn't keep that. FieldTypes evaluates to the types for the fields. It's FieldTypes rather than Fields for clarity, since it's not at all obvious what Fields is supposed to give you (if I'd had to guess, I would have guessed the symbols, not the types). FieldSymbols is new. Its usefulness is questionable, since it does almost exactly the same thing that tupleof does. However, I've included it because of the subtle issues that you get with nested structs - namely that tupleof includes a context pointer in its result, which you probably don't want (though that obviously depends on what you're doing), and FieldNames and FieldTypes don't include it (just like their std.traits counterparts don't), so it seemed like it would make it less error-prone to have FieldSymbols for the cases where symbols are needed. The documentation explains (and the examples show) the difference between FieldSymbols and tupleof so that the programmer can decide which makes more sense for their particular use case. --- phobos/sys/traits.d | 797 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 797 insertions(+) diff --git a/phobos/sys/traits.d b/phobos/sys/traits.d index 65d56af141b..595dc7a15b6 100644 --- a/phobos/sys/traits.d +++ b/phobos/sys/traits.d @@ -74,6 +74,11 @@ $(LREF isSameSymbol) $(LREF isSameType) )) + $(TR $(TD Aggregate Type Traits) $(TD + $(LREF FieldNames) + $(LREF FieldSymbols) + $(LREF FieldTypes) + )) $(TR $(TD General Types) $(TD $(LREF KeyType) $(LREF OriginalType) @@ -2468,6 +2473,798 @@ template isSameType(T) static assert(indexOf!(isSameType!int, Types) == 2); } +/++ + Evaluates to an $(D AliasSeq) of the names (as $(D string)s) of the member + variables of an aggregate type (i.e. a struct, class, interface, or union). + + These are fields which take up memory space within an instance of the type + (i.e. not enums / manifest constants, since they don't take up memory + space, and not static member variables, since they don't take up memory + space within an instance). + + Hidden fields (like the virtual function table pointer or the context + pointer for nested types) are not included. + + For classes, only the direct member variables are included and not those + of any base classes. + + For interfaces, the result of FieldNames is always empty, because + interfaces cannot have member variables. However, because interfaces are + aggregate types, they work with FieldNames for consistency so that code + that's written to work on aggregate types doesn't have to worry about + whether it's dealing with an interface. + + See_Also: + $(LREF FieldSymbols) + $(LREF FieldTypes) + $(DDSUBLINK spec/struct.html, struct_instance_properties, $(D tupleof)) + +/ +template FieldNames(T) +if (isAggregateType!T) +{ + import phobos.sys.meta : AliasSeq; + + static if (is(T == struct) && __traits(isNested, T)) + private alias Fields = AliasSeq!(T.tupleof[0 .. $ - 1]); + else + private alias Fields = T.tupleof; + + alias FieldNames = AliasSeq!(); + static foreach (Field; Fields) + FieldNames = AliasSeq!(FieldNames, Field.stringof); +} + +/// +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + struct S + { + int x; + float y; + } + static assert(FieldNames!S == AliasSeq!("x", "y")); + + // Since the AliasSeq contains values, all of which are of the same type, + // it can be used to create a dynamic array, which would be more + // efficient than operating on an AliasSeq in the cases where an + // AliasSeq is not necessary. + static assert([FieldNames!S] == ["x", "y"]); + + class C + { + // static variables are not included. + static int var; + + // Manifest constants are not included. + enum lang = "dlang"; + + // Functions are not included, even if they're @property functions. + @property int foo() { return 42; } + + string s; + int i; + int[] arr; + } + static assert(FieldNames!C == AliasSeq!("s", "i", "arr")); + + static assert([FieldNames!C] == ["s", "i", "arr"]); + + // Only direct member variables are included. Member variables from any base + // classes are not. + class D : C + { + real r; + } + static assert(FieldNames!D == AliasSeq!"r"); + + static assert([FieldNames!D] == ["r"]); + + // FieldNames will always be empty for an interface, since it's not legal + // for interfaces to have member variables. + interface I + { + } + static assert(FieldNames!I.length == 0); + + union U + { + int i; + double d; + long l; + S s; + } + static assert(FieldNames!U == AliasSeq!("i", "d", "l", "s")); + + static assert([FieldNames!U] == ["i", "d", "l", "s"]);; + + // FieldNames only operates on aggregate types. + static assert(!__traits(compiles, FieldNames!int)); + static assert(!__traits(compiles, FieldNames!(S*))); + static assert(!__traits(compiles, FieldNames!(C[]))); +} + +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + { + static struct S0 {} + static assert(FieldNames!S0.length == 0); + + static struct S1 { int a; } + static assert(FieldNames!S1 == AliasSeq!"a"); + + static struct S2 { int a; string b; } + static assert(FieldNames!S2 == AliasSeq!("a", "b")); + + static struct S3 { int a; string b; real c; } + static assert(FieldNames!S3 == AliasSeq!("a", "b", "c")); + } + { + int i; + struct S0 { void foo() { i = 0; }} + static assert(FieldNames!S0.length == 0); + static assert(__traits(isNested, S0)); + + struct S1 { int a; void foo() { i = 0; } } + static assert(FieldNames!S1 == AliasSeq!"a"); + static assert(__traits(isNested, S1)); + + struct S2 { int a; string b; void foo() { i = 0; } } + static assert(FieldNames!S2 == AliasSeq!("a", "b")); + static assert(__traits(isNested, S2)); + + struct S3 { int a; string b; real c; void foo() { i = 0; } } + static assert(FieldNames!S3 == AliasSeq!("a", "b", "c")); + static assert(__traits(isNested, S3)); + } + { + static class C0 {} + static assert(FieldNames!C0.length == 0); + + static class C1 { int a; } + static assert(FieldNames!C1 == AliasSeq!"a"); + + static class C2 { int a; string b; } + static assert(FieldNames!C2 == AliasSeq!("a", "b")); + + static class C3 { int a; string b; real c; } + static assert(FieldNames!C3 == AliasSeq!("a", "b", "c")); + + static class D0 : C3 {} + static assert(FieldNames!D0.length == 0); + + static class D1 : C3 { bool x; } + static assert(FieldNames!D1 == AliasSeq!"x"); + + static class D2 : C3 { bool x; int* y; } + static assert(FieldNames!D2 == AliasSeq!("x", "y")); + + static class D3 : C3 { bool x; int* y; short[] z; } + static assert(FieldNames!D3 == AliasSeq!("x", "y", "z")); + } + { + int i; + class C0 { void foo() { i = 0; }} + static assert(FieldNames!C0.length == 0); + static assert(__traits(isNested, C0)); + + class C1 { int a; void foo() { i = 0; } } + static assert(FieldNames!C1 == AliasSeq!"a"); + static assert(__traits(isNested, C1)); + + class C2 { int a; string b; void foo() { i = 0; } } + static assert(FieldNames!C2 == AliasSeq!("a", "b")); + static assert(__traits(isNested, C2)); + + class C3 { int a; string b; real c; void foo() { i = 0; } } + static assert(FieldNames!C3 == AliasSeq!("a", "b", "c")); + static assert(__traits(isNested, C3)); + + class D0 : C3 {} + static assert(FieldNames!D0.length == 0); + static assert(__traits(isNested, D0)); + + class D1 : C3 { bool x; } + static assert(FieldNames!D1 == AliasSeq!"x"); + static assert(__traits(isNested, D1)); + + class D2 : C3 { bool x; int* y; } + static assert(FieldNames!D2 == AliasSeq!("x", "y")); + static assert(__traits(isNested, D2)); + + class D3 : C3 { bool x; int* y; short[] z; } + static assert(FieldNames!D3 == AliasSeq!("x", "y", "z")); + static assert(__traits(isNested, D3)); + } + { + static union U0 {} + static assert(FieldNames!U0.length == 0); + + static union U1 { int a; } + static assert(FieldNames!U1 == AliasSeq!"a"); + + static union U2 { int a; string b; } + static assert(FieldNames!U2 == AliasSeq!("a", "b")); + + static union U3 { int a; string b; real c; } + static assert(FieldNames!U3 == AliasSeq!("a", "b", "c")); + } + { + static struct S + { + enum e = 42; + static str = "foobar"; + + string name() { return "foo"; } + + int[] arr; + + struct Inner1 { int i; } + + static struct Inner2 { long gnol; } + + union { int a; string b; } + + alias Foo = Inner1; + } + + static assert(FieldNames!S == AliasSeq!("arr", "a", "b")); + static assert(FieldNames!(const S) == AliasSeq!("arr", "a", "b")); + static assert(FieldNames!(S.Inner1) == AliasSeq!"i"); + static assert(FieldNames!(S.Inner2) == AliasSeq!"gnol"); + } +} + +/++ + Evaluates to an $(D AliasSeq) of the symbols for the member variables of an + aggregate type (i.e. a struct, class, interface, or union). + + These are fields which take up memory space within an instance of the type + (i.e. not enums / manifest constants, since they don't take up memory + space, and not static member variables, since they don't take up memory + space within an instance). + + Hidden fields (like the virtual function table pointer or the context + pointer for nested types) are not included. + + For classes, only the direct member variables are included and not those + of any base classes. + + For interfaces, the result of FieldSymbols is always empty, because + interfaces cannot have member variables. However, because interfaces are + aggregate types, they work with FieldSymbols for consistency so that code + that's written to work on aggregate types doesn't have to worry about + whether it's dealing with an interface. + + In most cases, $(D FieldSymbols!T) has the same result as $(D T.tupleof). + The difference is that for nested structs with a context pointer, + $(D T.tupleof) includes the context pointer, whereas $(D FieldSymbols!T) + does not. For non-nested structs, and for classes, interfaces, and unions, + $(D FieldSymbols!T) and $(D T.tupleof) are the same. + + So, for most cases, $(D T.tupleof) is sufficient and avoids instantiating + an additional template, but FieldSymbols is provided so that the code that + needs to avoid including context pointers in the list of fields can do so + without the programmer having to figure how to do that correctly. It also + provides a template that's equivalent to what $(LREF FieldNames) and + $(LREF FieldTypes) do in terms of which fields it gives (the difference of + course then being whether you get the symbols, names, or types for the + fields), whereas the behavior for $(D tupleof) is subtly different. + + See_Also: + $(LREF FieldNames) + $(LREF FieldTypes) + $(DDSUBLINK spec/struct.html, struct_instance_properties, $(D tupleof)) + $(DDSUBLINK spec/traits, isNested, $(D __traits(isNested, ...))). + $(DDSUBLINK spec/traits, isSame, $(D __traits(isSame, ...))). + +/ +template FieldSymbols(T) +if (isAggregateType!T) +{ + static if (is(T == struct) && __traits(isNested, T)) + { + import phobos.sys.meta : AliasSeq; + alias FieldSymbols = AliasSeq!(T.tupleof[0 .. $ - 1]); + } + else + alias FieldSymbols = T.tupleof; +} + +/// +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + struct S + { + int x; + float y; + } + static assert(__traits(isSame, FieldSymbols!S, AliasSeq!(S.x, S.y))); + + // FieldSymbols!S and S.tupleof are the same, because S is not nested. + static assert(__traits(isSame, FieldSymbols!S, S.tupleof)); + + // Note that type qualifiers _should_ be passed on to the result, but due + // to https://issues.dlang.org/show_bug.cgi?id=24516, they aren't. + // FieldTypes does not have this problem, because it aliases the types + // rather than the symbols, so if you need the types from the symbols, you + // should use either FieldTypes or tupleof until the compiler bug has been + // fixed (and if you use tupleof, you need to avoid aliasing the result + // before getting the types from it). + static assert(is(typeof(FieldSymbols!S[0]) == int)); + + // These currently fail when they shouldn't: + //static assert(is(typeof(FieldSymbols!(const S)[0]) == const int)); + //static assert(is(typeof(FieldSymbols!(shared S)[0]) == shared int)); + + class C + { + // static variables are not included. + static int var; + + // Manifest constants are not included. + enum lang = "dlang"; + + // Functions are not included, even if they're @property functions. + @property int foo() { return 42; } + + string s; + int i; + int[] arr; + } + static assert(__traits(isSame, FieldSymbols!C, AliasSeq!(C.s, C.i, C.arr))); + + // FieldSymbols!C and C.tupleof have the same symbols, because they are + // always the same for classes. + static assert(__traits(isSame, FieldSymbols!C, C.tupleof)); + + // Only direct member variables are included. Member variables from any base + // classes are not. + class D : C + { + real r; + } + static assert(__traits(isSame, FieldSymbols!D, AliasSeq!(D.r))); + static assert(__traits(isSame, FieldSymbols!D, D.tupleof)); + + // FieldSymbols will always be empty for an interface, since it's not legal + // for interfaces to have member variables. + interface I + { + } + static assert(FieldSymbols!I.length == 0); + static assert(I.tupleof.length == 0); + + union U + { + int i; + double d; + long l; + S s; + } + static assert(__traits(isSame, FieldSymbols!U, AliasSeq!(U.i, U.d, U.l, U.s))); + + // FieldSymbols!C and C.tupleof have the same symbols, because they are + // always the same for unions. + static assert(__traits(isSame, FieldSymbols!U, U.tupleof)); + + // FieldSymbols only operates on aggregate types. + static assert(!__traits(compiles, FieldSymbols!int)); + static assert(!__traits(compiles, FieldSymbols!(S*))); + static assert(!__traits(compiles, FieldSymbols!(C[]))); +} + +/// Some examples with nested types. +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + int outside; + + struct S + { + long l; + string s; + + void foo() { outside = 2; } + } + static assert(__traits(isNested, S)); + static assert(__traits(isSame, FieldSymbols!S, AliasSeq!(S.l, S.s))); + + // FieldSymbols!S and S.tupleof are not the same, because S is nested, and + // the context pointer to the outer scope is included in S.tupleof, whereas + // it is excluded from FieldSymbols!S. + static assert(__traits(isSame, S.tupleof[0 .. $ - 1], AliasSeq!(S.l, S.s))); + static assert(S.tupleof[$ - 1].stringof == "this"); + + class C + { + bool b; + int* ptr; + + void foo() { outside = 7; } + } + static assert(__traits(isNested, C)); + static assert(__traits(isSame, FieldSymbols!C, AliasSeq!(C.b, C.ptr))); + + // FieldSymbols!C and C.tupleof have the same symbols, because they are + // always the same for classes. No context pointer is provided as part of + // tupleof for nested classes. + static assert(__traits(isSame, FieldSymbols!C, C.tupleof)); + + // __traits(isNested, ...) is never true for interfaces or unions, since + // they cannot have a context pointer to an outer scope. So, tupleof and + // FieldSymbols will always be the same for interfaces and unions. +} + +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + { + static struct S0 {} + static assert(FieldSymbols!S0.length == 0); + + static struct S1 { int a; } + static assert(__traits(isSame, FieldSymbols!S1, AliasSeq!(S1.a))); + + static struct S2 { int a; string b; } + static assert(__traits(isSame, FieldSymbols!S2, AliasSeq!(S2.a, S2.b))); + + static struct S3 { int a; string b; real c; } + static assert(__traits(isSame, FieldSymbols!S3, AliasSeq!(S3.a, S3.b, S3.c))); + } + { + int i; + struct S0 { void foo() { i = 0; }} + static assert(FieldSymbols!S0.length == 0); + static assert(__traits(isNested, S0)); + + struct S1 { int a; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!S1, AliasSeq!(S1.a))); + static assert(__traits(isNested, S1)); + + struct S2 { int a; string b; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!S2, AliasSeq!(S2.a, S2.b))); + static assert(__traits(isNested, S2)); + + struct S3 { int a; string b; real c; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!S3, AliasSeq!(S3.a, S3.b, S3.c))); + static assert(__traits(isNested, S3)); + } + { + static class C0 {} + static assert(FieldSymbols!C0.length == 0); + + static class C1 { int a; } + static assert(__traits(isSame, FieldSymbols!C1, AliasSeq!(C1.a))); + + static class C2 { int a; string b; } + static assert(__traits(isSame, FieldSymbols!C2, AliasSeq!(C2.a, C2.b))); + + static class C3 { int a; string b; real c; } + static assert(__traits(isSame, FieldSymbols!C3, AliasSeq!(C3.a, C3.b, C3.c))); + + static class D0 : C3 {} + static assert(FieldSymbols!D0.length == 0); + + static class D1 : C3 { bool x; } + static assert(__traits(isSame, FieldSymbols!D1, AliasSeq!(D1.x))); + + static class D2 : C3 { bool x; int* y; } + static assert(__traits(isSame, FieldSymbols!D2, AliasSeq!(D2.x, D2.y))); + + static class D3 : C3 { bool x; int* y; short[] z; } + static assert(__traits(isSame, FieldSymbols!D3, AliasSeq!(D3.x, D3.y, D3.z))); + } + { + int i; + class C0 { void foo() { i = 0; }} + static assert(FieldSymbols!C0.length == 0); + static assert(__traits(isNested, C0)); + + class C1 { int a; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!C1, AliasSeq!(C1.a))); + static assert(__traits(isNested, C1)); + + class C2 { int a; string b; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!C2, AliasSeq!(C2.a, C2.b))); + static assert(__traits(isNested, C2)); + + class C3 { int a; string b; real c; void foo() { i = 0; } } + static assert(__traits(isSame, FieldSymbols!C3, AliasSeq!(C3.a, C3.b, C3.c))); + static assert(__traits(isNested, C3)); + + class D0 : C3 {} + static assert(FieldSymbols!D0.length == 0); + static assert(__traits(isNested, D0)); + + class D1 : C3 { bool x; } + static assert(__traits(isSame, FieldSymbols!D1, AliasSeq!(D1.x))); + static assert(__traits(isNested, D1)); + + class D2 : C3 { bool x; int* y; } + static assert(__traits(isSame, FieldSymbols!D2, AliasSeq!(D2.x, D2.y))); + static assert(__traits(isNested, D2)); + + class D3 : C3 { bool x; int* y; short[] z; } + static assert(__traits(isSame, FieldSymbols!D3, AliasSeq!(D3.x, D3.y, D3.z))); + static assert(__traits(isNested, D3)); + } + { + static union U0 {} + static assert(FieldSymbols!U0.length == 0); + + static union U1 { int a; } + static assert(__traits(isSame, FieldSymbols!U1, AliasSeq!(U1.a))); + + static union U2 { int a; string b; } + static assert(__traits(isSame, FieldSymbols!U2, AliasSeq!(U2.a, U2.b))); + + static union U3 { int a; string b; real c; } + static assert(__traits(isSame, FieldSymbols!U3, AliasSeq!(U3.a, U3.b, U3.c))); + } + { + static struct S + { + enum e = 42; + static str = "foobar"; + + string name() { return "foo"; } + + int[] arr; + + struct Inner1 { int i; } + + static struct Inner2 { long gnol; } + + union { int a; string b; } + + alias Foo = Inner1; + } + + static assert(__traits(isSame, FieldSymbols!S, AliasSeq!(S.arr, S.a, S.b))); + static assert(__traits(isSame, FieldSymbols!(const S), AliasSeq!(S.arr, S.a, S.b))); + static assert(__traits(isSame, FieldSymbols!(S.Inner1), AliasSeq!(S.Inner1.i))); + static assert(__traits(isSame, FieldSymbols!(S.Inner2), AliasSeq!(S.Inner2.gnol))); + } +} + +/++ + Evaluates to an $(D AliasSeq) of the types of the member variables of an + aggregate type (i.e. a struct, class, interface, or union). + + These are fields which take up memory space within an instance of the type + (i.e. not enums / manifest constants, since they don't take up memory + space, and not static member variables, since they don't take up memory + space within an instance). + + Hidden fields (like the virtual function table pointer or the context + pointer for nested types) are not included. + + For classes, only the direct member variables are included and not those + of any base classes. + + For interfaces, the result of FieldTypes is always empty, because + interfaces cannot have member variables. However, because interfaces are + aggregate types, they work with FieldTypes for consistency so that code + that's written to work on aggregate types doesn't have to worry about + whether it's dealing with an interface. + + See_Also: + $(LREF FieldNames) + $(LREF FieldSymbols) + $(DDSUBLINK spec/struct.html, struct_instance_properties, $(D tupleof)) + +/ +template FieldTypes(T) +if (isAggregateType!T) +{ + static if (is(T == struct) && __traits(isNested, T)) + alias FieldTypes = typeof(T.tupleof[0 .. $ - 1]); + else + alias FieldTypes = typeof(T.tupleof); +} + +/// +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + struct S + { + int x; + float y; + } + static assert(is(FieldTypes!S == AliasSeq!(int, float))); + + // Type qualifers will be passed on to the result. + static assert(is(FieldTypes!(const S) == AliasSeq!(const int, const float))); + static assert(is(FieldTypes!(shared S) == AliasSeq!(shared int, shared float))); + + class C + { + // static variables are not included. + static int var; + + // Manifest constants are not included. + enum lang = "dlang"; + + // Functions are not included, even if they're @property functions. + @property int foo() { return 42; } + + string s; + int i; + int[] arr; + } + static assert(is(FieldTypes!C == AliasSeq!(string, int, int[]))); + + // Only direct member variables are included. Member variables from any base + // classes are not. + class D : C + { + real r; + } + static assert(is(FieldTypes!D == AliasSeq!real)); + + // FieldTypes will always be empty for an interface, since it's not legal + // for interfaces to have member variables. + interface I + { + } + static assert(FieldTypes!I.length == 0); + + union U + { + int i; + double d; + long l; + S s; + } + static assert(is(FieldTypes!U == AliasSeq!(int, double, long, S))); + + // FieldTypes only operates on aggregate types. + static assert(!__traits(compiles, FieldTypes!int)); + static assert(!__traits(compiles, FieldTypes!(S*))); + static assert(!__traits(compiles, FieldTypes!(C[]))); +} + +@safe unittest +{ + import phobos.sys.meta : AliasSeq; + + { + static struct S0 {} + static assert(FieldTypes!S0.length == 0); + + static struct S1 { int a; } + static assert(is(FieldTypes!S1 == AliasSeq!int)); + + static struct S2 { int a; string b; } + static assert(is(FieldTypes!S2 == AliasSeq!(int, string))); + + static struct S3 { int a; string b; real c; } + static assert(is(FieldTypes!S3 == AliasSeq!(int, string, real))); + } + { + int i; + struct S0 { void foo() { i = 0; }} + static assert(FieldTypes!S0.length == 0); + static assert(__traits(isNested, S0)); + + struct S1 { int a; void foo() { i = 0; } } + static assert(is(FieldTypes!S1 == AliasSeq!int)); + static assert(__traits(isNested, S1)); + + struct S2 { int a; string b; void foo() { i = 0; } } + static assert(is(FieldTypes!S2 == AliasSeq!(int, string))); + static assert(__traits(isNested, S2)); + + struct S3 { int a; string b; real c; void foo() { i = 0; } } + static assert(is(FieldTypes!S3 == AliasSeq!(int, string, real))); + static assert(__traits(isNested, S3)); + } + { + static class C0 {} + static assert(FieldTypes!C0.length == 0); + + static class C1 { int a; } + static assert(is(FieldTypes!C1 == AliasSeq!int)); + + static class C2 { int a; string b; } + static assert(is(FieldTypes!C2 == AliasSeq!(int, string))); + + static class C3 { int a; string b; real c; } + static assert(is(FieldTypes!C3 == AliasSeq!(int, string, real))); + + static class D0 : C3 {} + static assert(FieldTypes!D0.length == 0); + + static class D1 : C3 { bool x; } + static assert(is(FieldTypes!D1 == AliasSeq!bool)); + + static class D2 : C3 { bool x; int* y; } + static assert(is(FieldTypes!D2 == AliasSeq!(bool, int*))); + + static class D3 : C3 { bool x; int* y; short[] z; } + static assert(is(FieldTypes!D3 == AliasSeq!(bool, int*, short[]))); + } + { + int i; + class C0 { void foo() { i = 0; }} + static assert(FieldTypes!C0.length == 0); + static assert(__traits(isNested, C0)); + + class C1 { int a; void foo() { i = 0; } } + static assert(is(FieldTypes!C1 == AliasSeq!int)); + static assert(__traits(isNested, C1)); + + class C2 { int a; string b; void foo() { i = 0; } } + static assert(is(FieldTypes!C2 == AliasSeq!(int, string))); + static assert(__traits(isNested, C2)); + + class C3 { int a; string b; real c; void foo() { i = 0; } } + static assert(is(FieldTypes!C3 == AliasSeq!(int, string, real))); + static assert(__traits(isNested, C3)); + + class D0 : C3 {} + static assert(FieldTypes!D0.length == 0); + static assert(__traits(isNested, D0)); + + class D1 : C3 { bool x; } + static assert(is(FieldTypes!D1 == AliasSeq!bool)); + static assert(__traits(isNested, D1)); + + class D2 : C3 { bool x; int* y; } + static assert(is(FieldTypes!D2 == AliasSeq!(bool, int*))); + static assert(__traits(isNested, D2)); + + class D3 : C3 { bool x; int* y; short[] z; } + static assert(is(FieldTypes!D3 == AliasSeq!(bool, int*, short[]))); + static assert(__traits(isNested, D3)); + } + { + static union U0 {} + static assert(FieldTypes!U0.length == 0); + + static union U1 { int a; } + static assert(is(FieldTypes!U1 == AliasSeq!int)); + + static union U2 { int a; string b; } + static assert(is(FieldTypes!U2 == AliasSeq!(int, string))); + + static union U3 { int a; string b; real c; } + static assert(is(FieldTypes!U3 == AliasSeq!(int, string, real))); + } + { + static struct S + { + enum e = 42; + static str = "foobar"; + + string name() { return "foo"; } + + int[] arr; + + struct Inner1 { int i; } + + static struct Inner2 { long gnol; } + + union { int a; string b; } + + alias Foo = Inner1; + } + + static assert(is(FieldTypes!S == AliasSeq!(int[], int, string))); + static assert(is(FieldTypes!(const S) == AliasSeq!(const(int[]), const int, const string))); + static assert(is(FieldTypes!(S.Inner1) == AliasSeq!int)); + static assert(is(FieldTypes!(S.Inner2) == AliasSeq!long)); + } +} + /++ Takes a type which is an associative array and evaluates to the type of the keys in that associative array.