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 }