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. conversion must be defined.
17     abstract void[] convertTo(TypeInfo to);
18     /// checks if stored value can be converted to given "to" type.
19     abstract bool canConvertTo(TypeInfo to);
20 }
21 
22 /// returns stored value if type T is precisely the type of variable stored, variable stored can be implicitly to that type
23 T get(T)(Dynamic d)
24 {
25     import std.exception : enforce;
26     import std.format : format;
27 
28     // in addition to init property requirement disallow user defined value types which can have alias this to null-able type
29     static if (!is(T==union) && !is(T==struct) && is(typeof(T.init is null)))
30     {
31         if (d.type == typeid(typeof(null)))
32             return null;
33     }
34     if (d.type == typeid(T))
35         return ((cast(DynamicT!T)d).data());
36 
37     enforce(d.canConvertTo(typeid(T)), format!"Cannot convert stored value of type '%s' to '%s'!"(d.type, T.stringof));
38 
39     void[] convertResult = d.convertTo(typeid(T));
40 
41     return (cast(T*)convertResult.ptr)[0];
42 }
43 
44 /// a helper function for creating Dynamic obhects
45 Dynamic dynamic(T)(auto ref T t)
46 {
47     return new DynamicT!T(t);
48 }
49 
50 class DynamicT(T) : Dynamic
51 {
52     private T _data;
53     this(T t)
54     {
55         _data = t;
56     }
57 
58     ///
59     override TypeInfo type()
60     {
61         return typeid(T);
62     }
63 
64     ///
65     override string toString()
66     {
67         static if (is(typeof(_data) == class) && !hasFunctionAttributes!(typeof(_data).toString, "const"))
68         {
69             return (cast(Unqual!(typeof(_data))) _data).toString();
70         }
71         else
72         {
73             return _data.to!string;
74         }
75     }
76 
77     /// two dynamics are equal when they store same type and the values pass opEquals
78     override bool opEquals(Object object)
79     {
80         auto dyn = cast(DynamicT!T)object;
81         if (dyn is null)
82             return false;
83         if (dyn.type != type)
84             return false;
85 
86         return _data == dyn._data;
87     }
88 
89     ///
90     override size_t toHash()
91     {
92         return typeid(T).getHash(&_data);
93     }
94 
95     ///
96     T data()
97     {
98         return _data;
99     }
100 
101     ///
102     override bool canConvertTo(TypeInfo to)
103     {
104         import std.algorithm : any;
105 
106         static if (is(T == typeof(null)))
107         {
108             if (cast(TypeInfo_Array) to || cast(TypeInfo_Pointer) to)
109             {
110                 return true;
111             }
112         }
113 
114         enum getTypeId(T) = typeid(T);
115         alias ConversionTargets = ImplicitConversionTargets!T;
116 
117         static if (ConversionTargets.length)
118         {
119             return type == to || [staticMap!(getTypeId, ConversionTargets)].any!(t => t == to);
120         }
121         else
122         {
123             return type == to;
124         }
125     }
126 
127     ///
128     override void[] convertTo(TypeInfo to)
129     {
130         static if (is(T == typeof(null)))
131         {
132             if (cast(TypeInfo_Array) to)
133             {
134                 auto ret = new void[][1];
135                 ret[0] = null;
136                 return ret;
137             }
138             if (cast(TypeInfo_Pointer) to)
139             {
140                 auto ret = new void*[1];
141                 ret[0] = null;
142                 return ret;
143             }
144         }
145 
146         foreach(Target; ImplicitConversionTargets!(T))
147         {
148             if (typeid(Target) == to)
149             {
150                 auto ret = new Target[1];
151                 ret[0] = cast(Target) _data;
152                 return ret;
153             }
154         }
155 
156         assert(false);
157     }
158 }
159 
160 version (unittest)
161 {
162     class A
163     {
164     }
165 
166     class B : A
167     {
168     }
169 
170     struct C
171     {
172     }
173 
174     struct D
175     {
176         private C _c;
177         alias _c this;
178     }
179 }
180 
181 unittest
182 {
183     auto d = dynamic(6);
184     assert(d.toString == "6");
185     assert(d.type.toString == "int");
186     auto e = dynamic(6);
187     assert(e == d);
188     assert(e.get!int == 6);
189 }
190 
191 unittest
192 {
193     auto d = dynamic(new B);
194     assert(d.get!A !is null);
195     assert(d.get!B !is null);
196 }
197 
198 unittest
199 {
200     auto d = dynamic(null);
201     assert(d.get!A is null);
202 }
203 
204 unittest
205 {
206     int[5] a;
207     auto d = dynamic(a);
208     assert(d.get!(int[5]) == [0,0,0,0,0]);
209 }
210 
211 unittest
212 {
213     import std.exception : assertThrown;
214 
215     float f;
216     auto d = dynamic(f);
217 
218     assertThrown!Exception(d.get!int);
219 }
220 
221 /+ ImplicitConversionTargets doesn't include alias thises
222 unittest
223 {
224     auto d = dynamic(D());
225     d.get!C;
226     d.get!D;
227 }
228 +/
229 
230 @("supports const object with non-const toString")
231 unittest
232 {
233     static assert(is(typeof(dynamic(new const Object))));
234     static assert(is(typeof(dynamic(new immutable Object))));
235 }