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