A VM perspective:

*invocation*
        
*dynamic receiver*
        
*resolution*
*NOT invoked*
        
*selection:*
*actual execution*
invokevirtual D::m(LDT)
        
D
        
D.m(LDT)
        
D.m(LDT)
invokevirtual D::m(LDT)
        
E
        
D.m(LDT)
        
E.m(LDT)
reverser: adapt LDT->Date
  invoke local E.m(Date)
               if return had changed, adapt return back
invokevirtual D::m(Date)
        
D
        
D.m(Date)
        
D.m(Date)
forwarder: adapt Date->LDT
                 invoke local m(LDT)
                 if return had changed, adapt
invokevirtual D.m(Date)
        
E
        
D.m(Date)
        
E.m(Date)
invokevirtual E.m(LDT)
        
E
        
E.m(LDT)
reverser)
        
E.m(LDT):
reverser: adapt LDT->Date
invoke local E.m(Date)
               if return had changed, adapt return back
invokevirtual E.m(Date)
        
E
        
E.m(Date)
        
E.m(Date) // original - unchanged behavior



Let me try from the other direction, using the JVMS terminology rather than appealing to as-if.  Where I think we're saying slightly different things is in the interpretation of the lines I colored in blue above (hope the formatting came through.)  You are talking about the E.m(Date) that appears in E.class (good so far).  But I'm talking about the _members_ of E.  And the E.m(Date) that appears in E.class should _not_ be considered a (new-to-E) member of E. Instead, that E.m(Date) gives rise to a synthetic member E.m(LDT). I have colored two cases in red because I think this is where our assumptions really parted ways; will come back to this at the bottom.

Here's why I'm harping on this distinction; we mark methods as "forwarders" and do something special when we see something override a forwarder.  Taking the same hierarchy:

    // before
    class D {
        void m(Date) { }
    }

    class E extends D {
        void m(Date) { }
    }

    // middle -- D migrates, but E not yet
    class D {
        void m(LDT) { }
        @Forwarding( m(LDT) } void m(Date);
    }

    class E extends D {
        void m(Date) { }
    }

    // after -- E finally gets the memo
    class D {
        void m(LDT) { }
        @Forwarding( m(LDT) } void m(Date);
    }

    class E extends D {
        void m(LDT) { }
    }

Now, let's draw inheritance diagrams (these are not vtables, they are member tables).  I'll use your notation, where I think D.m(X) means "the Code attribute declaredin D for m(X)".

Before
        m(Date)
D
        D.m(Date)
E
        E.m(Date)


This part is easy; D has m(Date), and E overrides it.

Middle
        m(LDT)
        m(Date)
D
        D.m(LDT)
        forwarder -> m(LDT)
E
        reverser adapted from E.m(Date)
        inherits forwarder


Now, both D and E have both m(Date) and m(LDT).  D has a real method for m(LDT), and a forwarder for m(Date).  E has an m(Date), which we see overrides a forwarder.  So we adapt it to be an m(LDT), but we consider E to have inherited the forwarder from D.  I'll come back to this in a minute.

After   m(LDT)
        m(Date)
D
        D.m(LDT)
        forwarder -> m(LDT)
E
        E.m(LDT)
        inherits forwarder


In this nirvana, there is a forwarder still, but it doesn't affect E, because E has already gotten the memo.  It sits around purely in the case that someone calls m(Date).

OK, so why am I saying that membership has to be tilted this way? Let's go back to the middle case, and add

    class F extends E {
        void m(Date) { } // still didn't get the memo
    }


Middle
        m(LDT)
        m(Date)
D
        D.m(LDT)
        forwarder -> m(LDT)
E
        reverser adapted from E.m(Date)
        inherits forwarder
F
        reverser adapted from F.m(Date)
        inherits forwarder


When we go to compute members, I want to see that _F.m(Date) overrides a forwarder too_.  If we merely put E.m(Date) in the (E, m(Date)) box, then it looks like F is overriding an ordinary member, and no reverser is generated.  (Or, we have to keep walking up the chain to see if E.m(Date) in turn overrides a forwarder -- yuck, plus, that makes forwarder-overrides-forwarder even messier.

Now, back to your table.  The above interpretation of what is going on comes to the same answer for all of the rows of your table, except these:


*invocation*
        
*dynamic receiver*
        
*resolution*
*NOT invoked*
        
*selection:*
*actual execution*
invokevirtual D.m(Date)
        
E
        
D.m(Date)
        
E.m(Date)
invokevirtual E.m(Date)
        
E
        
E.m(Date)
        
E.m(Date) // original - unchanged behavior



You are thinking "E has a perfectly good m(Date), let's just select that".  Makes sense, but the cost of that is that it complicates calculation of membership and overriding.  I think I am content to let invocations of m(Date) on receivers of type E go through both rounds of adaptation: forward the call (with adaptation) to m(LDT), which, in the case of E, does the reverse adaptations and ends up at the original Code attribute of E.m(Date).  This sounds ugly (and we'd need to justify some potential failures) but leads us to a simpler interpretation of migration.

In your model, we basically have to split the box in two:


After   m(LDT)
        m(Date)
D
        D.m(LDT)
        forwarder -> m(LDT)
E
        E.m(LDT)
        E.m(Date), but also is viewed as a forwarder by subclasses


I think its a good goal, but I was trying to eliminate that complexity by accepting the round-trip adaptation -- which goes away when E gets the memo.


Reply via email to