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.isDerivedFrom
Browse files Browse the repository at this point in the history
Equivalent to C#/Java isAssignableFrom with the argument swapped with
the receiver. 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.
  • Loading branch information
n8sh committed Aug 30, 2019
1 parent a2ee5cc commit d567a8b
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 1 deletion.
70 changes: 70 additions & 0 deletions src/object.d
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,61 @@ class TypeInfo_Class : TypeInfo
}
return o;
}

/**
* Returns true if the class described by this TypeInfo_Class derives from
* the interface described by `parent`.
*
* Params:
* parent = TypeInfo for some interface
* Returns:
* true if the class described by this TypeInfo_Class derives from the
* interface described by `parent`, false otherwise.
*/
bool isDerivedFrom(scope const TypeInfo_Interface parent) const @nogc nothrow pure @safe
{
return parent !is null && interfaceConvertibleFrom(parent.info, this);
}

/**
* Returns true if the class described by this TypeInfo_Class derives from
* the class described by `parent`.
*
* Params:
* parent = TypeInfo for some class
* Returns:
* true if the class described by this TypeInfo_Class derives from the
* class described by `parent`, false otherwise.
*/
bool isDerivedFrom(scope const TypeInfo_Class parent) const @nogc nothrow pure @safe
{
if (parent is null)
return false;
if (parent is this || parent.name == this.name)
return true;
return this.base !is null && this.base.isDerivedFrom(parent);
}

/*
* Params:
* interfaceInfo = the classinfo of the maybe-parent interface
* ti = the classinfo of the maybe-child interface
* Returns:
* true if `ti` is derived from `interfaceInfo`, false otherwise
*/
private static bool interfaceConvertibleFrom(scope const TypeInfo_Class interfaceInfo,
scope const TypeInfo_Class ti) @nogc nothrow pure @safe
{
if (interfaceInfo is ti || interfaceInfo.name == ti.name)
return true;
foreach (interface_; ti.interfaces)
if (interface_.classinfo is interfaceInfo || interface_.classinfo.name == interfaceInfo.name)
return true;
foreach (interface_; ti.interfaces)
if (interfaceConvertibleFrom(interfaceInfo, interface_.classinfo))
return true;
return ti.base is null ? false : interfaceConvertibleFrom(interfaceInfo, ti.base);
}
}

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

TypeInfo_Class info;

/**
* Returns true if the interface described by this TypeInfo_Interface
* derives from the interface described by `parent`.
*
* Params:
* parent = TypeInfo for some interface
* Returns:
* true if the interface described by this TypeInfo_Interface derives from
* the interface described by `parent`, false otherwise.
*/
bool isDerivedFrom(scope const TypeInfo_Interface parent) const @nogc nothrow pure @safe
{
return parent !is null && TypeInfo_Class.interfaceConvertibleFrom(parent.info, this.info);
}
}

class TypeInfo_Struct : TypeInfo
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 isderivedfrom

.PHONY: all clean
all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS)))
Expand Down
46 changes: 46 additions & 0 deletions test/typeinfo/src/isderivedfrom.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 {}
class C1 : I {}
class C2 : C1 {}
class C3 : J {}

// It might seem sensible to give TypeInfo_Class & TypeInfo_Interface
// a method called "isAssignableFrom" instead of "isDerivedFrom" since
// that matches C# and Java. However, the existences of "alias this" and
// overloadable "opAssign" mean that that this would not actually indicate
// whether arguments of the argument type could be assigned to a field
// whose value is the receiver's type.
bool isAssignableFrom(T,U)(T parent, U child)
{
return child.isDerivedFrom(parent);
}

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

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

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

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

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

0 comments on commit d567a8b

Please sign in to comment.