www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Bug 179] New: Covariance screws up when overriding two interfaces or a class and an interface

reply d-bugmail puremagic.com writes:
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
parent d-bugmail puremagic.com writes:
http://d.puremagic.com/bugzilla/show_bug.cgi?id=179





------- Comment #1 from daiphoenix lycos.com  2006-06-11 16:55 -------
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