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     static if (is(T == class) || // a class is memory-wise
24                isPointer!T)      // just a pointer, consistent with opCmp
25     {
26         enum mustAddGCRange = true;
27     }
28     else static if (isScalarType!T)
29     {
30         enum mustAddGCRange = false;
31     }
32     else static if (is(T == struct) ||
33                     is(T == union))
34     {
35         enum mustAddGCRange = mustAddGCRangeOfStructOrUnion!T;
36     }
37     else static if (isArray!T)
38     {
39         static if (isStaticArray!T)
40         {
41             static if (T.length == 0)
42             {
43                 enum mustAddGCRange = false;
44             }
45             else
46             {
47                 enum mustAddGCRange = mustAddGCRange!(typeof(T.init[0]));
48             }
49         }
50         else
51         {
52             enum mustAddGCRange = true;
53         }
54     }
55     else
56     {
57         static assert(0, "Handle type " ~ T.stringof);
58     }
59 }
60 
61 ///
62 @safe pure nothrow @nogc unittest
63 {
64     static assert(!mustAddGCRange!int);
65     static assert(mustAddGCRange!(int*));
66     static assert(mustAddGCRange!(int*[1]));
67     static assert(!mustAddGCRange!(int*[0]));
68     static assert(mustAddGCRange!(int[]));
69 }
70 
71 /// Helper for `mustAddGCRange`.
72 private template mustAddGCRangeOfStructOrUnion(T)
73 if (is(T == struct) ||
74     is(T == union))
75 {
76     import std.traits : hasUDA;
77     import std.meta : anySatisfy;
78     /* TODO: remove and adapt according to answers here:
79      * https://forum.dlang.org/thread/dkohvpbmakbdbhnmnmbg@forum.dlang.org */
80     // static if (__traits(hasMember, T, "__postblit"))
81     // {
82     //     static if (__traits(isDisabled, T.__postblit))
83     //     {
84     //         enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof[0 .. $ - 1]);
85     //     }
86     //     else
87     //     {
88     //         enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
89     //     }
90     // }
91     // else
92     // {
93     //     enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
94     // }
95     enum mustAddGCRangeOfStructOrUnion = anySatisfy!(mustAddGCRangeOfMember, T.tupleof);
96 }
97 
98 private template mustAddGCRangeOfMember(alias member)
99 {
100     import std.traits : hasUDA;
101     enum mustAddGCRangeOfMember = !hasUDA!(member, NoGc) && mustAddGCRange!(typeof(member));
102 }
103 
104 /// no-GC-managed struct with a disabled postblit
105 @safe pure nothrow @nogc unittest
106 {
107     static struct S
108     {
109         @disable this(this);
110         @NoGc int* _ptr;
111     }
112     static if (__traits(hasMember, S, "__postblit"))
113     {
114         static assert(__traits(isDisabled, S.__postblit));
115     }
116     // See https://forum.dlang.org/post/dkohvpbmakbdbhnmnmbg@forum.dlang.org
117     static assert(!mustAddGCRangeOfStructOrUnion!S);
118 }
119 
120 /// GC-managed struct
121 @safe pure nothrow @nogc unittest
122 {
123     static struct S
124     {
125         int* _ptr;
126     }
127     // See https://forum.dlang.org/post/dkohvpbmakbdbhnmnmbg@forum.dlang.org
128     static assert(mustAddGCRangeOfStructOrUnion!S);
129 }
130 
131 ///
132 @safe pure nothrow @nogc unittest
133 {
134     struct SmallBin
135     {
136         string[1] s;
137     }
138     static assert(mustAddGCRange!SmallBin);
139 
140     union HybridBin
141     {
142         SmallBin small;
143     }
144     static assert(mustAddGCRange!HybridBin);
145 }
146 
147 ///
148 @safe pure nothrow @nogc unittest
149 {
150     struct S
151     {
152         @NoGc int[] a;
153     }
154     static assert(!mustAddGCRange!S);
155 }
156 
157 ///
158 version(none)
159 @safe pure nothrow @nogc unittest
160 {
161     class Foo
162     {
163         @NoGc int[] a;
164         @NoGc void* b;
165     }
166     static assert(!mustAddGCRange!Foo);
167 
168     class Bar
169     {
170         int[] a;
171         @NoGc void* b;
172     }
173     static assert(mustAddGCRange!Bar);
174 
175     class Baz : Bar
176     {
177         @NoGc void* c;
178     }
179     static assert(mustAddGCRange!Baz);
180 
181     struct S
182     {
183         int x;
184     }
185     static assert(!mustAddGCRange!S);
186 
187     struct T
188     {
189         int* x;
190     }
191     static assert(mustAddGCRange!T);
192     static assert(mustAddGCRange!(T[1]));
193 
194     struct U
195     {
196         @NoGc int* x;
197     }
198     static assert(!mustAddGCRange!U);
199     static assert(!mustAddGCRange!(U[1]));
200 
201     union N
202     {
203         S s;
204         U u;
205     }
206     static assert(!mustAddGCRange!N);
207     static assert(!mustAddGCRange!(N[1]));
208 
209     union M
210     {
211         S s;
212         T t;
213     }
214     static assert(mustAddGCRange!M);
215     static assert(mustAddGCRange!(M[1]));
216 }