Here we propose a syntax and semantics for subtyping…
Consider the following JPIs with the stated subtyping relationship:
jpi void A(Object o); jpi void B(Object a, Object b) extends A(b); //extend A, equating b with o above
The semantics is here that B is a special kind of A that exposes more context: the additional parameter a
.
Now consider an advice advising joinpoints of type A:
void around A(Object s) { //do something proceed("foo"); }
If this advice received a joinpoint of type B as an input this has the semantics that s
will be bound to the second argument of the B joinpoint. Calling proceed(s)
will bind this second argument to the String foo, but leave the first argument, named a
in the declaration of B, unchanged! In other words, if an advice operates on the “super-type view” of a joinpoint then it can read and replace only those parameters of the joinpoint that the super type exposes.
We cannot allow covariant return types or contravariant argument types because of the same signature is currently being used for calling (1) the around advice and (2) calling proceed
within the around advice. For (1) we can allow only covariant return types and contravariant argument types and for (2) we can conversely allow contravariant return types and covariant argument types. Combining both, we can see that we need invariant return and argument types.
The following shows an example test case for return types in CJPs:
import java.util.*; public aspect CheckReturnTypesIncorrect { static void wrongActualReturnType() { Set s = exhibit JPHashSet { return new Object(); //Error: Object is no HashSet }; } static void wrongDeclaredReturnType() { HashSet s = exhibit JPSet { //Error: Set is no HashSet return new HashSet(); }; } public static void main(String args[]) { wrongActualReturnType(); wrongDeclaredReturnType(); } joinpoint Set JPSet(); joinpoint HashSet JPHashSet(); }