Hi,
This e-mail is an attempt to describe what we should be doing about nested objects and InnerClasses attribute in corresponding class files.
I've been postponing this e-mail for a long time in a hope of some enlightenment coming to me. I don't aspire to truly enlightened on all aspects of InnerClasses issue anymore. I think incremental approach should bring some improvement to current rather broken situation. Thus I'll focus my attention only on nested objects and classes (including generic ones) and InnerClasses attribute. Specifically, I put out of consideration following things: Java reflection (should not be more broken than it is right now), objects/classes nested in anonymous classes of any kind.
Problem statement
Consider following code:
class A[T] {
object B {
def m(x: T): T = x
}
}
object B is going to be represented by 'A$B$' class that is not declared as an inner class of A. However, in definition of A$B$ there will be some references to parameter T in generic signatures. This leads to reference to type parameter not declared anywhere.
Proposed solution + discussion
We should be always declaring nested objects as inner classes. It's worth noting that classes declared in object (at any level of nesting) have correct InnerClasses table. E.g.
object A {
class B
}
will give us correct InnerClasses table for both A and A$B classes. Specifically:
Compiled from "inner.scala"
public final class A extends java.lang.Object
[...]
InnerClass:
public #13= #10 of #12; //B=class A$B of class A
Compiled from "inner.scala"
public class A$B extends java.lang.Object implements scala.ScalaObject
[...]
InnerClass:
public #17= #14 of #16; //B=class A$B of class A
Note that B is declared as inner class of A and not A$. Two classes are generated only for top-level objects so let's see what happens in following case:
class A {
object B {
class C
}
}
Compiled from "inner.scala"
public final class A$B$ extends java.lang.Object implements scala.ScalaObject
[...]
InnerClass:
public #23= #20 of #22; //C=class A$B$C of class A$B$
Compiled from "inner.scala"
public class A$B$C extends java.lang.Object implements scala.ScalaObject
[...]
InnerClass:
public #30= #10 of #29; //C=class A$B$C of class A$B$
You can see that C is declared as being InnerClass of A$B$ class. Thus, we found one irregularity: classes might be declared as inner classes of either dollar-postfixed class or without dollar-postfixed class corresponding to an object. Another interesting observation is that full name of C is 'A$B$C' and not 'A$B$$C' (one dollar sign for B$ and another one inner class separator). I checked that briefly against Eclipse and javac and they both seems to be fine with that. I guess reflection is not going to like it as per SI-4316 but since Scala reflection is coming we can easily ignore this issue.
Armed with those observations we can make a more precise rule: dollar-postfixed classes corresponding to objects should always be declared as inner classes and should be declared as outer classes for other members in an object (classes or objects). For example, following code:
object A {
class B {
object C {
class D
}
}
}
Should result in following chain: A$ <- A$B <- A$B$C$ <- A$B$C$D of nesting declarations. And only that one. Specifically, 'A' class should put out of considerations when it comes to inner classes declaration.
Name clashes
Some time ago Paul brought a point that current name mangling scheme might result in conflicting names. I thought a bit about it and came to conclusion that it's not true. Let me discuss when it could happen and explain why it doesn't happen. Let's assume for a while that inner classes of objects get two dollar signs in their name contrary to what we observed above. Consider following code:
class A {
class B {
class `#`
}
}
Classes we get are:
- A //class A
- A$B //class A.B
-
A$B$$hash //class A.B.`#`
Now let's introduce companion object:
class A {
object B {
class hash
}
class B {
class `#`
}
}
Classes we get are:
- A //class A
- A$B //class A.B
- A$B$ //object A.B
- A$B$$hash //class A.B.`#`
-
A$B$$hash //class A.B.hash
We've got a conflict. All that was under assomption made above. However, actual implementation will produce following list of classes:
- A //class A
- A$B //class A.B
- A$B$ //object A.B
- A$B$$hash //class A.B.`#`
- A$B$hash //class A.B.hash
Thus current scheme seems to be safe. It's worth stressing out that this proposal doesn't touch name mangling scheme. What I propose is purely changing contents of InnerClasses attribute.
Empirical evidence
Ideally I'd like to compile my first example using current trunk to produce broken class files. Then edit them according to rules I outlined above and check using
badsigs tool me and Paul has been working on. This way we could easily check if changes proposed here bring anticipated results. However, I failed to find any convenient java class file editor. I tried this one:
http://sourceforge.net/projects/classeditor/ but it seems to be incapable of editing InnerClasses attribute. Also, I'd like to check following code:
class A[T] {
object B {
class C {
def m(x: T): T = x
}
}
}
so we make sure that inner classes with additional dollar sign do not confuse Eclipse compiler when it tries to resolve generic signatures. Again, I need class file editor to verify that. Any tips would be welcome.
Summary
It turns out that, if this proposal has any merit, changes to class files are pretty straightforward. Essentially, we just need to declare dollar sign-postfixed classes corresponding to objects as inner classes. We don't need to change name mangling schem or anything like that. Overall, impact of this proposal is just change to InnerClass attribute.
Paul, I'm interested in your thoughts.
--
Grzegorz Kossakowski