1 module nxt.gc_traits;
2 
3 /** Used as an UDA to mark a variable of a type that looks like GC-managed but
4  * that is actually not GC-managed, because its allocated by `malloc`, `calloc`
5  * or some other non-GC allocator.
6  */
7 enum NoGc;
8 
9 /**
10  * When this enum is used as UDA on aggregate types whose instances are
11  * created with construct() a compile time message indicates if a GC range
12  * will be added for the members.
13  */
14 enum TellRangeAdded;
15 
16 /** Indicates if an aggregate contains members that might be collected by the
17  * garbage collector. This is used in constructors to determine if the content
18  * of a manually allocated aggregate must be declared to the GC.
19  */
20 template mustAddGCRange(T)
21 {
22     import std.traits : isPointer, isArray, isStaticArray, isScalarType;
23 
24     private enum isAddress(T) = (is(T == class) || // a class is memory-wise
25                                  isPointer!T);     // just a pointer, consistent with opCmp
26 
27     static if (isAddress!T)
28     {
29         enum mustAddGCRange = true;
30     }
31     else static if (isScalarType!T)
32     {
33         enum mustAddGCRange = false;
34     }
35     else static if (is(T == struct) ||
36                     is(T == union))
37     {
38         enum mustAddGCRange = mustAddGCRangeOfStructOrUnion!T;
39     }
40     else static if (isArray!T)
41     {
42         static if (isStaticArray!T)
43         {
44             static if (T.length == 0)
45             {
46                 enum mustAddGCRange = false;
47             }
48             else
49             {
50                 enum mustAddGCRange = mustAddGCRange!(typeof(T.init[0]));
51             }
52         }
53         else
54         {
55             enum mustAddGCRange = true;
56         }
57     }
58     else
59     {
60         static assert(0, "Handle type " ~ T.stringof);
61     }
62 }
63 
64 ///
65 @safe pure nothrow @nogc unittest
66 {
67     static assert(!mustAddGCRange!int);
68     static assert(mustAddGCRange!(int*));
69     static assert(mustAddGCRange!(int*[1]));
70     static assert(!mustAddGCRange!(int*[0]));
71     static assert(mustAddGCRange!(int[]));
72 }
73 
74 private template mustAddGCRangeOfMember(alias member)
75 {
76     import std.traits : hasUDA;
77     enum mustAddGCRangeOfMember = !hasUDA!(member, NoGc) && mustAddGCRange!(typeof(member));
78 }
79 
80 /// Helper for `mustAddGCRange`.
81 private template mustAddGCRangeOfStructOrUnion(T)
82 if (is(T == struct) ||
83     is(T == union))
84 {
85     import std.traits : hasUDA;
86     import std.meta : anySatisfy;
87     /* TODO remove and adapt according to answers here:
88      * https://forum.dlang.org/thread/dkohvpbmakbdbhnmnmbg@forum.dlang.org */
89     // static if (__traits(hasMember, T, "__postblit"))
90     // {
91     //     static if (__traits(isDisabled, T.__postblit))
92     //     {
93     //         enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof[0 .. $ - 1]);
94     //     }
95     //     else
96     //     {
97     //         enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
98     //     }
99     // }
100     // else
101     // {
102     //     enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
103     // }
104     enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
105 }
106 
107 version(unittest)
108 {
109     private static struct S
110     {
111         @disable this(this);
112         @NoGc int* _ptr;
113     }
114 }
115 
116 /// no-GC-managed struct with a disabled postblit
117 @safe pure nothrow @nogc unittest
118 {
119     static if (__traits(hasMember, S, "__postblit"))
120     {
121         static assert(__traits(isDisabled, S.__postblit));
122     }
123     // See https://forum.dlang.org/post/dkohvpbmakbdbhnmnmbg@forum.dlang.org
124     static assert(!mustAddGCRangeOfStructOrUnion!S);
125 }
126 
127 ///
128 @safe pure nothrow @nogc unittest
129 {
130     struct SmallBin
131     {
132         string[1] s;
133     }
134     static assert(mustAddGCRange!SmallBin);
135 
136     union HybridBin
137     {
138         SmallBin small;
139     }
140     static assert(mustAddGCRange!HybridBin);
141 }
142 
143 ///
144 @safe pure nothrow @nogc unittest
145 {
146     struct S
147     {
148         @NoGc int[] a;
149     }
150     static assert(!mustAddGCRange!S);
151 }
152 
153 ///
154 version(none)
155 @safe pure nothrow @nogc unittest
156 {
157     class Foo
158     {
159         @NoGc int[] a;
160         @NoGc void* b;
161     }
162     static assert(!mustAddGCRange!Foo);
163 
164     class Bar
165     {
166         int[] a;
167         @NoGc void* b;
168     }
169     static assert(mustAddGCRange!Bar);
170 
171     class Baz : Bar
172     {
173         @NoGc void* c;
174     }
175     static assert(mustAddGCRange!Baz);
176 
177     struct S
178     {
179         int x;
180     }
181     static assert(!mustAddGCRange!S);
182 
183     struct T
184     {
185         int* x;
186     }
187     static assert(mustAddGCRange!T);
188     static assert(mustAddGCRange!(T[1]));
189 
190     struct U
191     {
192         @NoGc int* x;
193     }
194     static assert(!mustAddGCRange!U);
195     static assert(!mustAddGCRange!(U[1]));
196 
197     union N
198     {
199         S s;
200         U u;
201     }
202     static assert(!mustAddGCRange!N);
203     static assert(!mustAddGCRange!(N[1]));
204 
205     union M
206     {
207         S s;
208         T t;
209     }
210     static assert(mustAddGCRange!M);
211     static assert(mustAddGCRange!(M[1]));
212 }