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]

Reply via email to