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

Give TypeInfo_Class/TypeInfo_Interface.isBaseOf like C#/Java isAssignableFrom #2770

Merged
merged 1 commit into from
Apr 16, 2020
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
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 @@ -960,6 +960,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 @@ -1103,6 +1105,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 @@ -1192,6 +1224,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 && _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 && _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:
Copy link
Member Author

@n8sh n8sh Aug 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding some attributes here so the compiler enforces the same attributes I claim in the private extern(C) declaration in object.d.


/******************************************
* 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto recursion

{
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use tail recursion and make this const, in order to avoid the cast.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not mess with the implementation of _d_isbaseof in this PR.

{
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
46 changes: 46 additions & 0 deletions test/typeinfo/src/isbaseof.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// https://issues.dlang.org/show_bug.cgi?id=20178

interface I {}
interface J : I {}
interface K(T) {}
class C1 : I {}
class C2 : C1 {}
class C3 : J {}
class C4(T) : C3, K!T {}
class C5(T) : C4!T {}

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)));

assert(typeid(C4!int).isBaseOf(typeid(C5!int)));
assert(typeid(K!int).isBaseOf(typeid(C5!int)));
assert(!typeid(C4!Object).isBaseOf(typeid(C5!int)));
assert(!typeid(K!Object).isBaseOf(typeid(C5!int)));

static assert(!__traits(compiles, TypeInfo.init.isBaseOf(typeid(C1))));
static assert(!__traits(compiles, typeid(C1).isBaseOf(TypeInfo.init)));
}