Improve operator forwarding.
- Added @disableIndex to disable opIndex forwarding for certain fields - Added field/properly forwarding - Fixed binary operator overloading - Allow implicit conversions when trying to match return types to TaggedAlgebraic during operator return type deduction
This commit is contained in:
parent
8fba150b03
commit
f8835076f8
|
@ -42,8 +42,9 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
{
|
||||
import std.algorithm : among;
|
||||
import std.string : format;
|
||||
import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor;
|
||||
import std.traits : FieldTypeTuple, FieldNameTuple, Largest, hasElaborateCopyConstructor, hasElaborateDestructor;
|
||||
|
||||
private alias Union = U;
|
||||
private alias FieldTypes = FieldTypeTuple!U;
|
||||
private alias fieldNames = FieldNameTuple!U;
|
||||
|
||||
|
@ -52,7 +53,7 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
|
||||
|
||||
private {
|
||||
void[Largest!FieldTypes.sizeof] m_data;
|
||||
void[Largest!FieldTypes.sizeof] m_data = void;
|
||||
Type m_type;
|
||||
}
|
||||
|
||||
|
@ -66,6 +67,12 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
//pragma(msg, generateConstructors!U());
|
||||
mixin(generateConstructors!U);
|
||||
|
||||
this(TaggedAlgebraic other)
|
||||
{
|
||||
import std.algorithm : swap;
|
||||
swap(this, other);
|
||||
}
|
||||
|
||||
void opAssign(TaggedAlgebraic other)
|
||||
{
|
||||
import std.algorithm : swap;
|
||||
|
@ -136,6 +143,8 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
|
||||
/// Enables the invocation of methods of the stored value.
|
||||
auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.method, name)(this, args); }
|
||||
/// Enables accessing properties/fields of the stored value.
|
||||
@property auto opDispatch(string name, this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.field, name, ARGS) && !hasOp!(TA, OpKind.method, name, ARGS)) { return implementOp!(OpKind.field, name)(this, args); }
|
||||
/// Enables equality comparison with the stored value.
|
||||
auto opEquals(T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, "==", T)) { return implementOp!(OpKind.binary, "==")(this, other); }
|
||||
/// Enables relational comparisons with the stored value.
|
||||
|
@ -143,7 +152,7 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
/// Enables the use of unary operators with the stored value.
|
||||
auto opUnary(string op, this TA)() if (hasOp!(TA, OpKind.unary, op)) { return implementOp!(OpKind.unary, op)(this); }
|
||||
/// Enables the use of binary operators with the stored value.
|
||||
auto opBinary(string op, T, this TA)(auto ref T other) inout if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); }
|
||||
auto opBinary(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op, T)) { return implementOp!(OpKind.binary, op)(this, other); }
|
||||
/// Enables operator assignments on the stored value.
|
||||
auto opOpAssign(string op, T, this TA)(auto ref T other) if (hasOp!(TA, OpKind.binary, op~"=", T)) { return implementOp!(OpKind.binary, op~"=")(this, other); }
|
||||
/// Enables indexing operations on the stored value.
|
||||
|
@ -153,41 +162,6 @@ struct TaggedAlgebraic(U) if (is(U == union) || is(U == struct))
|
|||
/// Enables call syntax operations on the stored value.
|
||||
auto opCall(this TA, ARGS...)(auto ref ARGS args) if (hasOp!(TA, OpKind.call, null, ARGS)) { return implementOp!(OpKind.call, null)(this, args); }
|
||||
|
||||
private template hasOp(TA, OpKind kind, string name, ARGS...)
|
||||
{
|
||||
alias UQ = CopyTypeQualifiers!(TA, U);
|
||||
enum hasOp = .hasOp!(UQ, kind, name, ARGS);
|
||||
}
|
||||
|
||||
private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args)
|
||||
{
|
||||
import std.array : join;
|
||||
import std.variant : Algebraic, Variant;
|
||||
alias UQ = CopyTypeQualifiers!(T, U);
|
||||
|
||||
alias info = OpInfo!(UQ, kind, name, ARGS);
|
||||
|
||||
switch (self.m_type) {
|
||||
default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", "));
|
||||
foreach (i, f; info.fields) {
|
||||
alias FT = FieldTypes[i];
|
||||
case __traits(getMember, Type, f):
|
||||
static if (NoDuplicates!(info.ReturnTypes).length == 1)
|
||||
return info.perform(self.trustedGet!f, args);
|
||||
else static if (allSatisfy!(isMatchingUniqueType!U, info.ReturnTypes))
|
||||
return TaggedAlgebraic(info.perform(self.trustedGet!f, args));
|
||||
else static if (allSatisfy!(isNoVariant, info.ReturnTypes))
|
||||
return Algebraic!(NoDuplicates!(info.ReturnTypes))(info.perform(self.trustedGet!f, args));
|
||||
else static if (is(FT == Variant))
|
||||
return info.perform(self.trustedGet!f, args);
|
||||
else
|
||||
return Variant(info.perform(self.trustedGet!f, args));
|
||||
}
|
||||
}
|
||||
|
||||
assert(false); // never reached
|
||||
}
|
||||
|
||||
private @trusted @property ref inout(typeof(__traits(getMember, U, f))) trustedGet(string f)() inout { return trustedGet!(inout(typeof(__traits(getMember, U, f)))); }
|
||||
private @trusted @property ref inout(T) trustedGet(T)() inout { return *cast(inout(T)*)m_data.ptr; }
|
||||
}
|
||||
|
@ -457,13 +431,49 @@ unittest { // Ambiguous binary op between two TaggedAlgebraic values
|
|||
|
||||
TA a = 1, b = 2;
|
||||
static assert(is(typeof(a + b) == TA));
|
||||
assert((a + b).typeID == TA.Type.i);
|
||||
assert(a + b == 3);
|
||||
}
|
||||
|
||||
unittest {
|
||||
struct S {
|
||||
union U {
|
||||
@disableIndex string str;
|
||||
S[] array;
|
||||
S[string] object;
|
||||
}
|
||||
alias TA = TaggedAlgebraic!U;
|
||||
TA payload;
|
||||
alias payload this;
|
||||
}
|
||||
|
||||
S a = S(S.TA("hello"));
|
||||
S b = S(S.TA(["foo": a]));
|
||||
S c = S(S.TA([a]));
|
||||
assert(b["foo"] == a);
|
||||
assert(b["foo"] == "hello");
|
||||
assert(c[0] == a);
|
||||
assert(c[0] == "hello");
|
||||
}
|
||||
|
||||
/// Convenience type that can be used for union fields that have no value (`void` is not allowed).
|
||||
struct Void {}
|
||||
|
||||
private enum hasOp(U, OpKind kind, string name, ARGS...) = TypeTuple!(OpInfo!(U, kind, name, ARGS).fields).length > 0;
|
||||
/// User-defined attibute to disable `opIndex` forwarding for a particular tagged union member.
|
||||
@property auto disableIndex() { assert(__ctfe, "disableIndex must only be used as an attribute."); return DisableOpAttribute(OpKind.index, null); }
|
||||
|
||||
private struct DisableOpAttribute {
|
||||
OpKind kind;
|
||||
string name;
|
||||
}
|
||||
|
||||
|
||||
private template hasOp(TA, OpKind kind, string name, ARGS...)
|
||||
{
|
||||
import std.traits : CopyTypeQualifiers;
|
||||
alias UQ = CopyTypeQualifiers!(TA, TA.Union);
|
||||
enum hasOp = TypeTuple!(OpInfo!(UQ, kind, name, ARGS).fields).length > 0;
|
||||
}
|
||||
|
||||
unittest {
|
||||
static struct S {
|
||||
|
@ -473,21 +483,124 @@ unittest {
|
|||
}
|
||||
|
||||
static union U { int i; string s; S st; }
|
||||
alias TA = TaggedAlgebraic!U;
|
||||
|
||||
static assert(hasOp!(U, OpKind.binary, "+", int));
|
||||
static assert(hasOp!(U, OpKind.binary, "~", string));
|
||||
static assert(hasOp!(U, OpKind.binary, "==", int));
|
||||
static assert(hasOp!(U, OpKind.binary, "==", string));
|
||||
static assert(hasOp!(U, OpKind.binary, "==", int));
|
||||
static assert(hasOp!(U, OpKind.binary, "==", S));
|
||||
static assert(hasOp!(U, OpKind.method, "m", int));
|
||||
static assert(hasOp!(U, OpKind.binary, "+=", int));
|
||||
static assert(!hasOp!(U, OpKind.binary, "~", int));
|
||||
static assert(!hasOp!(U, OpKind.binary, "~", int));
|
||||
static assert(!hasOp!(U, OpKind.method, "m", string));
|
||||
static assert(!hasOp!(U, OpKind.method, "m"));
|
||||
static assert(!hasOp!(const(U), OpKind.binary, "+=", int));
|
||||
static assert(!hasOp!(const(U), OpKind.method, "m", int));
|
||||
static assert(hasOp!(TA, OpKind.binary, "+", int));
|
||||
static assert(hasOp!(TA, OpKind.binary, "~", string));
|
||||
static assert(hasOp!(TA, OpKind.binary, "==", int));
|
||||
static assert(hasOp!(TA, OpKind.binary, "==", string));
|
||||
static assert(hasOp!(TA, OpKind.binary, "==", int));
|
||||
static assert(hasOp!(TA, OpKind.binary, "==", S));
|
||||
static assert(hasOp!(TA, OpKind.method, "m", int));
|
||||
static assert(hasOp!(TA, OpKind.binary, "+=", int));
|
||||
static assert(!hasOp!(TA, OpKind.binary, "~", int));
|
||||
static assert(!hasOp!(TA, OpKind.binary, "~", int));
|
||||
static assert(!hasOp!(TA, OpKind.method, "m", string));
|
||||
static assert(!hasOp!(TA, OpKind.method, "m"));
|
||||
static assert(!hasOp!(const(TA), OpKind.binary, "+=", int));
|
||||
static assert(!hasOp!(const(TA), OpKind.method, "m", int));
|
||||
}
|
||||
|
||||
unittest {
|
||||
struct S {
|
||||
union U {
|
||||
string s;
|
||||
S[] arr;
|
||||
S[string] obj;
|
||||
}
|
||||
alias TA = TaggedAlgebraic!(S.U);
|
||||
TA payload;
|
||||
alias payload this;
|
||||
}
|
||||
static assert(hasOp!(S.TA, OpKind.index, null, size_t));
|
||||
static assert(hasOp!(S.TA, OpKind.index, null, int));
|
||||
static assert(hasOp!(S.TA, OpKind.index, null, string));
|
||||
static assert(hasOp!(S.TA, OpKind.field, "length"));
|
||||
}
|
||||
|
||||
private static auto implementOp(OpKind kind, string name, T, ARGS...)(ref T self, auto ref ARGS args)
|
||||
{
|
||||
import std.array : join;
|
||||
import std.traits : CopyTypeQualifiers;
|
||||
import std.variant : Algebraic, Variant;
|
||||
alias UQ = CopyTypeQualifiers!(T, T.Union);
|
||||
|
||||
alias info = OpInfo!(UQ, kind, name, ARGS);
|
||||
|
||||
static assert(hasOp!(T, kind, name, ARGS));
|
||||
|
||||
static assert(info.fields.length > 0, "Implementing operator that has no valid implementation for any supported type.");
|
||||
|
||||
//pragma(msg, "Fields for "~kind.stringof~" "~name~", "~T.stringof~": "~info.fields.stringof);
|
||||
//pragma(msg, "Return types for "~kind.stringof~" "~name~", "~T.stringof~": "~info.ReturnTypes.stringof);
|
||||
//pragma(msg, typeof(T.Union.tupleof));
|
||||
//import std.meta : staticMap; pragma(msg, staticMap!(isMatchingUniqueType!(T.Union), info.ReturnTypes));
|
||||
|
||||
switch (self.m_type) {
|
||||
default: assert(false, "Operator "~name~" ("~kind.stringof~") can only be used on values of the following types: "~[info.fields].join(", "));
|
||||
foreach (i, f; info.fields) {
|
||||
alias FT = typeof(__traits(getMember, T.Union, f));
|
||||
case __traits(getMember, T.Type, f):
|
||||
static if (NoDuplicates!(info.ReturnTypes).length == 1)
|
||||
return info.perform(self.trustedGet!FT, args);
|
||||
else static if (allSatisfy!(isMatchingUniqueType!(T.Union), info.ReturnTypes))
|
||||
return TaggedAlgebraic!(T.Union)(info.perform(self.trustedGet!FT, args));
|
||||
else static if (allSatisfy!(isNoVariant, info.ReturnTypes)) {
|
||||
alias Alg = Algebraic!(NoDuplicates!(info.ReturnTypes));
|
||||
info.ReturnTypes[i] ret = info.perform(self.trustedGet!FT, args);
|
||||
import std.traits : isInstanceOf;
|
||||
static if (isInstanceOf!(TaggedAlgebraic, typeof(ret))) return Alg(ret.payload);
|
||||
else return Alg(ret);
|
||||
}
|
||||
else static if (is(FT == Variant))
|
||||
return info.perform(self.trustedGet!FT, args);
|
||||
else
|
||||
return Variant(info.perform(self.trustedGet!FT, args));
|
||||
}
|
||||
}
|
||||
|
||||
assert(false); // never reached
|
||||
}
|
||||
|
||||
unittest { // opIndex on recursive TA with closed return value set
|
||||
static struct S {
|
||||
union U {
|
||||
char ch;
|
||||
string str;
|
||||
S[] arr;
|
||||
}
|
||||
alias TA = TaggedAlgebraic!U;
|
||||
TA payload;
|
||||
alias payload this;
|
||||
|
||||
this(T)(T t) { this.payload = t; }
|
||||
}
|
||||
S a = S("foo");
|
||||
S s = S([a]);
|
||||
|
||||
assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
|
||||
static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S.TA));
|
||||
assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
|
||||
}
|
||||
|
||||
unittest { // opIndex on recursive TA with closed return value set using @disableIndex
|
||||
static struct S {
|
||||
union U {
|
||||
@disableIndex string str;
|
||||
S[] arr;
|
||||
}
|
||||
alias TA = TaggedAlgebraic!U;
|
||||
TA payload;
|
||||
alias payload this;
|
||||
|
||||
this(T)(T t) { this.payload = t; }
|
||||
}
|
||||
S a = S("foo");
|
||||
S s = S([a]);
|
||||
|
||||
assert(implementOp!(OpKind.field, "length")(s.payload) == 1);
|
||||
static assert(is(typeof(implementOp!(OpKind.index, null)(s.payload, 0)) == S));
|
||||
assert(implementOp!(OpKind.index, null)(s.payload, 0) == "foo");
|
||||
}
|
||||
|
||||
|
||||
|
@ -496,6 +609,7 @@ private auto performOpRaw(U, OpKind kind, string name, T, ARGS...)(ref T value,
|
|||
static if (kind == OpKind.binary) return mixin("value "~name~" args[0]");
|
||||
else static if (kind == OpKind.unary) return mixin("name "~value);
|
||||
else static if (kind == OpKind.method) return __traits(getMember, value, name)(args);
|
||||
else static if (kind == OpKind.field) return __traits(getMember, value, name);
|
||||
else static if (kind == OpKind.index) return value[args];
|
||||
else static if (kind == OpKind.indexAssign) return value[args[1 .. $]] = args[0];
|
||||
else static if (kind == OpKind.call) return value(args);
|
||||
|
@ -555,15 +669,30 @@ unittest {
|
|||
|
||||
private template OpInfo(U, OpKind kind, string name, ARGS...)
|
||||
{
|
||||
import std.traits : FieldTypeTuple, FieldNameTuple, ReturnType;
|
||||
import std.traits : CopyTypeQualifiers, FieldTypeTuple, FieldNameTuple, ReturnType;
|
||||
|
||||
alias FieldTypes = FieldTypeTuple!U;
|
||||
alias fieldNames = FieldNameTuple!U;
|
||||
private alias FieldTypes = FieldTypeTuple!U;
|
||||
private alias fieldNames = FieldNameTuple!U;
|
||||
|
||||
private template isOpEnabled(string field)
|
||||
{
|
||||
alias attribs = TypeTuple!(__traits(getAttributes, __traits(getMember, U, field)));
|
||||
template impl(size_t i) {
|
||||
static if (i < attribs.length) {
|
||||
static if (is(typeof(attribs[i]) == DisableOpAttribute)) {
|
||||
static if (kind == attribs[i].kind && name == attribs[i].name)
|
||||
enum impl = false;
|
||||
else enum impl = impl!(i+1);
|
||||
} else enum impl = impl!(i+1);
|
||||
} else enum impl = true;
|
||||
}
|
||||
enum isOpEnabled = impl!0;
|
||||
}
|
||||
|
||||
template fieldsImpl(size_t i)
|
||||
{
|
||||
static if (i < FieldTypes.length) {
|
||||
static if (is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) {
|
||||
static if (isOpEnabled!(fieldNames[i]) && is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) {
|
||||
alias fieldsImpl = TypeTuple!(fieldNames[i], fieldsImpl!(i+1));
|
||||
} else alias fieldsImpl = fieldsImpl!(i+1);
|
||||
} else alias fieldsImpl = TypeTuple!();
|
||||
|
@ -571,11 +700,9 @@ private template OpInfo(U, OpKind kind, string name, ARGS...)
|
|||
alias fields = fieldsImpl!0;
|
||||
|
||||
template ReturnTypesImpl(size_t i) {
|
||||
static if (i < FieldTypes.length) {
|
||||
static if (is(typeof(&performOp!(U, kind, name, FieldTypes[i], ARGS)))) {
|
||||
alias T = ReturnType!(performOp!(U, kind, name, FieldTypes[i], ARGS));
|
||||
alias ReturnTypesImpl = TypeTuple!(T, ReturnTypesImpl!(i+1));
|
||||
} else alias ReturnTypesImpl = ReturnTypesImpl!(i+1);
|
||||
static if (i < fields.length) {
|
||||
alias FT = CopyTypeQualifiers!(U, typeof(__traits(getMember, U, fields[i])));
|
||||
alias ReturnTypesImpl = TypeTuple!(ReturnType!(performOp!(U, kind, name, FT, ARGS)), ReturnTypesImpl!(i+1));
|
||||
} else alias ReturnTypesImpl = TypeTuple!();
|
||||
}
|
||||
alias ReturnTypes = ReturnTypesImpl!0;
|
||||
|
@ -583,10 +710,21 @@ private template OpInfo(U, OpKind kind, string name, ARGS...)
|
|||
static auto perform(T)(ref T value, auto ref ARGS args) { return performOp!(U, kind, name)(value, args); }
|
||||
}
|
||||
|
||||
private template ImplicitUnqual(T) {
|
||||
import std.traits : Unqual, hasAliasing;
|
||||
static if (is(T == void)) alias ImplicitUnqual = void;
|
||||
else {
|
||||
private static struct S { T t; }
|
||||
static if (hasAliasing!S) alias ImplicitUnqual = T;
|
||||
else alias ImplicitUnqual = Unqual!T;
|
||||
}
|
||||
}
|
||||
|
||||
private enum OpKind {
|
||||
binary,
|
||||
unary,
|
||||
method,
|
||||
field,
|
||||
index,
|
||||
indexAssign,
|
||||
call
|
||||
|
@ -727,16 +865,44 @@ private template isMatchingType(U) {
|
|||
}
|
||||
|
||||
private template isMatchingUniqueType(U) {
|
||||
import std.traits : FieldTypeTuple;
|
||||
import std.traits : staticMap;
|
||||
alias UniqueTypes = staticMap!(FieldTypeOf!U, UniqueTypeFields!U);
|
||||
template isMatchingUniqueType(T) {
|
||||
alias Types = FieldTypeTuple!U;
|
||||
enum idx = staticIndexOf!(T, Types);
|
||||
static if (idx < 0) enum isMatchingUniqueType = false;
|
||||
else static if (staticIndexOf!(T, Types[idx+1 .. $]) >= 0) enum isMatchingUniqueType = false;
|
||||
else enum isMatchingUniqueType = true;
|
||||
static if (is(T : TaggedAlgebraic!U)) enum isMatchingUniqueType = true;
|
||||
else enum isMatchingUniqueType = staticIndexOfImplicit!(T, UniqueTypes) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
private template FieldTypeOf(U) {
|
||||
template FieldTypeOf(string name) {
|
||||
alias FieldTypeOf = typeof(__traits(getMember, U, name));
|
||||
}
|
||||
}
|
||||
|
||||
private template staticIndexOfImplicit(T, Types...) {
|
||||
template impl(size_t i) {
|
||||
static if (i < Types.length) {
|
||||
static if (is(T : Types[i])) {
|
||||
pragma(msg, "YEPP "~T.stringof~" "~Types[i].stringof);
|
||||
enum impl = i;
|
||||
} else {
|
||||
pragma(msg, "NOPE "~T.stringof~" "~Types[i].stringof);
|
||||
enum impl = impl!(i+1);
|
||||
}
|
||||
} else enum impl = -1;
|
||||
}
|
||||
enum staticIndexOfImplicit = impl!0;
|
||||
}
|
||||
|
||||
unittest {
|
||||
static assert(staticIndexOfImplicit!(immutable(char), char) == 0);
|
||||
static assert(staticIndexOfImplicit!(int, long) == 0);
|
||||
static assert(staticIndexOfImplicit!(long, int) < 0);
|
||||
static assert(staticIndexOfImplicit!(int, int, double) == 0);
|
||||
static assert(staticIndexOfImplicit!(double, int, double) == 1);
|
||||
}
|
||||
|
||||
|
||||
private template isNoVariant(T) {
|
||||
import std.variant : Variant;
|
||||
enum isNoVariant = !is(T == Variant);
|
||||
|
|
Loading…
Reference in a new issue