summaryrefslogtreecommitdiff
path: root/libphobos/libdruntime/object.d
diff options
context:
space:
mode:
Diffstat (limited to 'libphobos/libdruntime/object.d')
-rw-r--r--libphobos/libdruntime/object.d383
1 files changed, 302 insertions, 81 deletions
diff --git a/libphobos/libdruntime/object.d b/libphobos/libdruntime/object.d
index c989caa64bf..7bb6bec7ed9 100644
--- a/libphobos/libdruntime/object.d
+++ b/libphobos/libdruntime/object.d
@@ -180,7 +180,30 @@ class Object
/**
* Test whether $(D this) is equal to $(D o).
* The default implementation only compares by identity (using the $(D is) operator).
- * Generally, overrides for $(D opEquals) should attempt to compare objects by their contents.
+ * Generally, overrides and overloads for $(D opEquals) should attempt to compare objects by their contents.
+ * A class will most likely want to add an overload that takes your specific type as the argument
+ * and does the content comparison. Then you can override this and forward it to your specific
+ * typed overload with a cast. Remember to check for `null` on the typed overload.
+ *
+ * Examples:
+ * ---
+ * class Child {
+ * int contents;
+ * // the typed overload first. It can use all the attribute you want
+ * bool opEquals(const Child c) const @safe pure nothrow @nogc
+ * {
+ * if (c is null)
+ * return false;
+ * return this.contents == c.contents;
+ * }
+ *
+ * // and now the generic override forwards with a cast
+ * override bool opEquals(Object o)
+ * {
+ * return this.opEquals(cast(Child) o);
+ * }
+ * }
+ * ---
*/
bool opEquals(Object o)
{
@@ -237,41 +260,50 @@ class Object
}
}
-bool opEquals(Object lhs, Object rhs)
+/++
+ Implementation for class opEquals override. Calls the class-defined methods after a null check.
+ Please note this is not nogc right now, even if your implementation is, because of
+ the typeinfo name string compare. This is because of dmd's dll implementation. However,
+ it can infer to @safe if your class' opEquals is.
++/
+bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) if (is(LHS : const Object) && is(RHS : const Object))
{
- // If aliased to the same object or both null => equal
- if (lhs is rhs) return true;
+ static if (__traits(compiles, lhs.opEquals(rhs)) && __traits(compiles, rhs.opEquals(lhs)))
+ {
+ // If aliased to the same object or both null => equal
+ if (lhs is rhs) return true;
- // If either is null => non-equal
- if (lhs is null || rhs is null) return false;
+ // If either is null => non-equal
+ if (lhs is null || rhs is null) return false;
- if (!lhs.opEquals(rhs)) return false;
+ if (!lhs.opEquals(rhs)) return false;
- // If same exact type => one call to method opEquals
- if (typeid(lhs) is typeid(rhs) ||
- !__ctfe && typeid(lhs).opEquals(typeid(rhs)))
- /* CTFE doesn't like typeid much. 'is' works, but opEquals doesn't
- (issue 7147). But CTFE also guarantees that equal TypeInfos are
- always identical. So, no opEquals needed during CTFE. */
- {
- return true;
- }
+ // If same exact type => one call to method opEquals
+ if (typeid(lhs) is typeid(rhs) ||
+ !__ctfe && typeid(lhs).opEquals(typeid(rhs)))
+ /* CTFE doesn't like typeid much. 'is' works, but opEquals doesn't
+ (issue 7147). But CTFE also guarantees that equal TypeInfos are
+ always identical. So, no opEquals needed during CTFE. */
+ {
+ return true;
+ }
- // General case => symmetric calls to method opEquals
- return rhs.opEquals(lhs);
-}
+ // General case => symmetric calls to method opEquals
+ return rhs.opEquals(lhs);
+ }
+ else
+ {
+ // this is a compatibility hack for the old const cast behavior
+ // if none of the new overloads compile, we'll go back plain Object,
+ // including casting away const. It does this through the pointer
+ // to bypass any opCast that may be present on the original class.
+ return .opEquals!(Object, Object)(*cast(Object*) &lhs, *cast(Object*) &rhs);
-/************************
-* Returns true if lhs and rhs are equal.
-*/
-bool opEquals(const Object lhs, const Object rhs)
-{
- // A hack for the moment.
- return opEquals(cast()lhs, cast()rhs);
+ }
}
/// If aliased to the same object or both null => equal
-@system unittest
+@system unittest // this one is not @safe because it goes through the Object base method
{
class F { int flag; this(int flag) { this.flag = flag; } }
@@ -291,7 +323,8 @@ bool opEquals(const Object lhs, const Object rhs)
}
/// If same exact type => one call to method opEquals
-@system unittest
+/// This test passes `@safe` because it defines a new opEquals with `@safe`
+@safe unittest
{
class F
{
@@ -302,9 +335,9 @@ bool opEquals(const Object lhs, const Object rhs)
this.flag = flag;
}
- override bool opEquals(const Object o)
+ bool opEquals(const F o) const @safe nothrow pure
{
- return flag == (cast(F) o).flag;
+ return flag == o.flag;
}
}
@@ -314,7 +347,7 @@ bool opEquals(const Object lhs, const Object rhs)
}
/// General case => symmetric calls to method opEquals
-@system unittest
+@safe unittest
{
int fEquals, gEquals;
@@ -331,10 +364,10 @@ bool opEquals(const Object lhs, const Object rhs)
{
this(int flag) { super(flag); }
- override bool opEquals(const Object o)
+ bool opEquals(const Base o) @safe
{
fEquals++;
- return flag == (cast(Base) o).flag;
+ return flag == o.flag;
}
}
@@ -342,10 +375,10 @@ bool opEquals(const Object lhs, const Object rhs)
{
this(int flag) { super(flag); }
- override bool opEquals(const Object o)
+ bool opEquals(const Base o) @safe
{
gEquals++;
- return flag == (cast(Base) o).flag;
+ return flag == o.flag;
}
}
@@ -354,6 +387,114 @@ bool opEquals(const Object lhs, const Object rhs)
assert(gEquals == 1);
}
+/++
+ This test shows an example for a comprehensive inheritance equality chain too.
++/
+unittest
+{
+ static class Base
+ {
+ int member;
+
+ this(int member) pure @safe nothrow @nogc
+ {
+ this.member = member;
+ }
+
+ override bool opEquals(Object rhs) const
+ {
+ return this.opEquals(cast(Base) rhs);
+ }
+
+ bool opEquals(const Base rhs) const @nogc pure nothrow @safe
+ {
+ if (rhs is null)
+ return false;
+ return this.member == rhs.member;
+ }
+ }
+
+ // works through the direct class with attributes enabled, except for pure and nogc in the current TypeInfo implementation
+ bool testThroughBase() nothrow @safe
+ {
+ Base b1 = new Base(0);
+ Base b2 = new Base(0);
+ assert(b1 == b2);
+ Base b3 = new Base(1);
+ assert(b1 != b3);
+ return true;
+ }
+
+ static assert(testThroughBase());
+
+ // also works through the base class interface thanks to the override, but no more attributes
+ bool testThroughObject()
+ {
+ Object o1 = new Base(0);
+ Object o2 = new Base(0);
+ assert(o1 == o2);
+ Object o3 = new Base(1);
+ assert(o1 != o3);
+ return true;
+ }
+
+ static assert(testThroughObject());
+
+ // Each time you make a child, you want to override all old opEquals
+ // and add a new overload for the new child.
+ static class Child : Base
+ {
+ int member2;
+
+ this(int member, int member2) pure @safe nothrow @nogc
+ {
+ super(member);
+ this.member2 = member2;
+ }
+
+ // override the whole chain so it works consistently though any base
+ override bool opEquals(Object rhs) const
+ {
+ return this.opEquals(cast(Child) rhs);
+ }
+ override bool opEquals(const Base rhs) const
+ {
+ return this.opEquals(cast(const Child) rhs);
+ }
+ // and then add the new overload, if necessary, to handle new members
+ bool opEquals(const Child rhs) const @nogc pure nothrow @safe
+ {
+ if (rhs is null)
+ return false;
+ // can call back to the devirtualized base test with implicit conversion
+ // then compare the new member too. or we could have just compared the base
+ // member directly here as well.
+ return Base.opEquals(rhs) && this.member2 == rhs.member2;
+ }
+
+ // a mixin template, of course, could automate this.
+ }
+
+ bool testThroughChild()
+ {
+ Child a = new Child(0, 0);
+ Child b = new Child(0, 1);
+ assert(a != b);
+
+ Base ba = a;
+ Base bb = b;
+ assert(ba != bb);
+
+ Object oa = a;
+ Object ob = b;
+ assert(oa != ob);
+
+ return true;
+ }
+
+ static assert(testThroughChild());
+}
+
// To cover const Object opEquals
@system unittest
{
@@ -396,7 +537,8 @@ void setSameMutex(shared Object ownee, shared Object owner)
*/
struct Interface
{
- TypeInfo_Class classinfo; /// .classinfo for this interface (not for containing class)
+ /// Class info returned by `typeid` for this interface (not for containing class)
+ TypeInfo_Class classinfo;
void*[] vtbl;
size_t offset; /// offset to Interface 'this' from Object 'this'
}
@@ -447,13 +589,17 @@ class TypeInfo
override bool opEquals(Object o)
{
+ return opEquals(cast(TypeInfo) o);
+ }
+
+ bool opEquals(const TypeInfo ti) @safe nothrow const
+ {
/* TypeInfo instances are singletons, but duplicates can exist
* across DLL's. Therefore, comparing for a name match is
* sufficient.
*/
- if (this is o)
+ if (this is ti)
return true;
- auto ti = cast(const TypeInfo)o;
return ti && this.toString() == ti.toString();
}
@@ -462,7 +608,7 @@ class TypeInfo
auto anotherObj = new Object();
assert(typeid(void).opEquals(typeid(void)));
- assert(!typeid(void).opEquals(anotherObj));
+ assert(typeid(void) != anotherObj); // calling .opEquals here directly is a type mismatch
}
/**
@@ -1397,13 +1543,13 @@ private extern (C) int _d_isbaseof(scope TypeInfo_Class child,
/**
* Runtime type information about a class.
* Can be retrieved from an object instance by using the
- * $(DDSUBLINK spec/property,classinfo, .classinfo) property.
+ * $(DDSUBLINK spec/expression,typeid_expressions,typeid expression).
*/
class TypeInfo_Class : TypeInfo
{
override string toString() const pure { return name; }
- override bool opEquals(Object o)
+ override bool opEquals(const TypeInfo o) const
{
if (this is o)
return true;
@@ -1739,12 +1885,8 @@ class TypeInfo_Struct : TypeInfo
return false;
else if (xopEquals)
{
- version (GNU)
- { // BUG: GDC and DMD use different calling conventions
- return (*xopEquals)(p2, p1);
- }
- else
- return (*xopEquals)(p1, p2);
+ const dg = _memberFunc(p2, xopEquals);
+ return dg.xopEquals(p1);
}
else if (p1 == p2)
return true;
@@ -1766,12 +1908,8 @@ class TypeInfo_Struct : TypeInfo
return true;
else if (xopCmp)
{
- version (GNU)
- { // BUG: GDC and DMD use different calling conventions
- return (*xopCmp)(p1, p2);
- }
- else
- return (*xopCmp)(p2, p1);
+ const dg = _memberFunc(p1, xopCmp);
+ return dg.xopCmp(p2);
}
else
// BUG: relies on the GC not moving objects
@@ -1876,6 +2014,28 @@ class TypeInfo_Struct : TypeInfo
TypeInfo m_arg2;
}
immutable(void)* m_RTInfo; // data for precise GC
+
+ // The xopEquals and xopCmp members are function pointers to member
+ // functions, which is not guaranteed to share the same ABI, as it is not
+ // known whether the `this` parameter is the first or second argument.
+ // This wrapper is to convert it to a delegate which will always pass the
+ // `this` parameter in the correct way.
+ private struct _memberFunc
+ {
+ union
+ {
+ struct // delegate
+ {
+ const void* ptr;
+ const void* funcptr;
+ }
+ @safe pure nothrow
+ {
+ bool delegate(in void*) xopEquals;
+ int delegate(in void*) xopCmp;
+ }
+ }
+ }
}
@system unittest
@@ -2715,7 +2875,7 @@ void clear(Value, Key)(Value[Key] aa)
_aaClear(*cast(AA *) &aa);
}
-/* ditto */
+/** ditto */
void clear(Value, Key)(Value[Key]* aa)
{
_aaClear(*cast(AA *) aa);
@@ -2762,21 +2922,21 @@ T rehash(T : Value[Key], Value, Key)(T aa)
return aa;
}
-/* ditto */
+/** ditto */
T rehash(T : Value[Key], Value, Key)(T* aa)
{
_aaRehash(cast(AA*)aa, typeid(Value[Key]));
return *aa;
}
-/* ditto */
+/** ditto */
T rehash(T : shared Value[Key], Value, Key)(T aa)
{
_aaRehash(cast(AA*)&aa, typeid(Value[Key]));
return aa;
}
-/* ditto */
+/** ditto */
T rehash(T : shared Value[Key], Value, Key)(T* aa)
{
_aaRehash(cast(AA*)aa, typeid(Value[Key]));
@@ -2784,8 +2944,8 @@ T rehash(T : shared Value[Key], Value, Key)(T* aa)
}
/***********************************
- * Create a new associative array of the same size and copy the contents of the
- * associative array into it.
+ * Creates a new associative array of the same size and copies the contents of
+ * the associative array into it.
* Params:
* aa = The associative array.
*/
@@ -2826,7 +2986,7 @@ V[K] dup(T : V[K], K, V)(T aa)
return result;
}
-/* ditto */
+/** ditto */
V[K] dup(T : V[K], K, V)(T* aa)
{
return (*aa).dup;
@@ -2853,11 +3013,27 @@ private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe
}
/***********************************
- * Returns a forward range over the keys of the associative array.
+ * Returns a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
+ * which will iterate over the keys of the associative array. The keys are
+ * returned by reference.
+ *
+ * If structural changes are made to the array (removing or adding keys), all
+ * ranges previously obtained through this function are invalidated. The
+ * following example program will dereference a null pointer:
+ *
+ *---
+ * import std.stdio : writeln;
+ *
+ * auto dict = ["k1": 1, "k2": 2];
+ * auto keyRange = dict.byKey;
+ * dict.clear;
+ * writeln(keyRange.front); // Segmentation fault
+ *---
+ *
* Params:
* aa = The associative array.
* Returns:
- * A forward range.
+ * A forward range referencing the keys of the associative array.
*/
auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
{
@@ -2880,7 +3056,7 @@ auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
return Result(_aaToRange(aa));
}
-/* ditto */
+/** ditto */
auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc
{
return (*aa).byKey();
@@ -2889,7 +3065,7 @@ auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc
///
@safe unittest
{
- auto dict = [1: 0, 2: 0];
+ auto dict = [1: "v1", 2: "v2"];
int sum;
foreach (v; dict.byKey)
sum += v;
@@ -2898,11 +3074,27 @@ auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc
}
/***********************************
- * Returns a forward range over the values of the associative array.
+ * Returns a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
+ * which will iterate over the values of the associative array. The values are
+ * returned by reference.
+ *
+ * If structural changes are made to the array (removing or adding keys), all
+ * ranges previously obtained through this function are invalidated. The
+ * following example program will dereference a null pointer:
+ *
+ *---
+ * import std.stdio : writeln;
+ *
+ * auto dict = ["k1": 1, "k2": 2];
+ * auto valueRange = dict.byValue;
+ * dict.clear;
+ * writeln(valueRange.front); // Segmentation fault
+ *---
+ *
* Params:
* aa = The associative array.
* Returns:
- * A forward range.
+ * A forward range referencing the values of the associative array.
*/
auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
{
@@ -2925,7 +3117,7 @@ auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
return Result(_aaToRange(aa));
}
-/* ditto */
+/** ditto */
auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc
{
return (*aa).byValue();
@@ -2943,11 +3135,35 @@ auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc
}
/***********************************
- * Returns a forward range over the key value pairs of the associative array.
+ * Returns a $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives)
+ * which will iterate over the key-value pairs of the associative array. The
+ * returned pairs are represented by an opaque type with `.key` and `.value`
+ * properties for accessing references to the key and value of the pair,
+ * respectively.
+ *
+ * If structural changes are made to the array (removing or adding keys), all
+ * ranges previously obtained through this function are invalidated. The
+ * following example program will dereference a null pointer:
+ *
+ *---
+ * import std.stdio : writeln;
+ *
+ * auto dict = ["k1": 1, "k2": 2];
+ * auto kvRange = dict.byKeyValue;
+ * dict.clear;
+ * writeln(kvRange.front.key, ": ", kvRange.front.value); // Segmentation fault
+ *---
+ *
+ * Note that this is a low-level interface to iterating over the associative
+ * array and is not compatible withth the
+ * $(LINK2 $(ROOT_DIR)phobos/std_typecons.html#.Tuple,`Tuple`) type in Phobos.
+ * For compatibility with `Tuple`, use
+ * $(LINK2 $(ROOT_DIR)phobos/std_array.html#.byPair,std.array.byPair) instead.
+ *
* Params:
* aa = The associative array.
* Returns:
- * A forward range.
+ * A forward range referencing the pairs of the associative array.
*/
auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
{
@@ -2987,7 +3203,7 @@ auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
return Result(_aaToRange(aa));
}
-/* ditto */
+/** ditto */
auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc
{
return (*aa).byKeyValue();
@@ -2999,18 +3215,21 @@ auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc
auto dict = ["k1": 1, "k2": 2];
int sum;
foreach (e; dict.byKeyValue)
+ {
+ assert(e.key[1] == e.value + '0');
sum += e.value;
+ }
assert(sum == 3);
}
/***********************************
- * Returns a dynamic array, the elements of which are the keys in the
- * associative array.
+ * Returns a newly allocated dynamic array containing a copy of the keys from
+ * the associative array.
* Params:
* aa = The associative array.
* Returns:
- * A dynamic array.
+ * A dynamic array containing a copy of the keys.
*/
Key[] keys(T : Value[Key], Value, Key)(T aa) @property
{
@@ -3028,7 +3247,7 @@ Key[] keys(T : Value[Key], Value, Key)(T aa) @property
return res;
}
-/* ditto */
+/** ditto */
Key[] keys(T : Value[Key], Value, Key)(T *aa) @property
{
return (*aa).keys;
@@ -3089,12 +3308,12 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property
}
/***********************************
- * Returns a dynamic array, the elements of which are the values in the
- * associative array.
+ * Returns a newly allocated dynamic array containing a copy of the values from
+ * the associative array.
* Params:
* aa = The associative array.
* Returns:
- * A dynamic array.
+ * A dynamic array containing a copy of the values.
*/
Value[] values(T : Value[Key], Value, Key)(T aa) @property
{
@@ -3112,7 +3331,7 @@ Value[] values(T : Value[Key], Value, Key)(T aa) @property
return res;
}
-/* ditto */
+/** ditto */
Value[] values(T : Value[Key], Value, Key)(T *aa) @property
{
return (*aa).values;
@@ -3188,12 +3407,13 @@ inout(V) get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue)
return p ? *p : defaultValue;
}
-/* ditto */
+/** ditto */
inout(V) get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue)
{
return (*aa).get(key, defaultValue);
}
+///
@safe unittest
{
auto aa = ["k1": 1];
@@ -4651,8 +4871,6 @@ public import core.internal.array.construction : _d_arrayctor;
public import core.internal.array.construction : _d_arraysetctor;
public import core.internal.array.capacity: _d_arraysetlengthTImpl;
-public import core.lifetime : _d_delstructImpl;
-
public import core.internal.dassert: _d_assert_fail;
public import core.internal.destruction: __ArrayDtor;
@@ -4664,6 +4882,9 @@ public import core.internal.postblit: __ArrayPostblit;
public import core.internal.switch_: __switch;
public import core.internal.switch_: __switch_error;
+public import core.lifetime : _d_delstructImpl;
+public import core.lifetime : _d_newThrowable;
+
public @trusted @nogc nothrow pure extern (C) void _d_delThrowable(scope Throwable);
// Compare class and interface objects for ordering.