diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java index 6138bd7aca3..84aae3af827 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java @@ -831,6 +831,8 @@ public interface IProblem { int ShouldImplementHashcode = TypeRelated + 332; /** @since 3.5 */ int AbstractMethodsInConcreteClass = TypeRelated + 333; + /** @since 3.40 */ + int IllegalModifierCombinationForType = TypeRelated + 334; /** @deprecated - problem is no longer generated, use {@link #UndefinedType} instead */ int SuperclassNotFound = TypeRelated + 329 + ProblemReasons.NotFound; // TypeRelated + 330 diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java index c1856353149..ff7316ff965 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ClassScope.java @@ -612,6 +612,13 @@ private void checkAndSetModifiers() { boolean isSealedSupported = JavaFeature.SEALED_CLASSES.isSupported(options); boolean flagSealedNonModifiers = isSealedSupported && (modifiers & (ExtraCompilerModifiers.AccSealed | ExtraCompilerModifiers.AccNonSealed)) != 0; + + switch (modifiers & (ExtraCompilerModifiers.AccSealed | ExtraCompilerModifiers.AccNonSealed | ClassFileConstants.AccFinal)) { + case ExtraCompilerModifiers.AccSealed, ExtraCompilerModifiers.AccNonSealed, ClassFileConstants.AccFinal, ClassFileConstants.AccDefault : break; + default : + problemReporter().IllegalModifierCombinationForType(sourceType); + break; + } if (sourceType.isRecord()) { /* JLS 14 Records Sec 8.10 - A record declaration is implicitly final. */ modifiers |= ClassFileConstants.AccFinal; @@ -1192,7 +1199,7 @@ private boolean connectSuperclass() { if (superclass != null) { // is null if a cycle was detected cycle or a problem if (!superclass.isClass() && (superclass.tagBits & TagBits.HasMissingType) == 0) { problemReporter().superclassMustBeAClass(sourceType, superclassRef, superclass); - } else if (superclass.isFinal()) { + } else if (superclass.isFinal() && !superclass.isSealed()) { // sealed AND final complained about elsewhere, no value in additional complaint here. if (superclass.isRecord()) { problemReporter().classExtendFinalRecord(sourceType, superclassRef, superclass); } else { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index 01ff84bddfb..3eb2e9651cf 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -3430,6 +3430,15 @@ public void illegalVisibilityModifierCombinationForField(ReferenceBinding type, fieldDecl.sourceStart, fieldDecl.sourceEnd); } +public void IllegalModifierCombinationForType(SourceTypeBinding type) { + String[] arguments = new String[] {new String(type.sourceName())}; + this.handle( + IProblem.IllegalModifierCombinationForType, + arguments, + arguments, + type.sourceStart(), + type.sourceEnd()); +} public void illegalVisibilityModifierCombinationForMemberType(SourceTypeBinding type) { String[] arguments = new String[] {new String(type.sourceName())}; this.handle( diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties index 9432d12e890..06008e016dd 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -330,6 +330,7 @@ 331 = Redundant superinterface {0} for the type {1}, already defined by {2} 332 = The type {0} should also implement hashCode() since it overrides Object.equals() 333 = The type {0} must be an abstract class to define abstract methods +334 = The type {0} may have only one modifier out of sealed, non-sealed, and final ###[obsolete] 330 = {0} cannot be resolved or is not a valid superclass ###[obsolete] 331 = Superclass {0} is not visible diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java index 7ca0e44c964..3a3af4f2ddd 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java @@ -1364,6 +1364,7 @@ class ProblemAttributes { expectedProblemAttributes.put("NamedPatternVariablesDisallowedHere", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("OperandStackExceeds64KLimit", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("OperandStackSizeInappropriate", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); + expectedProblemAttributes.put("IllegalModifierCombinationForType", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); StringBuilder failures = new StringBuilder(); StringBuilder correctResult = new StringBuilder(70000); @@ -2497,6 +2498,7 @@ class ProblemAttributes { expectedProblemAttributes.put("NamedPatternVariablesDisallowedHere", SKIP); expectedProblemAttributes.put("OperandStackExceeds64KLimit", SKIP); expectedProblemAttributes.put("OperandStackSizeInappropriate", SKIP); + expectedProblemAttributes.put("IllegalModifierCombinationForType", SKIP); Map constantNamesIndex = new HashMap(); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesTests.java index 02c1c5d5b85..3991f1dff08 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesTests.java @@ -1067,11 +1067,16 @@ public void testBug563806_032() { "1. ERROR in p1\\X.java (at line 2)\n" + " public sealed non-sealed interface X {\n" + " ^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares X as its direct superclass or superinterface\n" + + "The type X may have only one modifier out of sealed, non-sealed, and final\n" + "----------\n" + "2. ERROR in p1\\X.java (at line 2)\n" + " public sealed non-sealed interface X {\n" + " ^\n" + + "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares X as its direct superclass or superinterface\n" + + "----------\n" + + "3. ERROR in p1\\X.java (at line 2)\n" + + " public sealed non-sealed interface X {\n" + + " ^\n" + "An interface X is declared both sealed and non-sealed\n" + "----------\n"); } @@ -1192,6 +1197,11 @@ public void testBug563806_038() { "1. ERROR in p1\\X.java (at line 4)\n" + " non-sealed sealed class Y{}\n" + " ^\n" + + "The type Y may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n" + + "2. ERROR in p1\\X.java (at line 4)\n" + + " non-sealed sealed class Y{}\n" + + " ^\n" + "Illegal modifier for the local class Y; only abstract or final is permitted\n" + "----------\n"); } @@ -6322,4 +6332,74 @@ public static void main(String [] args) { }, "42"); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2707 + // [Sealed types] ECJ allows a class to be declared as both sealed and non-sealed + public void testIssue2707() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed class X permits Y { + } + + sealed non-sealed class Y extends X permits K {} + + final class K extends Y {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " sealed non-sealed class Y extends X permits K {}\n" + + " ^\n" + + "The type Y may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2707 + // [Sealed types] ECJ allows a class to be declared as both sealed and non-sealed + public void testIssue2707_2() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed class X permits Y { + } + + final non-sealed class Y extends X {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " final non-sealed class Y extends X {}\n" + + " ^\n" + + "The type Y may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2707 + // [Sealed types] ECJ allows a class to be declared as both sealed and non-sealed + public void testIssue2707_3() { + runNegativeTest( + new String[] { + "X.java", + """ + public final sealed class X permits Y { + } + + final non-sealed class Y extends X {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 1)\n" + + " public final sealed class X permits Y {\n" + + " ^\n" + + "The type X may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n" + + "2. ERROR in X.java (at line 4)\n" + + " final non-sealed class Y extends X {}\n" + + " ^\n" + + "The type Y may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n"); + } }