Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions source/mir/algebraic_alias/ion.d
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,56 @@ unittest
value = annotated.IonAlgebraic;
assert(value == annotated);
}

version (unittest)
{
private import mir.algebraic : Variant;
private import mir.annotated : AnnotatedOnce;
private alias OnceAnnotatedAlgebraic = Variant!(int, AnnotatedOnce!This);
}

// Compile this module together with mir.annotated in both source orders.
// Recursive equality must preserve attributes and compare tags and values.
unittest
{
import mir.annotated : AnnotatedOnce;

static foreach (once; [false, true])
{{
static if (!once)
{
alias Node = IonAlgebraic;
alias W = Annotated!Node;
auto a = W(["tag"], 1);
auto b = W(["tag"], 1);
auto differentTag = W(["other"], 1);
auto differentValue = W(["tag"], 2);
auto parentA = W(["parent"], a);
auto parentB = W(["parent"], b);
}
else
{
alias Node = OnceAnnotatedAlgebraic;
alias W = AnnotatedOnce!Node;
auto a = W("tag", 1);
auto b = W("tag", 1);
auto differentTag = W("other", 1);
auto differentValue = W("tag", 2);
auto parentA = W("parent", a);
auto parentB = W("parent", b);
}
static bool equal(scope const ref W lhs, scope const ref W rhs) pure nothrow @nogc
{
return lhs == rhs;
}
assert(a == b);
const ca = a;
assert(ca == b);
assert(equal(a, b));
assert(!equal(a, differentTag));
assert(!equal(a, differentValue));
assert(equal(parentA, parentB));
parentB.value = differentValue;
assert(!equal(parentA, parentB));
}}
}
77 changes: 69 additions & 8 deletions source/mir/annotated.d
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Macros:
module mir.annotated;

import mir.internal.meta: basicElementType;
import mir.algebraic: isVariant;
import mir.serde: serdeRegister, serdeAnnotation, serdeIsDynamicAlgebraic;

static immutable excMsg = "At least one annotation is required to create an annotated value.";
Expand All @@ -33,7 +34,6 @@ struct Annotated(T) {
private enum _alloc_ = true;
else
{
import mir.algebraic: isVariant;
static if (isVariant!T)
private enum _alloc_ = true;
else
Expand Down Expand Up @@ -63,10 +63,23 @@ struct Annotated(T) {
return *_value;
}

///
bool opEquals(scope const Annotated rhs) scope const
// Algebraic equality already promises these attributes. State them here
// to break recursive attribute inference through annotated alternatives.
static if (isVariant!T)
{
///
bool opEquals(scope const Annotated rhs) scope const pure nothrow @nogc
{
return annotations == rhs.annotations && value == rhs.value;
}
}
else
{
return annotations == rhs.annotations && value == rhs.value;
/// ditto
bool opEquals(scope const Annotated rhs) scope const
{
return annotations == rhs.annotations && value == rhs.value;
}
}

size_t toHash() scope @trusted const pure nothrow @nogc
Expand Down Expand Up @@ -186,7 +199,6 @@ struct AnnotatedOnce(T) {
private enum _alloc_ = true;
else
{
import mir.algebraic: isVariant;
static if (isVariant!T)
private enum _alloc_ = true;
else
Expand Down Expand Up @@ -215,10 +227,23 @@ struct AnnotatedOnce(T) {
return *_value;
}

///
bool opEquals(scope const AnnotatedOnce rhs) scope const
// Algebraic equality already promises these attributes. State them here
// to break recursive attribute inference through annotated alternatives.
static if (isVariant!T)
{
///
bool opEquals(scope const AnnotatedOnce rhs) scope const pure nothrow @nogc
{
return annotation == rhs.annotation && value == rhs.value;
}
}
else
{
return annotation == rhs.annotation && value == rhs.value;
/// ditto
bool opEquals(scope const AnnotatedOnce rhs) scope const
{
return annotation == rhs.annotation && value == rhs.value;
}
}
}
else
Expand Down Expand Up @@ -300,3 +325,39 @@ unittest
assert(ac.annotation == annotation);
assert(ac.value.x == 5);
}

// Equality must not acquire attributes that the wrapped value cannot satisfy.
unittest
{
static struct Effectful
{
enum _serdeRecursiveAlgebraic = true;
int payload;
static int comparisons;

bool opEquals(scope const Effectful rhs) const
{
++comparisons;
auto copy = new int(payload);
if (payload < 0)
throw new Exception("negative payload");
return *copy == rhs.payload;
}
}
static foreach (once; [false, true])
{{
static if (once)
alias W = AnnotatedOnce!Effectful;
else
alias W = Annotated!Effectful;
static assert(!__traits(compiles, () pure { W a, b; return a == b; }));
static assert(!__traits(compiles, () nothrow { W a, b; return a == b; }));
static assert(!__traits(compiles, () @nogc { W a, b; return a == b; }));
W a, b;
a.value = Effectful(1);
b.value = Effectful(1);
auto before = Effectful.comparisons;
assert(a == b);
assert(Effectful.comparisons > before);
}}
}
Loading