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 }