8254105: allow static nested declarations

Reviewed-by: mcimadamore
This commit is contained in:
Vicente Romero 2020-11-21 03:17:57 +00:00
parent 14de791d60
commit 9a19eb6918
25 changed files with 581 additions and 459 deletions

View File

@ -383,30 +383,31 @@ public class Flags {
/** Modifier masks.
*/
public static final int
AccessFlags = PUBLIC | PROTECTED | PRIVATE,
LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC,
StaticLocalFlags = LocalClassFlags | STATIC | INTERFACE,
MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags,
MemberRecordFlags = MemberClassFlags | STATIC,
ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION,
InterfaceVarFlags = FINAL | STATIC | PUBLIC,
VarFlags = AccessFlags | FINAL | STATIC |
VOLATILE | TRANSIENT | ENUM,
ConstructorFlags = AccessFlags,
InterfaceMethodFlags = ABSTRACT | PUBLIC,
MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE |
SYNCHRONIZED | FINAL | STRICTFP,
RecordMethodFlags = AccessFlags | ABSTRACT | STATIC |
SYNCHRONIZED | FINAL | STRICTFP;
AccessFlags = PUBLIC | PROTECTED | PRIVATE,
LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC,
StaticLocalFlags = LocalClassFlags | STATIC | INTERFACE,
MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags,
MemberStaticClassFlags = MemberClassFlags | STATIC,
ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION,
InterfaceVarFlags = FINAL | STATIC | PUBLIC,
VarFlags = AccessFlags | FINAL | STATIC |
VOLATILE | TRANSIENT | ENUM,
ConstructorFlags = AccessFlags,
InterfaceMethodFlags = ABSTRACT | PUBLIC,
MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE |
SYNCHRONIZED | FINAL | STRICTFP,
RecordMethodFlags = AccessFlags | ABSTRACT | STATIC |
SYNCHRONIZED | FINAL | STRICTFP;
public static final long
ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED,
ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED,
ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED,
ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED,
InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT,
AnnotationTypeElementMask = ABSTRACT | PUBLIC,
LocalVarFlags = FINAL | PARAMETER,
ReceiverParamFlags = PARAMETER;
ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED,
ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED,
ExtendedMemberStaticClassFlags = (long) MemberStaticClassFlags | SEALED | NON_SEALED,
ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED,
ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED,
InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT,
AnnotationTypeElementMask = ABSTRACT | PUBLIC,
LocalVarFlags = FINAL | PARAMETER,
ReceiverParamFlags = PARAMETER;
@SuppressWarnings("preview")
public static Set<Modifier> asModifierSet(long flags) {

View File

@ -171,6 +171,7 @@ public class Attr extends JCTree.Visitor {
allowReifiableTypesInInstanceof =
Feature.REIFIABLE_TYPES_INSTANCEOF.allowedInSource(source) &&
(!preview.isPreview(Feature.REIFIABLE_TYPES_INSTANCEOF) || preview.isEnabled());
allowRecords = Feature.RECORDS.allowedInSource(source);
sourceName = source.name;
useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning");
@ -207,6 +208,10 @@ public class Attr extends JCTree.Visitor {
*/
boolean allowReifiableTypesInInstanceof;
/** Are records allowed
*/
private final boolean allowRecords;
/**
* Switch: warn about use of variable before declaration?
* RFE: 6425594
@ -5309,14 +5314,15 @@ public class Attr extends JCTree.Visitor {
attribStat(l.head, env);
// Check that declarations in inner classes are not static (JLS 8.1.2)
// Make an exception for static constants.
if (c.owner.kind != PCK &&
((c.flags() & STATIC) == 0 || c.name == names.empty) &&
(TreeInfo.flags(l.head) & (STATIC | INTERFACE)) != 0) {
if (!allowRecords &&
c.owner.kind != PCK &&
((c.flags() & STATIC) == 0 || c.name == names.empty) &&
(TreeInfo.flags(l.head) & (STATIC | INTERFACE)) != 0) {
Symbol sym = null;
if (l.head.hasTag(VARDEF)) sym = ((JCVariableDecl) l.head).sym;
if (sym == null ||
sym.kind != VAR ||
((VarSymbol) sym).getConstValue() == null)
sym.kind != VAR ||
((VarSymbol) sym).getConstValue() == null)
log.error(l.head.pos(), Errors.IclsCantHaveStaticDecl(c));
}
}

View File

@ -1216,23 +1216,20 @@ public class Check {
implicit |= sym.owner.flags_field & STRICTFP;
break;
case TYP:
if (sym.isLocal()) {
if (sym.owner.kind.matches(KindSelector.VAL_MTH)) {
boolean implicitlyStatic = !sym.isAnonymous() &&
((flags & RECORD) != 0 || (flags & ENUM) != 0 || (flags & INTERFACE) != 0);
boolean staticOrImplicitlyStatic = (flags & STATIC) != 0 || implicitlyStatic;
// local statics are allowed only if records are allowed too
mask = staticOrImplicitlyStatic && allowRecords && (flags & ANNOTATION) == 0 ? StaticLocalFlags : LocalClassFlags;
implicit = implicitlyStatic ? STATIC : implicit;
if (staticOrImplicitlyStatic) {
if (sym.owner.kind == TYP) {
log.error(pos, Errors.StaticDeclarationNotAllowedInInnerClasses);
}
}
} else if (sym.owner.kind == TYP) {
mask = (flags & RECORD) != 0 ? MemberRecordFlags : ExtendedMemberClassFlags;
// statics in inner classes are allowed only if records are allowed too
mask = ((flags & STATIC) != 0) && allowRecords ? ExtendedMemberStaticClassFlags : ExtendedMemberClassFlags;
if (sym.owner.owner.kind == PCK ||
(sym.owner.flags_field & STATIC) != 0)
(sym.owner.flags_field & STATIC) != 0) {
mask |= STATIC;
else if ((flags & ENUM) != 0 || (flags & RECORD) != 0) {
} else if (!allowRecords && ((flags & ENUM) != 0 || (flags & RECORD) != 0)) {
log.error(pos, Errors.StaticDeclarationNotAllowedInInnerClasses);
}
// Nested interfaces and enums are always STATIC (Spec ???)

View File

@ -24,10 +24,11 @@
/*
* @test
* @bug 4279339
* @summary Verify that an anonymous class cannot contain a static field.
* @summary Verify that an anonymous class can contain a static field only if source >= 16
* @author maddox
*
* @run compile/fail AnonStaticMember_1.java
* @compile/fail/ref=AnonStaticMember_1.out -source 15 -XDrawDiagnostics AnonStaticMember_1.java
* @compile AnonStaticMember_1.java
*/
class AnonStaticMember_1 {

View File

@ -0,0 +1,4 @@
- compiler.warn.source.no.system.modules.path: 15
AnonStaticMember_1.java:36:20: compiler.err.icls.cant.have.static.decl: compiler.misc.anonymous.class: AnonStaticMember_1$1
1 error
1 warning

View File

@ -1,10 +1,11 @@
/*
* @test /nodynamiccopyright/
* @bug 4279339 6969184
* @summary Verify that an anonymous class cannot contain a static method.
* @summary Verify that an anonymous class can contain a static method only if source >= 16
* @author maddox
*
* @run compile/fail/ref=AnonStaticMember_2.out -XDrawDiagnostics AnonStaticMember_2.java
* @compile/fail/ref=AnonStaticMember_2.out -source 15 -XDrawDiagnostics AnonStaticMember_2.java
* @compile AnonStaticMember_2.java
*/
class AnonStaticMember_2 {

View File

@ -1,2 +1,4 @@
AnonStaticMember_2.java:12:21: compiler.err.icls.cant.have.static.decl: compiler.misc.anonymous.class: AnonStaticMember_2$1
- compiler.warn.source.no.system.modules.path: 15
AnonStaticMember_2.java:13:21: compiler.err.icls.cant.have.static.decl: compiler.misc.anonymous.class: AnonStaticMember_2$1
1 error
1 warning

View File

@ -4,7 +4,8 @@
* @summary Verify rejection of illegal static variables in inner classes.
* @author William Maddox (maddox)
*
* @compile/fail/ref=InnerNamedConstant_2.out -XDrawDiagnostics InnerNamedConstant_2.java
* @compile/fail/ref=InnerNamedConstant_2_A.out -XDrawDiagnostics -source 15 InnerNamedConstant_2.java
* @compile/fail/ref=InnerNamedConstant_2_B.out -XDrawDiagnostics InnerNamedConstant_2.java
*/
public class InnerNamedConstant_2 {

View File

@ -1,5 +0,0 @@
InnerNamedConstant_2.java:22:20: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner2
InnerNamedConstant_2.java:23:29: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner2
InnerNamedConstant_2.java:25:13: compiler.err.cant.assign.val.to.final.var: z
InnerNamedConstant_2.java:34:26: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner3
4 errors

View File

@ -0,0 +1,7 @@
- compiler.warn.source.no.system.modules.path: 15
InnerNamedConstant_2.java:23:20: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner2
InnerNamedConstant_2.java:24:29: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner2
InnerNamedConstant_2.java:26:13: compiler.err.cant.assign.val.to.final.var: z
InnerNamedConstant_2.java:35:26: compiler.err.icls.cant.have.static.decl: InnerNamedConstant_2.Inner3
4 errors
1 warning

View File

@ -0,0 +1,2 @@
InnerNamedConstant_2.java:26:13: compiler.err.cant.assign.val.to.final.var: z
1 error

View File

@ -1,10 +1,11 @@
/*
* @test /nodynamiccopyright/
* @bug 4063740 6969184
* @summary Interfaces may only be declared in top level classes.
* @summary Interfaces can be declared in inner classes only for source >= 16
* @author turnidge
*
* @compile/fail/ref=InterfaceInInner.out -XDrawDiagnostics InterfaceInInner.java
* @compile/fail/ref=InterfaceInInner.out -XDrawDiagnostics -source 15 InterfaceInInner.java
* @compile InterfaceInInner.java
*/
class InterfaceInInner {
InterfaceInInner() {

View File

@ -1,2 +1,4 @@
InterfaceInInner.java:12:13: compiler.err.static.declaration.not.allowed.in.inner.classes
- compiler.warn.source.no.system.modules.path: 15
InterfaceInInner.java:13:13: compiler.err.icls.cant.have.static.decl: foo
1 error
1 warning

View File

@ -25,7 +25,8 @@
* @test
* @bug 8222035
* @summary minimal inference context optimization is forcing resolution with incomplete constraints
* @compile/fail/ref=MinContextOpTest.out -XDrawDiagnostics MinContextOpTest.java
* @compile/fail/ref=MinContextOpTest_A.out -XDrawDiagnostics -source 15 MinContextOpTest.java
* @compile/fail/ref=MinContextOpTest_B.out -XDrawDiagnostics MinContextOpTest.java
*/
import java.util.Map;

View File

@ -0,0 +1,6 @@
- compiler.warn.source.no.system.modules.path: 15
MinContextOpTest.java:39:25: compiler.err.mod.not.allowed.here: static
MinContextOpTest.java:45:25: compiler.err.mod.not.allowed.here: static
MinContextOpTest.java:51:34: compiler.err.prob.found.req: (compiler.misc.infer.no.conforming.assignment.exists: T,K,V,E, (compiler.misc.inconvertible.types: java.util.function.Function<MinContextOpTest.A.T,MinContextOpTest.A.T>, java.util.function.Function<? super MinContextOpTest.A.T,? extends MinContextOpTest.A.T<?>>))
3 errors
1 warning

View File

@ -1,4 +1,2 @@
MinContextOpTest.java:38:25: compiler.err.mod.not.allowed.here: static
MinContextOpTest.java:44:25: compiler.err.mod.not.allowed.here: static
MinContextOpTest.java:50:34: compiler.err.prob.found.req: (compiler.misc.infer.no.conforming.assignment.exists: T,K,V,E, (compiler.misc.inconvertible.types: java.util.function.Function<MinContextOpTest.A.T,MinContextOpTest.A.T>, java.util.function.Function<? super MinContextOpTest.A.T,? extends MinContextOpTest.A.T<?>>))
3 errors
MinContextOpTest.java:51:34: compiler.err.prob.found.req: (compiler.misc.infer.no.conforming.assignment.exists: T,K,V,E, (compiler.misc.inconvertible.types: java.util.function.Function<MinContextOpTest.A.T,MinContextOpTest.A.T>, java.util.function.Function<? super MinContextOpTest.A.T,? extends MinContextOpTest.A.T<?>>))
1 error

View File

@ -22,6 +22,8 @@
*/
// key: compiler.err.static.declaration.not.allowed.in.inner.classes
// key: compiler.warn.source.no.system.modules.path
// options: -source 15
class EnumsMustBeStatic {
class Nested {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,6 +22,8 @@
*/
// key: compiler.err.icls.cant.have.static.decl
// key: compiler.warn.source.no.system.modules.path
// options: -source 15
class InnerClassCantHaveStatic {
class Inner {

View File

@ -1,30 +0,0 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.err.static.declaration.not.allowed.in.inner.classes
class Outer {
class Inner {
record R(int a) {}
}
}

View File

@ -1,10 +1,11 @@
/*
* @test /nodynamiccopyright/
* @bug 5071831
* @summary javac allows enum in an inner class
* @summary javac allows enum in an inner class for source >= 16
* @author gafter
*
* @compile/fail/ref=NestedEnum.out -XDrawDiagnostics NestedEnum.java
* @compile/fail/ref=NestedEnum.out -XDrawDiagnostics -source 15 NestedEnum.java
* @compile NestedEnum.java
*/
class NestedEnum {

View File

@ -1,2 +1,4 @@
NestedEnum.java:12:9: compiler.err.static.declaration.not.allowed.in.inner.classes
- compiler.warn.source.no.system.modules.path: 15
NestedEnum.java:13:9: compiler.err.static.declaration.not.allowed.in.inner.classes
1 error
1 warning

View File

@ -1,9 +1,10 @@
/*
* @test /nodynamiccopyright/
* @bug 5081785
* @summary Empty Enums allowed in non-static contexts
* @summary enums should be allowed in non-static contexts
* @author Peter von der Ah\u00e9
* @compile/fail/ref=T5081785.out -XDrawDiagnostics T5081785.java
* @compile/fail/ref=T5081785.out -XDrawDiagnostics -source 15 T5081785.java
* @compile T5081785.java
*/
class A1 {

View File

@ -1,5 +1,7 @@
T5081785.java:29:9: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:12:13: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:19:27: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:24:31: compiler.err.static.declaration.not.allowed.in.inner.classes
- compiler.warn.source.no.system.modules.path: 15
T5081785.java:30:9: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:13:13: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:20:27: compiler.err.static.declaration.not.allowed.in.inner.classes
T5081785.java:25:31: compiler.err.static.declaration.not.allowed.in.inner.classes
4 errors
1 warning

View File

@ -48,6 +48,12 @@ import combo.ComboTask;
import combo.ComboTask.Result;
import combo.ComboTestHelper;
/** this test checks two thinks:
* 1 - that static declarations are allowed inside inner classes
* 2 - and in addtion that non-static variables can't be captured
* by static contexts
*/
public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclarations> {
static final String sourceTemplate =
@ -57,10 +63,12 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
int INSTANCE_FIELD = 0;
static int STATIC_FIELD = 0;
// instance initializer
{ int LOCAL_VARIABLE = 0;
{
int LOCAL_VARIABLE = 0;
#{CONTAINER}
}
Test() {
int LOCAL_VARIABLE = 0;
#{CONTAINER}
}
void m() {
@ -93,7 +101,7 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
),
RECORD("record CR() { #{STATIC_LOCAL} }"),
CLASS("class CC { #{STATIC_LOCAL} }"),
ENUM("enum CE { #{STATIC_LOCAL} }"),
ENUM("enum CE { CE1; #{STATIC_LOCAL} }"),
LAMBDA("Runnable run = () -> { #{STATIC_LOCAL} };");
String container;
@ -124,7 +132,6 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
}
enum Member implements ComboParameter {
NONE(""),
METHOD("int foo() { return #{EXPR}; }"),
DEFAULT_METHOD("default int foo() { return #{EXPR}; }");
@ -179,13 +186,13 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
}
boolean notTriviallyIncorrect() {
return decl == StaticLocalDecl.INTERFACE && (member == Member.DEFAULT_METHOD || member == Member.NONE) ||
decl != StaticLocalDecl.INTERFACE && (member == Member.METHOD || member == Member.NONE);
return decl == StaticLocalDecl.INTERFACE && member == Member.DEFAULT_METHOD ||
decl != StaticLocalDecl.INTERFACE && member == Member.METHOD;
}
void check(ComboTask.Result<Iterable<? extends JavaFileObject>> result) {
if (shouldFail()) {
Assert.check(result.hasErrors(), result.compilationInfo());
Assert.check(result.hasErrors(), "unexpected compilation\n" + result.compilationInfo());
if (!expectedDiagFound(result)) {
fail("test failing with unexpected error message\n" + result.compilationInfo());
}
@ -195,10 +202,7 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
}
boolean shouldFail() {
return ((container != Container.NO_CONTAINER &&
container != Container.LAMBDA &&
container != Container.ANONYMOUS)) ||
(member != Member.NONE && !acceptableExpr());
return (expr == Expression.LOCAL_VARIABLE || expr == Expression.INSTANCE_FIELD);
}
boolean acceptableExpr() {
@ -206,14 +210,9 @@ public class LocalStaticDeclarations extends ComboInstance<LocalStaticDeclaratio
}
boolean expectedDiagFound(ComboTask.Result<Iterable<? extends JavaFileObject>> result) {
if ((container == Container.NO_CONTAINER ||
container == Container.LAMBDA ||
container == Container.ANONYMOUS) &&
!acceptableExpr()) {
if (expr == Expression.LOCAL_VARIABLE || expr == Expression.INSTANCE_FIELD) {
return result.containsKey("compiler.err.non-static.cant.be.ref");
} else if (container == Container.ENUM) {
return result.containsKey("compiler.err.enum.constant.expected" );
}
return result.containsKey("compiler.err.static.declaration.not.allowed.in.inner.classes" );
return false;
}
}

View File

@ -477,365 +477,483 @@ public class RecordCompilationTests extends CompilationTestCase {
assertFail("compiler.err.already.defined", template);
}
public void testStaticLocalTypes() {
// local records can also be final
assertOK("class R { \n" +
" void m() { \n" +
" final record RR(int x) { };\n" +
" }\n" +
"}");
public void testStaticLocals() {
// static locals can't capture local variables, instance fields or type variables
for (String s : List.of(
"record RR(int x) { public int x() { return y; }};",
"record RR(int x) { public int x() { return z; }};",
"record RR(int x) { public int x() { return instance; }};",
"record RR(T t) {};",
"record RR(U u) {};",
// Can't capture locals
assertFail("compiler.err.non-static.cant.be.ref",
"class R { \n" +
" void m(int y) { \n" +
" record RR(int x) { public int x() { return y; }};\n" +
" }\n" +
"}");
"interface I { default int x() { return y; }};",
"interface I { default int x() { return z; }};",
"interface I { default int x() { return instance; }};",
"interface I { default int x(T t) { return 0; }};",
"interface I { default int x(U u) { return 0; }};",
assertFail("compiler.err.non-static.cant.be.ref",
"class R { \n" +
" void m() {\n" +
" int y;\n" +
" record RR(int x) { public int x() { return y; }};\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m() {\n" +
" String hello = \"hello\";\n" +
" interface I {\n" +
" public default void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(hello);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m() {\n" +
" String hello = \"hello\";\n" +
" record R(int i) {\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(hello);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m() {\n" +
" String hello = \"hello\";\n" +
" enum E {\n" +
" A;\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(hello);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m(String param) {\n" +
" interface I {\n" +
" public default void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(param);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m(String param) {\n" +
" record R(int i) {\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(param);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static void m(String param) {\n" +
" enum E {\n" +
" A;\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(param);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" String instanceField = \"instance\";\n" +
" public static void m() {\n" +
" interface I {\n" +
" public default void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(instanceField);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" String instanceField = \"instance\";\n" +
" public static void m(String param) {\n" +
" record R(int i) {\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(instanceField);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" String instanceField = \"instance\";\n" +
" public static void m(String param) {\n" +
" enum E {\n" +
" A;\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" System.err.println(instanceField);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
// instance fields
assertFail("compiler.err.non-static.cant.be.ref",
"class R { \n" +
" int z = 0;\n" +
" void m() { \n" +
" record RR(int x) { public int x() { return z; }};\n" +
" }\n" +
"}");
// or type variables
assertFail("compiler.err.non-static.cant.be.ref",
"class R<T> { \n" +
" void m() { \n" +
" record RR(T t) {};\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class R {\n" +
" static <U> U make(U u) { //method is static\n" +
" interface Checker {\n" +
" void check(U u);\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class LocalEnum {\n" +
" static <U> U getAndSet(U u) { //method is static\n" +
" enum X {\n" +
" A;\n" +
" U u;\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}\n");
assertFail("compiler.err.non-static.cant.be.ref",
"class R {\n" +
" static <U> U make(U u) { //method is static\n" +
" record Checker() {\n" +
" void check(U u);\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class R {\n" +
" <U> U make(U u) { // enclosing method is not static\n" +
" interface Checker {\n" +
" void check(U u);\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class LocalEnum {\n" +
" <U> U getAndSet(U u) { // enclosing method is not static\n" +
" enum X {\n" +
" A;\n" +
" U u;\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}\n");
assertFail("compiler.err.non-static.cant.be.ref",
"class R {\n" +
" <U> U make(U u) { // enclosing method is not static\n" +
" record Checker() {\n" +
" void check(U u);\n" +
" }\n" +
" return null;\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static <T> void main(String[] args) {\n" +
" interface I {\n" +
" public default void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" T t = null;\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static <T> void main(String[] args) {\n" +
" record R(int i) {\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" T t = null;\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
assertFail("compiler.err.non-static.cant.be.ref",
"class C {\n" +
" public static <T> void main(String[] args) {\n" +
" enum E {\n" +
" A;\n" +
" public void test1() {\n" +
" class X {\n" +
" public void test2() {\n" +
" T t = null;\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}");
// but static fields are OK
assertOK("class R { \n" +
" static int z = 0;\n" +
" void m() { \n" +
" record RR(int x) { public int x() { return z; }};\n" +
" }\n" +
"}");
// Can't self-shadow
assertFail("compiler.err.already.defined",
"enum E { A; int x() { return y; }};",
"enum E { A; int x() { return z; }};",
"enum E { A; int x() { return instance; }};",
"enum E { A; int x(T t) { return 0; }};",
"enum E { A; int x(U u) { return 0; }};"
)) {
assertFail("compiler.err.non-static.cant.be.ref",
"""
class R {
void m() {
record R(int x) { };
class R<T> {
int instance = 0;
<U> U m(int y) {
int z;
#S
return null;
}
}
"""
);
""".replaceFirst("#S", s));
}
// a similar example but a bit more complex
for (String s : List.of(
"record R() { void test1() { class X { void test2() { System.err.println(localVar); } } } }",
"record R() { void test1() { class X { void test2() { System.err.println(param); } } } }",
"record R() {void test1() { class X { void test2() { System.err.println(instanceField); } } } }",
"record R() { void test1() { class X { T t; } } }",
"record R() { void test1() { class X { U u; } } }",
"interface I { default void test1() { class X { void test2() { System.err.println(localVar); } } } }",
"interface I() { default void test1() { class X { void test2() {System.err.println(param);} } } }",
"interface I { default void test1() { class X { void test2() { System.err.println(instanceField); } } } }",
"interface I { default void test1() { class X { T t; } } }",
"interface I() { default void test1() { class X {U u;} } }",
"enum E { A; void test1() { class X { void test2() { System.err.println(localVar); } } } }",
"enum E { A; void test1() { class X { void test2() {System.err.println(param);} } } }",
"enum E { A; void test1() { class X { void test2() { System.err.println(instanceField); } } } }",
"enum E { A; void test1() { class X { T t; } } }",
"enum E { A; void test1() { class X {U u;} } }"
)) {
assertFail("compiler.err.non-static.cant.be.ref",
"""
class C<T> {
String instanceField = "instance";
static <U> U m(String param) {
String localVar = "local";
#S
return null;
}
}
""".replaceFirst("#S", s));
}
// can't self-shadow
for (String s : List.of("record R() {}", "interface R {}", "enum R { A }")) {
assertFail("compiler.err.already.defined", "class R { void m() { #S } }".replaceFirst("#S", s));
}
// can't be explicitly static
assertFail("compiler.err.illegal.start.of.expr",
for (String s : List.of("static record RR() { }", "static interface I {}", "static enum E { A }")) {
assertFail("compiler.err.illegal.start.of.expr", "class R { void m() { #S } }".replaceFirst("#S", s));
}
// but static fields can be accessed
for (String s : List.of(
"record RR() { public int x() { return z; } };",
"interface I { default int x() { return z; } }",
"enum E { A; int x() { return z; } }"
)) {
assertOK("class R { static int z = 0; void m() { #S } }".replaceFirst("#S", s));
}
// local records can also be final
assertOK("class R { void m() { final record RR(int x) { }; } }");
}
public void testStaticDefinitionsInInnerClasses() {
// static defs in inner classes can't capture instance fields or type variables
for (String s : List.of(
"""
class R {
void m() {
static record RR(int x) { };
record R() {
void test() { System.err.println(field); }
}
""",
"""
record R() {
void test(T t) {}
}
""",
"""
record R() {
void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
record R() {
void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
interface I {
default void test() { System.err.println(field); }
}
""",
"""
interface I {
default void test(T t) {}
}
""",
"""
interface I {
default void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
interface I {
default void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
enum E {
A;
void test() { System.err.println(field); }
}
""",
"""
enum E {
A;
void test(T t) {}
}
""",
"""
enum E {
A;
void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
enum E {
A;
void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
static class SC {
void test() { System.err.println(field); }
}
""",
"""
static class SC {
void test(T t) {}
}
""",
"""
static class SC {
void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
static class SC {
void test1() {
class X { void test2(T t) {} }
}
}
"""
);
)) {
assertFail("compiler.err.non-static.cant.be.ref",
"""
class C<T> {
String field = "field";
class Inner {
#S
}
}
""".replaceFirst("#S", s));
}
// positive cases
assertOK(
// another, more complex, example
// static defs in inner classes can't capture instance locals, fields or type variables
for (String s : List.of(
"""
import java.security.*;
class Test {
static Test newInstance(Object provider) {
return new Test() {
private final PrivilegedExceptionAction<KeyStore> action = new PrivilegedExceptionAction<KeyStore>() {
public KeyStore run() throws Exception {
if (provider == null) {}
return null;
}
};
};
record R() {
void test() { System.err.println(field); }
}
""",
"""
record R() {
void test1() {
class X { void test2() { System.err.println(field); } }
}
}
""",
"""
record R() {
void test() { System.err.println(param); }
}
""",
"""
record R() {
void test1() {
class X { void test2() { System.err.println(param); } }
}
}
""",
"""
record R() {
void test() { System.err.println(local); }
}
""",
"""
record R() {
void test1() {
class X { void test2() { System.err.println(local); } }
}
}
""",
"""
record R() {
void test(T t) {}
}
""",
"""
record R() {
void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
interface I {
default void test() { System.err.println(field); }
}
""",
"""
interface I {
default void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
interface I {
default void test() { System.err.println(param); }
}
""",
"""
interface I {
default void test1() {
class X {
void test2() { System.err.println(param); }
}
}
}
""",
"""
interface I {
default void test() { System.err.println(local); }
}
""",
"""
interface I {
default void test1() {
class X {
void test2() { System.err.println(local); }
}
}
}
""",
"""
interface I {
default void test(T t) {}
}
""",
"""
interface I {
default void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
enum E {
A;
void test() { System.err.println(field); }
}
""",
"""
enum E {
A;
void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
enum E {
A;
void test() { System.err.println(param); }
}
""",
"""
enum E {
A;
void test1() {
class X {
void test2() { System.err.println(param); }
}
}
}
""",
"""
enum E {
A;
void test() { System.err.println(local); }
}
""",
"""
enum E {
A;
void test1() {
class X {
void test2() { System.err.println(local); }
}
}
}
""",
"""
enum E {
A;
void test(T t) {}
}
""",
"""
enum E {
A;
void test1() {
class X { void test2(T t) {} }
}
}
""",
"""
static class SC {
void test() { System.err.println(field); }
}
""",
"""
static class SC {
void test1() {
class X {
void test2() { System.err.println(field); }
}
}
}
""",
"""
static class SC {
void test() { System.err.println(param); }
}
""",
"""
static class SC {
void test1() {
class X {
void test2() { System.err.println(param); }
}
}
}
""",
"""
static class SC {
void test() { System.err.println(local); }
}
""",
"""
static class SC {
void test1() {
class X {
void test2() { System.err.println(local); }
}
}
}
""",
"""
static class SC {
void test(T t) {}
}
""",
"""
static class SC {
void test1() {
class X { void test2(T t) {} }
}
}
"""
);
assertOK(
"""
import java.security.*;
class Test {
static Test newInstance(Object provider) {
return new Test() {
int m(PrivilegedExceptionAction<KeyStore> a) { return 0; }
{
m(
new PrivilegedExceptionAction<KeyStore>() {
public KeyStore run() throws Exception {
if (provider == null) {}
return null;
}
}
);
)) {
assertFail("compiler.err.non-static.cant.be.ref",
"""
class C<T> {
String field = "field";
<U> U m(String param) {
String local = "local";
class Local {
class Inner { #S }
}
};
return null;
}
}
""".replaceFirst("#S", s));
}
// inner classes can contain static methods too
assertOK(
"""
class C {
class Inner {
// static method inside inner class
static void m() {}
}
}
"""
);
assertOK(
"""
class C {
void m() {
new Object() {
// static method inside inner class
static void m() {}
};
}
}
"""
);
@ -869,7 +987,7 @@ public class RecordCompilationTests extends CompilationTestCase {
}
public void testRecordsInsideInner() {
assertFail("compiler.err.static.declaration.not.allowed.in.inner.classes",
assertOK(
"""
class Outer {
class Inner {
@ -878,7 +996,7 @@ public class RecordCompilationTests extends CompilationTestCase {
}
"""
);
assertFail("compiler.err.static.declaration.not.allowed.in.inner.classes",
assertOK(
"""
class Outer {
public void test() {
@ -888,7 +1006,7 @@ public class RecordCompilationTests extends CompilationTestCase {
}
}
""");
assertFail("compiler.err.static.declaration.not.allowed.in.inner.classes",
assertOK(
"""
class Outer {
Runnable run = new Runnable() {
@ -897,7 +1015,7 @@ public class RecordCompilationTests extends CompilationTestCase {
};
}
""");
assertFail("compiler.err.static.declaration.not.allowed.in.inner.classes",
assertOK(
"""
class Outer {
void m() {