1 module dmocks.dynamic; 2 3 import std.format; 4 import std.meta : AliasSeq, ApplyLeft; 5 import std.traits; 6 7 /++ 8 + This is a very very simple class for storing a variable regardless of it's size and type 9 +/ 10 abstract class Dynamic 11 { 12 // toHash, toString and opEquals are also part of this class 13 // but i'm not sure how to express that in code so this comment has to be enough:) 14 15 /// returns stored typeinfo 16 abstract TypeInfo type(); 17 /// returns stored typename (work around dmd bug https://issues.dlang.org/show_bug.cgi?id=3831) 18 abstract string typename(); 19 /// converts stored value to given "to" type and returns 1el array of target type vals. conversion must be defined. 20 abstract const(void)[] convertTo(TypeInfo to); 21 /// checks if stored value can be converted to given "to" type. 22 abstract bool canConvertTo(TypeInfo to); 23 } 24 25 /// returns stored value if type T is precisely the type of variable stored, variable stored can be implicitly to that type 26 T get(T)(Dynamic d) 27 { 28 import std.exception : enforce; 29 import std.format : format; 30 31 if (d.type == typeid(T)) 32 return ((cast(DynamicT!T) d).data()); 33 34 enforce( 35 d.canConvertTo(typeid(T)), 36 format!"Cannot convert stored value of type '%s' to '%s'!"(d.typename, T.stringof)); 37 38 const(void)[] convertResult = d.convertTo(typeid(T)); 39 40 return (cast(T*) convertResult.ptr)[0]; 41 } 42 43 /// a helper function for creating Dynamic obhects 44 Dynamic dynamic(T)(auto ref T t) 45 { 46 return new DynamicT!T(t); 47 } 48 49 class DynamicT(T) : Dynamic 50 { 51 private T _data; 52 this(T t) 53 { 54 _data = t; 55 } 56 57 /// 58 override TypeInfo type() 59 { 60 return typeid(T); 61 } 62 63 /// 64 override string typename() 65 { 66 return T.stringof; 67 } 68 69 /// 70 override string toString() 71 { 72 static if (is(typeof(_data) == class) && !hasFunctionAttributes!(typeof(_data).toString, "const")) 73 { 74 return (cast(Unqual!(typeof(_data))) _data).toString(); 75 } 76 else 77 { 78 // not to!string: has problem with T == Nullable!string due to implicit conversion 79 return format!"%s"(_data); 80 } 81 } 82 83 /// two dynamics are equal when they store same type and the values pass opEquals 84 override bool opEquals(Object object) 85 { 86 auto dyn = cast(DynamicT!T) object; 87 if (dyn is null) 88 return false; 89 if (dyn.type != type) 90 return false; 91 92 return _data == dyn._data; 93 } 94 95 /// 96 override size_t toHash() 97 { 98 return typeid(T).getHash(&_data); 99 } 100 101 /// 102 T data() 103 { 104 return _data; 105 } 106 107 /// 108 override bool canConvertTo(TypeInfo to) 109 { 110 import std.algorithm : any; 111 112 static if (is(T == typeof(null))) 113 { 114 to = unqual(to); 115 // types that have implicit conversion from null 116 if (cast(TypeInfo_Array) to || cast(TypeInfo_Pointer) to 117 || cast(TypeInfo_Class) to || cast(TypeInfo_Interface) to 118 || cast(TypeInfo_Function) to || cast(TypeInfo_Delegate) to) 119 { 120 return true; 121 } 122 } 123 124 enum getTypeId(T) = typeid(T); 125 alias ConversionTargets = ConstAwareImplicitConversionTargets!T; 126 127 static if (ConversionTargets.length) 128 { 129 return [staticMap!(getTypeId, ConversionTargets)].any!(t => t == to); 130 } 131 else 132 { 133 return false; 134 } 135 } 136 137 /// 138 override const(void)[] convertTo(TypeInfo to) 139 { 140 import std.format : format; 141 import std.meta : AliasSeq; 142 143 static if (is(T == typeof(null))) 144 { 145 interface Intf 146 { 147 } 148 149 to = unqual(to); 150 static foreach (pair; [ 151 [q{TypeInfo_Array}, q{void[]}], 152 [q{TypeInfo_Pointer}, q{void*}], 153 [q{TypeInfo_Class}, q{Object}], 154 [q{TypeInfo_Interface}, q{Intf}], 155 [q{TypeInfo_Function}, q{void function()}], 156 [q{TypeInfo_Delegate}, q{void delegate()}], 157 ]) 158 { 159 mixin(format!q{ 160 if (cast(%s) to) 161 { 162 %s[] ret = [null]; 163 return ret; 164 } 165 }(pair[0], pair[1])); 166 } 167 } 168 169 foreach (Target; ConstAwareImplicitConversionTargets!T) 170 { 171 if (typeid(Target) == to) 172 { 173 Target[] ret = [_data]; 174 return ret; 175 } 176 } 177 178 assert(false); 179 } 180 } 181 182 private TypeInfo unqual(TypeInfo type) 183 { 184 if (auto type_const = cast(TypeInfo_Const) type) 185 { 186 return type_const.base; 187 } 188 if (auto type_immutable = cast(TypeInfo_Invariant /* sic */ ) type) 189 { 190 return type_immutable.base; 191 } 192 return type; 193 } 194 195 version (unittest) 196 { 197 class A 198 { 199 } 200 201 class B : A 202 { 203 } 204 205 struct C 206 { 207 } 208 209 struct D 210 { 211 private C _c; 212 alias _c this; 213 } 214 } 215 216 unittest 217 { 218 auto d = dynamic(6); 219 assert(d.toString == "6"); 220 assert(d.typename == "int"); 221 auto e = dynamic(6); 222 assert(e == d); 223 assert(e.get!int == 6); 224 } 225 226 unittest 227 { 228 auto d = dynamic((void delegate(int)).init); 229 assert(d.typename == "void delegate(int)"); 230 } 231 232 unittest 233 { 234 auto d = dynamic(new B); 235 assert(d.get!A !is null); 236 assert(d.get!B !is null); 237 } 238 239 unittest 240 { 241 auto d = dynamic(null); 242 assert(d.get!A is null); 243 } 244 245 unittest 246 { 247 auto d = dynamic(null); 248 assert(d.get!(int[]) is null); 249 assert(d.get!(const int[]) is null); 250 assert(d.get!(immutable int[]) is null); 251 } 252 253 unittest 254 { 255 int[5] a; 256 auto d = dynamic(a); 257 assert(d.get!(int[5]) == [0, 0, 0, 0, 0]); 258 } 259 260 unittest 261 { 262 import std.exception : assertThrown; 263 264 float f; 265 auto d = dynamic(f); 266 267 assertThrown!Exception(d.get!int); 268 } 269 270 static if (__traits(compiles, AllImplicitConversionTargets)) 271 { 272 private alias AllImplicitConversionTargets = std.traits.AllImplicitConversionTargets; 273 } 274 else 275 { 276 private alias AllImplicitConversionTargets = std.traits.ImplicitConversionTargets; 277 } 278 279 private alias ConstAwareImplicitConversionTargets(T) = AllImplicitConversionTargets!T; 280 private alias ConstAwareImplicitConversionTargets(T : const Object) = 281 staticMap!(ApplyLeft!(CopyConstness, T), AllImplicitConversionTargets!T); 282 283 unittest 284 { 285 static assert(is(ConstAwareImplicitConversionTargets!int == AllImplicitConversionTargets!int)); 286 static assert(is(ConstAwareImplicitConversionTargets!(A) == AliasSeq!(Object))); 287 // fixed in 2.090.0 as a side effect of https://github.com/dlang/phobos/pull/7313 288 static if (!is(AllImplicitConversionTargets!(const A) == AliasSeq!())) 289 { 290 static assert(is(ConstAwareImplicitConversionTargets!(const A) == AliasSeq!(const Object))); 291 static assert(is( 292 ConstAwareImplicitConversionTargets!(immutable A) == AliasSeq!(immutable Object))); 293 } 294 } 295 296 /+ AllImplicitConversionTargets doesn't include alias thises 297 unittest 298 { 299 auto d = dynamic(D()); 300 d.get!C; 301 d.get!D; 302 } 303 +/ 304 305 @("supports const object with non-const toString") 306 unittest 307 { 308 static assert(is(typeof(dynamic(new const Object)))); 309 static assert(is(typeof(dynamic(new immutable Object)))); 310 }