1 ///
2 module aslike;
3 
4 import std.traits;
5 import std.meta;
6 import std.algorithm : canFind;
7 
8 private string refPref(alias fn)()
9 { return functionAttributes!fn & FunctionAttribute.ref_ ? "ref" : ""; }
10 
11 private string fnAttr(alias fn, string[] forbidden=[])()
12 {
13     enum f = forbidden ~ "ref";
14 
15     template impl(string[] s)
16     {
17         static if (s.length == 1) enum impl = f.canFind(s[0]) ? "" : s[0];
18         else enum impl = impl!(s[0..$/2]) ~ " " ~ impl!(s[$/2..$]);
19     }
20 
21     return impl!([__traits(getFunctionAttributes, fn)]);
22 }
23 
24 ///
25 struct Like(T) if (is(T == interface))
26 {
27     private template dlgName(alias fn) { enum dlgName = "__dlg_" ~ fn.mangleof; }
28 
29     private static string buildDelegate(alias fn)()
30     {
31         enum a1 = dlgName!fn~"_type";
32         enum a2 = refPref!fn ~ " ReturnType!fn";
33         enum a3 = fnAttr!(fn, ["const", "@property"]);
34         enum a4 = dlgName!fn;
35         return "private alias "~a1~" = "~a2~" delegate(Parameters!fn) "~a3~";
36         private "~a1~" "~a4~";\n";
37     }
38 
39     private static string buildCall(alias fn, string name)()
40     {
41         enum a2 = refPref!fn;
42         enum a3 = name;
43         enum a4 = fnAttr!fn;
44         enum a5 = dlgName!fn;
45         enum a6 = dlgName!fn~"_args";
46         return a2~" ReturnType!fn "~a3~"(Parameters!fn "~a6~") "~a4~
47                 " { return "~a5~"("~a6~"); }\n";
48     }
49 
50     alias IT = InterfacesTuple!T;
51     static if (IT.length)
52     {
53         static foreach (it; IT)
54         {
55             mixin("Like!it as_"~it.stringof~";");
56             // Only one alias this allowed by now
57             //mixin("alias as_"~IT[0].stringof~" this;");
58         }
59         static if (IT.length == 1)
60             mixin("alias as_"~IT[0].stringof~" this;");
61     }
62 
63     static foreach (m; [__traits(derivedMembers, T)])
64         static if (__traits(isVirtualFunction, __traits(getMember, T, m)))
65             static foreach (fn; __traits(getOverloads, T, m))
66                 mixin(buildDelegate!fn ~ buildCall!(fn, m));
67 
68     
69     void fillDelegatesFrom(bool nullCheck=true, X)(ref X src)
70     {
71         alias dst = this;
72 
73         static if (nullCheck && (is(X == interface) || is(X == class)))
74         {
75             version (D_BetterC) assert(src !is null, "object is null");
76             else enforce(src !is null, "object is null");
77         }
78 
79         static string buildMakeOrAssign(alias fn, string m)()
80         {
81             enum dstdlg = "dst." ~ dlgName!fn;
82             enum srcm = "src." ~ m;
83             static if (hasFunctionAttributes!(fn, "@property") &&
84                         !isFunction!(mixin(srcm)))
85             {
86                 enum retsrcm = "return " ~ srcm ~ ";";
87                 enum pref = refPref!fn;
88                 static if (arity!fn == 1)
89                 {
90                     enum ret = !is(ReturnType!fn == void);
91                     return dstdlg ~ " = "~pref~"(v) { "~srcm~" = v; "~(ret?retsrcm:"")~" };";
92                 }
93                 else static if (arity!fn == 0)
94                     return dstdlg ~ " = "~pref~" () " ~ fnAttr!(fn, ["@property"]) ~ " { "~retsrcm~" };";
95                 else
96                     static assert(0, "property must have 0 or 1 parameter");
97             }
98             else return dstdlg ~ " = &"~srcm~";";
99         }
100 
101         alias IT = InterfacesTuple!T;
102         static if (IT.length)
103             static foreach (it; IT)
104                 mixin("this.as_"~it.stringof~".fillDelegatesFrom!nullCheck(src);");
105         
106         static foreach (m; [__traits(derivedMembers, T)])
107             static if (__traits(isVirtualFunction, __traits(getMember, T, m)))
108                 static foreach (fn; __traits(getOverloads, T, m))
109                     mixin(buildMakeOrAssign!(fn, m));
110     }
111 }
112 
113 ///
114 Like!T as(T, bool nullCheck=true, X)(ref X obj)
115     if (is(T == interface))
116 {
117     Like!T ret;
118     ret.fillDelegatesFrom!nullCheck(obj);
119     return ret;
120 }
121 
122 
123 version (D_BetterC) { }
124 else
125 {
126     import std : format, enforce;
127 
128     alias LikeObj(T, S) = LikeWrapper!(T, S, true);
129 
130     alias LikeObjCtx(T, S) = LikeWrapper!(T, S, false);
131 
132     ///
133     class LikeWrapper(T, S, bool isRef=false, string ctxName="__context_", bool isFinal=true) : T
134         if (is(T == interface))
135     {
136         mixin((isFinal?"private":"protected")~" Unqual!S"~(isRef?"*":"")~" "~ctxName~";");
137 
138         static if (isRef)
139         {
140             this(ref Unqual!S c) { mixin(ctxName ~ " = &c;"); }
141             this(ref const Unqual!S c) const { mixin(ctxName ~ " = &c;"); }
142         }
143         else
144         {
145             this(S c) pure
146             {
147                 static if (__traits(compiles, S.init is null))
148                     enforce(c !is null, "context is null");
149                 mixin(ctxName ~ " = c;");
150             }
151         }
152 
153         private static string buildCall(alias fn, string name)()
154         {
155             enum args = fn.mangleof ~ "_args";
156             enum aref = refPref!fn;
157 
158             string callOrFieldUse()
159             {
160                 enum ctxfld = ctxName ~ "." ~ name;
161                 static if (hasFunctionAttributes!(fn, "@property") &&
162                             !isFunction!(typeof(mixin(ctxfld))))
163                 {
164                     enum retctxfld = "return " ~ ctxfld ~ ";";
165                     static if (arity!fn == 1)
166                     {
167                         enum ret = !is(ReturnType!fn == void);
168                         return ctxfld~" = "~args~"[0]; "~(ret?retctxfld:"");
169                     }
170                     else static if (arity!fn == 0) return retctxfld;
171                     else static assert(0, "property must have 0 or 1 parameter");
172                 }
173                 else return "return " ~ ctxfld ~ "(" ~ args ~ ");";
174             }
175 
176             return "%7$s override %1$s %2$s ReturnType!fn %3$s(Parameters!fn %4$s) %5$s { %6$s }\n"
177                     .format("", aref, name, args, fnAttr!fn, callOrFieldUse(), isFinal ? "final" : "");
178         }
179 
180         static foreach (m; [__traits(allMembers, T)])
181             static if (__traits(isVirtualFunction, __traits(getMember, T, m)))
182                 static foreach (fn; __traits(getOverloads, T, m))
183                     mixin(buildCall!(fn, m));
184     }
185 
186     ///
187     auto toObj(X)(auto ref X th) @property
188         if (is(Unqual!X == Like!T, T))
189     {
190         static if (is(Unqual!X == Like!T, T))
191         {
192             static if (is(ConstOf!X == X))
193                 return new const LikeObjCtx!(T, Like!T)(th);
194             else
195                 return new LikeObjCtx!(T, Like!T)(th);
196         }
197         else static assert(0);
198     }
199 
200     ///
201     auto asObj(T, bool nullCheck=true, X)(ref X obj)
202         if (is(T == interface))
203     {
204         static if (is(ConstOf!X == X))
205             return new const LikeObj!(T, X)(obj);
206         else
207             return new LikeObj!(T, X)(obj);
208     }
209 
210     ///
211     auto asObjCtx(T, bool nullCheck=true, X)(auto ref X obj)
212         if (is(T == interface))
213     {
214         static if (is(ConstOf!X == X))
215             return new const LikeObjCtx!(T, X)(obj);
216         else
217             return new LikeObjCtx!(T, X)(obj);
218     }
219 }
220 
221 version(unittest)
222 {
223     interface ConstFoo
224     {
225     @safe:
226     nothrow const:
227         int one();
228         int one(int x);
229         int two();
230 
231     }
232 
233     interface Foo : ConstFoo
234     {
235     @safe:
236         void okda();
237     }
238 
239     int useFoo(Like!Foo obj) @safe
240     {
241         obj.okda();
242         return obj.one() + obj.two() + obj.one(100);
243     }
244 }
245 
246 unittest
247 {
248     int k = 1;
249 
250     struct Bar
251     {
252         int a, b;
253     @safe:
254         void okda() { k = 3; }
255     nothrow const:
256         int one() { return a * 2 + k; }
257         int one(int x) { return a * x; }
258         int two() { return a + b; }
259     }
260 
261     auto bar = Bar(2, 4);
262     assert(useFoo(bar.as!Foo) == 213);
263     assert(k == 3);
264 }
265 
266 unittest
267 {
268     static class Bar : Foo
269     {
270         void okda() {}
271     override const:
272         int one() { return 5; }
273         int one(int x) { return x * 2; }
274         int two() { return 3; }
275     }
276 
277     auto bar = new Bar;
278     assert(useFoo(bar.as!Foo) == 208);
279 }
280 
281 unittest
282 {
283     static interface Foo2 { int func() @nogc; }
284     static void test(Like!Foo2 obj) @nogc { assert(obj.func() == 42); }
285 
286     static class BarC : Foo2
287     { override int func() @nogc { return 42; } }
288 
289     auto barC = new BarC;
290     (() @nogc { test(barC.as!(Foo2, false)); })();
291 
292     static struct BarS
293     { int func() @nogc { return 42; } }
294 
295     BarS barS;
296     (() @nogc { test(barS.as!Foo2); })();
297 
298     import std : assertThrown;
299     BarC nullBarC;
300     assertThrown( nullBarC.as!Foo2 );
301 }
302 
303 @safe
304 unittest
305 {
306     static interface Foo3 { int func() @safe; }
307     static void test(Like!Foo3 obj) @safe { assert(obj.func() == 42); }
308 
309     static class Bar : Foo3
310     { override int func() @nogc { return 42; } }
311 
312     auto bar = new Bar;
313     (() @safe { test(bar.as!Foo3); })();
314 }
315 
316 version(unittest)
317 {
318     int useFooObj(Foo obj) @safe
319     {
320         obj.okda();
321         return obj.one() + obj.two() + obj.one(100);
322     }
323 
324     int useConstFoo(const Like!ConstFoo obj) @safe
325     { return obj.one() + obj.two() + obj.one(1000); }
326 
327     int useConstFooObj(const ConstFoo obj) @safe
328     { return obj.one() + obj.two() + obj.one(1000); }
329 }
330 
331 unittest
332 {
333     int k = 1;
334 
335     struct Bar
336     {
337         int a, b;
338     @safe:
339         void okda() { k = 3; }
340     nothrow const:
341         int one() { return a * 2 + k; }
342         int one(int x) { return a * x; }
343         int two() { return a + b; }
344     }
345 
346     auto bar1 = Bar(2, 4);
347     assert(useFooObj(bar1.asObj!Foo) == 213);
348     assert(useFooObj(bar1.asObjCtx!Foo) == 213);
349     assert(k == 3);
350     auto b1 = bar1.as!Foo;
351     assert(useFooObj(b1.toObj) == 213);
352 
353     assert(useConstFooObj(bar1.asObj!Foo) == 2013);
354     assert(useConstFooObj(bar1.asObjCtx!Foo) == 2013);
355     auto b2 = bar1.as!Foo;
356     assert(useConstFooObj(b2.toObj) == 2013);
357 
358     const bar2 = Bar(1, 2);
359     assert(useConstFoo(bar2.as!ConstFoo) == 1008);
360     auto bb2 = bar2.as!ConstFoo;
361     assert(useConstFooObj(bb2.toObj) == 1008);
362 }
363 
364 version (unittest)
365 {
366     void testField(I, S)()
367     {
368         auto s = S(12);
369         auto w = s.as!I;
370 
371         enum setreturn = is(typeof(I.init.field = 12) == int);
372 
373         assert (w.field == 12);
374         assert ((cast(const)w).field == 12);
375         static if (setreturn) assert ((w.field = 42) == 42);
376         else w.field = 42;
377         assert (w.field == 42);
378         assert ((cast(const)w).field == 42);
379         assert (s.field == 42);
380 
381         auto o = w.toObj;
382         assert (o.field == 42);
383         static if (setreturn) assert ((o.field = 50) == 50);
384         else o.field = 50;
385         assert (w.field == 50);
386         assert (s.field == 50);
387         assert (o.field == 50);
388 
389         auto c = s.asObjCtx!I;
390         assert (c.field == 50);
391         static if (setreturn) assert ((c.field = 123) == 123);
392         else c.field = 123;
393         assert (w.field == 50);
394         assert (s.field == 50);
395         assert (o.field == 50);
396         assert (c.field == 123);
397     }
398 }
399 
400 unittest
401 {
402     static interface F1
403     {
404         @safe:
405         int field() const @property;
406         void field(int v) @property;
407     }
408 
409     static interface F2
410     {
411         @safe:
412         ref const(int) field() const @property;
413         ref int field() @property;
414         void field(int v) @property;
415     }
416 
417     static interface F3
418     {
419         @safe:
420         int field() const @property;
421         int field(int v) @property;
422     }
423 
424     struct S1 { int field; }
425 
426     static struct S2
427     {
428         int _field;
429         @safe:
430         int field() const @property { return _field; }
431         void field(int v) @property { _field = v; }
432     }
433 
434     static struct S3
435     {
436         int _field;
437         @safe:
438         int field() const @property { return _field; }
439         int field(int v) @property { _field = v; return _field; }
440     }
441 
442     testField!(F1, S1);
443     testField!(F2, S1);
444     testField!(F3, S1);
445 
446     testField!(F1, S2);
447     //testField!(F2, S2);
448     //testField!(F3, S2);
449 
450     //testField!(F1, S3);
451     //testField!(F2, S3);
452     testField!(F3, S3);
453 }
454 
455 unittest
456 {
457     static interface PFoo
458     {
459         ref inout(int) field() inout @property;
460     }
461 
462     struct SPFoo { int field; }
463 
464     auto spfoo = SPFoo(12);
465 
466     auto wrap = spfoo.asObjCtx!PFoo;
467 
468     assert (wrap.field == 12);
469     wrap.field = 42;
470     assert (wrap.field == 42);
471     assert (spfoo.field == 12);
472     wrap.field = 32;
473     assert (wrap.field == 32);
474     assert (spfoo.field == 12);
475 }