Towards cleaner nesting
daniel.smith at oracle.com
Thu Jan 9 00:17:04 UTC 2020
> On Jan 7, 2020, at 1:30 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> - 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;
FWIW, I think it's helpful for case analysis to flatten the list of things that can be nested/contain nested things as follows:
- top-level/static classes
- inner classes* (I always forget whether this includes local classes, but turns out it does)
- annotation types
- static methods/initializers
- instance methods/local methods/constructors/instance initializers*
The proposal is that each one of these can be nested in any of these.
Two of these eight are marked with asterisks because their bodies can reference enclosing non-static variables and type variables. All the rest are "static declarations".
Annotation types currently have some restrictions on methods that probably ought to be relaxed for uniformity?
Two things this presentation leaves off of the list: instance/local variables, and static variables. These both continue to have ad hoc nesting constraints, probably for good reason:
- instance/local variables cannot be nested in interfaces, records, or annotation types
- static variables cannot be nested in instance/local methods or static methods
(Some other languages support the second one, which I always find weird; on the other hand, once you have static classes nested in methods, you effectively have the same thing.)
> 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.)
Here's the JLS rule, from 22.214.171.124, that we're enhancing:
"If an expression name consists of a single Identifier, then there must be exactly one declaration denoting either a local variable, formal parameter, or field in scope at the point at which the Identifier occurs. Otherwise, a compile-time error occurs."
"If the declaration denotes an instance variable (§126.96.36.199), the expression name must appear within an instance method (§188.8.131.52), instance variable initializer (§8.3.2), instance initializer (§8.6), or constructor (§8.8). If the expression name appears within a class method, class variable initializer, or static initializer (§8.7), then a compile-time error occurs."
Could be modified to something like:
"If the declaration denotes a local variable, formal parameter, or instance variable, then the expression name must not appear within a static type declaration, static method, static field initializer, or static initializer, unless that declaration also encloses the variable declaration. Otherwise, a compile-time error occurs."
More information about the amber-spec-experts