- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
can one code base extend classes that have become generic in Java 7 and still compile on Java 6?
Thu, 2011-08-18, 17:34
The goal: use the same Scala source code when compiling on Java 6 and 7, *while leveraging the increased type safety offered by newly generic classes in Java 7*
The concrete source code I'm looking at is Scala Swing. As it stands, we need to maintain two code bases, which differ slightly and in a regular way.
Can we reduce this redundancy with some compiler magic?
Since this is not a soap opera, no cliffhangers here: the answer is "probably not".
Concretely:
// this snippet from Scala Swing has the right types and compiles on 1.7def newConstantModel[A](items: Seq[A]): ComboBoxModel[A] = new AbstractListModel[A] with ComboBoxModel[A] { def getElementAt(n: Int): A = items(n) // [other methods omitted]}
How can we make this compile as-is on 1.6?
Well, first the compiler should ignore type arguments passed to classes that weren't yet generic in 1.6, such as ComboBoxModel, pretending we wrote ComboBoxModel instead of ComboBoxModel[A].
The idea is to require annotating `ComboBoxModel[A]` with @uncheckedTypeApply to make this explicit.
Unfortunately, overriding is also affected. Once erroneous type applications are out of the way,we hit the following snag when compiling on 1.6:
// error: overriding method getElementAt in trait ListModel of type (x$1: Int)java.lang.Object; // method getElementAt has incompatible type
So, in addition to forgetting type applications to non-generic classes, the compiler would have to remember that we inherited from such a schizophrenic type, and be more flexible when checking overriding of its members. This is probably too much to ask to get for free.
More specifically, while checking overriding, we want matching to also consider erased signatures. This is currently already done for inherited concrete Java methods in javaErasedOverridingSym.
// Find a concrete Java method that overrides `sym' under the erasure model. // Bridge symbols qualify. // Used as a fall back if no overriding symbol of a Java abstract method can be found def javaErasedOverridingSym(sym: Symbol): Symbol = clazz.tpe.nonPrivateMemberAdmitting(sym.name, BRIDGE).filter(other => !other.isDeferred && other.isJavaDefined && {
We could do something similar for members annotated as follows: `@erasedOverriding def getElementAt(n: Int): A = items(n)`
Note that we can't use bridge methods here, since the signatures of the two methods we need are identical after erasure.
The final problem that I encountered in our Scala Swing code base, and the real killer, I think, is related to the definition of matching, which is intrinsic to the definition of the set of members of a type (computed by findMember). We'd need matching to take erasure into account... That dependency probably isn't acceptable, even if it's shielded by an annotation (extreme performance hotspot, for one!)
That said, feel free to check out my experiment at https://github.com/adriaanm/scala-dev/tree/openjdk_experiment_erasedoverride
The concrete source code I'm looking at is Scala Swing. As it stands, we need to maintain two code bases, which differ slightly and in a regular way.
Can we reduce this redundancy with some compiler magic?
Since this is not a soap opera, no cliffhangers here: the answer is "probably not".
Concretely:
// this snippet from Scala Swing has the right types and compiles on 1.7def newConstantModel[A](items: Seq[A]): ComboBoxModel[A] = new AbstractListModel[A] with ComboBoxModel[A] { def getElementAt(n: Int): A = items(n) // [other methods omitted]}
How can we make this compile as-is on 1.6?
Well, first the compiler should ignore type arguments passed to classes that weren't yet generic in 1.6, such as ComboBoxModel, pretending we wrote ComboBoxModel instead of ComboBoxModel[A].
The idea is to require annotating `ComboBoxModel[A]` with @uncheckedTypeApply to make this explicit.
Unfortunately, overriding is also affected. Once erroneous type applications are out of the way,we hit the following snag when compiling on 1.6:
// error: overriding method getElementAt in trait ListModel of type (x$1: Int)java.lang.Object; // method getElementAt has incompatible type
So, in addition to forgetting type applications to non-generic classes, the compiler would have to remember that we inherited from such a schizophrenic type, and be more flexible when checking overriding of its members. This is probably too much to ask to get for free.
More specifically, while checking overriding, we want matching to also consider erased signatures. This is currently already done for inherited concrete Java methods in javaErasedOverridingSym.
// Find a concrete Java method that overrides `sym' under the erasure model. // Bridge symbols qualify. // Used as a fall back if no overriding symbol of a Java abstract method can be found def javaErasedOverridingSym(sym: Symbol): Symbol = clazz.tpe.nonPrivateMemberAdmitting(sym.name, BRIDGE).filter(other => !other.isDeferred && other.isJavaDefined && {
We could do something similar for members annotated as follows: `@erasedOverriding def getElementAt(n: Int): A = items(n)`
Note that we can't use bridge methods here, since the signatures of the two methods we need are identical after erasure.
The final problem that I encountered in our Scala Swing code base, and the real killer, I think, is related to the definition of matching, which is intrinsic to the definition of the set of members of a type (computed by findMember). We'd need matching to take erasure into account... That dependency probably isn't acceptable, even if it's shielded by an annotation (extreme performance hotspot, for one!)
That said, feel free to check out my experiment at https://github.com/adriaanm/scala-dev/tree/openjdk_experiment_erasedoverride