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