1 /** Traits for introspection the nullability (undefinedness) of a type.
2  *
3  * If a type is nullable it has a special value reserved in its set of possible
4  * which is to indicated that the value is undefined.
5  */
6 module nxt.nullable_traits;
7 
8 /** Is `true` iff `T` is a type with a standardized null (zero address) value.
9  */
10 template hasStandardNullValue(T)
11 {
12 	static if (is(T == class) ||
13 			   is(T == typeof(null))) // fast compilation path
14 		enum hasStandardNullValue = true; // fast path first
15 	else static if (is(T == struct) ||
16 					is(T == interface) ||
17 					is(T == union))
18 		enum hasStandardNullValue = false;
19 	else						// slow compilation path
20 		enum hasStandardNullValue = (is(T == U*, U) ||
21 									 (is(T : const(E)[], E) &&
22 									  !__traits(isStaticArray, T))); // `isDynamicArrayFast`
23 }
24 
25 ///
26 pure nothrow @safe @nogc unittest {
27 	class C {}
28 	static assert( hasStandardNullValue!(C));
29 	static assert( hasStandardNullValue!(int*));
30 	static assert( hasStandardNullValue!(int[]));
31 	static assert( hasStandardNullValue!(const(int)[]));
32 	static assert(!hasStandardNullValue!(int[3]));
33 	static assert( hasStandardNullValue!(string));
34 	static assert(!hasStandardNullValue!(int));
35 }
36 
37 /** Is `true` iff `T` is a type with a member null value.
38  */
39 enum hasMemberNullValue(T) = __traits(compiles, { T _; _ = T.nullValue; });
40 
41 ///
42 pure nothrow @safe @nogc unittest {
43 	class S1
44 	{
45 		int x;
46 		int* xp;
47 		static nullValue = typeof(this).init;
48 	}
49 	static assert(hasMemberNullValue!S1);
50 }
51 
52 /** Is `true` iff `T` is a type with a standardized null (zero address) value.
53  */
54 enum hasNullValue(T) = (hasStandardNullValue!T ||
55 						hasMemberNullValue!T);
56 
57 ///
58 pure nothrow @safe @nogc unittest {
59 	static assert(!hasNullValue!int);
60 	static assert(!hasNullValue!float);
61 	struct S {
62 		int value;
63 		static immutable nullValue = typeof(this).init;
64 	}
65 	static assert(hasNullValue!S);
66 }
67 
68 /** Is `true` iff `T` is type with a predefined undefined (`null`) value.
69  */
70 template isNullable(T)
71 {
72 	/* TODO: remove this two first cases and rely solely on
73 	 * is(typeof(T.init.nullify()) == void) and
74 	 * is(typeof(T.init.isNull()) == bool)
75 	 */
76 	// use static if's for full lazyness of trait evaluations in order of likelyhood
77 	static if (is(T == class) ||
78 			   is(T == typeof(null)) ||
79 			   (is(T : const(E)[], E) &&
80 				!__traits(isStaticArray, T))) // `isDynamicArrayFast`
81 		enum isNullable = true; // fast path first, prevent instantiation of `hasStandardNullValue`
82 	else static if (hasStandardNullValue!T)
83 		enum isNullable = true;
84 	else static if (hasMemberNullValue!T)
85 		enum isNullable = true;
86 	else static if (__traits(hasMember, T, "nullifier"))
87 		enum isNullable = isNullable!(typeof(T.nullifier)); /+ TODO: require it to be an alias? +/
88 	else static if ((__traits(hasMember, T, "isNull") && // fast
89 					 __traits(hasMember, T, "nullify"))) // fast
90 		// lazy: only try semantic analysis when members exists
91 		enum isNullable = (is(typeof(T.init.isNull()) == bool)  &&
92 						   is(typeof(T.init.nullify()) == void));
93 	else
94 		/+ TODO: remove this later on +/
95 		// importf std.meta : anySatisfy;
96 		// static if ((is(T == struct) && // unions excluded for now
97 		//			 anySatisfy!(isNullable, typeof(T.init.tupleof))))
98 		// {
99 		//	 enum isNullable = true;
100 		// }
101 		// else
102 		// {
103 			enum isNullable = false;
104 		// }
105 }
106 
107 ///
108 pure nothrow @safe @nogc unittest {
109 	import std.typecons : Nullable;
110 
111 	class C {}
112 
113 	static assert( isNullable!(C));
114 	static assert( isNullable!(int*));
115 	static assert( isNullable!(int[]));
116 	static assert( isNullable!(const(int)[]));
117 	static assert(!isNullable!(int[3]));
118 	static assert( isNullable!(string));
119 	static assert( isNullable!(Nullable!int));
120 	static assert(!isNullable!(int));
121 
122 	struct S
123 	{
124 		int value;
125 		static immutable nullValue = typeof(this).init;
126 	}
127 
128 	struct S2 { C x, y; }
129 	static assert(!isNullable!S2);
130 
131 	struct S3 { int x, y; }
132 	static assert(!isNullable!S3);
133 
134 	struct S4 { C x, y; alias nullifier = x; }
135 	static assert(isNullable!S4);
136 }
137 
138 /** Default null key of type `T`,
139  */
140 template defaultNullKeyConstantOf(T)
141 {
142 	static if (isNullable!T)
143 		enum defaultNullKeyConstantOf = T.init;
144 	else
145 		static assert(0, "Unsupported type " ~ T.stringof);
146 }
147 
148 ///
149 pure nothrow @safe @nogc unittest {
150 	import std.typecons : Nullable;
151 
152 	static assert(defaultNullKeyConstantOf!(void*) == null);
153 
154 	alias Ni = Nullable!int;
155 	static assert(defaultNullKeyConstantOf!(Ni) == Ni.init);
156 
157 	// alias cNi = const(Nullable!int);
158 	// static assert(defaultNullKeyConstantOf!(cNi) == cNi.init);
159 
160 	alias NubM = Nullable!(ubyte, ubyte.max);
161 	assert(defaultNullKeyConstantOf!(NubM).isNull);
162 
163 	alias NuiM = Nullable!(uint, uint.max);
164 	assert(defaultNullKeyConstantOf!(NuiM).isNull);
165 
166 	const Nullable!(uint, uint.max) x = 13;
167 	assert(!x.isNull);
168 	const y = x;
169 	assert(!y.isNull);
170 	assert(!x.isNull);
171 }
172 
173 /** Returns: `true` iff `x` has a null value.
174  */
175 bool isNull(T)(const scope auto ref T x) pure nothrow @safe @nogc
176 if (isNullable!(T))
177 {
178 	version (D_Coverage) {} else pragma(inline, true);
179 	static if (is(T == class) ||
180 			   is(T == typeof(null))) // fast compilation path
181 		return x is null;
182 	else static if (is(T : const(E)[], E) &&
183 					!__traits(isStaticArray, T)) // `isDynamicArrayFast`
184 		return x.ptr is null;   // no need to check `length`, as in `x.ptr == T.init`
185 	else static if (hasStandardNullValue!T)
186 		return x is T.init;
187 	else static if (hasMemberNullValue!T)
188 		return x is T.nullValue;
189 	else static if (__traits(hasMember, T, "nullifier"))
190 		return x.nullifier.isNull;
191 	else
192 		static assert(0, "Unsupported type " ~ T.stringof);
193 }
194 
195 void nullify(T)(scope ref T x) pure nothrow @safe @nogc
196 if (isNullable!(T))
197 {
198 	version (D_Coverage) {} else pragma(inline, true);
199 	static if (is(T == class) ||
200 			   is(T == typeof(null))) // fast compilation path
201 		x = null;
202 	else static if (hasStandardNullValue!T)
203 		x = T.init;
204 	else static if (hasMemberNullValue!T)
205 		x = T.nullValue;
206 	else static if (__traits(hasMember, T, "nullifier"))
207 		x.nullifier.nullify();
208 	else
209 		static assert(0, "Unsupported type " ~ T.stringof);
210 }
211 
212 ///
213 pure nothrow @safe @nogc unittest {
214 	import std.typecons : Nullable;
215 
216 	assert(null.isNull);
217 
218 	assert((int[]).init.isNull);
219 	immutable int[2] x = [1, 2];
220 	assert(!x[].isNull);
221 
222 	alias Ni = Nullable!int;
223 	assert(Ni.init.isNull);
224 
225 	Ni ni = 3;
226 	assert(!ni.isNull);
227 
228 	ni.nullify();
229 	assert(ni.isNull);
230 
231 	const Ni ni2 = 3;
232 	assert(!ni2.isNull);
233 
234 	struct S
235 	{
236 		uint value;
237 		static immutable nullValue = S(value.max);
238 	}
239 	S s;
240 	assert(!s.isNull);
241 	s.nullify();
242 	assert(s.isNull);
243 }
244 
245 ///
246 pure nothrow @safe unittest {
247 	class C
248 	{
249 		@safe pure nothrow
250 		this(int value)
251 		{
252 			this.value = value;
253 		}
254 		int value;
255 	}
256 
257 	static assert(isNullable!C);
258 
259 	const x = C.init;
260 	assert(x.isNull);
261 
262 	const y = new C(42);
263 	assert(!y.isNull);
264 }