digitalmars.D.bugs - [Bug 179] New: Covariance screws up when overriding two interfaces or a class and an interface
- d-bugmail puremagic.com (84/84) Jun 07 2006 http://d.puremagic.com/bugzilla/show_bug.cgi?id=179
- d-bugmail puremagic.com (77/77) Jun 11 2006 http://d.puremagic.com/bugzilla/show_bug.cgi?id=179
http://d.puremagic.com/bugzilla/show_bug.cgi?id=179 Summary: Covariance screws up when overriding two interfaces or a class and an interface Product: D Version: 0.160 Platform: PC OS/Version: Windows Status: NEW Keywords: wrong-code Severity: normal Priority: P2 Component: DMD AssignedTo: bugzilla digitalmars.com ReportedBy: smjg iname.com If a class derives from two interfaces, or a class and an interface, and they have different expectations for the return type of some method, then covariance screws up. ---------- import std.stdio; interface Father { Father test(); void showData(); } class Mother { int data; this(int d) { data = d; } Mother test() { return new Child(102); } void showData() { writefln("Mother: %d", data); } } class Child : Mother, Father { this(int d) { super(d); } Child test() { return new Child(69); } void showData() { writefln("Child: %d", data); } } void main() { Child aChild = new Child(42); aChild.showData(); Mother childsMum = aChild; childsMum.showData(); Father childsDad = aChild; childsDad.showData(); writefln("entering childsMum.test.showData"); childsMum.test.showData(); writefln("exited childsMum.test.showData"); Father dadTest = childsDad.test; writefln("entering dadTest.showData"); dadTest.showData(); writefln("entering (cast(Child) dadTest).showData"); (cast(Child) dadTest).showData(); } ---------- Output: Child: 42 Child: 42 Child: 42 entering childsMum.test.showData exited childsMum.test.showData entering dadTest.showData entering (cast(Child) dadTest).showData Error: Access Violation So the penultimate showData call does nothing, and the final one throws the AV. The same happens if I switch the return types around. A similar example, in which both Father and Mother are interfaces, shows the same problem. I can see that this would be more complicated to get to work, as Father expects a Father interface reference and Mother expects an object reference for the same function - and Child.test cannot be directly compatible with both at the same time. A possible solution is for the compiler to generate two versions of Child.test. First it will generate one that returns an object reference as written, which will be used by the vtbls for Mother and Child. Then, it will generate a wrapper that converts the return from Child.test to a Father reference, which will be used in Father's vtbl. More generally, if the compiler detects a scenario like this, it would generate the function to return in the locally declared return type, and a wrapper for each interface that it needs to convert to. --
Jun 07 2006
http://d.puremagic.com/bugzilla/show_bug.cgi?id=179 In your example, one can remove the Mother class, and the bug still persists, like this: ---------------------------------- import std.stdio; interface Father { Father test(); void showData(); } class Child : Father { int data; this(int d) { data = d; } Child test() { writefln("in CovFunc Test"); return new Child(69); } void showData() { writefln("Child: %d", data); } } void icov2test() { Child aChild = new Child(42); aChild.showData(); Father childsDad = aChild; childsDad.showData(); Father dadTest = childsDad.test; writefln("FCALL dadTest.showData"); dadTest.showData(); writefln("FCALL dadTest.test"); dadTest.test(); writefln("FCALL (cast(Child) dadTest).showData"); (cast(Child) dadTest).showData(); } ---------------------------------- Here is an even shorter version: (yes ICov and IFoo could be the same) ---------------------------------- import std.stdio; interface IFoo { } interface ICov { IFoo test(); } class Child : ICov, IFoo { Child covfunc() { writefln("in CovFunc Test"); return new Child(); } } void icov3() { ICov icov = new Child(); IFoo ifoo = icov.covfunc(); writefln(ifoo.classinfo.name); //Segfault or prints garbage writefln((cast(Object)ifoo)); //Segfault or prints garbage } ---------------------------------- Regardless of all that, it is still quite true what you about "Father expects a Father interface reference and Mother expects an object reference for the same function - and Child.test cannot be directly compatible with both at the same time." I found this feature (allowing class-covariant-with-interface return types strange since the beggining when it was introduced. Now I'm thinking if this is feasable at all. You see, a class is *not* covariant with an (implemented) interface (meaning, "one can [not] substitute an interface with a class"), they are merely convertible to one another. Thus a function with a class return type is not covariant with a function with an interface return type, meaning: one can not substitute a interface-return-function with a class-return-function. The same doesn't happen for truly covariant return types (i.e. with Object -- Foo, one can substitute the functions). This is confirmable by looking at the asm code. DMD goes around this with some adaptions, doing transparent conversions, but I wonder if it is possible (or desirable) to make this work with all corner cases. --
Jun 11 2006