[
https://issues.apache.org/jira/browse/GROOVY-12509?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18131961#comment-18131961
]
ASF GitHub Bot commented on GROOVY-12509:
-----------------------------------------
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 subproject
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.
> STC: private members of another class of the same nest are not accessible
> -------------------------------------------------------------------------
>
> Key: GROOVY-12509
> URL: https://issues.apache.org/jira/browse/GROOVY-12509
> Project: Groovy
> Issue Type: Bug
> Components: Static Type Checker
> Affects Versions: 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> Under static type checking, a class nested in a top-level class cannot use
> the private constructors, methods and accessors of another class nested in
> the same top-level class:
> {code:groovy}
> @groovy.transform.CompileStatic
> class C {
> static class Box {
> private Box(String s) {}
> private static String sm() { 'sm' }
> private String im() { 'im' }
> private String getX() { 'x' }
> }
> static class A { private static class Hidden { public static int x = 1 } }
> static class Other {
> static test(Box box) {
> new Box('a') // Cannot find matching constructor
> C$Box(java.lang.String)
> Box.sm() // Cannot find matching method java.lang.Class#sm()
> or static method C$Box#sm()
> box.im() // Cannot find matching method C$Box#im()
> box.x // No such property: x for class: C$Box
> A.Hidden.x // Cannot access field: x of class: C$A$Hidden from
> class: C$Other
> }
> }
> }
> {code}
> Java allows all of these (JLS 6.6.1: a private member is accessible anywhere
> within the body of the top-level class that encloses its declaration), and
> the JVM does through nestmates. Groovy already writes the {{NestHost}} and
> {{NestMembers}} attributes for these classes, the class generator already
> calls private members of nestmates directly, and the type checker already
> allows private fields of nestmates (GROOVY-12290). Method references such as
> {{Box::new}} and {{Box::sm}} are rejected the same way.
> The modes also disagree. With a private {{Box(String)}} beside a public
> {{Box(Object)}}, {{new Box('s')}} in {{Other}} calls the private constructor
> in dynamic and {{@TypeChecked}} code, as javac does, but the public one under
> {{@CompileStatic}}. The same happens for a private {{B.m(String)}} beside an
> inherited {{A.m(Object)}}.
> A private getter or setter used through property syntax ({{box.x}}) is also
> rejected from the outer class into a nested class.
> The last case, a public member reached through a private nested type
> ({{A.Hidden.x}}, or a constant of a private nested enum), worked in 5.0.8 and
> fails since 6.0.0 (GROOVY-12310), from a sibling and from the outer class.
> The other cases fail in 4.0, 5.0 and 6.0.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)