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