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 }