Skip to content

Instantly share code, notes, and snippets.

@Biotronic
Last active July 9, 2019 08:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Biotronic/f362732f83e7b4c6a4b3370919d73be3 to your computer and use it in GitHub Desktop.
Save Biotronic/f362732f83e7b4c6a4b3370919d73be3 to your computer and use it in GitHub Desktop.
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