Hi Brian, Nice sump-up, i like it very much. I believe we also need to think about type parameters, they are also impacted by the nesting/static context so should be in NC(X).
Rémi > De: "Brian Goetz" <brian.go...@oracle.com> > À: "amber-spec-experts" <amber-spec-experts@openjdk.java.net> > Envoyé: Mardi 7 Janvier 2020 21:30:27 > Objet: Towards cleaner nesting > Everything about nesting in Java is a mess. The terminology is a mess (top > level > classes, nested classes, inner classes, local classes, anonymous classes); the > set of restrictions on what can nest in what is ad-hoc (can have local classes > but not local interfaces; inner classes cannot have static members, including > static nested classes), and the set of rules about what must be, can be, or > cannot be static is also ad-hoc (nested classes can be static or not, nested > interfaces are implicitly static, but local and anonymous classes may not be > static, even though it might make sense.) On top of that, we can nest classes > in methods (sometimes) and methods in classes but not methods in methods > (local > methods). > Not only does this make for a lot of accidental complexity in specification, > implementation, and user's brains, but it means every feature interact with > this complexity. Nested records are implicitly static, but this meant that in > 14 we can't have nested records in non-static classes, because, non-static > classes can't have static members. (Yes, this could be fixed; hold your "why > don't you just" suggestions.) And we borked up the implementation of local > records the first time around, where they accidentally capture effectively > final locals, which they shouldn't -- because we'd never really charted the > "static local class" territory, and got it wrong the first time. (Yes, this > can > be fixed too, and will be before 14 goes out.) > So, I'd like to propose a simpler, general story of nesting (which is > consistent > with the ad-hoc rubbish we have) which we can get to in stages. The purpose of > this mail is to discuss the model; in what increments we get there is a > separate story. > Goals: > - Anything (class, interface, record, enum, method) can be nested in anything; > - Some things are always static (enums, records, interfaces) when nested; the > rest can be made static when desired; > - The rule about "no static members in nonstatic nested classes" has to go; > - Rules about whether members / locals from enclosing contexts can be > specified > in a single place, using local reasoning. > The core of this is coming to an understanding of what "static" means. When > construct X nests in Y (whether X and Y are classes, methods, interfaces, > etc), > for "X" to be "static" means that nesting is being used purely for purposes of > namespacing, and not for purposes of having access to names (locals or > nonstatic class members) from enclosing constructs. > Unfortunately all the terms we might use for whether or not a symbol in an > outer > construct can be used in a nested construct -- such as "accessible" -- are > overloaded with other meanings. For purposes of this discussion, let's call > this "capturable" (this is also overloaded, but less so.) Each construct > (class > type or method) has two sets of names from outer constructs that are > capturable > -- a _statically capturable_ set SC(X), and a _non-statically capturable_ set > NC(X). We can define capturability using local reasoning: > Base cases: > - Names of static members in X are in SC(X); > - Names of instance members of X (if X is a class) or effectively final locals > of X (if X is a method) are in NC(X); > Induction cases, where X is nested directly in Y: > - SC(Y) is in SC(X) > - If _X is not static_, then NC(Y) is in NC(X) > We then say that X can capture names in SC(X) and NC(X); all we need to > compute > capturability is the capture sets of X's immediately enclosing construct, and > whether X is static or not in that construct (modulo shadowing etc.) > For the math-challenged, what this means is: > - A nested construct can access static members of all the enclosing > constructs; > - A nested non-static construct can access instance members and effectively > final locals of all enclosing constructs, up until we hit a static construct, > and then capturing stops. (So if Z is nested in Y is nested in static X, Z can > access instance members / eff final locals of Y and X but not anything > non-static from outside of X.) > Note that this is consistent with what currently happens when X is a method as > well as a class type; static methods in a class "capture" the static members > of > the enclosing class, and instance methods also capture the instance members of > the enclosing class -- and also consistent with capturing in lambdas and > anonymous classes, if we assume that these are always non-static constructs. > We then say enums, records, and interfaces are _always_ static when nested, > whether declared so or not, we eliminate the restriction about static members > in non-static nested classes (now that we have a clear semantics for them), > and > allow local classes to be declared as static. (Eventually, we also relax the > restrictions about methods in methods, static or not.) > (Additionally, the model supports the notion of "static lambda" and "static > anonymous class" with obvious semantics (can't capture anything); we can > decide > later whether adding this flexibility is worth the additional surface syntax.) > This is a strict superset of the status quo, and yields a more flexible and > regular language -- and hopefully a simpler spec (since so many of these cases > are specified as ad-hoc corner cases.)