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