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 }