Last active
July 9, 2019 08:50
-
-
Save Biotronic/f362732f83e7b4c6a4b3370919d73be3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct ModelA { | |
class Animal { | |
TypeInfo eat() { | |
return typeid(typeof(this)); | |
} | |
Food eatsFood() { | |
return null; | |
} | |
mixin fixCast!(); | |
} | |
class Dog : Animal { | |
override TypeInfo eat() { | |
return typeid(typeof(this)); | |
} | |
TypeInfo bark() { | |
return typeid(typeof(this)); | |
} | |
TypeInfo bite() { | |
return typeid(typeof(this)); | |
} | |
override Food eatsFood() { | |
return new Food(); | |
} | |
} | |
class Food { | |
mixin fixCast!(); | |
} | |
} | |
struct ModelB { | |
class Animal { | |
mixin inherit!ModelA; | |
} | |
class Dog : Animal { | |
mixin inherit!ModelA; | |
TypeInfo bark() { | |
return typeid(typeof(this)); | |
} | |
Food eatsFood() { | |
return new Food(); | |
} | |
} | |
class Food { | |
mixin inherit!ModelA; | |
} | |
} | |
unittest { | |
// Implicit conversions to ModelA: | |
ModelB.Dog a = new ModelB.Dog(); | |
ModelA.Dog b = a; | |
ModelB.Animal c = a; | |
ModelA.Animal d = c; | |
assert(a !is null); | |
assert(b !is null); | |
assert(c !is null); | |
assert(d !is null); | |
assert(a == b); | |
assert(a == c); | |
assert(a == d); | |
assert(b == c); | |
assert(b == d); | |
assert(c == d); | |
// Methods are inherited from the corresponding class in the base model: | |
assert(a.eat() == typeid(ModelA.Dog)); | |
assert(b.eat() == typeid(ModelA.Dog)); | |
assert(c.eat() == typeid(ModelA.Dog)); | |
assert(d.eat() == typeid(ModelA.Dog)); | |
// bark() is overridden in ModelB.Dog, so that's the version what's being called: | |
assert(a.bark() == typeid(ModelB.Dog)); | |
assert(b.bark() == typeid(ModelB.Dog)); | |
// bite(), however, is not overridden, so ModelA's version is used: | |
assert(a.bite() == typeid(ModelA.Dog)); | |
assert(b.bite() == typeid(ModelA.Dog)); | |
// Managed to fix casting from ModelA to ModelB: | |
assert(cast(ModelB.Dog)d !is null); | |
assert(cast(ModelB.Animal)d !is null); | |
// | |
ModelB.Food food1 = a.eatsFood; | |
ModelA.Food food3 = b.eatsFood; | |
ModelA.Food food4 = d.eatsFood; | |
// Can't do - ModelB.Animal does not override eatsFood(), | |
// so this is ModelA.Animal's eatsFood() method. | |
//ModelB.Food food2 = c.eatsFood; | |
} | |
mixin template fixCast() { | |
static if (!__traits(hasMember, typeof(this), "_hasFixCast")) { | |
enum _hasFixCast = true; | |
inout(T) opCast(T)() inout if (is(T == class)) { | |
auto tmp = _opCastImpl(typeid(T)); | |
return *cast(inout(T)*)&tmp; | |
} | |
inout(void)* _opCastImpl(TypeInfo_Class) inout { | |
return null; | |
} | |
} | |
} | |
mixin template inherit(ParentModel) { | |
static assert(is(typeof(this) == class), "inherit only works for classes"); | |
static assert(__traits(hasMember, ParentModel, typeof(this).stringof), "inherit works only for models matching the same class structure"); | |
alias ThisClass = typeof(this); | |
alias ParentClass = __traits(getMember, ParentModel, typeof(this).stringof); | |
static assert(__traits(hasMember, ParentClass, "_opCastImpl"), "Base model class doesn't correctly implement `opCast`. Please use `mixin fixCast!();` in `"~ParentClass.stringof~"`"); | |
private inout(ParentClass) _getProxyParentImpl() inout { | |
if (_proxyParent !is null) { | |
return _proxyParent; | |
} | |
import std.typecons : AutoImplement; | |
static class Base : ParentClass { | |
ThisClass zis; | |
this(inout(ThisClass) a) inout { | |
zis = a; | |
} | |
// Manually check if we're casting ThisClass or a supertype, otherwise return null. | |
override inout(void)* _opCastImpl(TypeInfo_Class t) inout { | |
auto thisType = typeid(ThisClass); | |
while (thisType !is null && thisType != t) { | |
thisType = thisType.base; | |
} | |
if (!t) return null; | |
union A { | |
ThisClass a; | |
void* b; | |
} | |
inout(A) a = {zis}; | |
return a.b; | |
} | |
override bool opEquals(Object o) { | |
if (*cast(void**)&o == *cast(void**)&zis) return true; | |
auto tmp = this; | |
return *cast(void**)&o == *cast(void**)&tmp; | |
} | |
override size_t toHash() @trusted nothrow { | |
return zis.toHash(); | |
} | |
} | |
// AutoImplement should only implement methods that are overridden in ThisClass. | |
template What(alias func) { | |
import std.algorithm.comparison : among; | |
enum funcName = __traits(identifier, func); | |
static if (funcName.among(__traits(allMembers, ThisClass))) { | |
alias thisFunc = __traits(getMember, ThisClass, __traits(identifier, func)); | |
enum What = __traits(isVirtualFunction, func) && | |
__traits(isVirtualFunction, thisFunc); | |
} else { | |
enum What = false; | |
} | |
} | |
// AutoImplement should forward calls to ThisClass where it defines an override. | |
template How(T, alias func) { | |
import std.format : format; | |
enum How = q{return zis.%1$s(args);} | |
.format(__traits(identifier, func)); | |
} | |
auto tmp = new inout(AutoImplement!(Base, How, What))(this); | |
// Ugly as fuck. I'm sorry. | |
*cast(ParentClass*)&_proxyParent = *cast(ParentClass*)&tmp; | |
return tmp; | |
} | |
ParentClass _proxyParent; | |
import std.traits : BaseClassesTuple; | |
static if (__traits(hasMember, BaseClassesTuple!ThisClass[0], "_getProxyParent")) { | |
override inout(ParentClass) _getProxyParent() inout { | |
return _getProxyParentImpl(); | |
} | |
} else { | |
inout(ParentClass) _getProxyParent() inout { | |
return _getProxyParentImpl(); | |
} | |
} | |
alias _getProxyParent this; | |
override bool opEquals(Object o) { | |
auto zis = _getProxyParent(); | |
if (*cast(void**)&o == *cast(void**)&zis) return true; | |
auto tmp = this; | |
return *cast(void**)&o == *cast(void**)&tmp; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment