Bug 14612 – typeid(interface) returns TypeInfo_Class object

Status
NEW
Severity
normal
Priority
P3
Component
dmd
Product
D
Version
D2
Platform
All
OS
All
Creation time
2015-05-20T13:49:42Z
Last change time
2024-12-13T18:42:57Z
Keywords
pull
Assigned to
No Owner
Creator
Kenji Hara
Moved to GitHub: dmd#18991 →

Comments

Comment #0 by k.hara.pg — 2015-05-20T13:49:42Z
In the following test code, typeid(i) does not return typeid(J) typed TypeInfo_Interface, rather it returns typeid(J).info that is TypeInfo_Class. import std.stdio; interface I {} interface J : I {} class C : J {} class D : C {} void main() { D d = new D(); Object o = cast(Object)d; I i = cast(I)d; writeln(typeid(d) is typeid(D)); // true writeln(typeid(o) is typeid(D)); // true writeln(typeid(i) is typeid(I)); // false ? writeln(typeid(i) is typeid(J)); // false ? writeln(typeid(i) is typeid(D)); // false ? writeln(typeid(i)); // prints 'test.J' !? // old exp.classinfo property is same with typeid(exp) writeln(d.classinfo is typeid(D)); // true writeln(o.classinfo is typeid(D)); // true writeln(i.classinfo is typeid(J)); // false, same with typeid() writeln(i.classinfo is typeid(I)); // false, same with typeid() writeln(i.classinfo is typeid(D)); // false, same with typeid() writeln( typeid(i) is typeid(J).info); // true writeln(i.classinfo is typeid(J).info); // true } ---- This nonintuitive behavior comes historically from D1. In D1, ClassInfo, TypeInfo_Class, and TypeInfo_Interface are different classes. In there, ClassInfo had had the information of class *and* interface definition in runtime, and two TypeInfo-s were purely identity objects that associated with the class/interface types. In D2, ClassInfo and TypeInfo_Class were merged into one (by issue 3380 ?). Now ClassInfo is an alias name of TypeInfo_Class. But, the separation between ClassInfo and TypeInfo_Interface is still there. And currently, typeid(i) is completely same with i.classinfo, therefore it still returns ClassInfo == TypeInfo_Class. ----- I think there's two ways to fix the behavior. 1. To make TypeInfo_Interface accessible from runtime interface references. To do that, change object.Interface struct as follows: struct Interface { version(none) // old { TypeInfo_Class classinfo; } else // new { TypeInfo_Interface tinfo; @property TypeInfo_Class classinfo() { return tinfo.info; } } void*[] vtbl; size_t offset; } and then the interface vtb[0] will be the runtime TypeInfo_Interface that we needing. Pros: We need very little compiler and druntime changes. Cons: It would add a small runtime cost in the dynamic cast. See: druntime/src/rt/cast_.d 2. Separate ClassInfo from TypeInfo_Class again and define TypeInfo hierarchy as follows: abstract class ClassInfo : TypeInfo { ... } class TypeInfo_Class : ClassInfo { ... } class TypeInfo_Interface : ClassInfo { ... } Pros: By handling ClassInfo, the runtime cost in dynamic cast operation can be kept same with today's. Cons: If an user still using the name 'ClassInfo', the code behavior will be changed.
Comment #1 by k.hara.pg — 2015-06-02T12:43:55Z
(In reply to Kenji Hara from comment #0) > 2. Separate ClassInfo from TypeInfo_Class again and define TypeInfo > hierarchy as follows: > > abstract class ClassInfo : TypeInfo { ... } > class TypeInfo_Class : ClassInfo { ... } > class TypeInfo_Interface : ClassInfo { ... } > > Pros: By handling ClassInfo, the runtime cost in dynamic cast operation can > be kept same with today's. > Cons: If an user still using the name 'ClassInfo', the code behavior will be > changed. Implemented: https://github.com/D-Programming-Language/dmd/pull/4711 https://github.com/D-Programming-Language/druntime/pull/1295
Comment #2 by r.sagitario — 2015-07-05T17:20:33Z
The language spec says for both typeid() and .classinfo that the dynamic type is returned. For classinfo, it says ".classinfo applied to an interface gives the information for the interface, not the class it might be an instance of." which makes it a compile time property for interfaces. I could not find a spec for typeid regarding interfaces. I suspect they are supposed to be the same. That means typeid(i) and i.classinfo should evaluate to typeid(I). This must be generated by the compiler. Actually I think the semantics are pretty arbitrary and not intuitive. Why would anyone expect J to be the correct type? I'd rather prefer if both classes and interfaces would return the dynamic type, i.e. typeid(D). You can get the static type of an expression by using typeid(typeof(i)).
Comment #3 by k.hara.pg — 2015-07-09T11:03:06Z
(In reply to Rainer Schuetze from comment #2) > The language spec says for both typeid() and .classinfo that the dynamic > type is returned. For classinfo, it says ".classinfo applied to an interface > gives the information for the interface, not the class it might be an > instance of." which makes it a compile time property for interfaces. I could > not find a spec for typeid regarding interfaces. > > I suspect they are supposed to be the same. That means typeid(i) and > i.classinfo should evaluate to typeid(I). This must be generated by the > compiler. It's correct. typeid(obj) and obj.classinfo are just same thing in implementation level. > Actually I think the semantics are pretty arbitrary and not intuitive. Why > would anyone expect J to be the correct type? I'd rather prefer if both > classes and interfaces would return the dynamic type, i.e. typeid(D). You > can get the static type of an expression by using typeid(typeof(i)). I don't know why so, but I guess it might be the dynamic cast cost. The current behavior, getting actual interface information (== classinfo) can be done with a trivial pointer (== class/interface reference) adjustment. On the other hand, a dynamic cast from interface to class needs more cost. 1. getting the interface information 2. downcast and get Object reference from the interface reference by using the information. 3. get the actual typeinfo from the Object reference. And you can do the latter behavior by typeid(cast(Object)obj). I feel that syntactic cost is enough small. In short, the orthogonality may have been priority than the intuitive, but complex behavior.
Comment #4 by r.sagitario — 2015-07-10T06:27:38Z
> And you can do the latter behavior by typeid(cast(Object)obj). > I feel that syntactic cost is enough small. cast(Object) creates a call into the runtime (_d_interface_cast) and is pretty expensive. It could be optimized to the series of indirections by the compiler, though. > In short, the orthogonality may have been priority than the intuitive, but complex behavior. The "complex" behaviour costs just an additional reading of an offset to the object base address, very much what a thunk does when calling a virtual function. I guess the current behaviour could be described as "typeid(I) evaluates to the most derived interface that is implemented by the object and that inherits from the static type." But "most derived" is not well defined when it comes to inheriting from multiple interfaces: import std.stdio; interface I {} interface J : I {} interface K : I {} class E : J, K {} void main() { E e = new E; J j = e; K k = e; I ij = j; I ik = k; writeln(typeid(ij)); // prints 'test.J' !? writeln(typeid(ik)); // prints 'test.K' !? }
Comment #5 by k.hara.pg — 2015-07-21T06:45:33Z
(In reply to Rainer Schuetze from comment #4) > cast(Object) creates a call into the runtime (_d_interface_cast) and is > pretty expensive. It could be optimized to the series of indirections by the > compiler, though. Actually _d_interface_cast is not so high cost so Object has no base class and implements no interfaces. > I guess the current behaviour could be described as "typeid(I) evaluates to > the most derived interface that is implemented by the object and that > inherits from the static type." > > But "most derived" is not well defined when it comes to inheriting from > multiple interfaces: It would be visible when you consider the class object layout. > interface I {} > interface J : I {} > interface K : I {} > class E : J, K {} // I I // E : J, K //=============== // ij ik // | | // j k // | | // e --[x]-[y] [x] is the "most derived vptr slot of ij, and [y] is of ik.
Comment #6 by k.hara.pg — 2015-07-21T06:54:42Z
Anyway, the downcast behavior is not a part of the suggested issue. It should go an enhancement report.
Comment #7 by k.hara.pg — 2015-07-21T07:05:27Z
(In reply to Rainer Schuetze from comment #4) > But "most derived" is not well defined when it comes to inheriting from > multiple interfaces: I found an existing report: Issue 13833 - .classinfo.name (and typeid(obj)) does not print proper dynamic type when using an interface
Comment #8 by r.sagitario — 2015-07-22T17:18:38Z
> Actually _d_interface_cast is not so high cost so Object has no > base class and implements no interfaces. It's still a lot more than just a null check and reading a vtbl entry an another indirection. If the instance is from a deeply derived class, _d_interface_cast walks through all base classes to find Object.
Comment #9 by r.sagitario — 2015-07-22T17:20:46Z
> [x] is the "most derived vptr slot of ij, and [y] is of ik. But if all you have is a pointer to I that's not very deterministic.
Comment #10 by k.hara.pg — 2015-08-03T02:10:00Z
I found slightly related issue tickets: Issue 4979 - Implementing an interface twice results in different a reference for each interface Issue 11683 - Document current Identity Expression over `interface`s behaviour
Comment #11 by robert.schadek — 2024-12-13T18:42:57Z
THIS ISSUE HAS BEEN MOVED TO GITHUB https://github.com/dlang/dmd/issues/18991 DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB