§4.9.3.(c) Covariant replace binding

When using the syntax of §4.9.3.(b) to capture base methods with covariant return types in a callin binding with the replace modifier, the role method must be specified using a free type parameter as follows:

<E extends RT> E rm() <- replace RT+ bm();

The role method rm referenced by this callin binding must use the same style of return type using a type parameter. The only possible non-null value of type E to be returned from such method is the value provided by a base-call or a tsuper-call.
This rule enforces the constraint (2) above.
Note that this rule is further generalized in §4.10.

Binding a parametric role method
1
public class SuperBase {
2
    SuperBase foo() { return this; }
3
    void check() { System.out.print("OK"); }
4
}
5
public class SubBase extends SuperBase {
6
    @Override
7
    SubBase foo() { return this; }
8
    void print() { System.out.print("SubBase"); }
9
    String test() { 
10
        this.foo().print(); // print() requires a SubBase
11
    }
12
}
13
14
public team class MyTeam {
15
    protected class R playedBy SuperBase {
16
        callin <E extends SuperBase> E ci() {
17
			E result= base.ci();
18
			result.check(); // check() is available on E via type bound SuperBase
19
			return result;
20
        }
21
        <E extends SuperBase> E  ci() <- replace SuperBase+ foo();
22
    }
23
}
Explanation:
  • Method SubBase.foo in line 7 redefines the return type from SuperBase (inherited version) to SubBase, thus clients like the method call in line 10 must be safe to assume that the return value will always conform to SubBase.
  • The callin binding in line 21 explicitly captures both versions of foo by specifying SuperBase+ as the expected return type. Thus, if an instance of MyTeam is active at the method call in line 10, this call to foo will indeed be intercepted even though this call is statically known to return a value of type SubBase.
  • The callin method in lines 16-20 has a return type which is not known statically, but the return type is represented by the type variable E. Since the base call is known to have the exact same signature as its enclosing method, the value provided by the base call is of the same type E and thus can be safely returned from ci. Note, that no other non-null value is known to have the type E.
  • By specifying SuperBase as an upper bound for the type E the callin method ci may invoke any method declared in type SuperBase on any value of type E. For an example see the call to check in line 18.

As an aside note that the above example uses type SuperBase in an undisciplined way: within role R this type is bound using playedBy and the same type is also used directly (as the upper bound for E). This is considered bad style and it is prohibited if SuperBase is imported using an base import (§2.1.2.(d)). Here this rule is neglegted just for the purpose of keeping the example small.