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 }