codeconsole opened a new pull request, #3044:
URL: https://github.com/apache/groovy/pull/3044
This makes `@TypeChecked` and `@CompileStatic` follow Java's rules for
private members of nested classes:
- classes of the same nest can use each other's private members (JLS 6.6.1);
- private members are not inherited (JLS 8.2);
- code without an enclosing instance cannot use instance members of an outer
class.
It also makes qualified `this` and `super` (`N.this`, `N.super`,
`Outer.super`) work as in Java, and fixes the `super` call bugs found on the
way. Each issue has its own commit (GROOVY-12509 has one per part, GROOVY-12514
one for `this` and one for `super`), followed by a documentation commit.
```groovy
@CompileStatic
class C {
static class Box {
Box() {}
private Box(String s) {}
private String im() { 'im' }
}
static class Sub extends Box {}
static class Other {
def ok(Box box) { new Box('a'); box.im() } // was "Cannot find
matching constructor/method"; now direct calls (GROOVY-12509)
}
def no() { new Sub().im() } // ran; now "Cannot find
matching method", as javac reports (GROOVY-12510)
}
```
## GROOVY-12510: a private method is not found through a subclass
When a private method of `Box` was visible to the caller, the type checker
also found it through a subclass: `new Sub().im()` in the outer class or in a
class nested in `Box`, or `im()` in a static subclass nested in `Box`.
`@TypeChecked` compiled the call and it then threw `MissingMethodException`;
`@CompileStatic` called it; javac rejects it. Both modes now report "Cannot
find matching method", and a public overload of the subclass is selected where
javac selects it.
These keep working: calls in the declaring class itself (`new Sub().im()`
inside `Box`), private static methods through a subclass (`Sub.sm()`), as in
dynamic Groovy, and calls through a receiver whose type declares the method (a
`Box` parameter or result, a cast, `super.im()`, a variable declared as `Box`
that flow typing narrows to `Sub`). Method references and pointers through a
subclass (`Sub::im`, `new Sub()::im`, `Sub::sm`, `new Sub().&im`) are rejected
everywhere, including inside `Box`, because dynamic Groovy fails on them there
too.
## GROOVY-12509: private members of another class of the same nest
- `new Box('a', 'b')`, `Box.sm()`, `box.im()`, `Box::new`, `Box::sm` and
`Box::im` from a sibling, cousin or nephew class, or from closures, lambdas,
anonymous classes and traits inside them, now compile to direct calls. They
failed with "Cannot find matching method/constructor", or, for method
references, passed `@TypeChecked` and failed under `@CompileStatic`.
- `box.x`, `box.y = v` and `Box.z` with a private `getX`, `setY` or static
`getZ` work from any class of the nest, including the outer class, but only
through the accessor's own class: `new Sub().x` still fails.
- `A.Hidden.x` and `A.E.ONE`, where `Hidden` and `E` are private nested
types of `A`, failed with "Cannot access field" from a sibling of `A` or from
the outer class. This is a regression in 6.0.0, from GROOVY-12310; 5.0.8
compiles the same code.
## GROOVY-12511: the enclosing instance
An inner or anonymous class that extends its outer class `Box` does not
inherit the private members of `Box`, so an unqualified `im()` or `f` in it
refers to the enclosing `Box` instance, as in javac, dynamic Groovy and
`@TypeChecked`. `@CompileStatic` used the subclass instance. It now uses the
enclosing instance, also in closures and lambdas, for `this.im()` and for a
private getter used as a property. `this.@f` in such a subclass reads its own
field, as in dynamic Groovy. `this.om()` in a plain inner class, which failed
with `GroovyCastException`, calls the outer instance's method. An enum constant
body, which has no enclosing instance, keeps calling the private methods of its
enum on itself.
## GROOVY-12512: no enclosing instance
An anonymous class created in a static method, a static initializer or a
static field initializer cannot call an instance method of the outer class.
`@CompileStatic` compiled such a call to code that failed with `VerifyError`
(or `ClassCastException` with a closure), and `@TypeChecked` to a
`MissingMethodException`. Both now report "Cannot find matching method".
`Outer.this` in such a class, or in a static nested class, failed with
`VerifyError` in every mode and is now a compile error.
## GROOVY-12513: the declared type of a narrowed variable
A private method of `Box` was found through a variable declared as `Box`
even when flow typing narrows it to a subclass (`Box b = new Sub(); b.im()`),
but a private field or accessor was not: `b.f`, `b.f = v`, `b.f += v`, `b.@f`
and `b.x` failed with "No such property", although the same access through a
`Box` parameter compiled and javac accepts it. Fields and accessors now follow
the same rule as methods. A receiver whose type is only the subclass (`new
Sub().f`, a `def` variable) stays rejected.
## GROOVY-12514: `N.this` and `N.super` within a nested class `N`
`N.this`, which is `this`, failed under `@TypeChecked` and `@CompileStatic`
with "No such property: this", and `N.super.m()` and `N.super.p` with "No such
property: super", in static nested, inner and enum classes and in closures and
lambdas there. Both now compile, as in javac. `N.super` means `super` in every
mode: dynamic Groovy read `N.super.p` as `N.this.p`. In a static context they
remain errors. `Top.this` and `Top.super` in a top-level class keep their error
(GROOVY-4321, `StaticThisTest`).
## GROOVY-12517: `Outer.super` in a class nested in `Outer`
In an inner class, an anonymous class, a closure or lambda in one of them,
or an inner class of an inner class, `Outer.super.m()` and `Outer.super.p`
failed under `@TypeChecked` and `@CompileStatic` with "No such property:
super". They now call the superclass's member on the enclosing `Outer`
instance, as in javac. Under `@CompileStatic` the calls go through `super$`
helper methods of `Outer`, since only `Outer` may call its superclass's methods
non-virtually. Property reads and writes, `++` and compound assignments work
too. Dynamic Groovy read and wrote `Outer.super.p` as `Outer.this.p`; it now
uses the superclass's property. Without an enclosing instance of `Outer` it
stays an error.
## GROOVY-12516, GROOVY-12518, GROOVY-12519: `super` calls
- GROOVY-12516: under `@CompileStatic`, two closures or lambdas of one class
calling the same super method generated the `super$` helper twice, and the
class failed to load with `ClassFormatError`. The helper is generated once.
- GROOVY-12518: under `@CompileStatic`, `super.p`, `super.name` and
`super.name = v` in a closure or lambda failed with `VerifyError` (the
superclass accessor was called with `invokespecial` from the closure class);
they now go through `super$` helpers, like super method calls. `super.n++`,
`++super.n` and `super.n += v` read the property through a dynamic call that
failed with `MissingPropertyException` when the class declares the same
property; they now call the superclass's accessors.
- GROOVY-12519: when a class declares a static method or property with the
same signature as its superclass, `super.sm()` threw `MissingMethodException`
in dynamic Groovy and under `@TypeChecked`, or called another overload with
`null`, and `super.sp` threw `MissingPropertyException`; under
`@CompileStatic`, `super.sm()` in a closure failed with
`IncompatibleClassChangeError`. Both now reach the superclass's static members,
as in javac. Private static methods of the superclass stay out of reach.
The "Inner class" section of the object orientation guide describes these
rules.
## Behaviour changes
Code that compiles today and no longer compiles. Under `@TypeChecked` it
threw `MissingMethodException` or `MissingPropertyException` at run time; under
`@CompileStatic` it ran, unless noted; javac rejects all of it:
- `new Sub().im()` in the outer class of `Box` or in a class nested in
`Box`, where `im()` is private in `Box` and `Sub extends Box`, also through a
`def` variable, a `with { }` delegate, an `instanceof` check on an `Object`, or
a subclass of `Sub`;
- `im()`, `this.im()` and `new S().im()` in a static subclass `S` nested in
`Box`, for example `class Shape { private String helper() {...}; static class
Circle extends Shape { String area() { helper() } } }`; `((Shape)
this).helper()` or a non-private method keeps working;
- `Sub::im`, `new Sub()::im` and `Sub::sm`, in any class including `Box`;
`new Sub().&im` and `Sub.&sm` failed at run time in every mode before;
- `this.f` in an inner subclass of `Box`, where `f` is private in `Box`;
`new Sub().f` and `new Sub().x` in a class nested in `Box`; a private getter of
the superclass used as a property in a static nested subclass or an enum
constant body;
- calls that failed at run time in every mode before: an instance method of
the outer class called from an anonymous class in a static context,
`Outer.this` or `Outer.super` without an enclosing instance, and a private
field of the superclass in a static nested subclass or an enum constant body.
Code that compiles today and behaves differently under `@CompileStatic`,
each time as dynamic Groovy and `@TypeChecked` already behave:
- a private overload of a nestmate is a candidate: from a sibling, `new
Box('x')` calls a private `Box(String)` instead of `Box(Object)`, `new
Box().m('x')` a private `Box.m(String)` instead of an inherited `m(Object)`,
and `box.with { n('x') }` the delegate's private `n(String)` instead of an
owner method `n(Object)`;
- a nestmate that reads or writes a private field through property syntax
calls a private getter or setter of the same name, if there is one, instead of
the field;
- through a subclass, a private superclass method is no longer an overload:
with a public `Sub.m(Object)` and a private `Box.m(String)`, `new Sub().m('x')`
calls `Sub.m(Object)`;
- in an inner or anonymous subclass of `Box`, `im()`, `f` and a private
getter used as a property use the enclosing `Box` instance, and `this.@f` uses
the subclass's own field.
Code that changes meaning in dynamic Groovy and under `@TypeChecked`, each
time to javac's meaning: `N.super.p` inside `N` reads the superclass's property
instead of `N.this.p` (GROOVY-12514); `Outer.super.p` and `Outer.super.f` in a
class nested in `Outer` use the superclass's member instead of `Outer.this`'s
(GROOVY-12517); `super.sm()` and `super.sp` for a static member the class hides
reach the superclass's member (GROOVY-12519).
Code that failed to compile and now compiles: GROOVY-12509, GROOVY-12513,
GROOVY-12514 and GROOVY-12517. Code that failed at run time and now runs under
`@CompileStatic`: GROOVY-12516, GROOVY-12518 and GROOVY-12519.
## Limits
- `@TypeChecked` dispatches dynamically, so `b.im()` or `b.f` on a
`Box`-typed receiver that holds a `Sub`, `super.im()` and a cast compile and
throw `MissingMethodException` or `MissingPropertyException` at run time when
called from another class of the nest, as dynamic Groovy does. `@CompileStatic`
calls them directly. This already happens from the outer class on master.
- Private static methods through a subclass (`Sub.sm()`, `sm()` in a
subclass) keep resolving in calls, as dynamic Groovy does; javac rejects them.
## Tests
Each commit has tests that fail without it, marked with its JIRA key:
`MethodCallsSTCTest` (also run statically compiled by
`MethodCallsStaticCompilationTest`), `ConstructorsSTCTest`,
`StaticCompileConstructorsTest`, `FieldsAndPropertiesSTCTest` (and
`FieldsAndPropertiesStaticCompileTest`), `MethodReferenceTest`,
`AnonymousInnerClassSTCTest` (and its static compilation subclass),
`gls.innerClass.InnerClassTest`, `groovy.ThisAndSuperTest` and the closure
tests. Every commit passes the touched test classes on its own. 15,030 tests in
the `groovy`, `gls`, `bugs`, `org.codehaus.groovy.classgen`, `transform`,
`runtime`, `reflection`, `vmplugin`, `control` and `ast` packages and the stub
generator tests pass, apart from `bugs.Groovy12363`, which fails on master too
when it runs after `bugs.Groovy12142` in the same JVM and passes on its own
(#3025 fixes it). `groovy-contracts`, `groovy-typecheckers`, `groovy-ginq`,
`groovy-macro-library` and `groovy-json` pass their tests, every subproje
ct compiles its main and test sources, and the Checkstyle and CodeNarc gates
pass.
## Backport
The method, constructor and accessor parts of GROOVY-12509, and GROOVY-12510
to GROOVY-12514 and GROOVY-12516 to GROOVY-12519, behave the same in 5.0.8 and
6.0.0; the private nested type part of GROOVY-12509 is a 6.0.0 regression.
Several changes alter overload selection or make code that compiles today stop
compiling, so whether to backport to `GROOVY_6_0_X`, in full or only the
regression fix, is left to the maintainers.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]