Let's consider only example 3 (which is clearly buggy. In that example, T<L> has the following supertypes:
while S<L> has the following supertypes
In our example we have a cast from a type S (raw) to a type T (raw). Since the target type is an interface, the rule to be used for cast is the folowing (from JLS 5.5):
"If [source] is a class type:
* If [target] is an interface type:
o If [source] is not a final class (??8.1.1), then, if there exists a supertype X of [target], and a supertype Y of [source], such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs. Otherwise, the cast is always legal at compile time (because even if [source] does not implement [target], a subclass of S might)."
At first it may seems that a compiler error should be raised since there are indeed two provably distinct supertypes of T<L> and S<L>, namely Super<Double> and Super<Integer>. But in the example the target type is a raw type so JLS 4.8 applies
"The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of its parameterized invocations."
The supertypes of the target type T can be rewritten as follows:
it can be seen how, after we pick the correct supertypes for the raw T, the conflict between Super<Integer> and Super<Double> simply disappear. So this code should indeed compile.
The problem with this code is due to the fact that javac exploits a lazy scheme for supertypes resolution. The decision whether the supertype should be erased or not is based upon the current type being a raw type or not. So javac thinks the correct supertype list for the raw T is:
*) X (erasure of X is X)
*) Super<Double> (no erasure here since we are computing the supertype of X that is not raw)!
Since javac resolves supertypes in a lazy fashion, the only way to fix this bag is to mark a supertype X of a given raw type in such a way that when javac needs to compute X's supertype, the erased version of such supertype is used.
The behavior should be as follows:
the supertype of T is the erasure of T's supertype if one of the following holds
a) T is a raw type
b) T is a supertype of a raw type R
However, detecting b) is not trivial, since when we are computing T's supertype we don't know anything about R (R is a *subtype* of T, so it cannot be accessed by simply browsing T). This is why we need to keep this information in a variable of ClassType.