Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Fix Issue 20178 - Add TypeInfo_Class/TypeInfo_Interface.isBaseOf
Browse files Browse the repository at this point in the history
Equivalent to C#/Java isAssignableFrom. Naming the method
"isAssignableFrom" would be more familiar to people coming from C#/Java
but is potentially misleading: "alias this" and overloadable opAssign
mean that this would not actually indicate whether values of one type
could be assigned to another.

Adding qualifiers to rt.cast_ functions.
  • Loading branch information
n8sh committed Mar 7, 2020
1 parent 90eca1f commit 7d20e09
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 3 deletions.
21 changes: 21 additions & 0 deletions changelog/isbaseof.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Added TypeInfo_Class/TypeInfo_Interface.isBaseOf that works like C#/Java isAssignableFrom.

`TypeInfo_Class.isBaseOf` returns true if the argument and the receiver
are equal or if the class represented by the argument inherits from the
class represented by the receiver. This is called `isBaseOf` instead of
`isAssignableFrom` to avoid confusion for classes that overload
`opAssign` and so may allow assignment from classes outside their
inheritance hierarchy and to match existing terminology in the D
runtime. `TypeInfo_Interface.isBaseOf` is similar with the addition
that the argument may be either `TypeInfo_Class` or
`TypeInfo_Interface`.
-------
class ClassA {}
class ClassB : ClassA {}

auto a = new ClassA(), b = new ClassB();

assert(typeid(a).isBaseOf(typeid(a)));
assert(typeid(a).isBaseOf(typeid(b)));
assert(!typeid(b).isBaseOf(typeid(a)));
-------
64 changes: 64 additions & 0 deletions src/object.d
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,8 @@ class TypeInfo_Delegate : TypeInfo
}

private extern (C) Object _d_newclass(const TypeInfo_Class ci);
private extern (C) int _d_isbaseof(scope TypeInfo_Class child,
scope const TypeInfo_Class parent) @nogc nothrow pure @safe; // rt.cast_

/**
* Runtime type information about a class.
Expand Down Expand Up @@ -1102,6 +1104,36 @@ class TypeInfo_Class : TypeInfo
}
return o;
}

/**
* Returns true if the class described by `child` derives from or is
* the class described by this `TypeInfo_Class`. Always returns false
* if the argument is null.
*
* Params:
* child = TypeInfo for some class
* Returns:
* true if the class described by `child` derives from or is the
* class described by this `TypeInfo_Class`.
*/
final bool isBaseOf(scope const TypeInfo_Class child) const @nogc nothrow pure @trusted
{
if (m_init.length)
{
// If this TypeInfo_Class represents an actual class we only need
// to check the child and its direct ancestors.
for (auto ti = cast() child; ti !is null; ti = ti.base)
if (ti is this)
return true;
return false;
}
else
{
// If this TypeInfo_Class is the .info field of a TypeInfo_Interface
// we also need to recursively check the child's interfaces.
return child !is null && _d_isbaseof(cast() child, this);
}
}
}

alias ClassInfo = TypeInfo_Class;
Expand Down Expand Up @@ -1191,6 +1223,38 @@ class TypeInfo_Interface : TypeInfo
override @property uint flags() nothrow pure const { return 1; }

TypeInfo_Class info;

/**
* Returns true if the class described by `child` derives from the
* interface described by this `TypeInfo_Interface`. Always returns
* false if the argument is null.
*
* Params:
* child = TypeInfo for some class
* Returns:
* true if the class described by `child` derives from the
* interface described by this `TypeInfo_Interface`.
*/
final bool isBaseOf(scope const TypeInfo_Class child) const @nogc nothrow pure @trusted
{
return child !is null && cast(bool) _d_isbaseof(cast() child, this.info);
}

/**
* Returns true if the interface described by `child` derives from
* or is the interface described by this `TypeInfo_Interface`.
* Always returns false if the argument is null.
*
* Params:
* child = TypeInfo for some interface
* Returns:
* true if the interface described by `child` derives from or is
* the interface described by this `TypeInfo_Interface`.
*/
final bool isBaseOf(scope const TypeInfo_Interface child) const @nogc nothrow pure @trusted
{
return child !is null && cast(bool) _d_isbaseof(cast() child.info, this.info);
}
}

class TypeInfo_Struct : TypeInfo
Expand Down
7 changes: 5 additions & 2 deletions src/rt/cast_.d
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
module rt.cast_;

extern (C):
@nogc:
nothrow:
pure:

/******************************************
* Given a pointer:
Expand Down Expand Up @@ -74,7 +77,7 @@ void* _d_dynamic_cast(Object o, ClassInfo c)
return res;
}

int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset)
int _d_isbaseof2(scope ClassInfo oc, scope const ClassInfo c, scope ref size_t offset) @safe
{
if (oc is c)
return true;
Expand All @@ -101,7 +104,7 @@ int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset)
return false;
}

int _d_isbaseof(ClassInfo oc, ClassInfo c)
int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) @safe
{
if (oc is c)
return true;
Expand Down
2 changes: 1 addition & 1 deletion test/typeinfo/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include ../common.mak

TESTS:=comparison
TESTS:=comparison isbaseof

.PHONY: all clean
all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS)))
Expand Down
43 changes: 43 additions & 0 deletions test/typeinfo/src/isbaseof.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// https://issues.dlang.org/show_bug.cgi?id=20178

interface I {}
interface J : I {}
class C1 : I {}
class C2 : C1 {}
class C3 : J {}

void main() @nogc nothrow pure @safe
{
assert(typeid(C1).isBaseOf(typeid(C1)));
assert(typeid(C1).isBaseOf(typeid(C2)));

assert(!typeid(C2).isBaseOf(typeid(C1)));
assert(typeid(C2).isBaseOf(typeid(C2)));

assert(!typeid(C1).isBaseOf(typeid(Object)));
assert(!typeid(C2).isBaseOf(typeid(Object)));
assert(typeid(Object).isBaseOf(typeid(C1)));
assert(typeid(Object).isBaseOf(typeid(C2)));

assert(typeid(I).isBaseOf(typeid(I)));
assert(typeid(I).isBaseOf(typeid(J)));
assert(typeid(I).isBaseOf(typeid(C1)));
assert(typeid(I).isBaseOf(typeid(C2)));
assert(typeid(I).isBaseOf(typeid(C3)));
assert(!typeid(I).isBaseOf(typeid(Object)));

assert(!typeid(J).isBaseOf(typeid(I)));
assert(typeid(J).isBaseOf(typeid(J)));
assert(!typeid(J).isBaseOf(typeid(C1)));
assert(!typeid(J).isBaseOf(typeid(C2)));
assert(typeid(J).isBaseOf(typeid(C3)));
assert(!typeid(J).isBaseOf(typeid(Object)));

// Because isBaseOf is final it currently doesn't automatically
// segfault when the receiver is null. Verify it doesn't return
// true when the receiver and argument are both null. (Segfaulting
// would also be an acceptable behavior.)
assert(!(cast(TypeInfo_Class) null).isBaseOf(null));
assert(!(cast(TypeInfo_Interface) null).isBaseOf(cast(TypeInfo_Interface) null));
assert(!(cast(TypeInfo_Interface) null).isBaseOf(cast(TypeInfo_Class) null));
}

0 comments on commit 7d20e09

Please sign in to comment.