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.
Comment #1 by bruno.do.medeiros+deebugz — 2006-06-11T16:55:58Z
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.
Comment #2 by smjg — 2006-06-12T16:37:12Z
What compiler version and OS are you using exactly? The first testcase looks like one I thought was fixed. Can you still reproduce bug 65, from either the code posted there or the associated DStress testcases?
Comment #3 by lio+bugzilla — 2006-06-13T02:06:26Z
(In reply to comment #1)
Bruno, your second program doesn't compile:
t.d(21): no property 'covfunc' for type 't.ICov'
t.d(21): function expected before (), not 1 of type int
t.d(21): cannot implicitly convert expression (1()) of type int to t.IFoo
(Digital Mars D Compiler v0.160)
Comment #4 by bruno.do.medeiros+deebugz — 2006-06-13T07:45:24Z
(In reply to comment #3)
> (In reply to comment #1)
> Bruno, your second program doesn't compile:
That's right, damn, forgot to check the second program. ICov should be:
interface ICov {
IFoo covfunc();
}
Comment #5 by bruno.do.medeiros+deebugz — 2006-06-13T08:00:33Z
(In reply to comment #2)
> What compiler version and OS are you using exactly? The first testcase looks
> like one I thought was fixed. Can you still reproduce bug 65, from either the
> code posted there or the associated DStress testcases?
WinXP and the compiler is the latest of course (0.160). Bug 65 testcases run fine (no bugs), both yours, and the DStress ones.
How about you, what do you get running my first or second program?
This does look similar to bug 65, but the difference here is that the covariant parent (ICov) is an interface and not a class. Change ICov to a class, and the test runs fine.
Comment #6 by smjg — 2006-06-13T08:15:14Z
(In reply to comment #5)
> (In reply to comment #2)
>> What compiler version and OS are you using exactly? The first
>> testcase looks like one I thought was fixed. Can you still
>> reproduce bug 65, from either the code posted there or the
>> associated DStress testcases?
>
> WinXP and the compiler is the latest of course (0.160). Bug 65
> testcases run fine (no bugs), both yours, and the DStress ones.
> How about you, what do you get running my first or second program?
> This does look similar to bug 65, but the difference here is that
> the covariant parent (ICov) is an interface and not a class.
> Change ICov to a class, and the test runs fine.
Actually, it was meant to be part of bug 65, it was just
inadvertently omitted from the testcases.
I will take your code home and test it though.
Comment #7 by smjg — 2006-06-19T10:20:37Z
The cases given in comment 1 aren't of multiple types to override as is the essence of this bug. Rather, it's something else that needs to be dealt with first. I've filed issue 210 to take hold of it.
Comment #8 by bugzilla — 2006-06-30T20:27:14Z
Fixed DMD 0.162
(First example now gives correct error message, following two work.)