1 module dmocks.dynamic;
2 
3 import std.conv;
4 import std.traits;
5 
6 /++
7 + This is a very very simple class for storing a variable regardless of it's size and type
8 +/
9 abstract class Dynamic
10 {
11     // toHash, toString and opEquals are also part of this class
12     // but i'm not sure how to express that in code so this comment has to be enough:)
13 
14     /// returns stored typeinfo
15     abstract TypeInfo type();
16     /// converts stored value to given "to" type and returns 1el array of target type vals or null when conversion failed
17     abstract void[] convertTo(TypeInfo to);
18 
19     /// returns true if variable held by dynamic is convertible to given type
20     bool canConvertTo(TypeInfo to)
21     {
22         return type==to || convertTo(to) !is null;
23     }
24 }
25 
26 /// returns stored value if type T is precisely the type of variable stored, variable stored can be implicitly to that type
27 T get(T)(Dynamic d)
28 {
29     import std.exception : enforce;
30     import std.format : format;
31 
32     // in addition to init property requirement disallow user defined value types which can have alias this to null-able type
33     static if (!is(T==union) && !is(T==struct) && is(typeof(T.init is null)))
34     {
35         if (d.type == typeid(typeof(null)))
36             return null;
37     }
38     if (d.type == typeid(T))
39         return ((cast(DynamicT!T)d).data());
40 
41     void[] convertResult = d.convertTo(typeid(T));
42 
43     enforce(convertResult, format!"Cannot convert stored value of type '%s' to '%s'!"(d.type, T.stringof));
44 
45     return (cast(T*)convertResult)[0];
46 }
47 
48 /// a helper function for creating Dynamic obhects
49 Dynamic dynamic(T)(auto ref T t)
50 {
51     return new DynamicT!T(t);
52 }
53 
54 class DynamicT(T) : Dynamic
55 {
56     private T _data;
57     this(T t)
58     {
59         _data = t;
60     }
61 
62     ///
63     override TypeInfo type()
64     {
65         return typeid(T);
66     }
67 
68     ///
69     override string toString()
70     {
71         static if (is(typeof(_data) == class) && !hasFunctionAttributes!(typeof(_data).toString, "const"))
72         {
73             return (cast(Unqual!(typeof(_data))) _data).toString();
74         }
75         else
76         {
77             return _data.to!string;
78         }
79     }
80 
81     /// two dynamics are equal when they store same type and the values pass opEquals
82     override bool opEquals(Object object)
83     {
84         auto dyn = cast(DynamicT!T)object;
85         if (dyn is null)
86             return false;
87         if (dyn.type != type)
88             return false;
89 
90         return _data == dyn._data;
91     }
92 
93     ///
94     override size_t toHash()
95     {
96         return typeid(T).getHash(&_data);
97     }
98 
99     ///
100     T data()
101     {
102         return _data;
103     }
104 
105     ///
106     override void[] convertTo(TypeInfo to)
107     {
108         foreach(target;ImplicitConversionTargets!(T))
109         {
110             if (typeid(target) == to)
111             {
112                 auto ret = new target[1];
113                 ret[0] = cast(target)_data;
114                 return ret;
115             }
116         }
117 
118         return null;
119     }
120 }
121 
122 version (unittest)
123 {
124     class A
125     {
126     }
127 
128     class B : A
129     {
130     }
131 
132     struct C
133     {
134     }
135 
136     struct D
137     {
138         private C _c;
139         alias _c this;
140     }
141 }
142 
143 unittest
144 {
145     auto d = dynamic(6);
146     assert(d.toString == "6");
147     assert(d.type.toString == "int");
148     auto e = dynamic(6);
149     assert(e == d);
150     assert(e.get!int == 6);
151 }
152 
153 unittest
154 {
155     auto d = dynamic(new B);
156     assert(d.get!A !is null);
157     assert(d.get!B !is null);
158 }
159 
160 unittest
161 {
162     auto d = dynamic(null);
163     assert(d.get!A is null);
164 }
165 
166 unittest
167 {
168     int[5] a;
169     auto d = dynamic(a);
170     assert(d.get!(int[5]) == [0,0,0,0,0]);
171 }
172 
173 unittest
174 {
175     float f;
176     auto d = dynamic(f);
177     bool errored = false;
178 
179     try
180     {
181         int i = d.get!int;
182     }
183     catch (Exception)
184     {
185         errored = true;
186     }
187 
188     assert(errored);
189 }
190 
191 /+ ImplicitConversionTargets doesn't include alias thises
192 unittest
193 {
194     auto d = dynamic(D());
195     d.get!C;
196     d.get!D;
197 }
198 +/
199 
200 @("supports const object with non-const toString")
201 unittest
202 {
203     static assert(is(typeof(dynamic(new const Object))));
204     static assert(is(typeof(dynamic(new immutable Object))));
205 }