diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11d49ae386f..274d29a889f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Upload - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: Event File path: ${{ github.event_path }} @@ -46,7 +46,7 @@ jobs: mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-21 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 - name: Upload Test Results for Linux if: always() - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: test-results-linux if-no-files-found: warn diff --git a/JCL/converterJclMin9/src/java/io/PrintStream.java b/JCL/converterJclMin9/src/java/io/PrintStream.java new file mode 100644 index 00000000000..503f080425d --- /dev/null +++ b/JCL/converterJclMin9/src/java/io/PrintStream.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2000, 2004 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package java.io; + +public class PrintStream { + + public void println() { + } + + public void println(String s) { + } + + public void println(int i) { + } + + public void println(Object o) { + } + + public void print(String s) { + } + + public void print(Object o) { + } + + public void print(int i) { + } +} diff --git a/JCL/converterJclMin9/src/java/lang/AutoCloseable.java b/JCL/converterJclMin9/src/java/lang/AutoCloseable.java new file mode 100644 index 00000000000..b6fbe467732 --- /dev/null +++ b/JCL/converterJclMin9/src/java/lang/AutoCloseable.java @@ -0,0 +1,5 @@ +package java.lang; + +public interface AutoCloseable { + void close() throws Exception; +} diff --git a/JCL/converterJclMin9/src/java/lang/Override.java b/JCL/converterJclMin9/src/java/lang/Override.java new file mode 100644 index 00000000000..e30eba7b6df --- /dev/null +++ b/JCL/converterJclMin9/src/java/lang/Override.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package java.lang; + +import java.lang.annotation.ElementType; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; + +@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) +public @interface Override { +} \ No newline at end of file diff --git a/JCL/converterJclMin9/src/java/lang/System.java b/JCL/converterJclMin9/src/java/lang/System.java new file mode 100644 index 00000000000..a13acd2404d --- /dev/null +++ b/JCL/converterJclMin9/src/java/lang/System.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2000, 2004 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package java.lang; + +import java.io.PrintStream; + +public class System { + + public static PrintStream err; + public static PrintStream out; + + public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); + public static String getProperty(String s) { + return null; + } +} diff --git a/README.md b/README.md index 7d2e3321197..1602df20f69 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,13 @@ For more information and important links, refer to the [JDT wiki page](https://g [Contributions are always welcome!](https://github.com/eclipse-jdt/.github/blob/main/CONTRIBUTING.md) -Please bear in mind that this project is almost entirely developed by volunteers. If you do not provide the implementation yourself (or pay someone to do it for you), the bug might never get fixed. If it is a serious bug, other people than you might care enough to provide a fix. +Please bear in mind that this project is almost entirely developed by volunteers. +If you do not provide the implementation yourself (or pay someone to do it for you), the bug might never get fixed. +If it is a serious bug, other people than you might care enough to provide a fix. + +[![Create Eclipse Development Environment for JDT Core](https://download.eclipse.org/oomph/www/setups/svg/JDT_Core.svg)]( +https://www.eclipse.org/setups/installer/?url=https://raw.githubusercontent.com/eclipse-jdt/eclipse.jdt.core/master/org.eclipse.jdt.core.setup/JdtCoreConfiguration.setup&show=true +"Click to open Eclipse-Installer Auto Launch or drag into your running installer") ## License @@ -28,4 +34,4 @@ Please bear in mind that this project is almost entirely developed by volunteers - https://github.com/eclipse-jdt/eclipse.jdt.core/wiki - https://github.com/eclipse-jdt/.github/blob/main/CONTRIBUTING.md -- http://www.eclipse.org/projects/project.php?id=eclipse.jdt +- https://projects.eclipse.org/projects/eclipse.jdt diff --git a/org.eclipse.jdt.apt.pluggable.tests/META-INF/MANIFEST.MF b/org.eclipse.jdt.apt.pluggable.tests/META-INF/MANIFEST.MF index 43c523c9a87..1b6d60b40bb 100644 --- a/org.eclipse.jdt.apt.pluggable.tests/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.apt.pluggable.tests/META-INF/MANIFEST.MF @@ -14,9 +14,7 @@ Require-Bundle: org.junit, org.eclipse.jdt.core.tests.compiler, org.eclipse.test.performance, org.eclipse.jdt.core;bundle-version="[3.40.0,4.0.0)", - org.eclipse.ui.ide, - org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional, - org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional + org.eclipse.ui.ide Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName Bundle-RequiredExecutionEnvironment: JavaSE-17 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 409dd20af6f..eae9c3536e3 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 @@ -2231,6 +2231,8 @@ public interface IProblem { int PreviewAPIUsed = Compliance + 1108; /** @since 3.39*/ int JavaVersionNotSupported = Compliance + 1109; + /** @since 3.40*/ + int JavaVersionTooRecent = Compliance + 1110; /** @since 3.13 */ int UnlikelyCollectionMethodArgumentType = 1200; @@ -2536,7 +2538,9 @@ public interface IProblem { int SealedPermittedTypeOutsideOfPackage = TypeRelated + 1859; /** @since 3.28 */ int SealedSealedTypeMissingPermits = TypeRelated + 1860; - /** @since 3.28 */ + /** @since 3.28 + * @deprecated problem no longer generated + */ int SealedInterfaceIsSealedAndNonSealed = TypeRelated + 1861; /** @since 3.28 */ int SealedDisAllowedNonSealedModifierInInterface = TypeRelated + 1862; @@ -2546,11 +2550,19 @@ public interface IProblem { int SealedLocalDirectSuperTypeSealed = TypeRelated + 1864; /** @since 3.28 */ int SealedAnonymousClassCannotExtendSealedType = TypeRelated + 1865; - /** @since 3.28 */ + /** @since 3.28 + * @deprecated problem no longer generated + */ int SealedSuperTypeInDifferentPackage = TypeRelated + 1866; - /** @since 3.28 */ + /** @since 3.28 + * @deprecated problem no longer generated + */ int SealedSuperTypeDisallowed = TypeRelated + 1867; - /* Java15 errors - end */ + /** + * @since 3.40 + */ + int FunctionalInterfaceMayNotbeSealed = TypeRelated + 1868; + /* Java17 Sealed types errors - end */ /** * @since 3.28 diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java index 1dc5edb52cf..354576a4d49 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -385,8 +385,7 @@ else if (this.referenceBinding.isAnnotationType()) attributesNumber += generateBootstrapMethods(this.bootstrapMethods); } if (this.targetJDK >= ClassFileConstants.JDK17) { - // add record attributes - attributesNumber += generatePermittedTypeAttributes(); + attributesNumber += generatePermittedSubclassesAttribute(); } // Inner class attribute int numberOfInnerClasses = this.innerClassesBindings == null ? 0 : this.innerClassesBindings.size(); @@ -2852,10 +2851,9 @@ private int generateNestAttributes() { nAttrs += generateNestHostAttribute(); return nAttrs; } - private int generatePermittedTypeAttributes() { - SourceTypeBinding type = this.referenceBinding; + private int generatePermittedSubclassesAttribute() { int localContentsOffset = this.contentsOffset; - ReferenceBinding[] permittedTypes = type.permittedTypes(); + ReferenceBinding[] permittedTypes = this.referenceBinding.permittedTypes(); int l = permittedTypes != null ? permittedTypes.length : 0; if (l == 0) return 0; @@ -2864,6 +2862,14 @@ private int generatePermittedTypeAttributes() { if (exSize + localContentsOffset >= this.contents.length) { resizeContents(exSize); } + /* + * PermittedSubclasses_attribute { + u2 attribute_name_index; + u4 attribute_length; + u2 number_of_classes; + u2 classes[number_of_classes]; + } + */ int attributeNameIndex = this.constantPool.literalIndex(AttributeNamesConstants.PermittedSubclasses); this.contents[localContentsOffset++] = (byte) (attributeNameIndex >> 8); @@ -5836,7 +5842,6 @@ private int generateTypeAnnotationAttributeForTypeDeclaration() { superInterface.getAllAnnotationContexts(AnnotationTargetTypeConstants.CLASS_EXTENDS, i, allTypeAnnotationContexts); } } - // TODO: permittedTypes codegen TypeParameter[] typeParameters = typeDeclaration.typeParameters; if (typeParameters != null) { for (int i = 0, max = typeParameters.length; i < max; i++) { @@ -6038,7 +6043,7 @@ public void initialize(SourceTypeBinding aType, ClassFile parentClassFile, boole if (aType.isAnonymousType()) { ReferenceBinding superClass = aType.superclass; if (superClass == null || !(superClass.isEnum() && superClass.isSealed())) - accessFlags &= ~ClassFileConstants.AccFinal; + accessFlags &= ~ClassFileConstants.AccFinal; } int finalAbstract = ClassFileConstants.AccFinal | ClassFileConstants.AccAbstract; if ((accessFlags & finalAbstract) == finalAbstract) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/apt/model/Factory.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/apt/model/Factory.java index f4f7316bf84..e99a32406c4 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/apt/model/Factory.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/apt/model/Factory.java @@ -189,14 +189,14 @@ private static void decodeModifiers(Set result, int modifiers, int[] c try { appendModifier(result, modifiers, checkBit, Modifier.valueOf("NON_SEALED")); //$NON-NLS-1$ } catch(IllegalArgumentException iae) { - // Don't have JDK 15, just ignore and proceed. + // Don't have JDK 17, just ignore and proceed. } break; case ExtraCompilerModifiers.AccSealed : try { appendModifier(result, modifiers, checkBit, Modifier.valueOf("SEALED")); //$NON-NLS-1$ } catch(IllegalArgumentException iae) { - // Don't have JDK 15, just ignore and proceed. + // Don't have JDK 17, just ignore and proceed. } break; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java index a443980ab9c..85be2055391 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ASTNode.java @@ -58,6 +58,8 @@ import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationPosition; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.AccessRestriction; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.impl.StringConstant; import org.eclipse.jdt.internal.compiler.lookup.*; @SuppressWarnings({"rawtypes", "unchecked"}) @@ -498,6 +500,8 @@ public final boolean isFieldUseDeprecated(FieldBinding field, Scope scope, int f // inside same unit - no report if (scope.isDefinedInSameUnit(field.declaringClass)) return false; + if (sinceValueUnreached(field, scope)) return false; + // if context is deprecated, may avoid reporting if (!scope.compilerOptions().reportDeprecationInsideDeprecatedCode && scope.isInsideDeprecatedCode()) return false; return true; @@ -548,6 +552,8 @@ public final boolean isMethodUseDeprecated(MethodBinding method, Scope scope, // inside same unit - no report if (scope.isDefinedInSameUnit(method.declaringClass)) return false; + if (sinceValueUnreached(method, scope)) return false; + // non explicit use and non explicitly deprecated - no report if (!isExplicitUse && (method.modifiers & ClassFileConstants.AccDeprecated) == 0) { @@ -559,6 +565,37 @@ public final boolean isMethodUseDeprecated(MethodBinding method, Scope scope, return true; } + private boolean sinceValueUnreached(Binding binding, Scope scope) { + if (binding instanceof TypeBinding typeBinding) { + if (!typeBinding.isReadyForAnnotations()) { + return false; + } + } + AnnotationBinding[] annotations = binding.getAnnotations(); + for (AnnotationBinding annotation : annotations) { + if (annotation != null && annotation.getAnnotationType().id == TypeIds.T_JavaLangDeprecated) { + ElementValuePair[] pairs = annotation.getElementValuePairs(); + for (ElementValuePair pair : pairs) { + if (CharOperation.equals(pair.getName(), TypeConstants.SINCE)) { + if (pair.getValue() instanceof StringConstant strConstant) { + try { + String value = strConstant.stringValue(); + long sinceLevel = CompilerOptions.versionToJdkLevel(value); + long complianceLevel = scope.compilerOptions().complianceLevel; + if (complianceLevel < sinceLevel) { + return true; + } + } catch (NumberFormatException e) { + // do nothing and fall through + } + } + } + } + } + } + return false; + } + public boolean isSuper() { return false; @@ -619,6 +656,8 @@ public final boolean isTypeUseDeprecated(TypeBinding type, Scope scope) { // inside same unit - no report if (scope.isDefinedInSameUnit(refType)) return false; + if (sinceValueUnreached(refType, scope)) return false; + // if context is deprecated, may avoid reporting if (!scope.compilerOptions().reportDeprecationInsideDeprecatedCode && scope.isInsideDeprecatedCode()) return false; return true; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java index edead604e46..fdd29789637 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java @@ -546,7 +546,13 @@ public TypeBinding resolveType(BlockScope scope) { protected void checkEarlyConstructionContext(BlockScope scope) { if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(scope.compilerOptions()) && this.type != null && this.type.resolvedType instanceof ReferenceBinding currentType) { - TypeBinding uninitialized = scope.getMatchingUninitializedType(currentType, !currentType.isLocalType()); + // only enclosing types of non-static member types are relevant + if (currentType.isStatic() || currentType.isLocalType()) + return; + currentType = currentType.enclosingType(); + if (currentType == null) + return; + TypeBinding uninitialized = scope.getMatchingUninitializedType(currentType, true); if (uninitialized != null) scope.problemReporter().allocationInEarlyConstructionContext(this, this.resolvedType, uninitialized); } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java index 3de3773e464..77cb7cc0f05 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java @@ -371,6 +371,8 @@ private Constant resolveCasePattern(BlockScope scope, TypeBinding caseType, Type if (type != null) { constant = IntConstant.fromValue(switchStatement.constantIndex); switchStatement.caseLabelElements.add(e); + if (e instanceof RecordPattern) + switchStatement.containsRecordPatterns = true; if (isUnguarded) switchStatement.caseLabelElementTypes.add(type); @@ -379,11 +381,8 @@ private Constant resolveCasePattern(BlockScope scope, TypeBinding caseType, Type // The following code is copied from InstanceOfExpression#resolve() // But there are enough differences to warrant a copy if (!type.isReifiable()) { - if (expressionType != TypeBinding.NULL && !(e instanceof RecordPattern)) { - boolean isLegal = e.checkCastTypesCompatibility(scope, type, expressionType, e, false); - if (!isLegal || (e.bits & ASTNode.UnsafeCast) != 0) { - scope.problemReporter().unsafeCastInInstanceof(e, type, expressionType); - } + if (!e.isApplicable(switchExpressionType, scope, e)) { + return Constant.NotAConstant; } } else if (type.isValidBinding()) { // if not a valid binding, an error has already been reported for unresolved type diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CastExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CastExpression.java index d4463f4554e..c6c6ebf3edd 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CastExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CastExpression.java @@ -430,24 +430,21 @@ public static boolean checkUnsafeCast(Expression expression, Scope scope, TypeBi ParameterizedTypeBinding paramCastType = (ParameterizedTypeBinding) castType; TypeBinding[] castArguments = paramCastType.arguments; int length = castArguments == null ? 0 : castArguments.length; - if ((paramCastType.tagBits & (TagBits.HasDirectWildcard|TagBits.HasTypeVariable)) != 0) { - // verify alternate cast type, substituting different type arguments - for (int i = 0; i < length; i++) { - if (castArguments[i].isUnboundWildcard()) - continue; - TypeBinding[] alternateArguments; - // need to clone for each iteration to avoid env paramtype cache interference - System.arraycopy(paramCastType.arguments, 0, alternateArguments = new TypeBinding[length], 0, length); - alternateArguments[i] = TypeBinding.equalsEquals(paramCastType.arguments[i], scope.getJavaLangObject()) ? scope.getJavaLangBoolean() : scope.getJavaLangObject(); - LookupEnvironment environment = scope.environment(); - ParameterizedTypeBinding alternateCastType = environment.createParameterizedType((ReferenceBinding)castType.erasure(), alternateArguments, castType.enclosingType()); - if (TypeBinding.equalsEquals(alternateCastType.findSuperTypeOriginatingFrom(expressionType), match)) { - expression.bits |= ASTNode.UnsafeCast; - break; - } + // verify alternate cast type, substituting different type arguments + for (int i = 0; i < length; i++) { + if (castArguments[i].isUnboundWildcard()) + continue; + TypeBinding[] alternateArguments; + // need to clone for each iteration to avoid env paramtype cache interference + System.arraycopy(paramCastType.arguments, 0, alternateArguments = new TypeBinding[length], 0, length); + alternateArguments[i] = TypeBinding.equalsEquals(paramCastType.arguments[i], scope.getJavaLangObject()) ? scope.getJavaLangBoolean() : scope.getJavaLangObject(); + LookupEnvironment environment = scope.environment(); + ParameterizedTypeBinding alternateCastType = environment.createParameterizedType((ReferenceBinding)castType.erasure(), alternateArguments, castType.enclosingType()); + if (TypeBinding.equalsEquals(alternateCastType.findSuperTypeOriginatingFrom(expressionType), match)) { + expression.bits |= ASTNode.UnsafeCast; + break; } } - // Type arguments added by subtypes of S and removed by supertypes of T don't need to be checked since the type arguments aren't specified by either S or T return true; } else { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java index 2b3eda3b6b4..880d8286f45 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/ConditionalExpression.java @@ -36,7 +36,6 @@ import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.VANILLA_CONTEXT; import org.eclipse.jdt.internal.compiler.ASTVisitor; -import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; import org.eclipse.jdt.internal.compiler.flow.FlowContext; @@ -75,7 +74,6 @@ public class ConditionalExpression extends OperatorExpression implements IPolyEx private boolean isPolyExpression = false; private TypeBinding originalValueIfTrueType; private TypeBinding originalValueIfFalseType; - private boolean use18specifics; public ConditionalExpression(Expression condition, Expression valueIfTrue, Expression valueIfFalse) { this.condition = condition; @@ -473,17 +471,11 @@ public StringBuilder printExpressionNoParenthesis(int indent, StringBuilder outp public TypeBinding resolveType(BlockScope scope) { // JLS3 15.25 LookupEnvironment env = scope.environment(); - final long sourceLevel = scope.compilerOptions().sourceLevel; - boolean use15specifics = sourceLevel >= ClassFileConstants.JDK1_5; - this.use18specifics = sourceLevel >= ClassFileConstants.JDK1_8; - - if (this.use18specifics) { - if (this.expressionContext == ASSIGNMENT_CONTEXT || this.expressionContext == INVOCATION_CONTEXT) { - this.valueIfTrue.setExpressionContext(this.expressionContext); - this.valueIfTrue.setExpectedType(this.expectedType); - this.valueIfFalse.setExpressionContext(this.expressionContext); - this.valueIfFalse.setExpectedType(this.expectedType); - } + if (this.expressionContext == ASSIGNMENT_CONTEXT || this.expressionContext == INVOCATION_CONTEXT) { + this.valueIfTrue.setExpressionContext(this.expressionContext); + this.valueIfTrue.setExpectedType(this.expectedType); + this.valueIfFalse.setExpressionContext(this.expressionContext); + this.valueIfFalse.setExpectedType(this.expectedType); } if (this.constant != Constant.NotAConstant) { @@ -556,7 +548,7 @@ public TypeBinding resolveType(BlockScope scope) { TypeBinding valueIfTrueType = this.originalValueIfTrueType; TypeBinding valueIfFalseType = this.originalValueIfFalseType; - if (use15specifics && TypeBinding.notEquals(valueIfTrueType, valueIfFalseType)) { + if (TypeBinding.notEquals(valueIfTrueType, valueIfFalseType)) { if (valueIfTrueType.isBaseType()) { if (valueIfFalseType.isBaseType()) { // bool ? baseType : baseType @@ -667,48 +659,27 @@ public TypeBinding resolveType(BlockScope scope) { } // Type references (null null is already tested) if (valueIfTrueType.isBaseType() && valueIfTrueType != TypeBinding.NULL) { - if (use15specifics) { - valueIfTrueType = env.computeBoxingType(valueIfTrueType); - } else { - scope.problemReporter().conditionalArgumentsIncompatibleTypes(this, valueIfTrueType, valueIfFalseType); - return null; - } + valueIfTrueType = env.computeBoxingType(valueIfTrueType); } if (valueIfFalseType.isBaseType() && valueIfFalseType != TypeBinding.NULL) { - if (use15specifics) { - valueIfFalseType = env.computeBoxingType(valueIfFalseType); - } else { - scope.problemReporter().conditionalArgumentsIncompatibleTypes(this, valueIfTrueType, valueIfFalseType); - return null; - } + valueIfFalseType = env.computeBoxingType(valueIfFalseType); } - if (use15specifics) { - // >= 1.5 : LUB(operand types) must exist - TypeBinding commonType = null; - if (valueIfTrueType == TypeBinding.NULL) { - commonType = valueIfFalseType.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull - } else if (valueIfFalseType == TypeBinding.NULL) { - commonType = valueIfTrueType.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull - } else { - commonType = scope.lowerUpperBound(new TypeBinding[] { valueIfTrueType, valueIfFalseType }); - } - if (commonType != null) { - this.valueIfTrue.computeConversion(scope, commonType, this.originalValueIfTrueType); - this.valueIfFalse.computeConversion(scope, commonType, this.originalValueIfFalseType); - return this.resolvedType = commonType.capture(scope, this.sourceStart, this.sourceEnd); - } + + // >= 1.5 : LUB(operand types) must exist + TypeBinding commonType = null; + if (valueIfTrueType == TypeBinding.NULL) { + commonType = valueIfFalseType.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull + } else if (valueIfFalseType == TypeBinding.NULL) { + commonType = valueIfTrueType.withoutToplevelNullAnnotation(); // null on other branch invalidates any @NonNull } else { - // < 1.5 : one operand must be convertible to the other - if (valueIfFalseType.isCompatibleWith(valueIfTrueType)) { - this.valueIfTrue.computeConversion(scope, valueIfTrueType, this.originalValueIfTrueType); - this.valueIfFalse.computeConversion(scope, valueIfTrueType, this.originalValueIfFalseType); - return this.resolvedType = valueIfTrueType; - } else if (valueIfTrueType.isCompatibleWith(valueIfFalseType)) { - this.valueIfTrue.computeConversion(scope, valueIfFalseType, this.originalValueIfTrueType); - this.valueIfFalse.computeConversion(scope, valueIfFalseType, this.originalValueIfFalseType); - return this.resolvedType = valueIfFalseType; - } + commonType = scope.lowerUpperBound(new TypeBinding[] { valueIfTrueType, valueIfFalseType }); + } + if (commonType != null) { + this.valueIfTrue.computeConversion(scope, commonType, this.originalValueIfTrueType); + this.valueIfFalse.computeConversion(scope, commonType, this.originalValueIfFalseType); + return this.resolvedType = commonType.capture(scope, this.sourceStart, this.sourceEnd); } + scope.problemReporter().conditionalArgumentsIncompatibleTypes( this, valueIfTrueType, @@ -816,9 +787,6 @@ public boolean isFunctionalType() { @Override public boolean isPolyExpression() throws UnsupportedOperationException { - if (!this.use18specifics) - return false; - if (this.isPolyExpression) return true; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java index 9d4e81fe9b0..8ef74f12e1c 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/EitherOrMultiPattern.java @@ -54,6 +54,13 @@ public void setIsGuarded() { this.patterns[i].setIsGuarded(); } + @Override + public void setOuterExpressionType(TypeBinding expressionType) { + super.setOuterExpressionType(expressionType); + for (int i = 0; i < this.patternsCount; i++) + this.patterns[i].setOuterExpressionType(expressionType); + } + @Override public TypeBinding resolveType(BlockScope scope) { boolean hasError = false; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java index a4317e9c066..47e6f2b846f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/GuardedPattern.java @@ -76,6 +76,12 @@ public void setIsEitherOrPattern() { this.primaryPattern.setIsEitherOrPattern(); } + @Override + public void setOuterExpressionType(TypeBinding expressionType) { + super.setOuterExpressionType(expressionType); + this.primaryPattern.setOuterExpressionType(expressionType); + } + @Override public boolean coversType(TypeBinding type, Scope scope) { return isUnguarded() && this.primaryPattern.coversType(type, scope); @@ -130,7 +136,7 @@ public void traverse(ASTVisitor visitor, BlockScope scope) { } @Override - protected boolean isApplicable(TypeBinding other, BlockScope scope) { - return this.primaryPattern.isApplicable(other, scope); + protected boolean isApplicable(TypeBinding expressionType, BlockScope scope, ASTNode location) { + return this.primaryPattern.isApplicable(expressionType, scope, location); } } \ No newline at end of file diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java index fa23ce53502..1972bd87b09 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/InstanceOfExpression.java @@ -277,6 +277,13 @@ public TypeBinding resolveType(BlockScope scope) { if (expressionType == null || checkedType == null) return null; + if (this.pattern != null) { + if (this.pattern.isApplicable(expressionType, scope, this)) { + checkForPrimitives(scope, checkedType, expressionType); + } + return this.resolvedType = TypeBinding.BOOLEAN; + } + if (!checkedType.isReifiable()) { CompilerOptions options = scope.compilerOptions(); // Report same as before for older compliances @@ -286,7 +293,7 @@ public TypeBinding resolveType(BlockScope scope) { if (expressionType != TypeBinding.NULL) { boolean isLegal = checkCastTypesCompatibility(scope, checkedType, expressionType, this.expression, true); if (!isLegal || (this.bits & ASTNode.UnsafeCast) != 0) { - scope.problemReporter().unsafeCastInInstanceof(this.expression, checkedType, expressionType); + scope.problemReporter().unsafeCastInTestingContext(this.expression, checkedType, expressionType); } else { checkRefForPrimitivesAndAddSecretVariable(scope, checkedType, expressionType); } @@ -294,17 +301,18 @@ public TypeBinding resolveType(BlockScope scope) { } } else if (checkedType.isValidBinding()) { // if not a valid binding, an error has already been reported for unresolved type - if ((expressionType != TypeBinding.NULL && expressionType.isBaseType()) // disallow autoboxing - || checkedType.isBaseType() - || !checkCastTypesCompatibility(scope, checkedType, expressionType, null, true)) { - checkForPrimitives(scope, checkedType, expressionType); - } + checkForPrimitives(scope, checkedType, expressionType); } return this.resolvedType = TypeBinding.BOOLEAN; } private void checkForPrimitives(BlockScope scope, TypeBinding checkedType, TypeBinding expressionType) { + boolean needToCheck = (expressionType != TypeBinding.NULL && expressionType.isBaseType()) // disallow autoboxing + || checkedType.isBaseType() + || !checkCastTypesCompatibility(scope, checkedType, expressionType, null, true); + if (!needToCheck) + return; PrimitiveConversionRoute route = Pattern.findPrimitiveConversionRoute(checkedType, expressionType, scope); this.testContextRecord = new TestContextRecord(checkedType, expressionType, route); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java index f07c01e55a3..15d3fdad800 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Pattern.java @@ -139,25 +139,37 @@ public TypeReference getType() { } // 14.30.3 Properties of Patterns: A pattern p is said to be applicable at a type T if ... - protected boolean isApplicable(TypeBinding other, BlockScope scope) { - TypeBinding patternType = this.resolvedType; - if (patternType == null) // ill resolved pattern - return false; + protected boolean isApplicable(TypeBinding expressionType, BlockScope scope, ASTNode location) { + if (expressionType == TypeBinding.NULL) + return true; + TypeReference typeRef = getType(); + if (typeRef == null) + return true; // nothing to be checked for wildcard '_' + TypeBinding patternType = typeRef.resolvedType; + if (patternType == null || !patternType.isValidBinding() || !expressionType.isValidBinding()) + return false; // problem already reported + // 14.30.3 Properties of Patterns doesn't allow boxing nor unboxing, primitive widening/narrowing (< JLS23) - if (patternType.isBaseType() != other.isBaseType() && !JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) { - scope.problemReporter().incompatiblePatternType(this, other, patternType); + if (patternType.isBaseType() != expressionType.isBaseType() && !JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) { + scope.problemReporter().notCompatibleTypesError(location, expressionType, patternType); return false; } if (patternType.isBaseType()) { PrimitiveConversionRoute route = Pattern.findPrimitiveConversionRoute(this.resolvedType, this.outerExpressionType, scope); - if (!TypeBinding.equalsEquals(other, patternType) + if (!TypeBinding.equalsEquals(expressionType, patternType) && route == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) { - scope.problemReporter().incompatiblePatternType(this, other, patternType); + scope.problemReporter().notCompatibleTypesError(location, expressionType, patternType); + return false; + } + } else { + if (!checkCastTypesCompatibility(scope, patternType, expressionType, null, true)) { + scope.problemReporter().notCompatibleTypesError(location, expressionType, patternType); + return false; + } + if ((this.bits & ASTNode.UnsafeCast) != 0) { + scope.problemReporter().unsafeCastInTestingContext(location, patternType, this.outerExpressionType); return false; } - } else if (!checkCastTypesCompatibility(scope, other, patternType, null, true)) { - scope.problemReporter().incompatiblePatternType(this, other, patternType); - return false; } return true; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/QualifiedSuperReference.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/QualifiedSuperReference.java index d747e21bfc6..04f2649ec63 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/QualifiedSuperReference.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/QualifiedSuperReference.java @@ -74,8 +74,13 @@ public TypeBinding resolveType(BlockScope scope) { ReferenceBinding enclosingReceiverType = scope.enclosingReceiverType(); // interface-qualified this selects a default method in a super interface, // but here we are only interested in supers of *enclosing instances*: - if (enclosingReceiverType != null && !enclosingReceiverType.isInterface() && scope.isInsideEarlyConstructionContext(enclosingReceiverType, false)) - scope.problemReporter().errorExpressionInEarlyConstructionContext(this); + if (enclosingReceiverType != null && !enclosingReceiverType.isInterface()) { + TypeBinding typeToCheck = (enclosingReceiverType.isCompatibleWith(this.resolvedType)) + ? enclosingReceiverType // cannot reference super of the current type + : this.resolvedType; // assumeably not a super but an outer type + if (scope.isInsideEarlyConstructionContext(typeToCheck, false)) + scope.problemReporter().errorExpressionInEarlyConstructionContext(this); + } return this.resolvedType = (this.currentCompatibleType.isInterface() ? this.currentCompatibleType : this.currentCompatibleType.superclass()); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java index 1e64ee3e452..bc26d4f3489 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/RecordPattern.java @@ -152,7 +152,7 @@ public TypeBinding resolveType(BlockScope scope) { } } TypeBinding componentType = componentBinding.type; - if (p1.isApplicable(componentType, scope)) { + if (p1.isApplicable(componentType, scope, p1)) { p1.isTotalTypeNode = p1.coversType(componentType, scope); MethodBinding[] methods = this.resolvedType.getMethods(componentBinding.name); if (methods != null && methods.length > 0) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java index d31d16b2cb1..84545305744 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java @@ -76,8 +76,8 @@ public ExpressionContext getExpressionContext() { return this.expressionContext; } @Override - protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions, boolean isEnumSwitch) { - return isEnumSwitch; // mandatory error if not enum in switch expressions + protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions) { + return true; } @Override protected void reportMissingEnumConstantCase(BlockScope upperScope, FieldBinding enumConstant) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java index 537fb6e2591..b056f763969 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java @@ -25,7 +25,10 @@ import java.lang.invoke.ConstantBootstraps; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.function.IntPredicate; import org.eclipse.jdt.core.compiler.CharOperation; @@ -43,7 +46,15 @@ import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.impl.JavaFeature; -import org.eclipse.jdt.internal.compiler.lookup.*; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; +import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.RecordComponentBinding; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; @SuppressWarnings("rawtypes") @@ -76,6 +87,7 @@ public static record SingletonBootstrap(String id, char[] selector, char[] signa public int switchBits; public boolean containsPatterns; + public boolean containsRecordPatterns; public boolean containsNull; boolean nullProcessed = false; BranchLabel switchPatternRestartTarget; @@ -355,8 +367,7 @@ public boolean visit(TNode node) { } } if (node.type instanceof ReferenceBinding ref && ref.isSealed()) { - List allAllowedTypes = ref.getAllEnumerableReferenceTypes(); - this.covers &= isExhaustiveWithCaseTypes(allAllowedTypes, availableTypes); + this.covers &= caseElementsCoverSealedType(ref, availableTypes); return this.covers; } this.covers = false; @@ -813,13 +824,6 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { int max = localKeysCopy[constantCount - 1]; int min = localKeysCopy[0]; if ((long) (constantCount * 2.5) > ((long) max - (long) min)) { - - // work-around 1.3 VM bug, if max>0x7FFF0000, must use lookup bytecode - // see http://dev.eclipse.org/bugs/show_bug.cgi?id=21557 - if (max > 0x7FFF0000 && currentScope.compilerOptions().complianceLevel < ClassFileConstants.JDK1_4) { - codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels); - - } else { codeStream.tableswitch( defaultLabel, min, @@ -828,7 +832,6 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { sortedIndexes, this.constMapping, caseLabels); - } } else { codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels); } @@ -875,7 +878,6 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { if (this.preSwitchInitStateIndex != -1) { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); } - defaultLabel.place(); /* a default case is not needed for an exhaustive switch expression * we need to handle the default case to throw an error in order to make the stack map consistent. * All cases will return a value on the stack except the missing default case. @@ -884,9 +886,10 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { CompilerOptions compilerOptions = this.scope != null ? this.scope.compilerOptions() : null; if (compilerOptions.complianceLevel >= ClassFileConstants.JDK19) { // since 19 we have MatchException for this - if (codeStream.lastAbruptCompletion != codeStream.position) { + if (this.statements.length > 0 && this.statements[this.statements.length - 1].canCompleteNormally()) { codeStream.goto_(this.breakLabel); // hop, skip and jump over match exception throw. } + defaultLabel.place(); codeStream.newJavaLangMatchException(); codeStream.dup(); codeStream.aconst_null(); @@ -895,6 +898,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { codeStream.athrow(); } else { // old style using IncompatibleClassChangeError: + defaultLabel.place(); codeStream.newJavaLangIncompatibleClassChangeError(); codeStream.dup(); codeStream.invokeJavaLangIncompatibleClassChangeErrorDefaultConstructor(); @@ -1098,11 +1102,9 @@ boolean isAllowedType(TypeBinding type) { @Override public void resolve(BlockScope upperScope) { try { - boolean isEnumSwitch = false; boolean isStringSwitch = false; TypeBinding expressionType = this.expression.resolveType(upperScope); CompilerOptions compilerOptions = upperScope.compilerOptions(); - boolean isEnhanced = checkAndSetEnhanced(upperScope, expressionType); if (expressionType != null) { this.expression.computeConversion(upperScope, expressionType, expressionType); checkType: { @@ -1118,15 +1120,11 @@ public void resolve(BlockScope upperScope) { if (expressionType.isCompatibleWith(TypeBinding.INT)) break checkType; } else if (expressionType.isEnum()) { - isEnumSwitch = true; - if (compilerOptions.complianceLevel < ClassFileConstants.JDK1_5) { - upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=360317 - } break checkType; } else if (!this.containsPatterns && !this.containsNull && upperScope.isBoxingCompatibleWith(expressionType, TypeBinding.INT)) { this.expression.computeConversion(upperScope, TypeBinding.INT, expressionType); break checkType; - } else if (compilerOptions.complianceLevel >= ClassFileConstants.JDK1_7 && expressionType.id == TypeIds.T_JavaLangString) { + } else if (expressionType.id == TypeIds.T_JavaLangString) { if (this.containsPatterns || this.containsNull) { isStringSwitch = !JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions); this.isNonTraditional = true; @@ -1283,63 +1281,99 @@ && defaultFound && isExhaustive()) { } reportMixingCaseTypes(); - // check default case for non-enum switch: - boolean flagged = checkAndFlagDefaultSealed(upperScope, compilerOptions); - if (!flagged && this.defaultCase == null) { - if (ignoreMissingDefaultCase(compilerOptions, isEnumSwitch) && isEnumSwitch) { - upperScope.methodScope().hasMissingSwitchDefault = true; - } else { - if (!isEnumSwitch && !isExhaustive()) { + complainIfNotExhaustiveSwitch(upperScope, expressionType, compilerOptions); + + } finally { + if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block + } + } + private void complainIfNotExhaustiveSwitch(BlockScope upperScope, TypeBinding selectorType, CompilerOptions compilerOptions) { + + boolean isEnhanced = isEnhancedSwitch(upperScope, selectorType); + if (selectorType != null && selectorType.isEnum()) { + if (isEnhanced) + this.switchBits |= SwitchStatement.Exhaustive; // negated below if found otherwise + if (this.defaultCase != null && !compilerOptions.reportMissingEnumCaseDespiteDefault) + return; + + int constantCount = this.otherConstants == null ? 0 : this.otherConstants.length; + if (!((this.switchBits & TotalPattern) != 0) && + ((this.containsPatterns || this.containsNull) || + (constantCount >= this.caseCount && + constantCount != ((ReferenceBinding)selectorType).enumConstantCount()))) { + Set unenumeratedConstants = unenumeratedConstants((ReferenceBinding) selectorType, constantCount); + if (unenumeratedConstants.size() != 0) { + this.switchBits &= ~SwitchStatement.Exhaustive; + if (!(this.defaultCase != null && (this.defaultCase.bits & DocumentedCasesOmitted) != 0)) { if (isEnhanced) upperScope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); - else - upperScope.problemReporter().missingDefaultCase(this, isEnumSwitch, expressionType); - } - } - } - // Exhaustiveness check for enum switch - if (isEnumSwitch && compilerOptions.complianceLevel >= ClassFileConstants.JDK1_5) { - if (this.defaultCase == null || compilerOptions.reportMissingEnumCaseDespiteDefault) { - int constantCount = this.otherConstants == null ? 0 : this.otherConstants.length; - if (isEnhanced) - this.switchBits |= SwitchStatement.Exhaustive; // negated below if found otherwise - if (!((this.switchBits & TotalPattern) != 0) && - ((this.containsPatterns || this.containsNull) || - (constantCount >= this.caseCount && - constantCount != ((ReferenceBinding)expressionType).enumConstantCount()))) { - FieldBinding[] enumFields = ((ReferenceBinding) expressionType.erasure()).fields(); - for (int i = 0, max = enumFields.length; i < max; i++) { - FieldBinding enumConstant = enumFields[i]; - if ((enumConstant.modifiers & ClassFileConstants.AccEnum) == 0) continue; - findConstant : { - for (int j = 0; j < constantCount; j++) { - if ((enumConstant.id + 1) == this.otherConstants[j].c.intValue()) // zero should not be returned see bug 141810 - break findConstant; - } - this.switchBits &= ~SwitchStatement.Exhaustive; - // enum constant did not get referenced from switch - boolean suppress = (this.defaultCase != null && (this.defaultCase.bits & DocumentedCasesOmitted) != 0); - if (!suppress) { - if (isEnhanced) - upperScope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); - else - reportMissingEnumConstantCase(upperScope, enumConstant); - } + else { + for (FieldBinding enumConstant : unenumeratedConstants) { + reportMissingEnumConstantCase(upperScope, enumConstant); } } } } - if (this.defaultCase == null) { - if (ignoreMissingDefaultCase(compilerOptions, isEnumSwitch)) { - upperScope.methodScope().hasMissingSwitchDefault = true; - } else { - upperScope.problemReporter().missingDefaultCase(this, isEnumSwitch, expressionType); + } + + if (this.defaultCase == null) { + if (ignoreMissingDefaultCase(compilerOptions)) { + upperScope.methodScope().hasMissingSwitchDefault = true; + } else { + upperScope.problemReporter().missingDefaultCase(this, true, selectorType); + } + } + return; + } + + if (isExhaustive() || this.defaultCase != null || selectorType == null) { + if (isEnhanced) + this.switchBits |= SwitchStatement.Exhaustive; + return; + } + + if (JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) && selectorType.isSealed() && caseElementsCoverSealedType((ReferenceBinding) selectorType, this.caseLabelElementTypes)) { + this.switchBits |= SwitchStatement.Exhaustive; + return; + } + + if (selectorType.isRecordWithComponents() && this.containsRecordPatterns && caseElementsCoverRecordType(upperScope, compilerOptions, (ReferenceBinding) selectorType)) { + this.switchBits |= SwitchStatement.Exhaustive; + return; + } + + if (!isExhaustive()) { + if (isEnhanced) + upperScope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); + else + upperScope.problemReporter().missingDefaultCase(this, false, selectorType); + } + } + + // Return the set of enumerations belonging to the selector enum type that are NOT listed in case statements. + private Set unenumeratedConstants(ReferenceBinding enumType, int constantCount) { + FieldBinding[] enumFields = ((ReferenceBinding) enumType.erasure()).fields(); + Set unenumerated = new HashSet<>(Arrays.asList(enumFields)); + for (int i = 0, max = enumFields.length; i < max; i++) { + FieldBinding enumConstant = enumFields[i]; + if ((enumConstant.modifiers & ClassFileConstants.AccEnum) == 0) { + unenumerated.remove(enumConstant); + continue; + } + for (int j = 0; j < constantCount; j++) { + if (TypeBinding.equalsEquals(this.otherConstants[j].e.resolvedType, enumType)) { + if (this.otherConstants[j].e instanceof NameReference reference) { + FieldBinding field = reference.fieldBinding(); + int intValue = field.original().id + 1; + if ((enumConstant.id + 1) == intValue) { // zero should not be returned see bug 141810 + unenumerated.remove(enumConstant); + break; + } } } } - } finally { - if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block } + return unenumerated; } private boolean isCaseStmtNullDefault(CaseStatement caseStmt) { return caseStmt != null @@ -1356,9 +1390,9 @@ private boolean isExhaustive() { return (this.switchBits & SwitchStatement.Exhaustive) != 0; } - private boolean checkAndSetEnhanced(BlockScope upperScope, TypeBinding expressionType) { + private boolean isEnhancedSwitch(BlockScope upperScope, TypeBinding expressionType) { if (JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(upperScope.compilerOptions()) - && expressionType != null && !(this instanceof SwitchExpression )) { + && expressionType != null && !(this instanceof SwitchExpression)) { boolean acceptableType = !expressionType.isEnum(); switch (expressionType.id) { @@ -1382,7 +1416,7 @@ private boolean checkAndSetEnhanced(BlockScope upperScope, TypeBinding expressio return true; } } - if (expressionType != null && JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(upperScope.compilerOptions())) { + if (expressionType != null && !(this instanceof SwitchExpression) && JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(upperScope.compilerOptions())) { switch (expressionType.id) { case TypeIds.T_float: case TypeIds.T_double: @@ -1397,86 +1431,47 @@ private boolean checkAndSetEnhanced(BlockScope upperScope, TypeBinding expressio } return false; } - private boolean checkAndFlagDefaultSealed(BlockScope skope, CompilerOptions compilerOptions) { - if (this.defaultCase != null) { // mark covered as a side effect (since covers is intro in 406) - this.switchBits |= SwitchStatement.Exhaustive; - return false; - } - boolean checkSealed = this.containsPatterns - && JavaFeature.SEALED_CLASSES.isSupported(compilerOptions) - && JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) - && this.expression.resolvedType instanceof ReferenceBinding; - if (!checkSealed) return false; - ReferenceBinding ref = (ReferenceBinding) this.expression.resolvedType; - if (!(ref.isClass() || ref.isInterface() || ref.isTypeVariable() || ref.isIntersectionType())) - return false; - if (ref.isRecord()) { - boolean isRecordPattern = false; - for (Pattern pattern : this.caseLabelElements) { - if (pattern instanceof RecordPattern) { - isRecordPattern = true; - break; - } - } - if (isRecordPattern) - return checkAndFlagDefaultRecord(skope, compilerOptions, ref); - } - if (!ref.isSealed()) return false; - if (!isExhaustiveWithCaseTypes(ref.getAllEnumerableReferenceTypes(), this.caseLabelElementTypes)) { - if (this instanceof SwitchExpression) // non-exhaustive switch expressions will be flagged later. - return false; - skope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); - return true; - } - this.switchBits |= SwitchStatement.Exhaustive; - return false; - } - private boolean checkAndFlagDefaultRecord(BlockScope skope, CompilerOptions compilerOptions, ReferenceBinding ref) { - RecordComponentBinding[] comps = ref.components(); - List allallowedTypes = new ArrayList<>(); - allallowedTypes.add(ref); - if (comps == null || comps.length == 0) { - if (!isExhaustiveWithCaseTypes(allallowedTypes, this.caseLabelElementTypes)) { - skope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); - return true; - } - return false; - } - // non-zero components - RNode head = new RNode(ref); + private boolean caseElementsCoverRecordType(BlockScope skope, CompilerOptions compilerOptions, ReferenceBinding recordType) { + RNode head = new RNode(recordType); for (Pattern pattern : this.caseLabelElements) { head.addPattern(pattern); } CoverageCheckerVisitor ccv = new CoverageCheckerVisitor(); head.traverse(ccv); - if (!ccv.covers) { - skope.problemReporter().enhancedSwitchMissingDefaultCase(this.expression); - return true; // not exhaustive, error flagged - } - this.switchBits |= SwitchStatement.Exhaustive; - return false; + return ccv.covers; } - private boolean isExhaustiveWithCaseTypes(List allAllowedTypes, List listedTypes) { - int pendingTypes = allAllowedTypes.size(); - for (ReferenceBinding pt : allAllowedTypes) { - /* Per JLS 14.11.1.1: A type T that names an abstract sealed class or sealed interface is covered - if every permitted direct subclass or subinterface of it is covered. These subtypes are already - added to allAllowedTypes and subject to cover test. - */ - if (pt.isAbstract() && pt.isSealed()) { - --pendingTypes; + + private boolean caseElementsCoverSealedType(ReferenceBinding sealedType, List listedTypes) { + List allAllowedTypes = sealedType.getAllEnumerableReferenceTypes(); + Iterator iterator = allAllowedTypes.iterator(); + while (iterator.hasNext()) { + ReferenceBinding next = iterator.next(); + if (next.isAbstract() && next.isSealed()) { + /* Per JLS 14.11.1.1: A type T that names an abstract sealed class or sealed interface is covered + if every permitted direct subclass or subinterface of it is covered. These subtypes are already + added to allAllowedTypes and subject to cover test. + */ + iterator.remove(); continue; } + if (next.isEnum()) { + int constantCount = this.otherConstants == null ? 0 : this.otherConstants.length; + Set unenumeratedConstants = unenumeratedConstants(next, constantCount); + if (unenumeratedConstants.size() == 0) { + iterator.remove(); + continue; + } + } for (TypeBinding type : listedTypes) { // permits specifies classes, not parameterizations - if (pt.erasure().isCompatibleWith(type.erasure())) { - --pendingTypes; + if (next.erasure().isCompatibleWith(type.erasure())) { + iterator.remove(); break; } } } - return pendingTypes == 0; + return allAllowedTypes.size() == 0; } private boolean needPatternDispatchCopy() { if (this.containsPatterns || (this.switchBits & QualifiedEnum) != 0) @@ -1512,7 +1507,7 @@ private void addSecretPatternSwitchVariables(BlockScope upperScope) { protected void reportMissingEnumConstantCase(BlockScope upperScope, FieldBinding enumConstant) { upperScope.problemReporter().missingEnumConstantCase(this, enumConstant); } - protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions, boolean isEnumSwitch) { + protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions) { return compilerOptions.getSeverity(CompilerOptions.MissingDefaultCase) == ProblemSeverities.Ignore; } @Override diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java index 2ac63f1eb3f..163998c7aad 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeDeclaration.java @@ -109,7 +109,7 @@ public class TypeDeclaration extends Statement implements ProblemSeverities, Ref public int nRecordComponents; public static Set disallowedComponentNames; - // 15 Sealed Type preview support + // 17 Sealed Type support public TypeReference[] permittedTypes; // TEST ONLY: disable one fix here to challenge another related fix (in TypeSystem): @@ -1069,7 +1069,7 @@ public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, Fl Scope outerScope = currentScope.parent; if (!methodScope.isConstructorCall) { nestedType.addSyntheticArgumentAndField(nestedType.enclosingType()); - outerScope = outerScope.enclosingClassScope(); + outerScope = outerScope.enclosingInstanceScope(); earlySeen = methodScope.isInsideEarlyConstructionContext(nestedType.enclosingType(), false); } if (JavaFeature.FLEXIBLE_CONSTRUCTOR_BODIES.isSupported(currentScope.compilerOptions())) { @@ -1087,6 +1087,8 @@ public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, Fl earlySeen = cs.insideEarlyConstructionContext; } outerScope = outerScope.parent; + if (outerScope instanceof MethodScope ms && ms.isStatic) + break; } } } @@ -1124,6 +1126,7 @@ public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, Fl } } + /** * Access emulation for a local member type * force to emulation of access to direct enclosing instance. @@ -1362,7 +1365,9 @@ public void resolve() { this.scope.problemReporter().missingDeprecatedAnnotationForType(this); } if ((annotationTagBits & TagBits.AnnotationFunctionalInterface) != 0) { - if(!this.binding.isFunctionalInterface(this.scope)) { + if (this.binding.isSealed()) { + this.scope.problemReporter().functionalInterfaceMayNotBeSealed(this); + } else if (!this.binding.isFunctionalInterface(this.scope)) { this.scope.problemReporter().notAFunctionalInterface(this); } } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypePattern.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypePattern.java index 80f226ca1e0..4716fde7211 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypePattern.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypePattern.java @@ -145,7 +145,7 @@ public void generateTestingConversion(BlockScope scope, CodeStream codeStream) { case WIDENING_REFERENCE_AND_UNBOXING_COVERSION_AND_WIDENING_PRIMITIVE_CONVERSION: int rhsUnboxed = TypeIds.box2primitive(provided.superclass().id); codeStream.generateUnboxingConversion(rhsUnboxed); - this.computeConversion(scope, TypeBinding.wellKnownBaseType(rhsUnboxed), expected); + this.computeConversion(scope, expected, TypeBinding.wellKnownBaseType(rhsUnboxed)); codeStream.generateImplicitConversion(this.implicitConversion); break; case NARROWING_AND_UNBOXING_CONVERSION: @@ -202,7 +202,11 @@ public boolean dominates(Pattern p) { return false; if (p.resolvedType == null || this.resolvedType == null) return false; - return p.resolvedType.erasure().isSubtypeOf(this.resolvedType.erasure(), false); + + if (p.resolvedType.isSubtypeOf(this.resolvedType, false)) + return true; + + return p.resolvedType.erasure().findSuperTypeOriginatingFrom(this.resolvedType.erasure()) != null; } @Override diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeReference.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeReference.java index 5b9315289d9..954836a909c 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeReference.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/TypeReference.java @@ -556,9 +556,6 @@ && isTypeUseDeprecated(type, scope)) { public boolean isTypeReference() { return true; } -public boolean isImplicit() { - return false; -} public boolean isWildcard() { return false; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java index abe8805e653..58d46fda5e4 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/YieldStatement.java @@ -55,8 +55,6 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl flowInfo = this.expression.analyseCode(currentScope, flowContext, flowInfo); this.expression.checkNPEbyUnboxing(currentScope, flowContext, flowInfo); - if (flowInfo.reachMode() == FlowInfo.REACHABLE && currentScope.compilerOptions().isAnnotationBasedNullAnalysisEnabled) - checkAgainstNullAnnotation(currentScope, flowContext, flowInfo, this.expression); targetContext.recordAbruptExit(); targetContext.expireNullCheckedFieldInfo(); @@ -152,6 +150,13 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { assignment.generateCode(currentScope, codeStream); } else { this.expression.generateCode(currentScope, codeStream, this.switchExpression != null); + if (this.expression.resolvedType == TypeBinding.NULL) { + if (!this.switchExpression.resolvedType.isBaseType()) { + // no opcode called for to align the types, but we need to adjust the notion of type of TOS. + codeStream.operandStack.pop(TypeBinding.NULL); + codeStream.operandStack.push(this.switchExpression.resolvedType); + } + } } int pc = codeStream.position; // generation of code responsible for invoking the finally diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/FileSystem.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/FileSystem.java index 3092e4af272..18b53b4fa9e 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/FileSystem.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/batch/FileSystem.java @@ -292,7 +292,13 @@ public static Classpath getClasspath(String classpathName, String encoding, Acce return getClasspath(classpathName, encoding, false, accessRuleSet, null, options, release); } public static Classpath getJrtClasspath(String jdkHome, String encoding, AccessRuleSet accessRuleSet, Map options) { - return new ClasspathJrt(new File(convertPathSeparators(jdkHome)), true, accessRuleSet, null); + ClasspathJrt classpathJrt = new ClasspathJrt(new File(convertPathSeparators(jdkHome)), true, accessRuleSet, null); + try { + classpathJrt.initialize(); + } catch (IOException e) { + // Broken entry, but let clients have it anyway. + } + return classpathJrt; } public static Classpath getOlderSystemRelease(String jdkHome, String release, AccessRuleSet accessRuleSet) { return isJRE12Plus ? diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java index d37d1a6467b..d93ad08048c 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileReader.java @@ -60,7 +60,7 @@ public class ClassFileReader extends ClassFileStruct implements IBinaryType { private InnerClassInfo[] innerInfos; private final char[][] interfaceNames; private final int interfacesCount; - private final char[][] permittedSubtypesNames; + private char[][] permittedSubtypesNames; private int permittedSubtypesCount; private MethodInfo[] methods; private final int methodsCount; @@ -393,7 +393,6 @@ public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInit char[] enclosingTypeNam = null; char[] sourceFileNam = null; char[] signatur = null; - char[][] permittedSubtypesNam = null; for (int i = 0; i < attributesCount; i++) { int utf8Offset = this.constantPoolOffsets[u2At(readOffset)]; @@ -527,11 +526,9 @@ public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInit if (this.permittedSubtypesCount != 0) { accessFlag |= ExtraCompilerModifiers.AccSealed; offset += 2; - permittedSubtypesNam = new char[this.permittedSubtypesCount][]; + this.permittedSubtypesNames = new char[this.permittedSubtypesCount][]; for (int j = 0; j < this.permittedSubtypesCount; j++) { - utf8Offset = - this.constantPoolOffsets[u2At(this.constantPoolOffsets[u2At(offset)] + 1)]; - permittedSubtypesNam[j] = CharDeduplication.intern(utf8At(utf8Offset + 3, u2At(utf8Offset + 1))); + this.permittedSubtypesNames[j] = CharDeduplication.intern(getConstantClassNameAt(u2At(offset))); offset += 2; } } @@ -548,7 +545,6 @@ public ClassFileReader(byte[] classFileBytes, char[] fileName, boolean fullyInit this.enclosingTypeName = enclosingTypeNam; this.sourceFileName = sourceFileNam; this.signature = signatur; - this.permittedSubtypesNames= permittedSubtypesNam; if (fullyInitialize) { initialize(); } @@ -802,7 +798,7 @@ public char[][] getInterfaceNames() { } @Override -public char[][] getPermittedSubtypeNames() { +public char[][] getPermittedSubtypesNames() { return this.permittedSubtypesNames; } @@ -1115,14 +1111,14 @@ && hasStructuralTypeAnnotationChanges(getTypeAnnotations(), newClassFile.getType return true; } - // permitted sub-types - char[][] newPermittedSubtypeNames = newClassFile.getPermittedSubtypeNames(); - if (this.permittedSubtypesNames != newPermittedSubtypeNames) { - int newPermittedSubtypesLength = newPermittedSubtypeNames == null ? 0 : newPermittedSubtypeNames.length; + // permitted subtypes + char[][] newPermittedSubtypesNames = newClassFile.getPermittedSubtypesNames(); + if (this.permittedSubtypesNames != newPermittedSubtypesNames) { + int newPermittedSubtypesLength = newPermittedSubtypesNames == null ? 0 : newPermittedSubtypesNames.length; if (newPermittedSubtypesLength != this.permittedSubtypesCount) return true; for (int i = 0, max = this.permittedSubtypesCount; i < max; i++) - if (!CharOperation.equals(this.permittedSubtypesNames[i], newPermittedSubtypeNames[i])) + if (!CharOperation.equals(this.permittedSubtypesNames[i], newPermittedSubtypesNames[i])) return true; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java index 6917cf966af..0e9e340e0d3 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ExternalAnnotationDecorator.java @@ -142,8 +142,8 @@ public char[] getSuperclassName() { } @Override - public char[][] getPermittedSubtypeNames() { - return this.inputType.getPermittedSubtypeNames(); + public char[][] getPermittedSubtypesNames() { + return this.inputType.getPermittedSubtypesNames(); } @Override diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/OperandStack.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/OperandStack.java index 97ff7bf5f38..dd3cdf27308 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/OperandStack.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/codegen/OperandStack.java @@ -263,6 +263,20 @@ public void dup2_x2() { } } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + for (int i = 0, length = size(); i < length; i++) { + if (i != 0) + sb.append(", "); //$NON-NLS-1$ + TypeBinding type = this.stack.get(i); + sb.append(type.shortReadableName()); + } + sb.append("]\n"); //$NON-NLS-1$ + return sb.toString(); + } + public static class NullStack extends OperandStack { public NullStack() { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/env/IBinaryType.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/env/IBinaryType.java index ada248c8620..cce631129c9 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/env/IBinaryType.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/env/IBinaryType.java @@ -94,13 +94,13 @@ public interface IBinaryType extends IGenericType, IBinaryInfo { char[][] getInterfaceNames(); /** - * Answer the unresolved names of the receiver's permitted sub types + * Answer the unresolved names of the receiver's permitted subtypes in the + * class file format as specified in section 4.2 of the Java 2 VM spec * or null if the array is empty. * - * A name is a simple name or a qualified, dot separated name. - * For example, Hashtable or java.util.Hashtable. + * For example, java.lang.String is java/lang/String. */ -default char[][] getPermittedSubtypeNames() { +default char[][] getPermittedSubtypesNames() { return null; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java index e0ffe25ddc0..135cdd005ab 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java @@ -438,6 +438,14 @@ public class CompilerOptions { public long complianceLevel; /** Java source level, refers to a JDK version, e.g. {@link ClassFileConstants#JDK1_4} */ public long sourceLevel; + /** + * Initially requested source version, not necessarily consistent with {@link #sourceLevel} as + * sourceLevel forcibly contain a version that is compatible with ECJ. + *

Consumers are free to use {@code sourceLevel} or this + * {@code requestedSourceVersion} value to decide of their behavior.

+ *

May be {@code null}.

+ */ + public String requestedSourceVersion; /** VM target level, refers to a JDK version, e.g. {@link ClassFileConstants#JDK1_4} */ public long targetJDK; /** Source encoding format */ @@ -1774,7 +1782,7 @@ public void set(Map optionsMap) { long level = versionToJdkLevel(optionValue); if (level != 0) this.complianceLevel = level; } - if ((optionValue = optionsMap.get(OPTION_Source)) != null) { + if ((this.requestedSourceVersion = optionValue = optionsMap.get(OPTION_Source)) != null) { long level = versionToJdkLevel(optionValue); if (level != 0) this.sourceLevel = level; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/JavaFeature.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/JavaFeature.java index b89214f3565..c10d5be5601 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/JavaFeature.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/JavaFeature.java @@ -60,7 +60,7 @@ public enum JavaFeature { SEALED_CLASSES(ClassFileConstants.JDK17, Messages.bind(Messages.sealed_types), - new char[][] {TypeConstants.SEALED, TypeConstants.PERMITS}, + new char[][] {TypeConstants.SEALED, TypeConstants.NON_SEALED, TypeConstants.PERMITS}, false), PATTERN_MATCHING_IN_SWITCH(ClassFileConstants.JDK21, Messages.bind(Messages.pattern_matching_switch), diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java index 0ef0fb2b5b3..e5f6ffa46a2 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java @@ -438,7 +438,7 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) { // and still want to use binaries passed that point (e.g. type hierarchy resolver, see bug 63748). this.typeVariables = Binding.NO_TYPE_VARIABLES; this.superInterfaces = Binding.NO_SUPERINTERFACES; - this.permittedTypes = Binding.NO_PERMITTEDTYPES; + this.permittedTypes = Binding.NO_PERMITTED_TYPES; // must retrieve member types in case superclass/interfaces need them this.memberTypes = Binding.NO_MEMBER_TYPES; @@ -544,31 +544,16 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) { types.toArray(this.superInterfaces); this.tagBits |= TagBits.HasUnresolvedSuperinterfaces; } - - this.permittedTypes = Binding.NO_PERMITTEDTYPES; - if (!wrapper.atEnd()) { - // attempt to find each permitted type if it exists in the cache (otherwise - resolve it when requested) - java.util.ArrayList types = new java.util.ArrayList(2); - short rank = 0; - do { - types.add(this.environment.getTypeFromTypeSignature(wrapper, typeVars, this, missingTypeNames, toplevelWalker.toSupertype(rank++, wrapper.peekFullType()))); - } while (!wrapper.atEnd()); - this.permittedTypes = new ReferenceBinding[types.size()]; - types.toArray(this.permittedTypes); - this.extendedTagBits |= ExtendedTagBits.HasUnresolvedPermittedSubtypes; - } - } - // fall back, in case we haven't got them from signature - char[][] permittedSubtypeNames = binaryType.getPermittedSubtypeNames(); - if (this.permittedTypes == Binding.NO_PERMITTEDTYPES && permittedSubtypeNames != null) { + char[][] permittedSubtypesNames = binaryType.getPermittedSubtypesNames(); + if (permittedSubtypesNames != null) { this.modifiers |= ExtraCompilerModifiers.AccSealed; - int size = permittedSubtypeNames.length; + int size = permittedSubtypesNames.length; if (size > 0) { this.permittedTypes = new ReferenceBinding[size]; for (short i = 0; i < size; i++) - // attempt to find each superinterface if it exists in the cache (otherwise - resolve it when requested) - this.permittedTypes[i] = this.environment.getTypeFromConstantPoolName(permittedSubtypeNames[i], 0, -1, false, missingTypeNames, toplevelWalker.toSupertype(i, null)); + // attempt to find each permitted type if it exists in the cache (otherwise - resolve it when requested) + this.permittedTypes[i] = this.environment.getTypeFromConstantPoolName(permittedSubtypesNames[i], 0, -1, false, missingTypeNames); } } boolean canUseNullTypeAnnotations = this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && this.environment.globalOptions.sourceLevel >= ClassFileConstants.JDK1_8; @@ -582,12 +567,6 @@ void cachePartsFrom(IBinaryType binaryType, boolean needFieldsAndMethods) { break; } } - for (TypeBinding permsub : this.permittedTypes) { - if (permsub.hasNullTypeAnnotations()) { - this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED; - break; - } - } } } @@ -2573,9 +2552,8 @@ public ReferenceBinding[] permittedTypes() { return this.permittedTypes = this.prototype.permittedTypes(); } for (int i = this.permittedTypes.length; --i >= 0;) - this.permittedTypes[i] = (ReferenceBinding) resolveType(this.permittedTypes[i], this.environment, false); + this.permittedTypes[i] = (ReferenceBinding) resolveType(this.permittedTypes[i], this.environment, false); // re-resolution seems harmless - // Note: unlike for superinterfaces() hierarchy check not required here since these are subtypes return this.permittedTypes; } @Override @@ -2647,16 +2625,16 @@ public String toString() { } if (this.permittedTypes != null) { - if (this.permittedTypes != Binding.NO_PERMITTEDTYPES) { + if (this.permittedTypes != Binding.NO_PERMITTED_TYPES) { buffer.append("\n\tpermits : "); //$NON-NLS-1$ for (int i = 0, length = this.permittedTypes.length; i < length; i++) { - if (i > 0) + if (i > 0) buffer.append(", "); //$NON-NLS-1$ buffer.append((this.permittedTypes[i] != null) ? this.permittedTypes[i].debugName() : "NULL TYPE"); //$NON-NLS-1$ } } } else { - buffer.append("NULL PERMITTEDSUBTYPES"); //$NON-NLS-1$ + buffer.append("NULL PERMITTED SUBTYPES"); //$NON-NLS-1$ } if (this.enclosingType != null) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Binding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Binding.java index 34073261c30..c1beb405a48 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Binding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Binding.java @@ -62,7 +62,7 @@ public abstract class Binding { public static final ReferenceBinding[] ANY_EXCEPTION = new ReferenceBinding[] { null }; // special handler for all exceptions public static final FieldBinding[] NO_FIELDS = new FieldBinding[0]; public static final MethodBinding[] NO_METHODS = new MethodBinding[0]; - public static final ReferenceBinding[] NO_PERMITTEDTYPES = new ReferenceBinding[0]; + public static final ReferenceBinding[] NO_PERMITTED_TYPES = new ReferenceBinding[0]; public static final ReferenceBinding[] NO_SUPERINTERFACES = new ReferenceBinding[0]; public static final ReferenceBinding[] NO_MEMBER_TYPES = new ReferenceBinding[0]; public static final TypeVariableBinding[] NO_TYPE_VARIABLES = new TypeVariableBinding[0]; 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 84b3e686712..cf72c94dd10 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 @@ -32,13 +32,9 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; -import java.util.Set; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.*; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -95,7 +91,7 @@ void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding } } anonymousType.typeBits |= inheritedBits; - anonymousType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); // JLS 15 JEP 360 Preview - Sec 15.9.5 + anonymousType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); if (supertype.isInterface()) { anonymousType.setSuperClass(getJavaLangObject()); anonymousType.setSuperInterfaces(new ReferenceBinding[] { supertype }); @@ -107,7 +103,7 @@ void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding anonymousType.tagBits |= TagBits.HierarchyHasProblems; anonymousType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); } if (supertype.isSealed()) { - problemReporter().sealedAnonymousClassCannotExtendSealedType(typeReference, supertype); + problemReporter().anonymousClassCannotExtendSealedType(typeReference, supertype); anonymousType.tagBits |= TagBits.HierarchyHasProblems; anonymousType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); } @@ -115,7 +111,6 @@ void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding } else { anonymousType.setSuperClass(supertype); anonymousType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); - checkForEnumSealedPreview(supertype, anonymousType); TypeReference typeReference = this.referenceContext.allocation.type; if (typeReference != null) { // no check for enum constant body this.referenceContext.superclass = typeReference; @@ -138,7 +133,7 @@ void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding anonymousType.tagBits |= TagBits.HierarchyHasProblems; anonymousType.setSuperClass(getJavaLangObject()); } else if (supertype.isSealed()) { - problemReporter().sealedAnonymousClassCannotExtendSealedType(typeReference, supertype); + problemReporter().anonymousClassCannotExtendSealedType(typeReference, supertype); anonymousType.tagBits |= TagBits.HierarchyHasProblems; anonymousType.setSuperClass(getJavaLangObject()); } @@ -151,27 +146,6 @@ void buildAnonymousTypeBinding(SourceTypeBinding enclosingType, ReferenceBinding anonymousType.verifyMethods(environment().methodVerifier()); } - private void checkForEnumSealedPreview(ReferenceBinding supertype, LocalTypeBinding anonymousType) { - if (!JavaFeature.SEALED_CLASSES.isSupported(compilerOptions()) - || !supertype.isEnum() - || !(supertype instanceof SourceTypeBinding)) - return; - - SourceTypeBinding sourceSuperType = (SourceTypeBinding) supertype; - ReferenceBinding[] permTypes = sourceSuperType.permittedTypes(); - int sz = permTypes == null ? 0 : permTypes.length; - if (sz == 0) { - permTypes = new ReferenceBinding[] {anonymousType}; - } else { - System.arraycopy(permTypes, 0, - permTypes = new ReferenceBinding[sz + 1], 0, - sz); - permTypes[sz] = anonymousType; - } - anonymousType.modifiers |= ClassFileConstants.AccFinal; // JLS 15 / sealed preview/Sec 8.9.1 - sourceSuperType.setPermittedTypes(permTypes); - } - void buildComponents() { SourceTypeBinding sourceType = this.referenceContext.binding; if (!sourceType.isRecord()) return; @@ -373,7 +347,6 @@ void buildLocalTypeBinding(SourceTypeBinding enclosingType) { LocalTypeBinding localType = buildLocalType(enclosingType, enclosingType.fPackage); connectTypeHierarchy(); - connectImplicitPermittedTypes(); if (compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) { checkParameterizedTypeBounds(); checkParameterizedSuperTypeCollisions(); @@ -599,13 +572,12 @@ private void checkAndSetModifiers() { CompilerOptions options = compilerOptions(); boolean is16Plus = compilerOptions().sourceLevel >= ClassFileConstants.JDK16; boolean isSealedSupported = JavaFeature.SEALED_CLASSES.isSupported(options); - boolean flagSealedNonModifiers = isSealedSupported && - (modifiers & (ExtraCompilerModifiers.AccSealed | ExtraCompilerModifiers.AccNonSealed)) != 0; + boolean hierarchySealed = (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); + problemReporter().illegalModifierCombinationForType(sourceType); break; } if (sourceType.isRecord()) { @@ -643,7 +615,7 @@ private void checkAndSetModifiers() { } final int UNEXPECTED_MODIFIERS =~(ClassFileConstants.AccEnum | ClassFileConstants.AccStrictfp); if ((modifiers & ExtraCompilerModifiers.AccJustFlag & UNEXPECTED_MODIFIERS) != 0 - || flagSealedNonModifiers) { + || hierarchySealed) { problemReporter().illegalModifierForLocalEnumDeclaration(sourceType); return; } @@ -664,8 +636,19 @@ private void checkAndSetModifiers() { if (compilerOptions().complianceLevel < ClassFileConstants.JDK9) modifiers |= ClassFileConstants.AccFinal; // set AccEnum flag for anonymous body of enum constants - if (this.referenceContext.allocation.type == null) + if (this.referenceContext.allocation.type == null) { modifiers |= ClassFileConstants.AccEnum; + // 8.1.1.4 local enum classes are implicitly static - we can't trust isLocalType() which answers true for all anonymous types. + Scope scope = this; + while ((scope = scope.parent) != null) { + if (scope instanceof MethodScope methodScope) { + if (methodScope.referenceContext instanceof TypeDeclaration) + continue; + modifiers |= ClassFileConstants.AccStatic; + break; + } + } + } } else if (this.parent.referenceContext() instanceof TypeDeclaration) { TypeDeclaration typeDecl = (TypeDeclaration) this.parent.referenceContext(); if (TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.INTERFACE_DECL) { @@ -743,7 +726,7 @@ private void checkAndSetModifiers() { | ClassFileConstants.AccStrictfp | ClassFileConstants.AccAnnotation | ((is16Plus && this.parent instanceof ClassScope) ? ClassFileConstants.AccStatic : 0)); if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 - || flagSealedNonModifiers) + || hierarchySealed) problemReporter().localStaticsIllegalVisibilityModifierForInterfaceLocalType(sourceType); // if ((modifiers & ClassFileConstants.AccStatic) != 0) { // problemReporter().recordIllegalStaticModifierForLocalClassOrInterface(sourceType); @@ -765,24 +748,23 @@ private void checkAndSetModifiers() { modifiers |= ClassFileConstants.AccSynthetic; } modifiers |= ClassFileConstants.AccAbstract; - } else if ((realModifiers & ClassFileConstants.AccEnum) != 0) { + } else if ((realModifiers & ClassFileConstants.AccEnum) != 0) { // detect abnormal cases for enums if (isMemberType) { // includes member types defined inside local types - final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccPublic | ClassFileConstants.AccPrivate | ClassFileConstants.AccProtected | ClassFileConstants.AccStatic | ClassFileConstants.AccStrictfp | ClassFileConstants.AccEnum); - if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || flagSealedNonModifiers) { + final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccPublic | ClassFileConstants.AccPrivate + | ClassFileConstants.AccProtected | ClassFileConstants.AccStatic + | ClassFileConstants.AccStrictfp | ClassFileConstants.AccEnum); + if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || hierarchySealed) { problemReporter().illegalModifierForMemberEnum(sourceType); modifiers &= ~ClassFileConstants.AccAbstract; // avoid leaking abstract modifier realModifiers &= ~ClassFileConstants.AccAbstract; // modifiers &= ~(realModifiers & UNEXPECTED_MODIFIERS); // realModifiers = modifiers & ExtraCompilerModifiers.AccJustFlag; } - } else if (sourceType.isLocalType()) { -// if (flagSealedNonModifiers) -// problemReporter().illegalModifierForLocalEnum(sourceType); - // each enum constant is an anonymous local type and its modifiers were already checked as an enum constant field - } else { - final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccPublic | ClassFileConstants.AccStrictfp | ClassFileConstants.AccEnum); - if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || flagSealedNonModifiers) + } else if (!sourceType.isLocalType()) { // local types already handled earlier. + final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccPublic | ClassFileConstants.AccStrictfp + | ClassFileConstants.AccEnum); + if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || hierarchySealed) problemReporter().illegalModifierForEnum(sourceType); } if (!sourceType.isAnonymousType()) { @@ -797,14 +779,16 @@ private void checkAndSetModifiers() { TypeDeclaration typeDeclaration = this.referenceContext; FieldDeclaration[] fields = typeDeclaration.fields; int fieldsLength = fields == null ? 0 : fields.length; - if (fieldsLength == 0) break checkAbstractEnum; // has no constants so must implement the method itself + if (fieldsLength == 0) + break checkAbstractEnum; // has no constants so must implement the method itself AbstractMethodDeclaration[] methods = typeDeclaration.methods; int methodsLength = methods == null ? 0 : methods.length; // TODO (kent) cannot tell that the superinterfaces are empty or that their methods are implemented boolean definesAbstractMethod = typeDeclaration.superInterfaces != null; for (int i = 0; i < methodsLength && !definesAbstractMethod; i++) definesAbstractMethod = methods[i].isAbstract(); - if (!definesAbstractMethod) break checkAbstractEnum; // all methods have bodies + if (!definesAbstractMethod) + break checkAbstractEnum; // all methods have bodies boolean needAbstractBit = false; for (int i = 0; i < fieldsLength; i++) { FieldDeclaration fieldDecl = fields[i]; @@ -816,8 +800,10 @@ private void checkAndSetModifiers() { } } } - // tag this enum as abstract since an abstract method must be implemented AND all enum constants define an anonymous body - // as a result, each of its anonymous constants will see it as abstract and must implement each inherited abstract method + // tag this enum as abstract since an abstract method must be implemented AND all enum constants + // define an anonymous body + // as a result, each of its anonymous constants will see it as abstract and must implement each + // inherited abstract method if (needAbstractBit) { modifiers |= ClassFileConstants.AccAbstract; } @@ -882,7 +868,7 @@ private void checkAndSetModifiers() { } else if (sourceType.isLocalType()) { final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccAbstract | ClassFileConstants.AccFinal | ClassFileConstants.AccStrictfp | ((is16Plus && this.parent instanceof ClassScope) ? ClassFileConstants.AccStatic : 0)); - if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || flagSealedNonModifiers) + if ((realModifiers & UNEXPECTED_MODIFIERS) != 0 || hierarchySealed) problemReporter().illegalModifierForLocalClass(sourceType); } else { final int UNEXPECTED_MODIFIERS = ~(ClassFileConstants.AccPublic | ClassFileConstants.AccAbstract | ClassFileConstants.AccFinal | ClassFileConstants.AccStrictfp); @@ -1170,7 +1156,7 @@ private boolean connectSuperclass() { if (sourceType.id == TypeIds.T_JavaLangObject) { // handle the case of redefining java.lang.Object up front sourceType.setSuperClass(null); sourceType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); - sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); + sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); if (!sourceType.isClass()) problemReporter().objectMustBeClass(sourceType); if (this.referenceContext.superclass != null || (this.referenceContext.superInterfaces != null && this.referenceContext.superInterfaces.length > 0)) @@ -1204,6 +1190,14 @@ private boolean connectSuperclass() { } else { return connectRecordSuperclass(); } + } else if (superclass.isSealed() && sourceType.isLocalType()) { + sourceType.setSuperClass(superclass); + problemReporter().localTypeMayNotBePermittedType(sourceType, superclassRef, superclass); + return false; + } else if (superclass.isSealed() && !(sourceType.isFinal() || sourceType.isSealed() || sourceType.isNonSealed())) { + sourceType.setSuperClass(superclass); + problemReporter().permittedTypeNeedsModifier(sourceType, this.referenceContext, superclass); + return false; } else if ((superclass.tagBits & TagBits.HierarchyHasProblems) != 0 || !superclassRef.resolvedType.isValidBinding()) { sourceType.setSuperClass(superclass); @@ -1259,85 +1253,92 @@ private boolean connectEnumSuperclass() { } return !foundCycle; } - private void connectImplicitPermittedTypes(SourceTypeBinding sourceType) { - List types = new ArrayList<>(); - for (TypeDeclaration typeDecl : this.referenceCompilationUnit().types) { - types.addAll(sourceType.collectAllTypeBindings(typeDecl, this.compilationUnitScope())); - } - Set permSubTypes = new LinkedHashSet<>(); - for (ReferenceBinding type : types) { - if (!TypeBinding.equalsEquals(type, sourceType) && type.findSuperTypeOriginatingFrom(sourceType) != null) { - permSubTypes.add(type); - } - } - if (sourceType.isSealed() && sourceType.isLocalType()) { - // bug xxxx flag Error and return; - } - if (permSubTypes.size() == 0) { - if (!sourceType.isLocalType() && !sourceType.isRecord() && !sourceType.isEnum()) // error flagged already - problemReporter().sealedSealedTypeMissingPermits(sourceType, this.referenceContext); - return; - } - sourceType.setPermittedTypes(permSubTypes.toArray(new ReferenceBinding[0])); - } - void connectImplicitPermittedTypes() { - TypeDeclaration typeDecl = this.referenceContext; - SourceTypeBinding sourceType = typeDecl.binding; - if (sourceType.id == TypeIds.T_JavaLangObject) // already handled - return; - if (sourceType.isSealed() && (typeDecl.permittedTypes == null || - typeDecl.permittedTypes.length == 0 || typeDecl.permittedTypes[0].isImplicit())) { - connectImplicitPermittedTypes(sourceType); - } - ReferenceBinding[] memberTypes = sourceType.memberTypes; - if (memberTypes != null && memberTypes != Binding.NO_MEMBER_TYPES) { - for (ReferenceBinding memberType : memberTypes) - ((SourceTypeBinding) memberType).scope.connectImplicitPermittedTypes(); + + /* Check that the permitted subtype and the sealed type are located in close proximity: either in the same module (if the superclass is in a named module) + * or in the same package (if the superclass is in the unnamed module) + * Return true, if all is well. Report error and return false otherwise, + */ + private boolean checkSealingProximity(ReferenceBinding subType, TypeReference subTypeReference, ReferenceBinding sealedType) { + final PackageBinding sealedTypePackage = sealedType.getPackage(); + final ModuleBinding sealedTypeModule = sealedType.module(); + if (subType.getPackage() != sealedTypePackage) { + if (sealedTypeModule.isUnnamed()) + problemReporter().permittedTypeOutsideOfPackage(subType, sealedType, subTypeReference, sealedTypePackage); + else if (subType.module() != sealedTypeModule) + problemReporter().permittedTypeOutsideOfModule(subType, sealedType, subTypeReference, sealedTypeModule); } + return true; } + void connectPermittedTypes() { SourceTypeBinding sourceType = this.referenceContext.binding; - sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); - if (sourceType.id == TypeIds.T_JavaLangObject || sourceType.isEnum()) // already handled - return; if (this.referenceContext.permittedTypes != null) { + sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); try { sourceType.tagBits |= TagBits.SealingTypeHierarchy; + if (!sourceType.isSealed()) + problemReporter().missingSealedModifier(sourceType, this.referenceContext); int length = this.referenceContext.permittedTypes.length; ReferenceBinding[] permittedTypeBindings = new ReferenceBinding[length]; int count = 0; nextPermittedType : for (int i = 0; i < length; i++) { TypeReference permittedTypeRef = this.referenceContext.permittedTypes[i]; ReferenceBinding permittedType = findPermittedtype(permittedTypeRef); - if (permittedType == null) { // detected cycle + if (permittedType == null || !permittedType.isValidBinding()) { continue nextPermittedType; } - if (!isPermittedTypeInAllowedFormat(sourceType, permittedTypeRef, permittedType)) - continue nextPermittedType; - // check for simple interface collisions - // Check for a duplicate interface once the name is resolved, otherwise we may be confused (i.e. a.b.I and c.d.I) + if (sourceType.isClass()) { + ReferenceBinding superClass = permittedType.superclass(); + superClass = superClass == null ? null : superClass.actualType(); + if (!TypeBinding.equalsEquals(sourceType, superClass)) + problemReporter().sealedClassNotDirectSuperClassOf(permittedType, permittedTypeRef, sourceType); + } else if (sourceType.isInterface()) { + ReferenceBinding[] superInterfaces = permittedType.superInterfaces(); + boolean hierarchyOK = false; + if (superInterfaces != null) { + for (ReferenceBinding superInterface : superInterfaces) { + superInterface = superInterface == null ? null : superInterface.actualType(); + if (TypeBinding.equalsEquals(sourceType, superInterface)) { + hierarchyOK = true; + break; + } + } + if (!hierarchyOK) + problemReporter().sealedInterfaceNotDirectSuperInterfaceOf(permittedType, permittedTypeRef, sourceType); + } + } + + checkSealingProximity(permittedType, permittedTypeRef, sourceType); + for (int j = 0; j < i; j++) { if (TypeBinding.equalsEquals(permittedTypeBindings[j], permittedType)) { - problemReporter().sealedDuplicateTypeInPermits(sourceType, permittedTypeRef, permittedType); + problemReporter().duplicatePermittedType(permittedTypeRef, permittedType); continue nextPermittedType; } } - // only want to reach here when no errors are reported permittedTypeBindings[count++] = permittedType; } - // hold onto all correctly resolved superinterfaces if (count > 0) { if (count != length) System.arraycopy(permittedTypeBindings, 0, permittedTypeBindings = new ReferenceBinding[count], 0, count); sourceType.setPermittedTypes(permittedTypeBindings); } else { - sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); + sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); } } finally { sourceType.tagBits &= ~TagBits.SealingTypeHierarchy; } + } else { + ReferenceBinding[] permittedTypes = sourceType.permittedTypes(); + if (permittedTypes == null || permittedTypes.length == 0) { + sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); + if (sourceType.isSealed()) { + if (!sourceType.isLocalType() && !sourceType.isRecord() && !sourceType.isEnum()) // error flagged alread + problemReporter().missingPermitsClause(sourceType, this.referenceContext); + } + } } ReferenceBinding[] memberTypes = sourceType.memberTypes; if (memberTypes != null && memberTypes != Binding.NO_MEMBER_TYPES) { @@ -1346,29 +1347,6 @@ void connectPermittedTypes() { } } - private boolean isPermittedTypeInAllowedFormat(SourceTypeBinding sourceType, TypeReference permittedTypeRef, - ReferenceBinding permittedType) { - if (!(permittedType.isMemberType() && permittedTypeRef instanceof SingleTypeReference)) - return true; - ReferenceBinding enclosingType = permittedType.enclosingType(); - while (enclosingType != null) { - if (TypeBinding.equalsEquals(sourceType, enclosingType)) { - CompilationUnitScope cu = this.compilationUnitScope(); - if (cu.imports != null || cu.imports.length > 0) { - for (ImportBinding ib : cu.imports) { - Binding resolvedImport = cu.resolveSingleImport(ib, Binding.TYPE); - if (resolvedImport instanceof TypeBinding && - TypeBinding.equalsEquals(permittedType, (TypeBinding) resolvedImport)) - return true; - } - } - return false; - } - enclosingType = enclosingType.enclosingType(); - } - return true; - } - private boolean connectRecordSuperclass() { SourceTypeBinding sourceType = this.referenceContext.binding; ReferenceBinding rootRecordType = getJavaLangRecord(); @@ -1391,70 +1369,89 @@ private boolean connectRecordSuperclass() { */ private boolean connectSuperInterfaces() { SourceTypeBinding sourceType = this.referenceContext.binding; - sourceType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); - if (this.referenceContext.superInterfaces == null) { - if (sourceType.isAnnotationType() && compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) { // do not connect if source < 1.5 as annotation already got flagged as syntax error) { - ReferenceBinding annotationType = getJavaLangAnnotationAnnotation(); - boolean foundCycle = detectHierarchyCycle(sourceType, annotationType, null); - sourceType.setSuperInterfaces(new ReferenceBinding[] { annotationType }); - return !foundCycle; - } - return true; - } - if (sourceType.id == TypeIds.T_JavaLangObject) // already handled the case of redefining java.lang.Object - return true; - + boolean hasSealedSupertype = sourceType.superclass == null ? false : sourceType.superclass.isSealed(); boolean noProblems = true; - int length = this.referenceContext.superInterfaces.length; - ReferenceBinding[] interfaceBindings = new ReferenceBinding[length]; - int count = 0; - nextInterface : for (int i = 0; i < length; i++) { - TypeReference superInterfaceRef = this.referenceContext.superInterfaces[i]; - ReferenceBinding superInterface = findSupertype(superInterfaceRef); - if (superInterface == null) { // detected cycle - sourceType.tagBits |= TagBits.HierarchyHasProblems; - noProblems = false; - continue nextInterface; + try { + sourceType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); + if (this.referenceContext.superInterfaces == null) { + if (sourceType.isAnnotationType() && compilerOptions().sourceLevel >= ClassFileConstants.JDK1_5) { // do not connect if source < 1.5 as annotation already got flagged as syntax error) { + ReferenceBinding annotationType = getJavaLangAnnotationAnnotation(); + boolean foundCycle = detectHierarchyCycle(sourceType, annotationType, null); + sourceType.setSuperInterfaces(new ReferenceBinding[] { annotationType }); + return !foundCycle; + } + return true; } + if (sourceType.id == TypeIds.T_JavaLangObject) // already handled the case of redefining java.lang.Object + return true; - // check for simple interface collisions - // Check for a duplicate interface once the name is resolved, otherwise we may be confused (i.e. a.b.I and c.d.I) - for (int j = 0; j < i; j++) { - if (TypeBinding.equalsEquals(interfaceBindings[j], superInterface)) { - problemReporter().duplicateSuperinterface(sourceType, superInterfaceRef, superInterface); + int length = this.referenceContext.superInterfaces.length; + ReferenceBinding[] interfaceBindings = new ReferenceBinding[length]; + int count = 0; + nextInterface : for (int i = 0; i < length; i++) { + TypeReference superInterfaceRef = this.referenceContext.superInterfaces[i]; + ReferenceBinding superInterface = findSupertype(superInterfaceRef); + if (superInterface == null) { // detected cycle sourceType.tagBits |= TagBits.HierarchyHasProblems; noProblems = false; continue nextInterface; } + + if (superInterface.isSealed()) + hasSealedSupertype = true; + + // check for simple interface collisions + // Check for a duplicate interface once the name is resolved, otherwise we may be confused (i.e. a.b.I and c.d.I) + for (int j = 0; j < i; j++) { + if (TypeBinding.equalsEquals(interfaceBindings[j], superInterface)) { + problemReporter().duplicateSuperinterface(sourceType, superInterfaceRef, superInterface); + sourceType.tagBits |= TagBits.HierarchyHasProblems; + noProblems = false; + continue nextInterface; + } + } + if (!superInterface.isInterface() && (superInterface.tagBits & TagBits.HasMissingType) == 0) { + problemReporter().superinterfaceMustBeAnInterface(sourceType, superInterfaceRef, superInterface); + sourceType.tagBits |= TagBits.HierarchyHasProblems; + noProblems = false; + continue nextInterface; + } else if (superInterface.isAnnotationType()){ + problemReporter().annotationTypeUsedAsSuperinterface(sourceType, superInterfaceRef, superInterface); + } + if ((superInterface.tagBits & TagBits.HasDirectWildcard) != 0) { + problemReporter().superTypeCannotUseWildcard(sourceType, superInterfaceRef, superInterface); + sourceType.tagBits |= TagBits.HierarchyHasProblems; + noProblems = false; + continue nextInterface; + } + if ((superInterface.tagBits & TagBits.HierarchyHasProblems) != 0 + || !superInterfaceRef.resolvedType.isValidBinding()) { + sourceType.tagBits |= TagBits.HierarchyHasProblems; // propagate if missing supertype + noProblems &= superInterfaceRef.resolvedType.isValidBinding(); + } + if (superInterface.isSealed() && sourceType.isLocalType()) { + problemReporter().localTypeMayNotBePermittedType(sourceType, superInterfaceRef, superInterface); + noProblems = false; + } else if (superInterface.isSealed() && !(sourceType.isFinal() || sourceType.isSealed() || sourceType.isNonSealed())) { + problemReporter().permittedTypeNeedsModifier(sourceType, this.referenceContext, superInterface); + noProblems = false; + } + + // only want to reach here when no errors are reported + sourceType.typeBits |= (superInterface.typeBits & TypeIds.InheritableBits); + interfaceBindings[count++] = superInterface; } - if (!superInterface.isInterface() && (superInterface.tagBits & TagBits.HasMissingType) == 0) { - problemReporter().superinterfaceMustBeAnInterface(sourceType, superInterfaceRef, superInterface); - sourceType.tagBits |= TagBits.HierarchyHasProblems; - noProblems = false; - continue nextInterface; - } else if (superInterface.isAnnotationType()){ - problemReporter().annotationTypeUsedAsSuperinterface(sourceType, superInterfaceRef, superInterface); - } - if ((superInterface.tagBits & TagBits.HasDirectWildcard) != 0) { - problemReporter().superTypeCannotUseWildcard(sourceType, superInterfaceRef, superInterface); - sourceType.tagBits |= TagBits.HierarchyHasProblems; - noProblems = false; - continue nextInterface; + // hold onto all correctly resolved superinterfaces + if (count > 0) { + if (count != length) + System.arraycopy(interfaceBindings, 0, interfaceBindings = new ReferenceBinding[count], 0, count); + sourceType.setSuperInterfaces(interfaceBindings); } - if ((superInterface.tagBits & TagBits.HierarchyHasProblems) != 0 - || !superInterfaceRef.resolvedType.isValidBinding()) { - sourceType.tagBits |= TagBits.HierarchyHasProblems; // propagate if missing supertype - noProblems &= superInterfaceRef.resolvedType.isValidBinding(); + } finally { + if (sourceType.isNonSealed() && !hasSealedSupertype) { + if (!sourceType.isRecord() && !sourceType.isLocalType() && !sourceType.isEnum() && !sourceType.isSealed()) // avoid double jeopardy + problemReporter().disallowedNonSealedModifier(sourceType, this.referenceContext); } - // only want to reach here when no errors are reported - sourceType.typeBits |= (superInterface.typeBits & TypeIds.InheritableBits); - interfaceBindings[count++] = superInterface; - } - // hold onto all correctly resolved superinterfaces - if (count > 0) { - if (count != length) - System.arraycopy(interfaceBindings, 0, interfaceBindings = new ReferenceBinding[count], 0, count); - sourceType.setSuperInterfaces(interfaceBindings); } return noProblems; } @@ -1662,7 +1659,7 @@ private ReferenceBinding findSupertype(TypeReference typeReference) { } catch (AbortCompilation e) { SourceTypeBinding sourceType = this.referenceContext.binding; if (sourceType.superInterfaces == null) sourceType.setSuperInterfaces(Binding.NO_SUPERINTERFACES); // be more resilient for hierarchies (144976) - if (sourceType.permittedTypes == null) sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); + if (sourceType.permittedTypes == null) sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); e.updateContext(typeReference, referenceCompilationUnit().compilationResult); throw e; } finally { @@ -1690,7 +1687,7 @@ private ReferenceBinding findPermittedtype(TypeReference typeReference) { return permittedType != null ? permittedType.actualType() : permittedType; // while permitted classes/interfaces cannot be parameterized with type arguments, they are not raw either } catch (AbortCompilation e) { SourceTypeBinding sourceType = this.referenceContext.binding; - if (sourceType.permittedTypes == null) sourceType.setPermittedTypes(Binding.NO_PERMITTEDTYPES); + if (sourceType.permittedTypes == null) sourceType.setPermittedTypes(Binding.NO_PERMITTED_TYPES); e.updateContext(typeReference, referenceCompilationUnit().compilationResult); throw e; } finally { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java index 1809520b367..1f646063e7f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java @@ -426,8 +426,6 @@ void sealTypeHierarchy() { for (SourceTypeBinding sourceType : this.topLevelTypes) { sourceType.scope.connectPermittedTypes(); } - for (SourceTypeBinding topLevelType : this.topLevelTypes) - topLevelType.scope.connectImplicitPermittedTypes(); } void integrateAnnotationsInHierarchy() { // Only now that all hierarchy information is built we're ready for ... @@ -791,6 +789,7 @@ ImportBinding[] getDefaultImports() { this.environment.missingClassFileLocation, false, null/*resolving j.l.O is not specific to any referencing type*/); BinaryTypeBinding missingObject = this.environment.createMissingType(null, TypeConstants.JAVA_LANG_OBJECT); importBinding = missingObject.fPackage; + return new ImportBinding[] {new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null)}; } return this.environment.root.defaultImports = new ImportBinding[] {new ImportBinding(TypeConstants.JAVA_LANG, true, importBinding, null)}; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java index aa7b0fdd1a4..c28b24760df 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ExtraCompilerModifiers.java @@ -57,5 +57,5 @@ public interface ExtraCompilerModifiers { // modifier constant final int AccImplementing = ASTNode.Bit30; // record fact a method implements another one (it is concrete and overrides an abstract one) final int AccImplicitlyDeclared = ASTNode.Bit30; // used for implicitly declared classes final int AccGenericSignature = ASTNode.Bit31; // record fact a type/method/field involves generics in its signature (and need special signature attr) - final int AccOutOfFlowScope = ASTNode.Bit29; + final int AccOutOfFlowScope = ASTNode.Bit29; // used to signal pattern binding variables not being live. } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java index 7a7ebab30bd..2356f8a0f2f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java @@ -34,7 +34,7 @@ public MissingTypeBinding(PackageBinding packageBinding, char[][] compoundName, this.modifiers = ClassFileConstants.AccPublic; this.superclass = null; // will be fixed up using #setMissingSuperclass(...) this.superInterfaces = Binding.NO_SUPERINTERFACES; - this.permittedTypes = Binding.NO_PERMITTEDTYPES; + this.permittedTypes = Binding.NO_PERMITTED_TYPES; this.typeVariables = Binding.NO_TYPE_VARIABLES; this.memberTypes = Binding.NO_MEMBER_TYPES; this.fields = Binding.NO_FIELDS; diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java index 35d2a9fb398..a3e54cd2ebf 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ParameterizedTypeBinding.java @@ -1110,33 +1110,18 @@ public boolean isRawSubstitution() { @Override public ReferenceBinding[] permittedTypes() { List permittedTypes = new ArrayList<>(); -NextPermittedType: for (ReferenceBinding pt : this.type.permittedTypes()) { - // Step 1: Gather all type variables that would need to be solved. - Map map = new HashMap<>(); - TypeBinding current = pt; - do { - if (current.kind() == Binding.GENERIC_TYPE) { - for (TypeVariableBinding tvb : current.typeVariables()) { - map.put(tvb, null); - } - } - current = current.enclosingType(); - } while (current != null); - - // Step 2: Collect substitutes - current = this; TypeBinding sooper = pt.findSuperTypeOriginatingFrom(this); + if (sooper == null || !sooper.isValidBinding() || sooper.isProvablyDistinct(this)) + continue; + TypeBinding current = this; + Map map = new HashMap<>(); do { - if (sooper.isParameterizedType()) { - if (current.isParameterizedType()) { - for (int i = 0, length = sooper.typeArguments().length; i < length; i++) { - TypeBinding t = sooper.typeArguments()[i]; - if (t instanceof TypeVariableBinding tvb) { - map.put(tvb, current.typeArguments()[i]); - } else if (TypeBinding.notEquals(t, this.typeArguments()[i])) { - continue NextPermittedType; - } + if (sooper.isParameterizedType() && current.isParameterizedType()) { + for (int i = 0, length = sooper.typeArguments().length; i < length; i++) { + TypeBinding t = sooper.typeArguments()[i]; + if (t instanceof TypeVariableBinding tvb) { + map.put(tvb, current.typeArguments()[i]); } } } @@ -1163,12 +1148,7 @@ public TypeBinding substitute(TypeVariableBinding typeVariable) { return retVal; } }; - - // Step 3: compute subtype with parameterizations if any. - pt = (ReferenceBinding) Scope.substitute(substitution, pt); - - if (pt.isCompatibleWith(this)) - permittedTypes.add(pt); + permittedTypes.add((ReferenceBinding) Scope.substitute(substitution, pt)); } return permittedTypes.toArray(new ReferenceBinding[0]); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 71e27ac86f2..5b2d5017050 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -52,6 +52,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -64,7 +65,6 @@ import org.eclipse.jdt.internal.compiler.ast.NullAnnotationMatching; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -import org.eclipse.jdt.internal.compiler.impl.JavaFeature; import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; @@ -2367,10 +2367,8 @@ public MethodBinding getSingleAbstractMethod(Scope scope, boolean replaceWildcar return this.singleAbstractMethod[index]; } else { this.singleAbstractMethod = new MethodBinding[2]; - // Sec 9.8 of sealed preview - A functional interface is an interface that is not declared sealed... - if (JavaFeature.SEALED_CLASSES.isSupported(scope.compilerOptions()) - && this.isSealed()) - return this.singleAbstractMethod[index] = samProblemBinding; + if (this.isSealed()) + return this.singleAbstractMethod[index] = samProblemBinding; // JLS 9.8 } if (this.compoundName != null) @@ -2563,7 +2561,7 @@ public List getAllEnumerableReferenceTypes() { oldSet = permSet; permSet = tmp; } while (oldSet.size() != permSet.size()); - return Arrays.asList(permSet.toArray(new ReferenceBinding[0])); + return new ArrayList<>(permSet); } // 5.1.6.1 Allowed Narrowing Reference Conversion diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java index 24e49f7f2b5..421aba217dd 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/Scope.java @@ -1154,6 +1154,18 @@ public final ClassScope enclosingClassScope() { return null; // may answer null if no type around } + public ClassScope enclosingInstanceScope() { + Scope scope = this; + while (true) { + scope = scope.parent; + if (scope == null || scope instanceof MethodScope ms && ms.isStatic) { + return null; + } else if (scope instanceof ClassScope cs) { + return cs; + } + } + } + public final ClassScope enclosingTopMostClassScope() { Scope scope = this; while (scope != null) { @@ -5756,6 +5768,8 @@ public TypeBinding getMatchingUninitializedType(TypeBinding targetClass, boolean || (currentTarget instanceof ReferenceBinding currentRefBind && !currentRefBind.hasEnclosingInstanceContext())) { break; } + if (currentTarget.isStatic() || currentTarget.isLocalType()) + break; currentTarget = currentTarget.enclosingType(); } currentEnclosing = currentEnclosing.parent.classScope(); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java index ed0ed1d538c..881161fe30e 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java @@ -55,7 +55,6 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -67,7 +66,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; import org.eclipse.jdt.internal.compiler.ast.*; import org.eclipse.jdt.internal.compiler.ast.TypeReference.AnnotationPosition; @@ -1099,241 +1097,49 @@ private void checkAnnotationsInType() { void faultInTypesForFieldsAndMethods() { if (!isPrototype()) throw new IllegalStateException(); - checkPermitsInType(); + if (!this.isLocalType()) + complainIfUnpermittedSubtyping(); // this has nothing to do with fields and methods but time is ripe for this check. checkAnnotationsInType(); internalFaultInTypeForFieldsAndMethods(); } -private Map.Entry getFirstSealedSuperTypeOrInterface(TypeDeclaration typeDecl) { - boolean isAnySuperTypeSealed = typeDecl.superclass != null && this.superclass != null ? this.superclass.isSealed() : false; - if (isAnySuperTypeSealed) - return new AbstractMap.SimpleEntry<>(typeDecl.superclass, this.superclass); +private boolean isAnUnpermittedSubtypeOf(ReferenceBinding superType) { - ReferenceBinding[] superInterfaces1 = this.superInterfaces(); - int l = superInterfaces1 != null ? superInterfaces1.length : 0; - for (int i = 0; i < l; ++i) { - ReferenceBinding superInterface = superInterfaces1[i]; - if (superInterface.isSealed()) { - return new AbstractMap.SimpleEntry<>(typeDecl.superInterfaces[i], superInterface); - } + if (superType == null || !superType.isSealed()) + return false; + + for (ReferenceBinding permittedType : superType.actualType().permittedTypes()) { + if (TypeBinding.equalsEquals(this, permittedType)) + return false; } - return null; + + return true; } -// TODO: Optimize the multiple loops - defer until the feature becomes standard. -private void checkPermitsInType() { -// if (/* this.isRecordDeclaration || */this.isEnum()) -// return; // handled separately + +private void complainIfUnpermittedSubtyping() { + + // Diagnose unauthorized subtyping: This cannot be correctly hoisted into ClassScope.{ connectSuperclass() | connectSuperInterfaces() | connectPermittedTypes() } + // but can be taken up now + TypeDeclaration typeDecl = this.scope.referenceContext; - if (this.isInterface()) { - if (isSealed() && isNonSealed()) { - this.scope.problemReporter().sealedInterfaceIsSealedAndNonSealed(this, typeDecl); - return; - } - } - boolean hasPermittedTypes = this.permittedTypes != null && this.permittedTypes.length > 0; - if (hasPermittedTypes) { - if (!this.isSealed()) - this.scope.problemReporter().sealedMissingSealedModifier(this, typeDecl); - ModuleBinding sourceModuleBinding = this.module(); - boolean isUnnamedModule = sourceModuleBinding.isUnnamed(); - if (isUnnamedModule) { - PackageBinding sourceTypePackage = this.getPackage(); - for (int i =0, l = this.permittedTypes.length; i < l; i++) { - ReferenceBinding permType = this.permittedTypes[i]; - if (!permType.isValidBinding()) continue; - if (sourceTypePackage != permType.getPackage()) { - TypeReference permittedTypeRef = typeDecl.permittedTypes[i]; - this.scope.problemReporter().sealedPermittedTypeOutsideOfPackage(permType, this, permittedTypeRef, sourceTypePackage); - } - } - } else { - for (int i = 0, l = this.permittedTypes.length; i < l; i++) { - ReferenceBinding permType = this.permittedTypes[i]; - if (!permType.isValidBinding()) continue; - ModuleBinding permTypeModule = permType.module(); - if (permTypeModule != null && sourceModuleBinding != permTypeModule) { - TypeReference permittedTypeRef = typeDecl.permittedTypes[i]; - this.scope.problemReporter().sealedPermittedTypeOutsideOfModule(permType, this, permittedTypeRef, sourceModuleBinding); - } - } - } + if (this.isAnUnpermittedSubtypeOf(this.superclass)) { + this.scope.problemReporter().sealedSupertypeDoesNotPermit(this, typeDecl.superclass, this.superclass); } -// ReferenceBinding superType = this.superclass(); - Map.Entry sealedEntry = getFirstSealedSuperTypeOrInterface(typeDecl); - boolean foundSealedSuperTypeOrInterface = sealedEntry != null; - if (this.isLocalType()) { - if (this.isSealed() || this.isNonSealed()) - return; // already handled elsewhere - if (foundSealedSuperTypeOrInterface) { - this.scope.problemReporter().sealedLocalDirectSuperTypeSealed(this, sealedEntry.getKey(), sealedEntry.getValue()); - return; - } - } else if (this.isNonSealed()) { - if (!foundSealedSuperTypeOrInterface) { - if (this.isClass() && !this.isRecord()) // record to give only illegal modifier error. - this.scope.problemReporter().sealedDisAllowedNonSealedModifierInClass(this, typeDecl); - else if (this.isInterface()) - this.scope.problemReporter().sealedDisAllowedNonSealedModifierInInterface(this, typeDecl); - } - } - if (foundSealedSuperTypeOrInterface) { - if (!(this.isFinal() || this.isSealed() || this.isNonSealed())) { - if (this.isClass()) - this.scope.problemReporter().sealedMissingClassModifier(this, typeDecl, sealedEntry.getValue()); - else if (this.isInterface()) - this.scope.problemReporter().sealedMissingInterfaceModifier(this, typeDecl, sealedEntry.getValue()); - } - if (!typeDecl.isRecord() && typeDecl.superclass != null && !checkPermitsAndAdd(this.superclass)) { - reportSealedSuperTypeDoesNotPermitProblem(typeDecl.superclass, this.superclass); - } - for (int i = 0, l = this.superInterfaces.length; i < l; ++i) { - ReferenceBinding superInterface = this.superInterfaces[i]; - if (superInterface != null && !checkPermitsAndAdd(superInterface)) { - TypeReference superInterfaceRef = typeDecl.superInterfaces[i]; - reportSealedSuperTypeDoesNotPermitProblem(superInterfaceRef, superInterface); - } + for (int i = 0, l = this.superInterfaces.length; i < l; ++i) { + ReferenceBinding superInterface = this.superInterfaces[i]; + if (this.isAnUnpermittedSubtypeOf(superInterface)) { + TypeReference superInterfaceRef = typeDecl.superInterfaces[i]; + this.scope.problemReporter().sealedSupertypeDoesNotPermit(this, superInterfaceRef, superInterface); } } + for (ReferenceBinding memberType : this.memberTypes) - ((SourceTypeBinding) memberType).checkPermitsInType(); + ((SourceTypeBinding) memberType).complainIfUnpermittedSubtyping(); - if (this.scope.referenceContext.permittedTypes == null) { - // Ignore implicitly permitted case - return; - } - // In case of errors, be safe. - int l = this.permittedTypes.length <= this.scope.referenceContext.permittedTypes.length ? - this.permittedTypes.length : this.scope.referenceContext.permittedTypes.length; - for (int i = 0; i < l; i++) { - TypeReference permittedTypeRef = this.scope.referenceContext.permittedTypes[i]; - ReferenceBinding permittedType = this.permittedTypes[i]; - if (permittedType == null || !permittedType.isValidBinding()) - continue; - if (this.isClass()) { - ReferenceBinding permSuperType = permittedType.superclass(); - permSuperType = permSuperType.actualType(); - if (!TypeBinding.equalsEquals(this, permSuperType)) { - this.scope.problemReporter().sealedNotDirectSuperClass(permittedType, permittedTypeRef, this); - continue; - } - } else if (this.isInterface()) { - ReferenceBinding[] permSuperInterfaces = permittedType.superInterfaces(); - boolean foundSuperInterface = false; - if (permSuperInterfaces != null) { - for (ReferenceBinding psi : permSuperInterfaces) { - psi = psi.actualType(); - if (TypeBinding.equalsEquals(this, psi)) { - foundSuperInterface = true; - break; - } - } - if (!foundSuperInterface) { - this.scope.problemReporter().sealedNotDirectSuperInterface(permittedType, permittedTypeRef, this); - continue; - } - } - } - } return; } -private void reportSealedSuperTypeDoesNotPermitProblem(TypeReference superTypeRef, TypeBinding superType) { - ModuleBinding sourceModuleBinding = this.module(); - boolean isUnnamedModule = sourceModuleBinding.isUnnamed(); - boolean isClass = false; - if (superType.isClass()) { - isClass = true; - } - boolean sealedSuperTypeDoesNotPermit = false; - ReferenceBinding superReferenceBinding = null; - if (superType instanceof ReferenceBinding) { - superReferenceBinding = (ReferenceBinding) superType; - if (isUnnamedModule) { - PackageBinding superTypePackage = superReferenceBinding.getPackage(); - PackageBinding pkg = this.getPackage(); - sealedSuperTypeDoesNotPermit = pkg!= null && pkg.equals(superTypePackage); - } else { - ModuleBinding superTypeModule = superReferenceBinding.module(); - ModuleBinding mod = this.module(); - sealedSuperTypeDoesNotPermit = mod!= null && mod.equals(superTypeModule); - } - } - if (sealedSuperTypeDoesNotPermit) { - if (isClass) { - this.scope.problemReporter().sealedSuperClassDoesNotPermit(this, superTypeRef, superType); - } else { - this.scope.problemReporter().sealedSuperInterfaceDoesNotPermit(this, superTypeRef, superType); - } - } else { - if (superReferenceBinding instanceof SourceTypeBinding && isUnnamedModule) { - PackageBinding superTypePackage = superReferenceBinding.getPackage(); - if (isClass) { - this.scope.problemReporter().sealedSuperClassInDifferentPackage(this, superTypeRef, superType, superTypePackage); - } else { - this.scope.problemReporter().sealedSuperInterfaceInDifferentPackage(this, superTypeRef, superType, superTypePackage); - } - } else { - if (isClass) { - this.scope.problemReporter().sealedSuperClassDisallowed(this, superTypeRef, superType); - } else { - this.scope.problemReporter().sealedSuperInterfaceDisallowed(this, superTypeRef, superType); - } - } - } -} - -public List collectAllTypeBindings(TypeDeclaration typeDecl, CompilationUnitScope unitScope) { - class TypeBindingsCollector extends ASTVisitor { - List types = new ArrayList<>(); - @Override - public boolean visit( - TypeDeclaration localTypeDeclaration, - BlockScope scope1) { - checkAndAddBinding(localTypeDeclaration.binding); - return true; - } - @Override - public boolean visit( - TypeDeclaration memberTypeDeclaration, - ClassScope scope1) { - checkAndAddBinding(memberTypeDeclaration.binding); - return true; - } - @Override - public boolean visit( - TypeDeclaration typeDeclaration, - CompilationUnitScope scope1) { - checkAndAddBinding(typeDeclaration.binding); - return true; // do nothing by default, keep traversing - } - private void checkAndAddBinding(SourceTypeBinding stb) { - if (stb != null) - this.types.add(stb); - } - } - TypeBindingsCollector typeCollector = new TypeBindingsCollector(); - typeDecl.traverse(typeCollector, unitScope); - return typeCollector.types; -} - -private boolean checkPermitsAndAdd(ReferenceBinding superType) { - if (superType == null - || superType.equals(this.scope.getJavaLangObject()) - || !superType.isSealed()) - return true; - if (superType.isSealed()) { - superType = superType.actualType(); - ReferenceBinding[] superPermittedTypes = superType.permittedTypes(); - for (ReferenceBinding permittedType : superPermittedTypes) { - permittedType = permittedType.actualType(); - if (permittedType.isValidBinding() && TypeBinding.equalsEquals(this, permittedType)) - return true; - } - } - return false; -} - @Override public RecordComponentBinding[] components() { @@ -3240,7 +3046,7 @@ public RecordComponentBinding[] setComponents(RecordComponentBinding[] comps) { return this.methods = methods; } -//Propagate writes to all annotated variants so the clones evolve along. +// Propagate writes to all annotated variants so the clones evolve along. public ReferenceBinding [] setPermittedTypes(ReferenceBinding [] permittedTypes) { if (!isPrototype()) @@ -3256,6 +3062,22 @@ public RecordComponentBinding[] setComponents(RecordComponentBinding[] comps) { return this.permittedTypes = permittedTypes; } +private void setImplicitPermittedType(SourceTypeBinding permittedType) { + ReferenceBinding[] typesPermitted = this.permittedTypes(); + int sz = typesPermitted == null ? 0 : typesPermitted.length; + if (this.scope.referenceCompilationUnit() == permittedType.scope.referenceCompilationUnit()) { + if (sz == 0) { + typesPermitted = new ReferenceBinding[] { permittedType }; + } else { + System.arraycopy(typesPermitted, 0, typesPermitted = new ReferenceBinding[sz + 1], 0, sz); + typesPermitted[sz] = permittedType; + } + this.setPermittedTypes(typesPermitted); + } else if (sz == 0) { + this.setPermittedTypes(Binding.NO_PERMITTED_TYPES); + } +} + // Propagate writes to all annotated variants so the clones evolve along. public ReferenceBinding setSuperClass(ReferenceBinding superClass) { @@ -3269,6 +3091,11 @@ public ReferenceBinding setSuperClass(ReferenceBinding superClass) { annotatedType.superclass = superClass; } } + if (superClass != null && superClass.actualType() instanceof SourceTypeBinding sourceSuperType && sourceSuperType.isSealed() && sourceSuperType.scope.referenceContext.permittedTypes == null) { + sourceSuperType.setImplicitPermittedType(this); + if (this.isAnonymousType() && superClass.isEnum()) + this.modifiers |= ClassFileConstants.AccFinal; + } return this.superclass = superClass; } @@ -3285,6 +3112,12 @@ public ReferenceBinding setSuperClass(ReferenceBinding superClass) { annotatedType.superInterfaces = superInterfaces; } } + for (int i = 0, length = superInterfaces == null ? 0 : superInterfaces.length; i < length; i++) { + ReferenceBinding superInterface = superInterfaces[i]; + if (superInterface.actualType() instanceof SourceTypeBinding sourceSuperType && sourceSuperType.isSealed() && sourceSuperType.scope.referenceContext.permittedTypes == null) { + sourceSuperType.setImplicitPermittedType(this); + } + } return this.superInterfaces = superInterfaces; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java index 314cbce802b..c7479f912f0 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeBinding.java @@ -703,6 +703,10 @@ public boolean isRecord() { return false; } +public boolean isRecordWithComponents() { // do records without components make sense ??! + return isRecord() && components() instanceof RecordComponentBinding [] components && components.length > 0; +} + /* Answer true if the receiver type can be assigned to the argument type (right) */ public boolean isCompatibleWith(TypeBinding right) { @@ -1759,7 +1763,7 @@ public ReferenceBinding superclass() { } public ReferenceBinding[] permittedTypes() { - return Binding.NO_PERMITTEDTYPES; + return Binding.NO_PERMITTED_TYPES; } public ReferenceBinding[] superInterfaces() { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java index f9e2d8a59b6..2a72d85fa51 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/lookup/TypeConstants.java @@ -130,6 +130,7 @@ public interface TypeConstants { // JEP 360 Sealed char[] PERMITS = "permits".toCharArray(); //$NON-NLS-1$ char[] SEALED = "sealed".toCharArray(); //$NON-NLS-1$ + char[] NON_SEALED = "non-sealed".toCharArray(); //$NON-NLS-1$ String KEYWORD_EXTENDS = "extends"; //$NON-NLS-1$ String IMPLEMENTS = "implements"; //$NON-NLS-1$ diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Parser.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Parser.java index 01f3495673b..c3ba52f7ce3 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Parser.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/Parser.java @@ -37,6 +37,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.lang.Runtime.Version; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -1165,7 +1166,7 @@ protected void checkAndSetModifiers(int flag){ problemReporter().StrictfpNotRequired(this.scanner.startPosition, this.scanner.currentPosition - 1); } - if ((this.modifiers & flag) != 0){ // duplicate modifier + if ((this.modifiers & flag) != 0) { // duplicate modifier this.modifiers |= ExtraCompilerModifiers.AccAlternateModifierProblem; } this.modifiers |= flag; @@ -1175,10 +1176,6 @@ protected void checkAndSetModifiers(int flag){ if (this.currentElement != null) { this.currentElement.addModifier(flag, this.modifiersSourceStart); } - if (flag == ExtraCompilerModifiers.AccSealed || flag == ExtraCompilerModifiers.AccNonSealed) { - problemReporter().validateJavaFeatureSupport(JavaFeature.SEALED_CLASSES, this.scanner.startPosition, - this.scanner.currentPosition - 1); - } } public void checkComment() { @@ -6671,6 +6668,18 @@ protected void consumeZeroTypeAnnotations() { // Name ::= SimpleName // TypeAnnotationsopt ::= $empty pushOnTypeAnnotationLengthStack(0); // signal absence of @308 annotations. + if (this.currentElement instanceof RecoveredAnnotation ann) { + if (ann.parent instanceof RecoveredMethod meth + && !meth.foundOpeningBrace + && this.currentToken == TokenNameRPAREN) { + // take note of an incomplete annotation "@Ann(v=)": + meth.incompleteParameterAnnotationSeen = true; + } + if (this.identifierPtr > ann.identifierPtr) { + ann.hasPendingMemberValueName = true; + ann.errorToken = this.currentToken; + } + } } // BEGIN_AUTOGENERATED_REGION_CONSUME_RULE // This method is part of an automatic generation : do NOT edit-modify @@ -9939,7 +9948,6 @@ protected void consumeToken(int type) { pushOnIntStack(this.scanner.startPosition); break; case TokenNameRestrictedIdentifierpermits: - problemReporter().validateJavaFeatureSupport(JavaFeature.SEALED_CLASSES, this.scanner.startPosition,this.scanner.currentPosition - 1); pushOnIntStack(this.scanner.startPosition); break; case TokenNamecase : @@ -12833,6 +12841,21 @@ public CompilationUnitDeclaration parse( compilationResult, 0); + var problemReporterContext = this.problemReporter.referenceContext; + this.problemReporter.referenceContext = this.referenceContext; + if (this.problemReporter != null && this.options != null && this.options.requestedSourceVersion != null && !this.options.requestedSourceVersion.isBlank()) { + try { + var requestedVersion = Version.parse(this.options.requestedSourceVersion); + var latestVersion = Version.parse(CompilerOptions.getLatestVersion()); + if (requestedVersion.compareTo(latestVersion) > 0) { + this.problemReporter.tooRecentJavaVersion(requestedVersion.toString(), latestVersion.toString()); + } + } catch (Exception ex) { + this.problemReporter.abortDueToInternalError(ex.getMessage()); + } + } + this.problemReporter.referenceContext = problemReporterContext; + /* scanners initialization */ char[] contents; try { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredAnnotation.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredAnnotation.java index 101211281ae..2de80c2dc22 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredAnnotation.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredAnnotation.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008 IBM Corporation and others. + * Copyright (c) 2008, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -21,12 +21,13 @@ public class RecoveredAnnotation extends RecoveredElement { public static final int SINGLE_MEMBER = 2; private int kind; - private final int identifierPtr; + final int identifierPtr; private final int identifierLengthPtr; private final int sourceStart; public boolean hasPendingMemberValueName; public int memberValuPairEqualEnd = -1; public Annotation annotation; + public int errorToken; public RecoveredAnnotation(int identifierPtr, int identifierLengthPtr, int sourceStart, RecoveredElement parent, int bracketBalance) { super(parent, bracketBalance); @@ -70,16 +71,24 @@ public void updateFromParserState() { boolean needUpdateRParenPos = false; MemberValuePair pendingMemberValueName = null; + Expression singleValue = null; if (this.hasPendingMemberValueName && this.identifierPtr < parser.identifierPtr) { char[] memberValueName = parser.identifierStack[this.identifierPtr + 1]; long pos = parser.identifierPositionStack[this.identifierPtr + 1]; int start = (int) (pos >>> 32); int end = (int)pos; - int valueEnd = this.memberValuPairEqualEnd > -1 ? this.memberValuPairEqualEnd : end; - SingleNameReference fakeExpression = new SingleNameReference(RecoveryScanner.FAKE_IDENTIFIER, (((long) valueEnd + 1) << 32) + (valueEnd)); - pendingMemberValueName = new MemberValuePair(memberValueName, start, end, fakeExpression); + if (this.errorToken == TerminalTokens.TokenNameDOT) { + // do we need to consult identifierLengthStack to pull more than one name segment? + char[][] qname = new char[][] { memberValueName, RecoveryScanner.FAKE_IDENTIFIER }; + singleValue = new QualifiedNameReference(qname, new long[] {pos,pos}, start, end); + this.kind = SINGLE_MEMBER; + } else { + int valueEnd = this.memberValuPairEqualEnd > -1 ? this.memberValuPairEqualEnd : end; + SingleNameReference fakeExpression = new SingleNameReference(RecoveryScanner.FAKE_IDENTIFIER, (((long) valueEnd + 1) << 32) + (valueEnd)); + pendingMemberValueName = new MemberValuePair(memberValueName, start, end, fakeExpression); + } } parser.identifierPtr = this.identifierPtr; parser.identifierLengthPtr = this.identifierLengthPtr; @@ -136,9 +145,10 @@ public void updateFromParserState() { break; case SINGLE_MEMBER: - if (parser.expressionPtr > -1) { - Expression memberValue = parser.expressionStack[parser.expressionPtr--]; - + Expression memberValue = singleValue != null ? singleValue + : parser.expressionPtr > -1 ? parser.expressionStack[parser.expressionPtr--] + : null; + if (memberValue != null) { SingleMemberAnnotation singleMemberAnnotation = new SingleMemberAnnotation(typeReference, this.sourceStart); singleMemberAnnotation.memberValue = memberValue; singleMemberAnnotation.declarationSourceEnd = memberValue.sourceEnd; @@ -191,6 +201,10 @@ public void setKind(int kind) { this.kind = kind; } + public int sourceStart() { + return this.sourceStart; + } + @Override public int sourceEnd() { if (this.annotation == null) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java index 39e24a20eeb..9e5dea4a78a 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredMethod.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.parser; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.eclipse.jdt.core.compiler.CharOperation; @@ -48,6 +49,8 @@ public class RecoveredMethod extends RecoveredElement implements TerminalTokens RecoveredAnnotation[] pendingAnnotations; int pendingAnnotationCount; + public boolean incompleteParameterAnnotationSeen = false; + public RecoveredMethod(AbstractMethodDeclaration methodDeclaration, RecoveredElement parent, int bracketBalance, Parser parser){ super(parent, bracketBalance, parser); this.methodDeclaration = methodDeclaration; @@ -97,6 +100,10 @@ public RecoveredElement add(Block nestedBlockDeclaration, int bracketBalanceValu */ @Override public RecoveredElement add(FieldDeclaration fieldDeclaration, int bracketBalanceValue) { + if (recoverAsArgument(fieldDeclaration)) { + resetPendingModifiers(); + return this; + } resetPendingModifiers(); /* local variables inside method can only be final and non void */ @@ -133,6 +140,37 @@ public RecoveredElement add(FieldDeclaration fieldDeclaration, int bracketBalanc // still inside method, treat as local variable return this; // ignore } + +private boolean recoverAsArgument(FieldDeclaration fieldDeclaration) { + if (!this.foundOpeningBrace + && this.methodDeclaration.declarationSourceEnd == 0 + && this.incompleteParameterAnnotationSeen) { // misparsed parameter? + long position = ((long) fieldDeclaration.sourceStart << 32)+fieldDeclaration.sourceEnd; + Argument arg = new Argument(fieldDeclaration.name, position, fieldDeclaration.type, fieldDeclaration.modifiers); + if (this.methodDeclaration.arguments == null) { + this.methodDeclaration.arguments = new Argument[] { arg }; + } else { + int len = this.methodDeclaration.arguments.length; + this.methodDeclaration.arguments = Arrays.copyOf(this.methodDeclaration.arguments, len+1); + this.methodDeclaration.arguments[len] = arg; + } + int annotCount = this.pendingAnnotationCount; + if (this.pendingAnnotations != null) { + int end = 0; + arg.annotations = new Annotation[annotCount]; + for (int i = 0; i < this.pendingAnnotationCount; i++) { + arg.annotations[i] = this.pendingAnnotations[i].annotation; + if (i == 0) + arg.declarationSourceStart = arg.annotations[i].sourceStart; + end = arg.sourceEnd; + } + if (end > 0) + this.methodDeclaration.bodyStart = end + 1; + } + return true; + } + return false; +} /* * Record a local declaration - regular method should have been created a block body */ @@ -606,6 +644,11 @@ public RecoveredElement addAnnotationName(int identifierPtr, int identifierLengt this.pendingAnnotations = new RecoveredAnnotation[5]; this.pendingAnnotationCount = 0; } else { + if (this.pendingAnnotationCount > 0) { + RecoveredAnnotation lastAnnot = this.pendingAnnotations[this.pendingAnnotationCount-1]; + if (lastAnnot.sourceStart() == annotationStart) + return this; + } if (this.pendingAnnotationCount == this.pendingAnnotations.length) { System.arraycopy( this.pendingAnnotations, diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredType.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredType.java index 69b26b01c25..e0fb2107f70 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredType.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/parser/RecoveredType.java @@ -328,23 +328,26 @@ public int bodyEnd(){ if (this.bodyEnd == 0) return this.typeDeclaration.declarationSourceEnd; return this.bodyEnd; } -public boolean bodyStartsAtHeaderEnd(){ - if (this.typeDeclaration.superInterfaces == null){ - if (this.typeDeclaration.superclass == null){ - if(this.typeDeclaration.typeParameters == null) { - return this.typeDeclaration.bodyStart == this.typeDeclaration.sourceEnd+1; + +public boolean bodyStartsAtHeaderEnd() { + if (this.typeDeclaration.permittedTypes == null) { + if (this.typeDeclaration.superInterfaces == null) { + if (this.typeDeclaration.superclass == null) { + if (this.typeDeclaration.typeParameters == null) { + return this.typeDeclaration.bodyStart == this.typeDeclaration.sourceEnd + 1; + } else { + return this.typeDeclaration.bodyStart == this.typeDeclaration.typeParameters[this.typeDeclaration.typeParameters.length - 1].sourceEnd + 1; + } } else { - return this.typeDeclaration.bodyStart == this.typeDeclaration.typeParameters[this.typeDeclaration.typeParameters.length-1].sourceEnd+1; + return this.typeDeclaration.bodyStart == this.typeDeclaration.superclass.sourceEnd + 1; } } else { - return this.typeDeclaration.bodyStart == this.typeDeclaration.superclass.sourceEnd+1; + return this.typeDeclaration.bodyStart + == this.typeDeclaration.superInterfaces[this.typeDeclaration.superInterfaces.length - 1].sourceEnd + 1; } } else { - if (this.typeDeclaration.permittedTypes != null) - return this.typeDeclaration.bodyStart - == this.typeDeclaration.permittedTypes[this.typeDeclaration.permittedTypes.length-1].sourceEnd+1; return this.typeDeclaration.bodyStart - == this.typeDeclaration.superInterfaces[this.typeDeclaration.superInterfaces.length-1].sourceEnd+1; + == this.typeDeclaration.permittedTypes[this.typeDeclaration.permittedTypes.length - 1].sourceEnd + 1; } } /* 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 2890a77a419..6db11b809dd 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 @@ -826,6 +826,16 @@ public void abortDueToNotSupportedJavaVersion(String notSupportedVersion, String 0, 0); } +public void tooRecentJavaVersion(String notSupportedVersion, String firstSupportedVersion) { + String[] args = new String[] {notSupportedVersion, firstSupportedVersion}; + this.handle( + IProblem.JavaVersionTooRecent, + args, + args, + ProblemSeverities.Warning, + 0, + 0); +} public void abstractMethodCannotBeOverridden(SourceTypeBinding type, MethodBinding concreteMethod) { this.handle( @@ -3333,7 +3343,7 @@ public void illegalVisibilityModifierCombinationForField(ReferenceBinding type, fieldDecl.sourceStart, fieldDecl.sourceEnd); } -public void IllegalModifierCombinationForType(SourceTypeBinding type) { +public void illegalModifierCombinationForType(SourceTypeBinding type) { String[] arguments = new String[] {new String(type.sourceName())}; this.handle( IProblem.IllegalModifierCombinationForType, @@ -4864,7 +4874,7 @@ else if (type instanceof ArrayBinding) { } } - if (type.isParameterizedType()) { + if (!(type instanceof MissingTypeBinding)) { List missingTypes = type.collectMissingTypes(null); if (missingTypes != null) { ReferenceContext savedContext = this.referenceContext; @@ -5195,82 +5205,7 @@ public void isClassPathCorrect(char[][] wellKnownTypeName, CompilationUnitDeclar private boolean isIdentifier(int token) { return token == TerminalTokens.TokenNameIdentifier; } -private boolean isRestrictedIdentifier(int token) { - switch(token) { - case TerminalTokens.TokenNameRestrictedIdentifierYield: - case TerminalTokens.TokenNameRestrictedIdentifierrecord: - case TerminalTokens.TokenNameRestrictedIdentifiersealed: - case TerminalTokens.TokenNameRestrictedIdentifierpermits: - case TerminalTokens.TokenNameRestrictedIdentifierWhen: - return true; - default: return false; - } -} -private boolean isKeyword(int token) { - switch(token) { - case TerminalTokens.TokenNameabstract: - case TerminalTokens.TokenNameassert: - case TerminalTokens.TokenNamebyte: - case TerminalTokens.TokenNamebreak: - case TerminalTokens.TokenNameboolean: - case TerminalTokens.TokenNamecase: - case TerminalTokens.TokenNamechar: - case TerminalTokens.TokenNamecatch: - case TerminalTokens.TokenNameclass: - case TerminalTokens.TokenNamecontinue: - case TerminalTokens.TokenNamedo: - case TerminalTokens.TokenNamedouble: - case TerminalTokens.TokenNamedefault: - case TerminalTokens.TokenNameelse: - case TerminalTokens.TokenNameextends: - case TerminalTokens.TokenNamefor: - case TerminalTokens.TokenNamefinal: - case TerminalTokens.TokenNamefloat: - case TerminalTokens.TokenNamefalse: - case TerminalTokens.TokenNamefinally: - case TerminalTokens.TokenNameif: - case TerminalTokens.TokenNameint: - case TerminalTokens.TokenNameimport: - case TerminalTokens.TokenNameinterface: - case TerminalTokens.TokenNameimplements: - case TerminalTokens.TokenNameinstanceof: - case TerminalTokens.TokenNamelong: - case TerminalTokens.TokenNamenew: - case TerminalTokens.TokenNamenon_sealed: - case TerminalTokens.TokenNamenull: - case TerminalTokens.TokenNamenative: - case TerminalTokens.TokenNamepublic: - case TerminalTokens.TokenNamepackage: - case TerminalTokens.TokenNameprivate: - case TerminalTokens.TokenNameprotected: - case TerminalTokens.TokenNamereturn: - case TerminalTokens.TokenNameshort: - case TerminalTokens.TokenNamesuper: - case TerminalTokens.TokenNamestatic: - case TerminalTokens.TokenNameswitch: - case TerminalTokens.TokenNamestrictfp: - case TerminalTokens.TokenNamesynchronized: - case TerminalTokens.TokenNametry: - case TerminalTokens.TokenNamethis: - case TerminalTokens.TokenNametrue: - case TerminalTokens.TokenNamethrow: - case TerminalTokens.TokenNamethrows: - case TerminalTokens.TokenNametransient: - case TerminalTokens.TokenNamevoid: - case TerminalTokens.TokenNamevolatile: - case TerminalTokens.TokenNamewhile: - return true; - case TerminalTokens.TokenNameRestrictedIdentifierYield: - case TerminalTokens.TokenNameRestrictedIdentifierrecord: - case TerminalTokens.TokenNameRestrictedIdentifiersealed: - case TerminalTokens.TokenNameRestrictedIdentifierpermits: - case TerminalTokens.TokenNameRestrictedIdentifierWhen: - // making explicit - not a (restricted) keyword but restricted identifier. - //$FALL-THROUGH$ - default: - return false; - } -} + private boolean isLiteral(int token) { return Scanner.isLiteral(token); } @@ -7368,7 +7303,7 @@ public void noSuchEnclosingInstance(TypeBinding targetType, ASTNode location, bo location.sourceStart, location instanceof LambdaExpression ? ((LambdaExpression)location).diagnosticsSourceEnd() : location.sourceEnd); } -public void notCompatibleTypesError(EqualExpression expression, TypeBinding leftType, TypeBinding rightType) { +public void notCompatibleTypesError(ASTNode location, TypeBinding leftType, TypeBinding rightType) { String leftName = new String(leftType.readableName()); String rightName = new String(rightType.readableName()); String leftShortName = new String(leftType.shortReadableName()); @@ -7377,28 +7312,18 @@ public void notCompatibleTypesError(EqualExpression expression, TypeBinding left leftShortName = leftName; rightShortName = rightName; } - this.handle( - IProblem.IncompatibleTypesInEqualityOperator, - new String[] {leftName, rightName }, - new String[] {leftShortName, rightShortName }, - expression.sourceStart, - expression.sourceEnd); -} -public void notCompatibleTypesError(Expression expression, TypeBinding leftType, TypeBinding rightType) { - String leftName = new String(leftType.readableName()); - String rightName = new String(rightType.readableName()); - String leftShortName = new String(leftType.shortReadableName()); - String rightShortName = new String(rightType.shortReadableName()); - if (leftShortName.equals(rightShortName)){ - leftShortName = leftName; - rightShortName = rightName; + int problemId = IProblem.IncompatibleTypesInEqualityOperator; + if (location instanceof Pattern p && p.getEnclosingPattern() instanceof RecordPattern) { + problemId = IProblem.PatternTypeMismatch; + } else if (location instanceof InstanceOfExpression) { + problemId = IProblem.IncompatibleTypesInConditionalOperator; } this.handle( - IProblem.IncompatibleTypesInConditionalOperator, + problemId, new String[] {leftName, rightName }, new String[] {leftShortName, rightShortName }, - expression.sourceStart, - expression.sourceEnd); + location.sourceStart, + location.sourceEnd); } public void notCompatibleTypesErrorInForeach(Expression expression, TypeBinding leftType, TypeBinding rightType) { String leftName = new String(leftType.readableName()); @@ -7618,7 +7543,7 @@ public void parseError( String[] possibleTokens) { if (possibleTokens.length == 0) { //no suggestion available - if (isKeyword(currentToken)) { + if (Scanner.isKeyword(currentToken)) { String[] arguments = new String[] {new String(currentTokenSource)}; this.handle( IProblem.ParsingErrorOnKeywordNoSuggestion, @@ -7651,7 +7576,7 @@ public void parseError( list.append('"'); } - if (isKeyword(currentToken)) { + if (Scanner.isKeyword(currentToken)) { String[] arguments = new String[] {new String(currentTokenSource), list.toString()}; this.handle( IProblem.ParsingErrorOnKeyword, @@ -8488,14 +8413,14 @@ private void syntaxError( return; } String eTokenName; - if (isKeyword(currentKind) || + if (Scanner.isKeyword(currentKind) || isLiteral(currentKind) || isIdentifier(currentKind)) { eTokenName = new String(currentTokenSource); } else { eTokenName = errorTokenName; } - if (isRestrictedIdentifier(currentKind)) + if (TerminalTokens.isRestrictedKeyword(currentKind)) eTokenName = replaceIfSynthetic(eTokenName); String[] arguments; @@ -8603,21 +8528,21 @@ public void typeCastError(CastExpression expression, TypeBinding leftType, TypeB expression.sourceStart, expression.sourceEnd); } -public void unsafeCastInInstanceof(Expression expression, TypeBinding leftType, TypeBinding rightType) { - String leftName = new String(leftType.readableName()); - String rightName = new String(rightType.readableName()); - String leftShortName = new String(leftType.shortReadableName()); - String rightShortName = new String(rightType.shortReadableName()); - if (leftShortName.equals(rightShortName)){ - leftShortName = leftName; - rightShortName = rightName; +public void unsafeCastInTestingContext(ASTNode location, TypeBinding castType, TypeBinding expressionType) { + String castName = new String(castType.readableName()); + String exprName = new String(expressionType.readableName()); + String castShortName = new String(castType.shortReadableName()); + String exprShortName = new String(expressionType.shortReadableName()); + if (castShortName.equals(exprShortName)){ + castShortName = castName; + exprShortName = exprName; } this.handle( IProblem.UnsafeCast, - new String[] { rightName, leftName }, - new String[] { rightShortName, leftShortName }, - expression.sourceStart, - expression.sourceEnd); + new String[] { exprName, castName }, + new String[] { exprShortName, castShortName }, + location.sourceStart, + location.sourceEnd); } public void typeCollidesWithEnclosingType(TypeDeclaration typeDecl) { String[] arguments = new String[] {new String(typeDecl.name)}; @@ -9786,7 +9711,7 @@ public boolean validateRestrictedKeywords(char[] name, ASTNode node) { close(); } } -//Returns true if the problem is handled and reported (only errors considered and not warnings) +// Returns true if the problem is handled and reported (only errors considered and not warnings) public boolean validateJavaFeatureSupport(JavaFeature feature, int sourceStart, int sourceEnd) { boolean versionInRange = feature.getCompliance() <= this.options.sourceLevel; String version = CompilerOptions.versionFromJdkLevel(feature.getCompliance()); @@ -12220,140 +12145,80 @@ public void illegalModifierForLocalEnumDeclaration(SourceTypeBinding type) { type.sourceStart(), type.sourceEnd()); } -private void sealedMissingModifier(int problem, SourceTypeBinding type, TypeDeclaration typeDecl, TypeBinding superTypeBinding) { + +public void permittedTypeNeedsModifier(SourceTypeBinding type, TypeDeclaration typeDecl, TypeBinding superTypeBinding) { String name = new String(type.sourceName()); String superTypeFullName = new String(superTypeBinding.readableName()); String superTypeShortName = new String(superTypeBinding.shortReadableName()); if (superTypeShortName.equals(name)) superTypeShortName = superTypeFullName; this.handle( - problem, + type.isClass() ? IProblem.SealedMissingClassModifier : IProblem.SealedMissingInterfaceModifier, new String[] {superTypeFullName, name}, new String[] {superTypeShortName, name}, typeDecl.sourceStart, typeDecl.sourceEnd); } -public void sealedMissingClassModifier(SourceTypeBinding type, TypeDeclaration typeDecl, TypeBinding superTypeBinding) { - sealedMissingModifier(IProblem.SealedMissingClassModifier, type, typeDecl, superTypeBinding); -} -public void sealedMissingInterfaceModifier(SourceTypeBinding type, TypeDeclaration typeDecl, TypeBinding superTypeBinding) { - sealedMissingModifier(IProblem.SealedMissingInterfaceModifier, type, typeDecl, superTypeBinding); -} -public void sealedDisAllowedNonSealedModifierInClass(SourceTypeBinding type, TypeDeclaration typeDecl) { +public void disallowedNonSealedModifier(SourceTypeBinding type, TypeDeclaration typeDecl) { String name = new String(type.sourceName()); this.handle( - IProblem.SealedDisAllowedNonSealedModifierInClass, + type.isClass() ? IProblem.SealedDisAllowedNonSealedModifierInClass : IProblem.SealedDisAllowedNonSealedModifierInInterface, new String[] { name }, new String[] { name }, typeDecl.sourceStart, typeDecl.sourceEnd); } -private void sealedSuperTypeDoesNotPermit(int problem, SourceTypeBinding type, TypeReference superType, TypeBinding superTypeBinding) { - String name = new String(type.sourceName()); - String superTypeFullName = new String(superTypeBinding.readableName()); - String superTypeShortName = new String(superTypeBinding.shortReadableName()); - if (superTypeShortName.equals(name)) superTypeShortName = superTypeFullName; - this.handle( - problem, - new String[] {name, superTypeFullName}, - new String[] {name, superTypeShortName}, - superType.sourceStart, - superType.sourceEnd); -} - -public void sealedSuperTypeInDifferentPackage(int problem, SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding, PackageBinding superPackageBinding) { - String typeName = new String(type.sourceName); - String name = new String(superTypeBinding.sourceName()); - String packageName = superPackageBinding.compoundName == CharOperation.NO_CHAR_CHAR ? "default" : //$NON-NLS-1$ - CharOperation.toString(superPackageBinding.compoundName); - String[] arguments = new String[] {typeName, packageName, name}; - this.handle(problem, - arguments, - arguments, - curType.sourceStart, - curType.sourceEnd); -} - -public void sealedSuperTypeDisallowed(int problem, SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding) { - String typeName = new String(type.sourceName); - String name = new String(superTypeBinding.sourceName()); - String[] arguments = new String[] {typeName, name}; - this.handle(problem, - arguments, - arguments, - curType.sourceStart, - curType.sourceEnd); -} - -public void sealedSuperClassDoesNotPermit(SourceTypeBinding type, TypeReference superType, TypeBinding superTypeBinding) { - sealedSuperTypeDoesNotPermit(IProblem.SealedSuperClassDoesNotPermit, type, superType, superTypeBinding); -} - -public void sealedSuperClassInDifferentPackage(SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding, PackageBinding superPackageBinding) { - sealedSuperTypeInDifferentPackage(IProblem.SealedSuperTypeInDifferentPackage, type, curType, superTypeBinding, superPackageBinding); -} - -public void sealedSuperClassDisallowed(SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding) { - sealedSuperTypeDisallowed(IProblem.SealedSuperTypeDisallowed, type, curType, superTypeBinding); -} - -public void sealedSuperInterfaceDoesNotPermit(SourceTypeBinding type, TypeReference superType, TypeBinding superTypeBinding) { +public void sealedSupertypeDoesNotPermit(SourceTypeBinding type, TypeReference superType, TypeBinding superTypeBinding) { String name = new String(type.sourceName()); String superTypeFullName = new String(superTypeBinding.readableName()); String superTypeShortName = new String(superTypeBinding.shortReadableName()); - String keyword = type.isClass() ? new String(TypeConstants.IMPLEMENTS) : new String(TypeConstants.KEYWORD_EXTENDS); if (superTypeShortName.equals(name)) superTypeShortName = superTypeFullName; - this.handle( - IProblem.SealedSuperInterfaceDoesNotPermit, - new String[] {name, superTypeFullName, keyword}, - new String[] {name, superTypeShortName, keyword}, - superType.sourceStart, - superType.sourceEnd); -} - -public void sealedSuperInterfaceInDifferentPackage(SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding, PackageBinding superPackageBinding) { - sealedSuperTypeInDifferentPackage(IProblem.SealedSuperTypeInDifferentPackage, type, curType, superTypeBinding, superPackageBinding); -} - -public void sealedSuperInterfaceDisallowed(SourceTypeBinding type, TypeReference curType, TypeBinding superTypeBinding) { - sealedSuperTypeDisallowed(IProblem.SealedSuperTypeDisallowed, type, curType, superTypeBinding); + if (superTypeBinding.isClass()) { + this.handle( + IProblem.SealedSuperClassDoesNotPermit, + new String[] {name, superTypeFullName}, + new String[] {name, superTypeShortName}, + superType.sourceStart, + superType.sourceEnd); + } else { + String keyword = type.isClass() ? new String(TypeConstants.IMPLEMENTS) : new String(TypeConstants.KEYWORD_EXTENDS); + this.handle( + IProblem.SealedSuperInterfaceDoesNotPermit, + new String[] {name, superTypeFullName, keyword}, + new String[] {name, superTypeShortName, keyword}, + superType.sourceStart, + superType.sourceEnd); + } } -public void sealedMissingSealedModifier(SourceTypeBinding type, ASTNode node) { +public void missingSealedModifier(SourceTypeBinding type, ASTNode node) { String name = new String(type.sourceName()); this.handle(IProblem.SealedMissingSealedModifier, new String[] { name }, new String[] { name }, node.sourceStart, node.sourceEnd); } -public void sealedDuplicateTypeInPermits(SourceTypeBinding type, TypeReference reference, ReferenceBinding superType) { +public void duplicatePermittedType(TypeReference reference, ReferenceBinding superType) { this.handle( IProblem.SealedDuplicateTypeInPermits, - new String[] { - new String(superType.readableName()), - new String(type.sourceName())}, - new String[] { - new String(superType.shortReadableName()), - new String(type.sourceName())}, + new String[] { new String(superType.readableName()) }, + new String[] { new String(superType.shortReadableName()) }, reference.sourceStart, reference.sourceEnd); } -public void sealedNotDirectSuperClass(ReferenceBinding type, TypeReference reference, SourceTypeBinding superType) { - this.handle( - IProblem.SealedNotDirectSuperClass, - new String[] { - new String(type.sourceName()), - new String(superType.readableName())}, - new String[] { - new String(type.sourceName()), - new String(superType.readableName())}, - reference.sourceStart, - reference.sourceEnd); +public void sealedClassNotDirectSuperClassOf(ReferenceBinding type, TypeReference reference, SourceTypeBinding superType) { + if ((type.tagBits & TagBits.HierarchyHasProblems) == 0 && (superType.tagBits & TagBits.HierarchyHasProblems) == 0) { + this.handle(IProblem.SealedNotDirectSuperClass, + new String[] { new String(type.sourceName()), new String(superType.readableName()) }, + new String[] { new String(type.sourceName()), new String(superType.readableName()) }, + reference.sourceStart, reference.sourceEnd); + } } -public void sealedPermittedTypeOutsideOfModule(ReferenceBinding permType, SourceTypeBinding type, ASTNode node, ModuleBinding moduleBinding) { + +public void permittedTypeOutsideOfModule(ReferenceBinding permType, ReferenceBinding sealedType, ASTNode node, ModuleBinding moduleBinding) { String permTypeName = new String(permType.sourceName); - String name = new String(type.sourceName()); + String name = new String(sealedType.sourceName()); String moduleName = new String(moduleBinding.name()); String[] arguments = new String[] {permTypeName, moduleName, name}; this.handle(IProblem.SealedPermittedTypeOutsideOfModule, @@ -12362,15 +12227,10 @@ public void sealedPermittedTypeOutsideOfModule(ReferenceBinding permType, Source node.sourceStart, node.sourceEnd); } -public void sealedPermittedTypeOutsideOfModule(SourceTypeBinding type, ASTNode node) { - String name = new String(type.sourceName()); - this.handle(IProblem.SealedPermittedTypeOutsideOfModule, new String[] { name }, new String[] { name }, - node.sourceStart, node.sourceEnd); -} -public void sealedPermittedTypeOutsideOfPackage(ReferenceBinding permType, SourceTypeBinding type, ASTNode node, PackageBinding packageBinding) { +public void permittedTypeOutsideOfPackage(ReferenceBinding permType, ReferenceBinding sealedType, ASTNode node, PackageBinding packageBinding) { String permTypeName = new String(permType.sourceName); - String name = new String(type.sourceName()); + String name = new String(sealedType.sourceName()); String packageName = packageBinding.compoundName == CharOperation.NO_CHAR_CHAR ? "default" : //$NON-NLS-1$ CharOperation.toString(packageBinding.compoundName); String[] arguments = new String[] {permTypeName, packageName, name}; @@ -12381,45 +12241,22 @@ public void sealedPermittedTypeOutsideOfPackage(ReferenceBinding permType, Sourc node.sourceEnd); } -public void sealedSealedTypeMissingPermits(SourceTypeBinding type, ASTNode node) { +public void missingPermitsClause(SourceTypeBinding type, ASTNode node) { String name = new String(type.sourceName()); this.handle(IProblem.SealedSealedTypeMissingPermits, new String[] { name }, new String[] { name }, node.sourceStart, node.sourceEnd); } -public void sealedInterfaceIsSealedAndNonSealed(SourceTypeBinding type, ASTNode node) { - String name = new String(type.sourceName()); - this.handle(IProblem.SealedInterfaceIsSealedAndNonSealed, - new String[] { name }, - new String[] { name }, - node.sourceStart, - node.sourceEnd); -} - -public void sealedDisAllowedNonSealedModifierInInterface(SourceTypeBinding type, TypeDeclaration typeDecl) { - String name = new String(type.sourceName()); - this.handle( - IProblem.SealedDisAllowedNonSealedModifierInInterface, - new String[] { name }, - new String[] { name }, - typeDecl.sourceStart, - typeDecl.sourceEnd); -} - -public void sealedNotDirectSuperInterface(ReferenceBinding type, TypeReference reference, SourceTypeBinding superType) { - this.handle( - IProblem.SealedNotDirectSuperInterface, - new String[] { - new String(type.sourceName()), - new String(superType.readableName())}, - new String[] { - new String(type.sourceName()), - new String(superType.readableName())}, - reference.sourceStart, - reference.sourceEnd); +public void sealedInterfaceNotDirectSuperInterfaceOf(ReferenceBinding type, TypeReference reference, SourceTypeBinding superType) { + if ((type.tagBits & TagBits.HierarchyHasProblems) == 0 && (superType.tagBits & TagBits.HierarchyHasProblems) == 0) { + this.handle(IProblem.SealedNotDirectSuperInterface, + new String[] { new String(type.sourceName()), new String(superType.readableName()) }, + new String[] { new String(type.sourceName()), new String(superType.readableName()) }, + reference.sourceStart, reference.sourceEnd); + } } -public void sealedLocalDirectSuperTypeSealed(SourceTypeBinding type, TypeReference superclass, TypeBinding superTypeBinding) { +public void localTypeMayNotBePermittedType(SourceTypeBinding type, TypeReference superclass, TypeBinding superTypeBinding) { String name = new String(type.sourceName()); String superTypeFullName = new String(superTypeBinding.readableName()); String superTypeShortName = new String(superTypeBinding.shortReadableName()); @@ -12431,7 +12268,8 @@ public void sealedLocalDirectSuperTypeSealed(SourceTypeBinding type, TypeReferen superclass.sourceStart, superclass.sourceEnd); } -public void sealedAnonymousClassCannotExtendSealedType(TypeReference reference, TypeBinding type) { + +public void anonymousClassCannotExtendSealedType(TypeReference reference, TypeBinding type) { this.handle( IProblem.SealedAnonymousClassCannotExtendSealedType, new String[] {new String(type.readableName())}, @@ -12439,6 +12277,17 @@ public void sealedAnonymousClassCannotExtendSealedType(TypeReference reference, reference.sourceStart, reference.sourceEnd); } + +public void functionalInterfaceMayNotBeSealed(TypeDeclaration type) { + TypeBinding binding = type.binding; + this.handle( + IProblem.FunctionalInterfaceMayNotbeSealed, + new String[] {new String(binding.readableName()), }, + new String[] {new String(binding.shortReadableName()),}, + type.sourceStart, + type.sourceEnd); +} + public void StrictfpNotRequired(int sourceStart, int sourceEnd) { this.handle( IProblem.StrictfpNotRequired, @@ -12541,14 +12390,6 @@ public void recordPatternSignatureMismatch(TypeBinding type, ASTNode element) { element.sourceStart, element.sourceEnd); } -public void incompatiblePatternType(ASTNode element, TypeBinding type, TypeBinding expected) { - this.handle( - IProblem.PatternTypeMismatch, - new String[] {new String(type.readableName()), new String(expected.readableName())}, - new String[] {new String(type.shortReadableName()), new String(expected.readableName())}, - element.sourceStart, - element.sourceEnd); -} public void cannotInferRecordPatternTypes(RecordPattern pattern) { String arguments [] = new String [] { pattern.toString() }; 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 e9b69918495..a3b7203abde 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 @@ -908,6 +908,7 @@ 1107 = The Java feature ''{0}'' is only available with source level {1} and above 1108 = You are using an API that is part of a preview feature and may be removed in future 1109 = Compiling for Java version ''{0}'' is no longer supported. Minimal supported version is ''{1}'' +1110 = Compiling for Java version ''{0}'' is not supported yet. Using ''{1}'' instead # more programming problems: 1200 = Unlikely argument type {0} for {1} on a {2} 1201 = Unlikely argument type for equals(): {0} seems to be unrelated to {2} @@ -1130,25 +1131,26 @@ # Java 16 1820 = {0} is a value-based type which is a discouraged argument for the synchronized statement -# Java 15 Preview - cont -1850 = The class {1} with a sealed direct superclass or a sealed direct superinterface {0} should be declared either final, sealed, or non-sealed -1851 = A class {0} declared as non-sealed should have either a sealed direct superclass or a sealed direct superinterface -1852 = The type {0} extending a sealed class {1} should be a permitted subtype of {1} -1853 = The type {0} that {2} a sealed interface {1} should be a permitted subtype of {1} +# Sealed types - Java 17 +1850 = The class {1} with a sealed direct supertype {0} should be declared either final, sealed, or non-sealed +1851 = The non-sealed class {0} must have a sealed direct supertype +1852 = The class {0} cannot extend the class {1} as it is not a permitted subtype of {1} +1853 = The type {0} that {2} the sealed interface {1} should be a permitted subtype of {1} 1854 = A type declaration {0} that has a permits clause should have a sealed modifier 1855 = The interface {1} with a sealed direct superinterface {0} should be declared either sealed or non-sealed -1856 = Duplicate type {0} for the type {1} in the permits clause -1857 = Permitted class {0} does not declare {1} as direct super class +1856 = Duplicate permitted type {0} +1857 = Permitted type {0} does not declare {1} as a direct supertype 1858 = Permitted type {0} in a named module {1} should be declared in the same module {1} of declaring type {2} 1859 = Permitted type {0} in an unnamed module should be declared in the same package {1} of declaring type {2} -1860 = Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares {0} as its direct superclass or superinterface -1861 = An interface {0} is declared both sealed and non-sealed -1862 = An interface {0} declared as non-sealed should have a sealed direct superinterface +1860 = Sealed type {0} lacks a permits clause and no type from the same compilation unit declares {0} as its direct supertype +###[obsolete] 1861 = An interface {0} is declared both sealed and non-sealed +1862 = The non-sealed interface {0} must have a sealed direct superinterface 1863 = Permitted type {0} does not declare {1} as direct super interface -1864 = A local class {1} cannot have a sealed direct superclass or a sealed direct superinterface {0} +1864 = The local type {1} may not have a sealed supertype {0} 1865 = An anonymous class cannot subclass a sealed type {0} -1866 = Sealed type {2} and sub type {0} in an unnamed module should be declared in the same package {1} -1867 = Sealed type {1} cannot be super type of {0} as it is from a different package or split package or module +###[obsolete] 1866 = Sealed type {2} and sub type {0} in an unnamed module should be declared in the same package {1} +###[obsolete] 1867 = Sealed type {1} cannot be super type of {0} as it is from a different package or split package or module +1868 = A functional interface may not be declared sealed # Switch Patterns - Java 21 1900 = Local variable {0} referenced from a guard must be final or effectively final diff --git a/org.eclipse.jdt.core.setup/.project b/org.eclipse.jdt.core.setup/.project new file mode 100644 index 00000000000..0b3242f09d3 --- /dev/null +++ b/org.eclipse.jdt.core.setup/.project @@ -0,0 +1,11 @@ + + + org.eclipse.jdt.core.setup + + + + + + + + diff --git a/org.eclipse.jdt.core.setup/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jdt.core.setup/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/org.eclipse.jdt.core.setup/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.jdt.core.setup/JdtCoreConfiguration.setup b/org.eclipse.jdt.core.setup/JdtCoreConfiguration.setup new file mode 100644 index 00000000000..77f05a777b7 --- /dev/null +++ b/org.eclipse.jdt.core.setup/JdtCoreConfiguration.setup @@ -0,0 +1,90 @@ + + + + + https://www.eclipse.org/downloads/images/committers.png + + + JDT Core + + + + + + The JDT Core installation provides the latest tools needed to work with the project's source code. + + + + + + + record + + + + + + + + + + + + + + The JDT Core workspace provides all the source code of the project. + + + <p> + The <code>JDT Core</code> configuration provisions a dedicated development environment for the complete set of projects that comprise the JDT Core, + i.e. the projects that are contained in the <a href="https://github.com/eclipse-jdt/jdt.core">jdt.core</a> repository. + </p> + <p> + The installation is based on the latest successful integration build of the <code>Eclipse Platform SDK</code>, + the PDE target platform, like the installation, is also based on the latest integration build, + and the API baseline is based on the most recent release. + <p> + </p> + Please <a href="https://wiki.eclipse.org/Eclipse_Platform_SDK_Provisioning">read the tutorial instructions</a> for more details. + </p> + + diff --git a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF index bc00fbfe540..3bb787a3c47 100644 --- a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF @@ -14,7 +14,6 @@ Require-Bundle: org.junit;bundle-version="3.8.1", org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.test;bundle-version="[3.6.0,4.0.0)", org.eclipse.test.performance;bundle-version="[3.1.0,4.0.0)", - org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional, org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional, org.eclipse.jdt.apt.core Bundle-RequiredExecutionEnvironment: JavaSE-17 diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/AnnotationDependencyTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/AnnotationDependencyTests.java index e735f0b847c..b4da162ae70 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/AnnotationDependencyTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/AnnotationDependencyTests.java @@ -17,21 +17,19 @@ *******************************************************************************/ package org.eclipse.jdt.core.tests.builder; -import java.io.File; +import java.io.IOException; import junit.framework.Test; import org.eclipse.core.resources.IMarker; -import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; -import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.tests.compiler.regression.AbstractNullAnnotationTest; import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -import org.osgi.framework.Bundle; /** * Tests to verify that annotation changes cause recompilation of dependent types. @@ -144,17 +142,19 @@ private void addAnnotationType() { void setupProjectForNullAnnotations() throws JavaModelException { // add the org.eclipse.jdt.annotation library (bin/ folder or jar) to the project: - Bundle[] bundles = Platform.getBundles("org.eclipse.jdt.annotation","[1.1.0,2.0.0)"); - File bundleFile = FileLocator.getBundleFileLocation(bundles[0]).get(); - String annotationsLib = bundleFile.isDirectory() ? bundleFile.getPath()+"/bin" : bundleFile.getPath(); - IJavaProject javaProject = env.getJavaProject(this.projectPath); - IClasspathEntry[] rawClasspath = javaProject.getRawClasspath(); - int len = rawClasspath.length; - System.arraycopy(rawClasspath, 0, rawClasspath = new IClasspathEntry[len+1], 0, len); - rawClasspath[len] = JavaCore.newLibraryEntry(new Path(annotationsLib), null, null); - javaProject.setRawClasspath(rawClasspath, null); - - javaProject.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED); + try { + String annotationsLib = AbstractNullAnnotationTest.getAnnotationV1LibPath(); + IJavaProject javaProject = env.getJavaProject(this.projectPath); + IClasspathEntry[] rawClasspath = javaProject.getRawClasspath(); + int len = rawClasspath.length; + System.arraycopy(rawClasspath, 0, rawClasspath = new IClasspathEntry[len+1], 0, len); + rawClasspath[len] = JavaCore.newLibraryEntry(new Path(annotationsLib), null, null); + javaProject.setRawClasspath(rawClasspath, null); + + javaProject.setOption(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, JavaCore.ENABLED); + } catch (IOException ioe) { + throw new JavaModelException(ioe, -13); + } } /** diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests.java index 25629f50723..b6b56c65508 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/IncrementalTests.java @@ -1664,4 +1664,65 @@ public final class Child extends Parent { env.removeProject(projectPath); } + public void testExhaustiveness() throws JavaModelException { + String javaVersion = System.getProperty("java.version"); + if (javaVersion != null && JavaCore.compareJavaVersions(javaVersion, "18") < 0) + return; + + IPath projectPath = env.addProject("Project", "18"); + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + + // remove old package fragment root so that names don't collide + env.removePackageFragmentRoot(projectPath, ""); + + IPath root = env.addPackageFragmentRoot(projectPath, "src"); + env.setOutputFolder(projectPath, "bin"); + + IPath pathToX = env.addClass(root, "", "X", + """ + public class X { + + public static void main(String[] args) { + E e = E.getE(); + + String s = switch (e) { + case A -> "A"; + case B -> "B"; + case C -> "C"; + }; + System.out.println(s); + } + } + """); + + env.addClass(root, "", "E", + """ + public enum E { + A, B, C; + static E getE() { + return C; + } + } + """); + + fullBuild(projectPath); + expectingNoProblems(); + executeClass(projectPath, "X", "C", ""); + + env.addClass(root, "", "E", + """ + public enum E { + A, B, C, D; + static E getE() { + return D; + } + } + """); + + + incrementalBuild(projectPath); + expectingSpecificProblemFor(pathToX, new Problem("E", "A Switch expression should cover all possible values", pathToX, 100, 101, CategorizedProblem.CAT_SYNTAX, IMarker.SEVERITY_ERROR)); //$NON-NLS-1$ //$NON-NLS-2$ + env.removeProject(projectPath); + } + } diff --git a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF index b4db246297f..5789e272ced 100644 --- a/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core.tests.compiler/META-INF/MANIFEST.MF @@ -22,7 +22,6 @@ Require-Bundle: org.junit;bundle-version="3.8.1", org.eclipse.test;bundle-version="[3.6.0,4.0.0)", org.eclipse.test.performance;bundle-version="[3.10.0,4.0.0)", org.eclipse.core.resources;bundle-version="[3.21.0,4.0.0)", - org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional, org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional Import-Package: jakarta.annotation;version="[2.1.0,3.0.0)", org.eclipse.jdt.internal.compiler.apt.dispatch diff --git a/org.eclipse.jdt.core.tests.compiler/build.properties b/org.eclipse.jdt.core.tests.compiler/build.properties index cfa629f6809..1693443f0a3 100644 --- a/org.eclipse.jdt.core.tests.compiler/build.properties +++ b/org.eclipse.jdt.core.tests.compiler/build.properties @@ -17,7 +17,8 @@ bin.includes = test.xml,\ .,\ META-INF/,\ plugin.properties,\ - workspace/ + workspace/,\ + lib/org.eclipse.jdt.annotation_1.2.100.v20241001-0914.jar source.. = src/ output.. = bin/ src.includes = about.html diff --git a/org.eclipse.jdt.core.tests.compiler/lib/org.eclipse.jdt.annotation_1.2.100.v20241001-0914.jar b/org.eclipse.jdt.core.tests.compiler/lib/org.eclipse.jdt.annotation_1.2.100.v20241001-0914.jar new file mode 100644 index 00000000000..5db80bf2399 Binary files /dev/null and b/org.eclipse.jdt.core.tests.compiler/lib/org.eclipse.jdt.annotation_1.2.100.v20241001-0914.jar differ diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/AnnotationDietRecoveryTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/AnnotationDietRecoveryTest.java index 44c48c6fe00..18ba54146a5 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/AnnotationDietRecoveryTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/parser/AnnotationDietRecoveryTest.java @@ -38,6 +38,10 @@ public class AnnotationDietRecoveryTest extends AbstractCompilerTest { private static final boolean CHECK_ALL_PARSE = true; public static boolean optimizeStringLiterals = false; + static { +// TESTS_NAMES = new String[] { "test0025" }; + } + public AnnotationDietRecoveryTest(String testName){ super(testName); } @@ -1210,7 +1214,7 @@ public void test0024() { "public class X {\n" + " public X() {\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation(name) int param2) {\n" + " }\n" + "}\n"; @@ -1220,7 +1224,7 @@ public void test0024() { " public X() {\n" + " super();\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation(name) int param2) {\n" + " }\n" + "}\n"; @@ -1229,7 +1233,13 @@ public void test0024() { expectedDietUnitToString; String expectedCompletionDietUnitToString = - expectedDietUnitToString; + "package a;\n" + + "public class X {\n" + + " public X() {\n" + + " }\n" + + " void foo(int param1) {\n" + + " }\n" + + "}\n"; String testName = ""; checkParse( @@ -1254,7 +1264,7 @@ public void test0025() { "public class X {\n" + " public X() {\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation(name = $missing$) int param2) {\n" + " }\n" + "}\n"; @@ -1264,7 +1274,7 @@ public void test0025() { " public X() {\n" + " super();\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation(name = $missing$) int param2) {\n" + " }\n" + "}\n"; @@ -1272,7 +1282,13 @@ public void test0025() { expectedDietUnitToString; String expectedCompletionDietUnitToString = - expectedDietUnitToString; + "package a;\n" + + "public class X {\n" + + " public X() {\n" + + " }\n" + + " void foo(int param1) {\n" + + " }\n" + + "}\n"; String testName = ""; checkParse( @@ -1297,7 +1313,7 @@ public void test0026() { "public class X {\n" + " public X() {\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation @AnAnnotation1(name1 = \"a\",name2 = $missing$) int param2) {\n" + " }\n" + "}\n"; @@ -1307,7 +1323,7 @@ public void test0026() { " public X() {\n" + " super();\n" + " }\n" + - " void foo(int param1) {\n" + + " void foo(int param1, @AnAnnotation @AnAnnotation1(name1 = \"a\",name2 = $missing$) int param2) {\n" + " }\n" + "}\n"; @@ -1315,7 +1331,13 @@ public void test0026() { expectedDietUnitToString; String expectedCompletionDietUnitToString = - expectedDietUnitToString; + "package a;\n" + + "public class X {\n" + + " public X() {\n" + + " }\n" + + " void foo(int param1) {\n" + + " }\n" + + "}\n"; String testName = ""; checkParse( diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractNullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractNullAnnotationTest.java index 5376767e4cb..a66a72c4f1a 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractNullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractNullAnnotationTest.java @@ -15,10 +15,11 @@ import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Map; import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.osgi.framework.Bundle; @@ -89,16 +90,24 @@ protected void setUpAnnotationLib() throws IOException { int len = defaultLibs.length; this.LIBS = new String[len+1]; System.arraycopy(defaultLibs, 0, this.LIBS, 0, len); - String version = this.complianceLevel >= ClassFileConstants.JDK1_8 ? "[2.0.0,3.0.0)" : "[1.1.0,2.0.0)"; - Bundle[] bundles = org.eclipse.jdt.core.tests.compiler.Activator.getPackageAdmin().getBundles("org.eclipse.jdt.annotation", version); - File bundleFile = FileLocator.getBundleFileLocation(bundles[0]).get(); - if (bundleFile.isDirectory()) - this.LIBS[len] = bundleFile.getPath()+"/bin"; - else - this.LIBS[len] = bundleFile.getPath(); + this.LIBS[len] = getAnnotationLibPath(); } } + protected String getAnnotationLibPath() throws IOException { + Bundle bundle = Platform.getBundle("org.eclipse.jdt.annotation"); + File bundleFile = FileLocator.getBundleFileLocation(bundle).get(); + if (bundleFile.isDirectory()) + return bundleFile.getPath()+"/bin"; + else + return bundleFile.getPath(); + } + + public static String getAnnotationV1LibPath() throws IOException { + URL libEntry = Platform.getBundle("org.eclipse.jdt.core.tests.compiler").getEntry("/lib/org.eclipse.jdt.annotation_1.2.100.v20241001-0914.jar"); + return FileLocator.toFileURL(libEntry).getPath(); + } + // Conditionally augment problem detection settings static boolean setNullRelatedOptions = true; diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java index 028d6281d3b..699b4d914cc 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/AbstractRegressionTest.java @@ -36,6 +36,8 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.element.TypeElement; +import junit.framework.Test; +import junit.framework.TestSuite; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; @@ -1341,6 +1343,59 @@ public void run() { public AbstractRegressionTest(String name) { super(name); } + + /* argument 'inheritedDepth' is not exposed in original API, therefore these helpers are copied below with this arg added */ + protected static void buildMinimalComplianceTestSuite(int minimalCompliance, int inheritedDepth, TestSuite suite, Class evaluationTestClass) { + int complianceLevels = getPossibleComplianceLevels(); + for (int[] map : complianceTestLevelMapping) { + if ((complianceLevels & map[0]) != 0) { + long complianceLevelForJavaVersion = ClassFileConstants.getComplianceLevelForJavaVersion(map[1]); + checkCompliance(evaluationTestClass, minimalCompliance, suite, complianceLevels, inheritedDepth, map[0], map[1], getVersionString(complianceLevelForJavaVersion)); + } + } + } + protected static void checkCompliance(Class evaluationTestClass, int minimalCompliance, TestSuite suite, int complianceLevels, int inheritedDepth, + int abstractCompilerTestCompliance, int classFileConstantsVersion, String release) { + int lev = complianceLevels & abstractCompilerTestCompliance; + if (lev != 0) { + if (lev < minimalCompliance) { + System.err.println("Cannot run "+evaluationTestClass.getName()+" at compliance " + release + "!"); + } else { + suite.addTest(buildUniqueComplianceTestSuite(evaluationTestClass, ClassFileConstants.getComplianceLevelForJavaVersion(classFileConstantsVersion), inheritedDepth)); + } + } + } + public static Test buildUniqueComplianceTestSuite(Class evaluationTestClass, long uniqueCompliance, int inheritedDepth) { + long highestLevel = highestComplianceLevels(); + if (highestLevel < uniqueCompliance) { + String complianceString; + if (highestLevel == ClassFileConstants.JDK10) + complianceString = "10"; + else if (highestLevel == ClassFileConstants.JDK9) + complianceString = "9"; + else if (highestLevel <= CompilerOptions.getFirstSupportedJdkLevel()) + complianceString = CompilerOptions.getFirstSupportedJavaVersion(); + else { + highestLevel = ClassFileConstants.getLatestJDKLevel(); + if (highestLevel > 0) { + complianceString = CompilerOptions.versionFromJdkLevel(highestLevel); + } else { + complianceString = "unknown"; + } + + } + + System.err.println("Cannot run "+evaluationTestClass.getName()+" at compliance "+complianceString+"!"); + return new TestSuite(); + } + TestSuite complianceSuite = new RegressionTestSetup(uniqueCompliance); + List tests = buildTestsList(evaluationTestClass, inheritedDepth); + for (int index=0, size=tests.size(); indexin parallel to different output directories. * There should be no interdependencies between compiler threads, but the test failed @@ -145,4 +151,78 @@ boolean match(String err) { return "StdErr: " + error + " StdOut: " + output; } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1774 + // Check behavior of expression switch in JDK18- + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096#issuecomment-2417954288 + public void testGHI1774_Expression() throws Exception { + String javaVersion = System.getProperty("java.version"); + if (javaVersion != null && JavaCore.compareJavaVersions(javaVersion, "18") < 0) + return; + String path = LIB_DIR; + String libPath = null; + if (path.endsWith(File.separator)) { + libPath = path + "lib.jar"; + } else { + libPath = path + File.separator + "lib.jar"; + } + Util.createJar(new String[] { + "p/Color.java", + "package p;\n" + + "public enum Color {\n" + + " R, Y;\n" + + " public static Color getColor() {\n" + + " return R;\n" + + " }\n" + + "}", + }, + libPath, + JavaCore.VERSION_18); + this.runConformTest( + new String[] { + "src/p/X.java", + "package p;\n" + + "import p.Color;\n" + + "public class X {\n" + + " public static void main(String argv[]) {\n" + + " Color c = Color.getColor();\n" + + " try {\n" + + " int a = switch (c) {\n" + + " case R -> 1;\n" + + " case Y -> 2;\n" + + " };\n" + + " } catch (IncompatibleClassChangeError e) {\n" + + " System.out.print(\"OK\");\n" + + " } catch (Exception e) {\n" + + " System.out.print(\"NOT OK: \" + e);\n" + + " }\n" + + " System.out.print(\"END\");\n" + + " }\n" + + "}", + }, + "\"" + OUTPUT_DIR + File.separator + "src/p/X.java\"" + + " -cp \"" + LIB_DIR + File.separator + "lib.jar\"" + + " -sourcepath \"" + OUTPUT_DIR + File.separator + "src\"" + + " -source 18 -warn:none" + + " -d \"" + OUTPUT_DIR + File.separator + "bin\" ", + "", + "", + true); + this.verifier.execute("p.X", new String[] {OUTPUT_DIR + File.separator + "bin", libPath}, new String[0], new String[] {"--enable-preview"}); + assertEquals("Incorrect output", "END", this.verifier.getExecutionOutput()); + Util.createJar(new String[] { + "p/Color.java", + "package p;\n" + + "public enum Color {\n" + + " R, Y, B;\n" + + " public static Color getColor() {\n" + + " return B;\n" + + " }\n" + + "}", + }, + libPath, + JavaCore.VERSION_18); + this.verifier.execute("p.X", new String[] {OUTPUT_DIR + File.separator + "bin", libPath}, new String[0], new String[] {"--enable-preview"}); + assertEquals("Incorrect output", "OKEND", this.verifier.getExecutionOutput()); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ClassFileReaderTest_17.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ClassFileReaderTest_17.java index 780a506cb38..e6e696542c5 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ClassFileReaderTest_17.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ClassFileReaderTest_17.java @@ -52,7 +52,7 @@ public void testBug564227_001() throws Exception { "final class Z extends X{}\n"; org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader classFileReader = getInternalClassFile("", "X", "X", source); - char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypeNames(); + char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypesNames(); assertEquals(2, permittedSubtypesNames.length); @@ -71,7 +71,7 @@ public void testBug565782_001() throws Exception { "}"; org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader classFileReader = getInternalClassFile("", "X", "X", source); - char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypeNames(); + char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypesNames(); assertEquals(1, permittedSubtypesNames.length); @@ -94,7 +94,7 @@ public void testBug565782_002() throws Exception { "}"; org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader classFileReader = getInternalClassFile("", "X.E", "X$E", source); - char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypeNames(); + char[][] permittedSubtypesNames = classFileReader.getPermittedSubtypesNames(); assertEquals(1, permittedSubtypesNames.length); 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 fa210f8defc..f0ecad9241e 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 @@ -65,6 +65,7 @@ import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.IrritantSet; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; @@ -1247,6 +1248,7 @@ class ProblemAttributes { expectedProblemAttributes.put("FeatureNotSupported", new ProblemAttributes(CategorizedProblem.CAT_COMPLIANCE)); expectedProblemAttributes.put("PreviewAPIUsed", new ProblemAttributes(CategorizedProblem.CAT_COMPLIANCE)); expectedProblemAttributes.put("JavaVersionNotSupported", new ProblemAttributes(CategorizedProblem.CAT_COMPLIANCE)); + expectedProblemAttributes.put("JavaVersionTooRecent", new ProblemAttributes(CategorizedProblem.CAT_COMPLIANCE)); expectedProblemAttributes.put("SwitchExpressionsYieldIncompatibleResultExpressionTypes", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("SwitchExpressionsYieldEmptySwitchBlock", new ProblemAttributes(CategorizedProblem.CAT_SYNTAX)); expectedProblemAttributes.put("SwitchExpressionsYieldNoResultExpression", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); @@ -1368,6 +1370,7 @@ class ProblemAttributes { expectedProblemAttributes.put("OperandStackSizeInappropriate", new ProblemAttributes(CategorizedProblem.CAT_INTERNAL)); expectedProblemAttributes.put("IllegalModifierCombinationForType", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("LambdaParameterIsNeverUsed", new ProblemAttributes(CategorizedProblem.CAT_UNNECESSARY_CODE)); + expectedProblemAttributes.put("FunctionalInterfaceMayNotbeSealed", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); StringBuilder failures = new StringBuilder(); StringBuilder correctResult = new StringBuilder(70000); @@ -2381,6 +2384,7 @@ class ProblemAttributes { expectedProblemAttributes.put("FeatureNotSupported", SKIP); expectedProblemAttributes.put("PreviewAPIUsed", SKIP); expectedProblemAttributes.put("JavaVersionNotSupported", SKIP); + expectedProblemAttributes.put("JavaVersionTooRecent", SKIP); expectedProblemAttributes.put("SwitchExpressionsYieldIncompatibleResultExpressionTypes", SKIP); expectedProblemAttributes.put("SwitchExpressionsYieldEmptySwitchBlock", SKIP); expectedProblemAttributes.put("SwitchExpressionsYieldNoResultExpression", SKIP); @@ -2503,6 +2507,7 @@ class ProblemAttributes { expectedProblemAttributes.put("OperandStackSizeInappropriate", SKIP); expectedProblemAttributes.put("IllegalModifierCombinationForType", SKIP); expectedProblemAttributes.put("LambdaParameterIsNeverUsed", SKIP); + expectedProblemAttributes.put("FunctionalInterfaceMayNotbeSealed", SKIP); Map constantNamesIndex = new HashMap(); @@ -2582,4 +2587,24 @@ private boolean isDeprecated(Field field) { } return false; } + + public void testTooNewJavaVersionRequested() { + Map options = new HashMap<>(JavaCore.getDefaultOptions()); + String latestJavaVersionSupportedByECJ = CompilerOptions.versionFromJdkLevel(ClassFileConstants.getLatestJDKLevel()); + String message = """ + ---------- + 1. WARNING in A.java (at line 1) + class A{} + ^ + Compiling for Java version 'XXX0' is not supported yet. Using 'XXX' instead + ---------- + """.replaceAll("XXX", latestJavaVersionSupportedByECJ); + options.put(CompilerOptions.OPTION_Source, latestJavaVersionSupportedByECJ + "0"); + runNegativeTest(new String[] {"A.java", "class A{}"}, + message, + null, + false, + null, + options); + } } \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/Deprecated9Test.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/Deprecated9Test.java index 87e31bc38a6..39b395c55cf 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/Deprecated9Test.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/Deprecated9Test.java @@ -20,6 +20,7 @@ import junit.framework.Test; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.INameEnvironment; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; @@ -913,7 +914,10 @@ public void testBug533063_2() throws Exception { "----------\n"; runner.runWarningTest(); } - public void testBug534304() throws Exception { + public void testBug534304_1() throws Exception { + if (this.complianceLevel < ClassFileConstants.JDK13) { + return; + } runNegativeTest( new String[] { "p1/C1.java", @@ -953,6 +957,43 @@ public void testBug534304() throws Exception { "CMissing cannot be resolved to a type\n" + "----------\n"); } + public void testBug534304_2() throws Exception { + if (this.complianceLevel < ClassFileConstants.JDK13) { + runNegativeTest( + new String[] { + "p1/C1.java", + "package p1;\n" + + "\n" + + "import pdep.Dep1;\n" + + "\n" + + "public class C1 {\n" + + " Dep1 f;\n" + + "}\n", + "pdep/Dep1.java", + "package pdep;\n" + + "\n" + + "import pmissing.CMissing;\n" + + "\n" + + "@Deprecated(since=\"13\")\n" + + "@CMissing\n" + + "public class Dep1 {\n" + + "\n" + + "}\n" + }, + "----------\n" + + "----------\n" + + "1. ERROR in pdep\\Dep1.java (at line 3)\n" + + " import pmissing.CMissing;\n" + + " ^^^^^^^^\n" + + "The import pmissing cannot be resolved\n" + + "----------\n" + + "2. ERROR in pdep\\Dep1.java (at line 6)\n" + + " @CMissing\n" + + " ^^^^^^^^\n" + + "CMissing cannot be resolved to a type\n" + + "----------\n"); + } + } public void testBug542795() throws Exception { Runner runner = new Runner(); runner.customOptions = new HashMap<>(); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java index 9115221af82..28f71bd72fe 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java @@ -3891,24 +3891,40 @@ public void test112() { } //https://bugs.eclipse.org/bugs/show_bug.cgi?id=93789 public void test113() { - if (this.complianceLevel >= ClassFileConstants.JDK16) { - return; - } - this.runNegativeTest( - new String[] { - "X.java", - "enum BugDemo {\n" + - " FOO() {\n" + - " static int bar;\n" + - " }\n" + - "}\n", - }, - "----------\n" + - "1. ERROR in X.java (at line 3)\n" + - " static int bar;\n" + - " ^^^\n" + - "The field bar cannot be declared static in a non-static inner type, unless initialized with a constant expression\n" + - "----------\n"); + if (this.complianceLevel < ClassFileConstants.JDK16) + this.runNegativeTest( + new String[] { + "X.java", + "enum BugDemo {\n" + + " FOO() {\n" + + " static int bar;\n" + + " }\n" + + "}\n" + + "public class X {\n" + + " public static void main(String [] args) {}\n" + + "}\n", + }, + "----------\n" + + "1. ERROR in X.java (at line 3)\n" + + " static int bar;\n" + + " ^^^\n" + + "The field bar cannot be declared static in a non-static inner type, unless initialized with a constant expression\n" + + "----------\n"); + else + this.runConformTest( + new String[] { + "X.java", + "enum BugDemo {\n" + + " FOO() {\n" + + " static int bar;\n" + + " }\n" + + "}\n" + + "public class X {\n" + + " public static void main(String [] args) {}\n" + + "}\n", + }, + ""); + } //https://bugs.eclipse.org/bugs/show_bug.cgi?id=99428 and https://bugs.eclipse.org/bugs/show_bug.cgi?id=99655 public void test114() { @@ -7346,4 +7362,75 @@ enum Inner extends com.test.Option { "com.test.Option cannot be resolved to a type\n" + "----------\n"); } +// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1368 +// JDT unable to detect variable reference errors in nested enum with an AnonymousClassDeclaration +public void testGHIssue1368() { + if (this.complianceLevel < ClassFileConstants.JDK16) + return; + this.runNegativeTest(new String[] { + "X.java", + """ + public class X { + static int f() { + int h = 20; + enum ENUM_C { + INT_C () { + @Override + void k() { + System.out.println(h + "null"); + } + }; + + void k() { + System.out.println(h); + } + } + + ENUM_C i = ENUM_C.INT_C; + i.k(); + return 20; + } + } + + + class C { + + int h = 20; + enum ENUM_C { + INT_C () { + @Override + void k() { + System.out.println(h + "null"); + } + }; + + void k() { + System.out.println(h); + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 8)\n" + + " System.out.println(h + \"null\");\n" + + " ^\n" + + "Cannot make a static reference to the non-static variable h\n" + + "----------\n" + + "2. ERROR in X.java (at line 13)\n" + + " System.out.println(h);\n" + + " ^\n" + + "Cannot make a static reference to the non-static variable h\n" + + "----------\n" + + "3. ERROR in X.java (at line 31)\n" + + " System.out.println(h + \"null\");\n" + + " ^\n" + + "Cannot make a static reference to the non-static field h\n" + + "----------\n" + + "4. ERROR in X.java (at line 36)\n" + + " System.out.println(h);\n" + + " ^\n" + + "Cannot make a static reference to the non-static field h\n" + + "----------\n"); +} } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java index f6f0ddea86a..ceec296e917 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/GenericsRegressionTest.java @@ -6975,5 +6975,47 @@ public void testBugGH656_2() { "The method JavacWillAlsoError.someAbstractMethod() does not override the inherited method from MyAbstract since it is private to a different package\n" + "----------\n"); } +// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1802 +// Unchecked casts go unreported with ECJ +public void testIssue1802() { + Runner runner = new Runner(); + runner.testFiles = + new String[] { + "X.java", + """ + class X { + void foo(I ix) { + A ay = (A) ix; + B bx = (B) ix; + } + } + class Y extends X {} + class Z extends X {} + + interface I { + } + + final class B implements I { + } + + + final class A implements I { + } + """ + }; + runner.expectedCompilerLog = + "----------\n" + + "1. WARNING in X.java (at line 3)\n" + + " A ay = (A) ix;\n" + + " ^^^^^^^^^\n" + + "Type safety: Unchecked cast from I to A\n" + + "----------\n" + + "2. WARNING in X.java (at line 4)\n" + + " B bx = (B) ix;\n" + + " ^^^^^^^^^\n" + + "Type safety: Unchecked cast from I to B\n" + + "----------\n"; + runner.runWarningTest(); +} } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/InstanceofPrimaryPatternTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/InstanceofPrimaryPatternTest.java index 201d0d2374c..2e1d5eeaaae 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/InstanceofPrimaryPatternTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/InstanceofPrimaryPatternTest.java @@ -646,4 +646,28 @@ public static void gain(String[] args) { + "A pattern variable with the same name is already defined in the statement\n" + "----------\n"); } + + public void testGH3074() { + runNegativeTest( + new String[] { + "Example.java", + """ + class Example { + private void foo(String x) { + if (x instanceof Example es) { + + } + } + } + """ + }, + """ + ---------- + 1. ERROR in Example.java (at line 3) + if (x instanceof Example es) { + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Incompatible conditional operand types String and Example + ---------- + """); + } } \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JEP441SnippetsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JEP441SnippetsTest.java new file mode 100644 index 00000000000..f3f8b31a046 --- /dev/null +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/JEP441SnippetsTest.java @@ -0,0 +1,1328 @@ +/******************************************************************************* +* Copyright (c) 2024 Advantest Europe GmbH and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Srikanth Sankaran - initial implementation +*******************************************************************************/ + +package org.eclipse.jdt.core.tests.compiler.regression; + +import junit.framework.Test; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; + +public class JEP441SnippetsTest extends AbstractRegressionTest9 { + + static { +// TESTS_NUMBERS = new int [] { 40 }; +// TESTS_RANGE = new int[] { 1, -1 }; +// TESTS_NAMES = new String[] { "test26"}; + } + + public static Class testClass() { + return JEP441SnippetsTest.class; + } + public static Test suite() { + return buildMinimalComplianceTestSuite(testClass(), F_21); + } + public JEP441SnippetsTest(String testName){ + super(testName); + } + + public void test01() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> obj.toString(); + }; + } + + @Override + public String toString() { + return "X X"; + } + + public static void main(String[] args) { + System.out.println(formatterPatternSwitch(10)); + System.out.println(formatterPatternSwitch(10L)); + System.out.println(formatterPatternSwitch(10.0)); + System.out.println(formatterPatternSwitch("String")); + System.out.println(formatterPatternSwitch(new X())); + } + } + """ + }, + "int 10\n" + + "long 10\n" + + "double 10.000000\n" + + "String String\n" + + "X X"); + } + + public void test02() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static void testFooBarNew(String s) { + switch (s) { + case null -> System.out.println("Oops"); + case "Foo", "Bar" -> System.out.println("Great"); + default -> System.out.println("Ok"); + } + } + + + public static void main(String[] args) { + testFooBarNew(null); + testFooBarNew("foo"); + testFooBarNew("Foo"); + testFooBarNew("Bar"); + } + } + """ + }, + "Oops\n" + + "Ok\n" + + "Great\n" + + "Great"); + } + + public void test03() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static void testStringOld(String response) { + switch (response) { + case null -> { } + case String s -> { + if (s.equalsIgnoreCase("YES")) + System.out.println("You got it"); + else if (s.equalsIgnoreCase("NO")) + System.out.println("Shame"); + else + System.out.println("Sorry?"); + } + } + } + + static void testStringNew(String response) { + switch (response) { + case null -> { } + case String s + when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s + when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + static void testStringEnhanced(String response) { + switch (response) { + case null -> { } + case "y", "Y" -> { + System.out.println("You got it"); + } + case "n", "N" -> { + System.out.println("Shame"); + } + case String s + when s.equalsIgnoreCase("YES") -> { + System.out.println("You got it"); + } + case String s + when s.equalsIgnoreCase("NO") -> { + System.out.println("Shame"); + } + case String s -> { + System.out.println("Sorry?"); + } + } + } + + + public static void main(String[] args) { + testStringOld(null); + testStringOld("Yes"); + testStringOld("NO"); + testStringOld("Bar"); + + testStringNew(null); + testStringNew("Yes"); + testStringNew("NO"); + testStringNew("Bar"); + + testStringEnhanced(null); + testStringEnhanced("Y"); + testStringEnhanced("y"); + testStringEnhanced("N"); + testStringEnhanced("n"); + testStringEnhanced("Yes"); + testStringEnhanced("NO"); + testStringEnhanced("Bar"); + + } + } + """ + }, + "You got it\n" + + "Shame\n" + + "Sorry?\n" + + "You got it\n" + + "Shame\n" + + "Sorry?\n" + + "You got it\n" + + "You got it\n" + + "Shame\n" + + "Shame\n" + + "You got it\n" + + "Shame\n" + + "Sorry?"); + } + + public void test04() { + runConformTest( + new String[] { + "X.java", + """ + sealed interface CardClassification permits Suit, Tarot { + } + + enum Suit implements CardClassification { + CLUBS, DIAMONDS, HEARTS, SPADES + } + + final class Tarot implements CardClassification { + } + + public class X { + static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { + switch (c) { + case Suit s when s == Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit s when s == Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit s when s == Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit s -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + + public static void main(String[] args) { + exhaustiveSwitchWithoutEnumSupport(Suit.CLUBS); + exhaustiveSwitchWithoutEnumSupport(Suit.DIAMONDS); + exhaustiveSwitchWithoutEnumSupport(Suit.HEARTS); + exhaustiveSwitchWithoutEnumSupport(Suit.SPADES); + exhaustiveSwitchWithoutEnumSupport(new Tarot()); + } + } + """ + }, + "It's clubs\n" + + "It's diamonds\n" + + "It's hearts\n" + + "It's spades\n" + + "It's a tarot"); + } + + public void test05() { + runConformTest( + new String[] { + "X.java", + """ + sealed interface CardClassification permits Suit, Tarot {} + enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES } + final class Tarot implements CardClassification {} + public class X { + static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { + switch (c) { + case Suit.CLUBS -> { + System.out.println("It's clubs"); + } + case Suit.DIAMONDS -> { + System.out.println("It's diamonds"); + } + case Suit.HEARTS -> { + System.out.println("It's hearts"); + } + case Suit.SPADES -> { + System.out.println("It's spades"); + } + case Tarot t -> { + System.out.println("It's a tarot"); + } + } + } + public static void main(String[] args) { + exhaustiveSwitchWithBetterEnumSupport(Suit.CLUBS); + exhaustiveSwitchWithBetterEnumSupport(Suit.DIAMONDS); + exhaustiveSwitchWithBetterEnumSupport(Suit.HEARTS); + exhaustiveSwitchWithBetterEnumSupport(Suit.SPADES); + exhaustiveSwitchWithBetterEnumSupport(new Tarot()); + } + } + """ + }, + "It's clubs\n" + + "It's diamonds\n" + + "It's hearts\n" + + "It's spades\n" + + "It's a tarot"); + } + + public void test06() { + runConformTest( + new String[] { + "X.java", + """ + // As of Java 21 + sealed interface Currency permits Coin {} + enum Coin implements Currency { HEADS, TAILS } + + public class X { + static void goodEnumSwitch1(Currency c) { + switch (c) { + case Coin.HEADS -> { // Qualified name of enum constant as a label + System.out.println("Heads"); + } + case Coin.TAILS -> { + System.out.println("Tails"); + } + } + } + + static void goodEnumSwitch2(Coin c) { + switch (c) { + case HEADS -> { + System.out.println("Heads"); + } + case Coin.TAILS -> { // Unnecessary qualification but allowed + System.out.println("Tails"); + } + } + } + + public static void main(String[] args) { + goodEnumSwitch1(Coin.HEADS); + goodEnumSwitch1(Coin.TAILS); + + goodEnumSwitch2(Coin.HEADS); + goodEnumSwitch2(Coin.TAILS); + + } + } + """ + }, + "Heads\n" + + "Tails\n" + + "Heads\n" + + "Tails"); + } + + public void test07() { + runNegativeTest( + new String[] { + "X.java", + """ + // As of Java 21 + sealed interface Currency permits Coin {} + enum Coin implements Currency { HEADS, TAILS } + + public class X { + static void badEnumSwitch(Currency c) { + switch (c) { + case Coin.HEADS -> { + System.out.println("Heads"); + } + case TAILS -> { // Error - TAILS must be qualified + System.out.println("Tails"); + } + default -> { + System.out.println("Some currency"); + } + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 11)\n" + + " case TAILS -> { // Error - TAILS must be qualified\n" + + " ^^^^^\n" + + "TAILS cannot be resolved to a variable\n" + + "----------\n"); + } + + public void test08() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static void testNew(Object obj) { + switch (obj) { + case String s when s.length() == 4 -> System.out.println("Bad word!"); + case String s when s.length() == 1 -> System.out.println("Monsyllable"); + default -> System.out.println(" "); + } + } + + public static void main(String[] args) { + testNew("X"); + testNew("***"); + testNew("*!#^"); + } + } + """ + }, + "Monsyllable\n" + + " \n" + + "Bad word!"); + } + + public void test09() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + record Point(int i, int j) {} + enum Color { RED, GREEN, BLUE; } + + static void typeTester(Object obj) { + switch (obj) { + case null -> System.out.println("null"); + case String s -> System.out.println("String"); + case Color c -> System.out.println("Color: " + c.toString()); + case Point p -> System.out.println("Record class: " + p.toString()); + case int[] ia -> System.out.println("Array of ints of length" + ia.length); + default -> System.out.println("Something else"); + } + } + + public static void main(String[] args) { + typeTester(null); + typeTester("Hello"); + typeTester(Color.RED); + typeTester(new Point(0, 0)); + typeTester(new int[10]); + typeTester(10); + } + } + """ + }, + "null\n" + + "String\n" + + "Color: RED\n" + + "Record class: Point[i=0, j=0]\n" + + "Array of ints of length10\n" + + "Something else"); + } + + public void test10() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static void first(Object obj) { + switch (obj) { + case String s -> + System.out.println("A string: " + s); + case CharSequence cs -> + System.out.println("A sequence of length " + cs.length()); + default -> { + break; + } + } + } + + public static void main(String[] args) { + first("Hello"); + first(new StringBuilder("Hello")); + } + } + """ + }, + "A string: Hello\n" + + "A sequence of length 5"); + } + + public void test11() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + + static void first(Object obj) { + switch (obj) { + case CharSequence cs -> + System.out.println("A sequence of length " + cs.length()); + case String s -> + System.out.println("A string: " + s); + default -> { + break; + } + } + } + + public static void main(String[] args) { + first("Hello"); + first(new StringBuilder("Hello")); + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 7)\n" + + " case String s ->\n" + + " ^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n"); + } + + public void test12() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + + static void first(Object obj) { + switch (obj) { + case String s -> + System.out.println("A String: " + s); + case String s when s.length() > 0 -> + System.out.println("A string: " + s); + default -> { + break; + } + } + } + + public static void main(String[] args) { + first("Hello"); + first(new StringBuilder("Hello")); + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 7)\n" + + " case String s when s.length() > 0 ->\n" + + " ^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n"); + } + + public void test13() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + + static void first(Object obj) { + switch (obj) { + case String s when s.length() > 0 -> + System.out.println("A guarded string: " + s); + case String s -> + System.out.println("A String: " + s); + default -> { + break; + } + } + } + + public static void main(String[] args) { + first("Hello"); + first(new StringBuilder("Hello")); + } + } + """ + }, + "A guarded string: Hello"); + } + + public void test14() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + + static void first(Object obj) { + switch (obj) { + case String s when true -> + System.out.println("A pseudo guarded string: " + s); + case String s when s.length() > 0 -> + System.out.println("A guarded string: " + s); + case String s -> + System.out.println("A String: " + s); + default -> { + break; + } + } + } + + public static void main(String[] args) { + first("Hello"); + first(new StringBuilder("Hello")); + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 7)\n" + + " case String s when s.length() > 0 ->\n" + + " ^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "2. ERROR in X.java (at line 9)\n" + + " case String s ->\n" + + " ^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n"); + } + + public void test15() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + static void first(Integer obj) { + switch (obj) { + case Integer i -> + System.out.println("An integer: " + i); + case 42 -> + System.out.println("42"); + default -> { + break; + } + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 6)\n" + + " case 42 ->\n" + + " ^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "2. ERROR in X.java (at line 8)\n" + + " default -> {\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); + } + + public void test16() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + enum E { + ONE, TWO; + } + static void first(E e) { + switch (e) { + case E e1 -> + System.out.println("An ENUM: " + e1); + case ONE -> + System.out.println("42"); + default -> { + break; + } + } + } + + static void second(E e) { + switch (e) { + case ONE -> + System.out.println("An ENUM: "); + case TWO -> + System.out.println("42"); + default -> { + break; + } + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 9)\n" + + " case ONE ->\n" + + " ^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "2. ERROR in X.java (at line 11)\n" + + " default -> {\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); + } + + public void test17() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + static void first(Object selector) { + switch (selector) { + case String s when s.length() > 1 -> + System.out.println("Some string"); + case "Hello" -> + System.out.println("42"); + default -> { + break; + } + } + } + + static void second(String selector) { + switch (selector) { + case String s when s.length() > 1 -> + System.out.println("Some string"); + case "Hello" -> + System.out.println("42"); + case String s -> + System.out.println("unconditional"); + default -> { + break; + } + } + } + + static void third(String selector) { + switch (selector) { + case String s when s.length() > 1 -> + System.out.println("Some string"); + case String s -> + System.out.println("unconditional"); + case "Hello" -> + System.out.println("42"); + default -> { + break; + } + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 6)\n" + + " case \"Hello\" ->\n" + + " ^^^^^^^\n" + + "Case constant of type String is incompatible with switch selector type Object\n" + + "----------\n" + + "2. ERROR in X.java (at line 22)\n" + + " default -> {\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n" + + "3. ERROR in X.java (at line 34)\n" + + " case \"Hello\" ->\n" + + " ^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "4. ERROR in X.java (at line 36)\n" + + " default -> {\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); + } + + public void test18() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + static void first(Integer i) { + switch (i) { + case -1, 1 -> System.out.println("Special cases"); + case Integer j when j > 0 -> System.out.println("Positive integer cases"); + case Integer j -> System.out.println("All the remaining integers"); + } + } + + public static void main(String[] args) { + first(-1); + first(1); + first(42); + first(0); + } + } + """ + }, + "Special cases\n" + + "Special cases\n" + + "Positive integer cases\n" + + "All the remaining integers"); + } + + public void test19() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + // As of Java 21 + static void matchAll(String s) { + switch(s) { + case String t: + System.out.println(t); + break; + default: + System.out.println("Something else"); // Error - dominated! + } + } + + static void matchAll2(String s) { + switch(s) { + case Object o: + System.out.println("An Object"); + break; + default: + System.out.println("Something else"); // Error - dominated! + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 8)\n" + + " default:\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n" + + "2. ERROR in X.java (at line 18)\n" + + " default:\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); + } + + public void test20() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + // As of Java 21 + static int coverage(Object obj) { + return switch (obj) { // Error - not exhaustive + case String s -> s.length(); + }; + } + + // As of Java 21 + static int coverage(Object obj) { + return switch (obj) { // Error - still not exhaustive + case String s -> s.length(); + case Integer i -> i; + }; + } + + // As of Java 21 + static int coverage(Object obj) { + return switch (obj) { + case String s -> s.length(); + case Integer i -> i; + default -> 0; + }; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 3)\n" + + " static int coverage(Object obj) {\n" + + " ^^^^^^^^^^^^^^^^^^^^\n" + + "Duplicate method coverage(Object) in type X\n" + + "----------\n" + + "2. ERROR in X.java (at line 4)\n" + + " return switch (obj) { // Error - not exhaustive\n" + + " ^^^\n" + + "A switch expression should have a default case\n" + + "----------\n" + + "3. ERROR in X.java (at line 10)\n" + + " static int coverage(Object obj) {\n" + + " ^^^^^^^^^^^^^^^^^^^^\n" + + "Duplicate method coverage(Object) in type X\n" + + "----------\n" + + "4. ERROR in X.java (at line 11)\n" + + " return switch (obj) { // Error - still not exhaustive\n" + + " ^^^\n" + + "A switch expression should have a default case\n" + + "----------\n" + + "5. ERROR in X.java (at line 18)\n" + + " static int coverage(Object obj) {\n" + + " ^^^^^^^^^^^^^^^^^^^^\n" + + "Duplicate method coverage(Object) in type X\n" + + "----------\n"); + } + + public void test21() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + enum Color { RED, YELLOW, GREEN } + + // As of Java 21 + static int coverage(Color color) { + + int numLetters = switch (color) { // Exhaustive! + case RED -> 3; + case GREEN -> 5; + case YELLOW -> 6; + }; + + numLetters = switch (color) { // Error - not exhaustive! + case RED -> 3; + case GREEN -> 5; + }; + + numLetters = switch (color) { + case RED -> 3; + case GREEN -> 5; + case YELLOW -> 6; + default -> throw new UnsupportedOperationException(color.toString()); + }; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 13)\n" + + " numLetters = switch (color) { // Error - not exhaustive!\n" + + " ^^^^^\n" + + "A Switch expression should cover all possible values\n" + + "----------\n"); + } + + public void test22() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + sealed interface S permits A, B, C {} + final class A implements S {} + final class B implements S {} + record C(int i) implements S {} // Implicitly final + + static int testSealedExhaustive(S s) { + return switch (s) { + case A a -> 1; + case B b -> 2; + case C c -> 3; + }; + } + + public static void main(String[] args) { + System.out.println(testSealedExhaustive(new X().new A())); + System.out.println(testSealedExhaustive(new X().new B())); + System.out.println(testSealedExhaustive(new C(10))); + + } + } + """ + }, + "1\n2\n3"); + } + + public void test23() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + sealed interface S permits A, B, C {} + final class A implements S {} + final class B implements S {} + record C(int i) implements S {} // Implicitly final + + static int testSealedExhaustive(S s) { + return switch (s) { + case A a -> 1; + case B b -> 2; + }; + } + + public static void main(String[] args) { + System.out.println(testSealedExhaustive(new X().new A())); + System.out.println(testSealedExhaustive(new X().new B())); + System.out.println(testSealedExhaustive(new C(10))); + + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 8)\r\n" + + " return switch (s) {\r\n" + + " ^\n" + + "A switch expression should have a default case\n" + + "----------\n"); + } + + public void test24() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + sealed interface I permits A, B {} + final class A implements I {} + final class B implements I {} + + static int testGenericSealedExhaustive(I i) { + return switch (i) { + // Exhaustive as no A case possible! + case B bi -> 42; + }; + } + + static int testGenericSealedExhaustive2(I i) { + return switch (i) { + // Exhaustive as no A case possible! + case B bi -> 42; + case A ai -> 42; + }; + } + } + """ + }, + "----------\n" + + "1. WARNING in X.java (at line 3)\n" + + " final class A implements I {}\n" + + " ^\n" + + "The type parameter X is hiding the type X\n" + + "----------\n" + + "2. ERROR in X.java (at line 17)\n" + + " case A ai -> 42;\n" + + " ^^^^^^^^^^^^^\n" + + "Incompatible operand types X.I and X.A\n" + + "----------\n"); + } + + public void test25() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + // As of Java 21 + sealed interface S permits A, B, C {} + final class A implements S {} + final class B implements S {} + record C(int i) implements S {} // Implicitly final + + static void switchStatementExhaustive(S s) { + switch (s) { // Error - not exhaustive; + // missing clause for permitted class B! + case A a : + System.out.println("A"); + break; + case C c : + System.out.println("C"); + break; + }; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 9)\n" + + " switch (s) { // Error - not exhaustive;\n" + + " ^\n" + + "An enhanced switch statement should be exhaustive; a default label expected\n" + + "----------\n"); + } + + public void test26() { + if (this.complianceLevel < ClassFileConstants.JDK22) // multi pattern case labels used + return; + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + static void testScope1(Object obj) { + switch (obj) { + case Character c + when c.charValue() == 7: + System.out.println("Ding!"); + break; + default: + break; + } + } + + static void testScope2(Object obj) { + switch (obj) { + case Character c -> { + if (c.charValue() == 7) { + System.out.println("Ding!"); + } + System.out.println("Character"); + } + case Integer i -> + throw new IllegalStateException("Invalid Integer argument: " + + i.intValue()); + default -> { + break; + } + } + } + + static void testScope3(Object obj) { + switch (obj) { + case Character c: + if (c.charValue() == 7) { + System.out.print("Ding "); + } + if (c.charValue() == 9) { + System.out.print("Tab "); + } + System.out.println("Character"); + default: + System.out.println(c); + } + } + + static void testScopeError(Object obj) { + switch (obj) { + case Character c: + if (c.charValue() == 7) { + System.out.print("Ding "); + } + if (c.charValue() == 9) { + System.out.print("Tab "); + } + System.out.println("character"); + case Integer i: // Compile-time error + System.out.println("An integer " + i); + default: + break; + } + } + + static void testScopeError2(Object obj) { + switch (obj) { + case Character c: + if (c.charValue() == 7) { + System.out.print("Ding "); + } + if (c.charValue() == 9) { + System.out.print("Tab "); + } + System.out.println("character"); + case Character c, Integer i: // Compile-time error + System.out.println("An integer " + i); + default: + break; + } + } + + void testScope4(Object obj) { + switch (obj) { + case String s: + System.out.println("A string: " + s); // s in scope here! + default: + System.out.println("Done"); // s not in scope here + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 41)\n" + + " System.out.println(c);\n" + + " ^\n" + + "c cannot be resolved to a variable\n" + + "----------\n" + + "2. ERROR in X.java (at line 55)\n" + + " case Integer i: // Compile-time error\n" + + " ^^^^^^^^^^^^^^\n" + + "Illegal fall-through to a pattern\n" + + "----------\n" + + "3. ERROR in X.java (at line 72)\n" + + " case Character c, Integer i: // Compile-time error\n" + + " ^^^^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "4. ERROR in X.java (at line 72)\n" + + " case Character c, Integer i: // Compile-time error\n" + + " ^\n" + + "Named pattern variables are not allowed here\n" + + "----------\n" + + "5. ERROR in X.java (at line 72)\n" + + " case Character c, Integer i: // Compile-time error\n" + + " ^\n" + + "Named pattern variables are not allowed here\n" + + "----------\n" + + "6. ERROR in X.java (at line 73)\n" + + " System.out.println(\"An integer \" + i);\n" + + " ^\n" + + "i cannot be resolved to a variable\n" + + "----------\n"); + } + + public void test27() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + static void nullMatch(Object obj) { + switch (obj) { + case null -> System.out.println("null!"); + case String s -> System.out.println("String"); + default -> System.out.println("Something else"); + } + } + + static void nullMatch2(Object obj) { + switch (obj) { + case String s -> System.out.println("String: " + s); + case Integer i -> System.out.println("Integer"); + default -> System.out.println("default"); + } + } + + static void nullMatch3(Object obj) { + switch (obj) { + case null -> throw new NullPointerException(); + case String s -> System.out.println("String: " + s); + case Integer i -> System.out.println("Integer"); + default -> System.out.println("default"); + } + } + + static void nullMatch4(Object obj) { + switch (obj) { + case String s -> System.out.println("String: " + s); + case Integer i -> System.out.println("Integer"); + case null, default -> System.out.println("null|default"); + } + } + + public static void main(String[] args) { + nullMatch(null); + int nulls = 0; + try { + nullMatch2(null); + } catch(NullPointerException npe) { + nulls++; + } + try { + nullMatch3(null); + } catch(NullPointerException npe) { + nulls++; + } + nullMatch4(null); + if (nulls == 2) { + System.out.println("OK!"); + } + + } + } + """ + }, + "null!\nnull|default\nOK!"); + } + + public void test28() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + // As of Java 21 + record R(int i) { + public int i() { // bad (but legal) accessor method for i + return i / 0; + } + } + + static void exampleAnR(R r) { + switch(r) { + case R(var i): System.out.println(i); + } + } + + static void example(Object obj) { + switch (obj) { + case R r when (r.i / 0 == 1): System.out.println("It's an R!"); + default: break; + } + } + + public static void main(String [] args) { + try { + exampleAnR(new R(42)); + } catch (MatchException m) { + System.out.println("ME:Ok!"); + } + + try { + example(new R(42)); + } catch (MatchException m) { + System.out.println("Broken!"); + } catch(ArithmeticException ae) { + System.out.println("AE:Ok!"); + } + } + + } + """ + }, + "ME:Ok!\nAE:Ok!"); + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java index 21351aeab31..eca3965acaf 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java @@ -5945,7 +5945,7 @@ public void test151() throws Exception { + " [inner class info: #1 p/X$1E, outer class info: #0\n" + " inner name: #54 E, accessflags: 17416 abstract static],\n" + " [inner class info: #14 p/X$1E$1, outer class info: #0\n" - + " inner name: #0, accessflags: 16384 default]\n" + + " inner name: #0, accessflags: 16392 static]\n" + " Enclosing Method: #48 #50 p/X.main([Ljava/lang/String;)V\n" + "\n" + "Nest Host: #48 p/X\n" diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java index f1a8d5138af..ee8aa0ee62b 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java @@ -31,7 +31,7 @@ @SuppressWarnings({ "unchecked", "rawtypes" }) public class NullAnnotationTest extends AbstractNullAnnotationTest { -private String TEST_JAR_SUFFIX = ".jar"; +protected String TEST_JAR_SUFFIX = "_1.8.jar"; public NullAnnotationTest(String name) { super(name); @@ -53,74 +53,78 @@ public static Class testClass() { return NullAnnotationTest.class; } +public boolean useDeclarationAnnotations() { + return false; +} + String mismatch_NonNull_Nullable(String type) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Null type mismatch: required \'@NonNull "+type+"\' but the provided value is specified as @Nullable\n" : "Null type mismatch (type annotations): required '@NonNull "+type+"' but this expression has type '@Nullable "+type+"'\n"; } String nullTypeSafety() { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Null type safety: " : "Null type safety (type annotations): "; } String variableMayBeNull(String var) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Potential null pointer access: The variable "+var+" may be null at this location\n" : "Potential null pointer access: this expression has a '@Nullable' type\n"; } String redundant_check_nonnull(String expr, String type) { - return this.complianceLevel < ClassFileConstants.JDK1_8 + return useDeclarationAnnotations() ? "Redundant null check: "+expr+" is specified as @NonNull\n" : "Redundant null check: comparing '"+type+"' against null\n"; } String redundantCheck_method_cannot_return_null(String method, String type) { - return this.complianceLevel < ClassFileConstants.JDK1_8 + return useDeclarationAnnotations() ? "Redundant null check: The method "+method+" cannot return null\n" : "Redundant null check: comparing '@NonNull "+type+"' against null\n"; } String checkAlwaysFalse_method_cannot_return_null(String method, String type) { - return this.complianceLevel < ClassFileConstants.JDK1_8 + return useDeclarationAnnotations() ? "Null comparison always yields false: The method "+method+" cannot return null\n" : "Redundant null check: comparing '@NonNull "+type+"' against null\n"; } String redundant_check_canonlynull(String expr, String type) { - return this.complianceLevel < ClassFileConstants.JDK1_8 + return useDeclarationAnnotations() ? "Redundant null check: "+expr+" can only be null at this location\n" : "Redundant null check: comparing '@NonNull "+type+"' against null\n"; } String checkAlwaysFalse_nonnull(String expr, String type) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Null comparison always yields false: "+expr+" is specified as @NonNull\n" : "Redundant null check: comparing '@NonNull "+type+"' against null\n"; } String potNPE_nullable(String expr) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Potential null pointer access: "+expr+" is specified as @Nullable\n" : "Potential null pointer access: this expression has a '@Nullable' type\n"; } String potNPE_nullable_maybenull(String expr) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "Potential null pointer access: "+expr+" may be null at this location\n" : "Potential null pointer access: this expression has a '@Nullable' type\n"; } String nonNullArrayOf(String string) { - return (this.complianceLevel < ClassFileConstants.JDK1_8) + return useDeclarationAnnotations() ? "@NonNull Object[]" : "Object @NonNull[]"; } String targetTypeUseIfAvailable() { - return this.complianceLevel >= ClassFileConstants.JDK1_8 - ? "@Target(ElementType.TYPE_USE)\n" - : ""; + return useDeclarationAnnotations() + ? "" + : "@Target(ElementType.TYPE_USE)\n"; } String cancenNonNullByDefault() { - return this.complianceLevel < ClassFileConstants.JDK1_8 - ? " @NonNullByDefault(false)\n" - : " @NonNullByDefault({})\n"; + return useDeclarationAnnotations() + ? " @NonNullByDefault(false)\n" + : " @NonNullByDefault({})\n"; } /** @@ -129,11 +133,7 @@ String cancenNonNullByDefault() { @Override protected void setUp() throws Exception { super.setUp(); - if (this.complianceLevel >= ClassFileConstants.JDK1_8) - this.TEST_JAR_SUFFIX = "_1.8.jar"; - if (this.LIBS == null) { - this.LIBS = getLibsWithNullAnnotations(this.complianceLevel); - } + this.TEST_JAR_SUFFIX = "_1.8.jar"; } // a nullable argument is dereferenced without a check @@ -539,13 +539,13 @@ public void test_nonnull_parameter_015() { "X.java", "import org.eclipse.jdt.annotation.*;\n" + "public class X {\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? " void foo(@NonNull Object ... o) {\n" : " void foo(Object @NonNull... o) {\n") + " if (o != null)\n" + " System.out.print(o.toString());\n" + " }\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? " void foo2(int i, @NonNull Object ... o) {\n" : " void foo2(int i, Object @NonNull ... o) {\n" ) + @@ -592,19 +592,19 @@ public void test_nonnull_parameter_016() { "X.java", "import org.eclipse.jdt.annotation.*;\n" + "public class X {\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? " X(@NonNull Object ... o) {\n" : " X(Object @NonNull... o) {\n") + " if (o != null)\n" + " System.out.print(o.toString());\n" + " }\n" + " class Y extends X {\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? " Y(int i, @NonNull Object ... o) {\n" : " Y(int i, Object @NonNull... o) {\n") + " super(i, (Object)null);\n" + " }\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? " Y(char c, @NonNull Object ... o) {\n" : " Y(char c, Object @NonNull... o) {\n") + " this(1, new Object(), null);\n" + @@ -727,7 +727,7 @@ public void test_nonnull_local_001() { "----------\n" + "1. ERROR in X.java (at line 4)\n" + " @NonNull Object o1 = b ? null : new Object();\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? " ^^^^^^^^^^^^^^^^^^^^^^^\n" + "Null type mismatch: required \'@NonNull Object\' but the provided value is inferred as @Nullable\n" : @@ -766,7 +766,7 @@ public void test_nonnull_local_002() { "----------\n" + "1. ERROR in X.java (at line 5)\n" + " o1 = b ? null : new Object();\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? " ^^^^^^^^^^^^^^^^^^^^^^^\n" + "Null type mismatch: required \'@NonNull Object\' but the provided value is inferred as @Nullable\n" : @@ -1281,7 +1281,7 @@ public void test_parameter_specification_inheritance_014() { "}\n", }, customOptions, - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? "----------\n" + "1. ERROR in p1\\Y.java (at line 2)\n" + " public class Y extends X implements IY {\n" + @@ -1804,7 +1804,7 @@ public void test_nonnull_return_011() { "1. ERROR in X.java (at line 5)\n" + " if (dubious == null)\n" + " ^^^^^^^\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? "Null comparison always yields false: The variable dubious is specified as @NonNull\n" : "Redundant null check: comparing '@NonNull Object' against null\n" ) + "----------\n" + @@ -2067,7 +2067,7 @@ public void test_illegal_annotation_001() { "1. ERROR in X.java (at line 2)\n" + " @NonNull public class X {\n" + " ^^^^^^^^\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? "The annotation @NonNull is disallowed for this location\n" : "The nullness annotation 'NonNull' is not applicable at this location\n") + "----------\n"); @@ -2110,7 +2110,7 @@ public void test_illegal_annotation_003() { "1. ERROR in X.java (at line 3)\n" + " @NonNull void foo() {}\n" + " ^^^^^^^^\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) + (useDeclarationAnnotations() ? "The nullness annotation @NonNull is not applicable for the primitive type void\n" : "Type annotation is illegal for a method that returns void\n") + "----------\n", @@ -2261,9 +2261,15 @@ public void test_illegal_annotation_008() { "1. ERROR in X.java (at line 3)\n" + " @NonNull X() {}\n" + " ^^^^^^^^\n" + - ((this.complianceLevel < ClassFileConstants.JDK1_8) - ? "The annotation @NonNull is disallowed for this location\n" - : "The nullness annotation 'NonNull' is not applicable at this location\n" ) + + "The nullness annotation 'NonNull' is not applicable at this location\n" + + (useDeclarationAnnotations() + ? // in this case the above error is redundant, but acceptable. + "----------\n" + + "2. ERROR in X.java (at line 3)\n" + + " @NonNull X() {}\n" + + " ^^^^^^^^\n" + + "The annotation @NonNull is disallowed for this location\n" + : "") + "----------\n"); } @@ -2519,7 +2525,7 @@ public void test_default_nullness_003b() { } // package level default is consumed from package-info.class, similarly for type level default - fine tuned default public void test_default_nullness_003c() { - if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // uses version 2.0 of @NonNullByDefault + if (useDeclarationAnnotations()) return; // uses version 2.0 of @NonNullByDefault Map customOptions = getCompilerOptions(); runConformTestWithLibs( new String[] { @@ -2804,7 +2810,7 @@ public void test_default_nullness_010() { "----------\n" + "2. WARNING in p2\\Y.java (at line 5)\n" + " protected @NonNull Object getObject(@NonNull Object o) {\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? " ^^^^^^^^^^^^^^^^^\n" : " ^^^^^^^^^^^^^^^\n" ) + @@ -3490,7 +3496,7 @@ public void test_nonnull_var_in_constrol_structure_1() { "----------\n" + "1. WARNING in X.java (at line 4)\n" + " void print4(@NonNull String s) {\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? " ^^^^^^^^^^^^^^^^^\n" : " ^^^^^^^^^^^^^^^\n" ) + @@ -3508,7 +3514,7 @@ public void test_nonnull_var_in_constrol_structure_1() { "----------\n" + "4. WARNING in X.java (at line 17)\n" + " void print(@NonNull String s) {\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? " ^^^^^^^^^^^^^^^^^\n" : " ^^^^^^^^^^^^^^^\n" ) + @@ -5494,7 +5500,7 @@ public void test_nullable_field_16() { "1. ERROR in X.java (at line 19)\n" + " test(this.prop);\n" + " ^^^^^^^^^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Null type mismatch: required '@NonNull Object' but the provided value is specified as @Nullable\n" : "Null type mismatch (type annotations): required \'@NonNull Object\' but this expression has type \'@Nullable Object\'\n") + "----------\n"); @@ -6175,7 +6181,7 @@ public void testBug388281_06() { // whereas I2A cancels that same default "}\n" }, - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? "----------\n" + "1. ERROR in ctest\\C.java (at line 2)\n" + " public class C extends c.C2 implements i2.I2A {\n" + @@ -6609,7 +6615,7 @@ public void testBug412076() { new String[] { "FooImpl.java", "import org.eclipse.jdt.annotation.*;\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "@NonNullByDefault\n" : "@NonNullByDefault({DefaultLocation.PARAMETER,DefaultLocation.RETURN_TYPE})\n" // avoid @NonNull on type argument ) + @@ -7032,7 +7038,7 @@ public void testBug418235() { " }\n" + "}\n" }; - if (this.complianceLevel < ClassFileConstants.JDK1_8) { + if (useDeclarationAnnotations()) { runNegativeTestWithLibs( testFiles, "----------\n" + @@ -7048,7 +7054,7 @@ public void testBug418235() { } } public void testBug418235b() { - if (this.complianceLevel < ClassFileConstants.JDK1_8) + if (useDeclarationAnnotations()) return; runNegativeTestWithLibs( new String[] { @@ -7084,7 +7090,7 @@ public void testTypeAnnotationProblemNotIn17() { " return local;\n" + " }\n" + "}\n"; - if (this.complianceLevel < ClassFileConstants.JDK1_8) + if (useDeclarationAnnotations()) runConformTestWithLibs( new String[] { "X.java", @@ -7230,7 +7236,7 @@ public void testBug424624a() { " static class DeepNested {}\n" + " }\n" + " static public final @NonNull Inner field1 = new Test3().new Inner();\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? " static public final @NonNull Inner.DeepInner field2 = field1.new DeepInner();\n" + " static public final @NonNull Nested.InnerInNested field3 = new Nested().new InnerInNested();\n" + " static public final @NonNull Nested.DeepNested field4 = new Nested.DeepNested();\n" @@ -7297,7 +7303,7 @@ public void testBug424624b() { " static class DeepNested {}\n" + " }\n" + " static public final @NonNull Inner field1 = new Test3().new Inner();\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? " static public final @NonNull Inner.DeepInner field2 = field1.new DeepInner();\n" + " static public final @NonNull Nested.InnerInNested field3 = new Nested().new InnerInNested();\n" + " static public final @NonNull Nested.DeepNested field4 = new Nested.DeepNested();\n" @@ -7361,7 +7367,7 @@ public void testBug432348() { "public enum E {\n" + " @Marker @NonNull A, B, C\n" + "}\n"; - if (this.complianceLevel < ClassFileConstants.JDK1_8) { + if (useDeclarationAnnotations()) { runConformTestWithLibs( new String[] { "E.java", @@ -7444,7 +7450,7 @@ public void testBug403674a() { "2. ERROR in X.java (at line 10)\n" + " switch (value) {}\n" + " ^^^^^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Potential null pointer access: The variable value may be null at this location\n" : @@ -7738,7 +7744,7 @@ public void testBug443347b() { "1. ERROR in X.java (at line 11)\n" + " new Super(s) {\n" + " ^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Null type mismatch: required \'@NonNull String\' but the provided value is specified as @Nullable\n" : "Null type mismatch (type annotations): required \'@NonNull String\' but this expression has type \'@Nullable String\'\n") + "----------\n"); @@ -7772,7 +7778,7 @@ public void testBug443347c() { "1. ERROR in X.java (at line 12)\n" + " new Super(s) {\n" + " ^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Null type mismatch: required \'@NonNull String\' but the provided value is specified as @Nullable\n" : "Null type mismatch (type annotations): required \'@NonNull String\' but this expression has type \'@Nullable String\'\n") + "----------\n"); @@ -7934,7 +7940,7 @@ public void testBug445708() { "3. ERROR in SwitchTest.java (at line 26)\n" + " switch (stringValue)\n" + " ^^^^^^^^^^^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Potential null pointer access: The variable stringValue may be null at this location\n" : "Potential null pointer access: this expression has a \'@Nullable\' type\n" ) + "----------\n" + @@ -7951,7 +7957,7 @@ public void testBug445708() { } // same as above but 1.8 with declaration annotations public void testBug445708b() { - if (this.complianceLevel < ClassFileConstants.JDK1_8) return; // only one combination tested + if (useDeclarationAnnotations()) return; // only one combination tested Map customOptions = getCompilerOptions(); customOptions.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "org.foo.NonNull"); customOptions.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "org.foo.Nullable"); @@ -8042,7 +8048,7 @@ public void testBug445708b() { } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=452780 - Internal compiler error: arrayIndexOutOfBounds public void testBug452780() { - if (this.complianceLevel < ClassFileConstants.JDK1_8) return; + if (useDeclarationAnnotations()) return; runConformTestWithLibs( new String[] { "Tools2.java", @@ -8098,7 +8104,7 @@ public void testBug455557() { "1. WARNING in X.java (at line 10)\n" + " for (@NonNull Object y : list) { \n" + " ^^^^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Null type safety: The expression of type \'String\' needs unchecked conversion to conform to \'@NonNull Object\'\n" : "Null type safety (type annotations): The expression of type \'String\' needs unchecked conversion to conform to \'@NonNull Object\'\n" ) + @@ -8199,7 +8205,7 @@ public void test_null_with_apt_comment4() { "1. WARNING in Test.java (at line 6)\n" + " public static final Test t = new Test(Integer.valueOf(0));\n" + " ^^^^^^^^^^^^^^^^^^\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "Null type safety: The expression of type \'Integer\' needs unchecked conversion to conform to \'@NonNull Integer\'\n" : "Null type safety (type annotations): The expression of type \'Integer\' needs unchecked conversion to conform to \'@NonNull Integer\'\n" ) + @@ -8284,7 +8290,7 @@ public void testBug462790() { " ^^^^^^\n" + "The type parameter T should not be bounded by the final type String. Final types cannot be further extended\n" + "----------\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? "3. WARNING in EclipseBug.java (at line 10)\n" + " return commandType.newInstance();\n" + @@ -8341,7 +8347,7 @@ public void testBug459967_Enum_values() { "X.java", "import org.eclipse.jdt.annotation.*;\n" + "public class X {\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? " @NonNull MyEnum[] getValues() {\n" : @@ -8355,7 +8361,7 @@ public void testBug459967_Enum_values() { " }\n" + "}\n" }; - if (this.complianceLevel < ClassFileConstants.JDK1_8) { + if (useDeclarationAnnotations()) { runConformTestWithLibs( testFiles, getCompilerOptions(), @@ -8377,7 +8383,7 @@ public void testBug459967_Enum_values_binary() { "X.java", "import org.eclipse.jdt.annotation.*;\n" + "public class X {\n" + - (this.complianceLevel < ClassFileConstants.JDK1_8 + (useDeclarationAnnotations() ? " @NonNull MyEnum[] getValues() {\n" : @@ -8396,7 +8402,7 @@ public void testBug459967_Enum_values_binary() { "MyEnum.java", "public enum MyEnum { V1, V2 }\n", }); - if (this.complianceLevel < ClassFileConstants.JDK1_8) { + if (useDeclarationAnnotations()) { runConformTestWithLibs( false /*flush*/, testFiles, @@ -9190,7 +9196,7 @@ public void testBug502214() { "", }, getCompilerOptions(), - (this.complianceLevel < ClassFileConstants.JDK1_8 ? + (useDeclarationAnnotations() ? "----------\n" + "1. ERROR in test\\X.java (at line 22)\n" + " class Y extends A implements I {\n" + @@ -10546,7 +10552,7 @@ public void testBug542707_002() { ); } public void testBug542707_003() { - if (this.complianceLevel < ClassFileConstants.JDK12) return; // switch expression + if (this.complianceLevel < ClassFileConstants.JDK12 || useDeclarationAnnotations()) return; // switch expression // outer expected type (from assignment) is propagated deeply into a switch expression Runner runner = new Runner(); runner.customOptions = getCompilerOptions(); @@ -10617,7 +10623,7 @@ public void _testBug542707_004() { runner.runNegativeTest(); } public void testBug542707_005() { - if (this.complianceLevel < ClassFileConstants.JDK12) return; // switch expression + if (this.complianceLevel < ClassFileConstants.JDK12 || useDeclarationAnnotations()) return; // switch expression // switch value must not be null (@Nullable) Runner runner = new Runner(); runner.customOptions = getCompilerOptions(); @@ -10714,7 +10720,7 @@ public void testBug545715() { new String[] {"--enable-preview"}); } public void testBug548418_001a() { - if (this.complianceLevel < ClassFileConstants.JDK14) return; + if (this.complianceLevel < ClassFileConstants.JDK14 || useDeclarationAnnotations()) return; runNegativeTestWithLibs( new String[] { "X.java", @@ -10765,7 +10771,7 @@ public void testBug548418_001a() { "----------\n"); } public void testBug548418_001b() { - if (this.complianceLevel < ClassFileConstants.JDK14) return; + if (this.complianceLevel < ClassFileConstants.JDK14 || useDeclarationAnnotations()) return; runNegativeTestWithLibs( new String[] { "X.java", @@ -11239,7 +11245,7 @@ public void testBug565246() { runner.customOptions.put(CompilerOptions.OPTION_ReportLocalVariableHiding, CompilerOptions.IGNORE); runner.customOptions.put(CompilerOptions.OPTION_ReportNullUncheckedConversion, CompilerOptions.ERROR); runner.classLibraries = this.LIBS; - if (this.complianceLevel >= ClassFileConstants.JDK1_8) { + if (!useDeclarationAnnotations()) { runner.expectedCompilerLog = "----------\n" + "1. ERROR in bug\\B.java (at line 39)\n" + diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests21.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests21.java index ac503d57f87..efbe1fe2ef9 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests21.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTests21.java @@ -1132,4 +1132,116 @@ public static void main(String[] args) { runner.classLibraries = this.LIBS; runner.runConformTest(); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2522 + // Pattern matching on sealed classes cannot infer NonNull (JDK 21) + public void testIssue2522() { + Runner runner = getDefaultRunner(); + runner.testFiles = new String[] { + "PatternMatching.java", + """ + import org.eclipse.jdt.annotation.*; + + public sealed interface PatternMatching { + + record Stuff() implements PatternMatching {} + + @NonNull + static Stuff match(PatternMatching pm, int v) { + if (v == 0) { + Stuff r = switch (pm) { + case Stuff s -> s; + case null -> throw new NullPointerException(); + }; + return r; // no error here - good + } else if (v == 1) { + Stuff r = switch (pm) { + case Stuff s -> s; + case null -> throw new NullPointerException(); + }; + return null; // get error here - good + } else if (v == 2) { + Stuff r = switch (pm) { + case Stuff s -> s; + }; + return r; // no error here -- good + } else if (v == 3) { + Stuff r = switch (pm) { + case Stuff s -> null; // <<<<<---------------------------- Line 28 - error Why ??? + }; + return r; // get error here - good + } else if (v == 4) { + Stuff r = switch (pm) { + case Stuff s -> s; + case null -> null; // <<<-------------------------------- Line 34 - error Why ?? + }; + return new Stuff(); // no error here // good + } + return new Stuff(); + } + } + """ + }; + runner.expectedCompilerLog = + """ + ---------- + 1. ERROR in PatternMatching.java (at line 20) + return null; // get error here - good + ^^^^ + Null type mismatch: required 'PatternMatching.@NonNull Stuff' but the provided value is null + ---------- + 2. ERROR in PatternMatching.java (at line 30) + return r; // get error here - good + ^ + Null type mismatch: required 'PatternMatching.@NonNull Stuff' but the provided value is null + ---------- + """; + runner.runNegativeTest(); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2522 + // Pattern matching on sealed classes cannot infer NonNull (JDK 21) + public void testIssue2522_2() { + Runner runner = getDefaultRunner(); + runner.testFiles = new String[] { + "PatternMatching.java", + """ + import org.eclipse.jdt.annotation.*; + + public sealed interface PatternMatching { + + record Stuff() implements PatternMatching {} + + @NonNull + static Stuff match(PatternMatching pm, int v) { + if (v == 0) { + return switch (pm) { + case Stuff s -> s; + case null -> throw new NullPointerException(); + }; // no error here - good + } else if (v == 2) { + return switch (pm) { + case Stuff s -> s; + }; // no error here -- good + } else if (v == 3) { + return switch (pm) { + case Stuff s -> null; // get error here - good + }; + } + return new Stuff(); + } + } + """ + }; + runner.expectedCompilerLog = + """ + ---------- + 1. ERROR in PatternMatching.java (at line 20) + case Stuff s -> null; // get error here - good + ^^^^ + Null type mismatch: required 'PatternMatching.@NonNull Stuff' but the provided value is null + ---------- + """; + runner.runNegativeTest(); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullDeclarationAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullDeclarationAnnotationTest.java new file mode 100644 index 00000000000..4e047231416 --- /dev/null +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullDeclarationAnnotationTest.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2024 GK Software SE and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Stephan Herrmann - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.compiler.regression; + +import java.io.IOException; +import junit.framework.Test; +import junit.framework.TestSuite; + +/** Run tests from the super class with (legacy) declaration annotations. */ +public class NullDeclarationAnnotationTest extends NullAnnotationTest { + + public NullDeclarationAnnotationTest(String name) { + super(name); + } + + // Static initializer to specify tests subset using TESTS_* static variables + // All specified tests which do not belong to the class are skipped... + static { +// TESTS_NAMES = new String[] { "testBug545715" }; +// TESTS_NUMBERS = new int[] { 561 }; +// TESTS_RANGE = new int[] { 1, 2049 }; + } + + public static Test suite() { + TestSuite suite = new TestSuite(testClass().getName()); + buildMinimalComplianceTestSuite(FIRST_SUPPORTED_JAVA_VERSION, 1, suite, testClass()); + return suite; + } + + public static Class testClass() { + return NullDeclarationAnnotationTest.class; + } + + public boolean useDeclarationAnnotations() { + return true; + } + + /** + * @deprecated + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + this.TEST_JAR_SUFFIX = ".jar"; + } + + @Override + protected String getAnnotationLibPath() throws IOException { + return getAnnotationV1LibPath(); + } + +} diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PatternMatching16Test.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PatternMatching16Test.java index 91637c164c5..061ba5cbfc6 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PatternMatching16Test.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PatternMatching16Test.java @@ -2405,7 +2405,7 @@ public void testBug562392c() { "----------\n" + "1. ERROR in X.java (at line 4)\n" + " if (obj instanceof T t) {\n" + - " ^^^\n" + + " ^^^^^^^^^^^^^^^^^^\n" + "Type Object cannot be safely cast to T\n" + "----------\n", "X.java:4: error: Object cannot be safely cast to T\n" + @@ -2464,7 +2464,7 @@ public void testBug562392e() { "----------\n" + "1. ERROR in X.java (at line 4)\n" + " if (obj instanceof X p) {\n" + - " ^^^\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + "Type X cannot be safely cast to X\n" + "----------\n", "", diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java index 5daf8a342a7..91998daf9cb 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java @@ -1514,29 +1514,93 @@ public static void main(String... args) { }, "12"); } - public void testInstanceof_widenUnbox() { - runConformTest(new String[] { - "X.java", - """ + void testInstanceof_widenUnbox(String fromBox, int idx, String expectedOuts) { + // for all numerical types challenge route WIDENING_REFERENCE_AND_UNBOXING_COVERSION_AND_WIDENING_PRIMITIVE_CONVERSION + // + // example (from="Integer", idx=4, ...): + // class X { + // // alternating for type variable (m1*) and wildcard (m2*): + // static long m1long(T in) { + // if (in instanceof long v) return v; + // return -1L; + // } + // static long m2Long(Optional in) { + // if (in.get() instanceof long v) return v; + // return -1L; + // } + // static float m1float(T in) { + // if (in instanceof float v) return v; + // return -1.0f; + // } + // ... + // public static void main(String... args) { + // Integer v = Integer.valueOf((int) 49); + // System.out.print(m1long(v)); + // System.out.print('+'); + // System.out.print(Optional.of(m2long(v)); + // System.out.print('|'); + // System.out.print(m1float(v)); + // System.out.print('|'); + // ... + // } + // } + String m1 = """ + static PRIM m1PRIM(T in) { + if (in instanceof PRIM v) return v; + return NEGVAL; + } + """.replace("FROM", fromBox); + String m2 = """ + static PRIM m2PRIM(Optional in) { + if (in.get() instanceof PRIM v) return v; + return NEGVAL; + } + """.replace("FROM", fromBox); + StringBuilder clazz = new StringBuilder(); + clazz.append(""" import java.util.Optional; public class X { - static int mInteger(T in) { - if (in instanceof int v) return v; - return -1; - } - static int mShort(Optional in) { - if (in.get() instanceof int v) return v; - return -1; - } - public static void main(String... args) { - System.out.print(mInteger(Integer.valueOf(1))); - System.out.print(mShort(Optional.of(Short.valueOf((short) 2)))); + """); + StringBuilder main = new StringBuilder(); + main.append("public static void main(String... args) {\n"); + main.append("\tFROM v = FROM.valueOf((CAST) VAL);\n" + .replace("FROM", fromBox) + .replace("CAST", PRIMITIVES[idx]) + .replace("VAL", GOODVALUES[idx])); + String call1Tmpl = "\tSystem.out.print(m1PRIM(v));\n".replace("FROM", fromBox); + String call2Tmpl = "\tSystem.out.print(m2PRIM(Optional.of(v)));\n".replace("FROM", fromBox); + for (int i=idx+1; i<8; i++) { + if (!IS_NUMERICAL[i]) continue; + clazz.append(fillIn(m1, i)); + clazz.append(fillIn(m2, i)); + main.append(fillIn(call1Tmpl, i)); + main.append("\tSystem.out.print('+');\n"); + main.append(fillIn(call2Tmpl, i)); + main.append("\tSystem.out.print('|');\n"); + } + clazz.append(main); + clazz.append(""" } } - """ - }, - "12"); + """); + runConformTest(new String[] {"X.java", clazz.toString()}, expectedOuts); + } + public void testInstanceof_widenUnbox_Byte() { + testInstanceof_widenUnbox("Byte", 1, "49+49|49+49|49+49|49.0+49.0|49.0+49.0|"); + } + public void testInstanceof_widenUnbox_Short() { + testInstanceof_widenUnbox("Short", 3, "49+49|49+49|49.0+49.0|49.0+49.0|"); } + public void testInstanceof_widenUnbox_Integer() { + testInstanceof_widenUnbox("Integer", 4, "49+49|49.0+49.0|49.0+49.0|"); + } + public void testInstanceof_widenUnbox_Long() { + testInstanceof_widenUnbox("Long", 5, "49.0+49.0|49.0+49.0|"); + } + public void testInstanceof_widenUnbox_Float() { + testInstanceof_widenUnbox("Float", 6, "49.0+49.0|"); + } + public void testInstanceof_genericExpression() { // regression test for a checkCast which we failed to generate earlier runConformTest(new String[] { "X.java", @@ -1923,7 +1987,7 @@ static int m1(boolean b) { 1. ERROR in X.java (at line 3) return switch (b) { ^ - An enhanced switch statement should be exhaustive; a default label expected + A switch expression should have a default case ---------- """); } @@ -2120,12 +2184,12 @@ static double m2(long l) { 1. ERROR in X.java (at line 3) return switch(l) { ^ - An enhanced switch statement should be exhaustive; a default label expected + A switch expression should have a default case ---------- 2. ERROR in X.java (at line 9) return switch(l) { ^ - An enhanced switch statement should be exhaustive; a default label expected + A switch expression should have a default case ---------- """); } @@ -2306,6 +2370,51 @@ int foo4(int myInt) { """); } + public void testIncompatiblePrimitiveInInstanceof() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + void foo() { + if (this instanceof int i) + return; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 3) + if (this instanceof int i) + ^^^^^^^^^^^^^^^^^^^^^ + Incompatible conditional operand types X and int + ---------- + """); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3113 + // [Switch][Record patterns] Unexpected operand error with switch pattern and widening unboxing conversion + public void testGH3113_ok() { + runConformTest(new String[] { + "X.java", + """ + record Record(T t) {} + public class X { + public static double convert(Record r) { + return switch (r) { + case Record(double d) -> d; + default -> 2; + }; + } + public static void main(String[] args) { + System.out.print(convert(new Record(2))); + } + } + """ + }, + "2.0"); + } + // test from spec public void _testSpec001() { runConformTest(new String[] { diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java index 812d8e49655..90f7870a193 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ProblemTypeAndMethodTest.java @@ -10328,4 +10328,106 @@ F test() { """; runner.runNegativeTest(); } +public void testGH3047() throws Exception { + Runner runner = new Runner(); + runner.testFiles = new String[] { + "resources/examples/mockito/MockingFromFinder.java", + """ + package examples.mockito; + public class MockingFromFinder{} + """, + "resources/examples/mockito/MockingWhileAdding.java", + """ + package examples.mockito; + public class MockingWhileAdding { + public static void calculateWithAdder(int x, int y) { + IOperation adder = new Adder()::execute; + } + public interface IOperation { + int execute(int x, int y); + } + public static class Adder implements IOperation { + public int execute(int x, int y) { + return x+y; + } + } + } + """ + }; + runner.classLibraries = new String[0]; + if (this.complianceLevel <= ClassFileConstants.JDK13) { + runner.expectedCompilerLog = + """ + ---------- + 1. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 1) + package examples.mockito; + ^ + The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files + ---------- + 2. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 2) + public class MockingFromFinder{} + ^^^^^^^^^^^^^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + ---------- + 1. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 1) + package examples.mockito; + ^ + The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files + ---------- + 2. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 2) + public class MockingWhileAdding { + ^^^^^^^^^^^^^^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + 3. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 9) + public static class Adder implements IOperation { + ^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + """; + } else { + runner.expectedCompilerLog = + """ + ---------- + 1. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 1) + package examples.mockito; + ^ + The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files + ---------- + 2. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 1) + package examples.mockito; + ^ + The type java.lang.Error cannot be resolved. It is indirectly referenced from required .class files + ---------- + 3. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 1) + package examples.mockito; + ^ + The type java.lang.String cannot be resolved. It is indirectly referenced from required .class files + ---------- + 4. ERROR in resources\\examples\\mockito\\MockingFromFinder.java (at line 2) + public class MockingFromFinder{} + ^^^^^^^^^^^^^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + ---------- + 1. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 1) + package examples.mockito; + ^ + The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files + ---------- + 2. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 2) + public class MockingWhileAdding { + ^^^^^^^^^^^^^^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + 3. ERROR in resources\\examples\\mockito\\MockingWhileAdding.java (at line 9) + public static class Adder implements IOperation { + ^^^^^ + Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor + ---------- + """; + } + runner.runNegativeTest(); +} } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordPatternTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordPatternTest.java index 33937c7e4ea..d8034188a64 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordPatternTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/RecordPatternTest.java @@ -299,12 +299,12 @@ public void test007() { "1. ERROR in X.java (at line 3)\n" + " if (r instanceof Rectangle(ColoredPoint(Point(String o1, String o2), Color c),\n" + " ^^^^^^^^^\n" + - "Record component with type int is not compatible with type java.lang.String\n" + + "Record component with type int is not compatible with type String\n" + "----------\n" + "2. ERROR in X.java (at line 3)\n" + " if (r instanceof Rectangle(ColoredPoint(Point(String o1, String o2), Color c),\n" + " ^^^^^^^^^\n" + - "Record component with type int is not compatible with type java.lang.String\n" + + "Record component with type int is not compatible with type String\n" + "----------\n"); } // Test that pattern types that don't match record component's types are reported @@ -1558,7 +1558,7 @@ public void test47() { + "public class X {\n" + " static void printGenericBoxString1(Box objectBox) {\n" + " if (objectBox instanceof Box(String s)) {\n" - + " System.out.println(s); // this one should report an unsafe cast error\n" + + " System.out.println(s);\n" + " }\n" + " }\n" + " public static void main(String[] args) {}\n" @@ -1568,8 +1568,8 @@ public void test47() { "----------\n" + "1. ERROR in X.java (at line 4)\n" + " if (objectBox instanceof Box(String s)) {\n" + - " ^^^^^^^^^\n" + - "Type Box cannot be safely cast to Box\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Incompatible conditional operand types Box and Box\n" + "----------\n"); } public void test48() { @@ -1958,7 +1958,7 @@ public void testRecordPatternTypeInference_009() { "1. ERROR in X.java (at line 7)\n" + " if (p instanceof R(String a)) {\n" + " ^^^^^^^^\n" + - "Record component with type capture#2-of ? extends I is not compatible with type java.lang.String\n" + + "Record component with type capture#2-of ? extends I is not compatible with type String\n" + "----------\n"); } public void testRecordPatternTypeInference_010() { @@ -2505,7 +2505,7 @@ public void testIssue1224_4() { "2. ERROR in X.java (at line 6)\n" + " case Record(Object o, StringBuilder s) -> {break;}\n" + " ^^^^^^^^^^^^^^^\n" + - "Record component with type String is not compatible with type java.lang.StringBuilder\n" + + "Record component with type String is not compatible with type StringBuilder\n" + "----------\n"); } public void testIssue1224_5() { @@ -2669,7 +2669,7 @@ public void testRecPatExhaust002() { "1. ERROR in X.java (at line 12)\n" + " return switch (box) { // Not Exhaustive!\n" + " ^^^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust003() { @@ -2753,7 +2753,7 @@ public void testRecPatExhaust004() { "1. ERROR in X.java (at line 16)\n" + " return switch (box) { // Not Exhaustive!\n" + " ^^^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust005() { @@ -2816,7 +2816,7 @@ public void testRecPatExhaust006() { "1. ERROR in X.java (at line 11)\n" + " return switch (box) { // Not Exhaustive!\n" + " ^^^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust007() { @@ -2849,7 +2849,7 @@ public void testRecPatExhaust007() { "1. ERROR in X.java (at line 12)\n" + " return switch (p) { // Not Exhaustive!\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust008() { @@ -2883,7 +2883,7 @@ public void testRecPatExhaust008() { "1. ERROR in X.java (at line 12)\n" + " return switch (p) { // Not Exhaustive!\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust009() { @@ -2961,7 +2961,7 @@ public void testRecPatExhaust011() { "1. ERROR in X.java (at line 12)\n" + " return switch (r) {\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } // implicit permitted - class @@ -2994,7 +2994,7 @@ public void testRecPatExhaust012() { "1. ERROR in X.java (at line 12)\n" + " return switch (r) {\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } // implicit permitted - class - the class C missing @@ -3028,7 +3028,7 @@ public void testRecPatExhaust013() { "1. ERROR in X.java (at line 12)\n" + " return switch (r) {\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust014() { @@ -3061,7 +3061,7 @@ public void testRecPatExhaust014() { "1. ERROR in X.java (at line 11)\n" + " return switch (r) {\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecPatExhaust015() { @@ -3177,7 +3177,7 @@ public void testRecPatExhaust018() { "1. ERROR in X.java (at line 12)\n" + " return switch (r) {\n" + " ^\n" + - "An enhanced switch statement should be exhaustive; a default label expected\n" + + "A switch expression should have a default case\n" + "----------\n"); } public void testRecordPatternTypeInference_012() { @@ -4401,7 +4401,7 @@ public static void main(String[] args) { "2. ERROR in X.java (at line 10)\n" + " if (o instanceof R2(Short d)) {\n" + " ^^^^^^^\n" + - "Record component with type short is not compatible with type java.lang.Short\n" + + "Record component with type short is not compatible with type Short\n" + "----------\n" + "3. ERROR in X.java (at line 13)\n" + " if (o instanceof R2(int d)) {\n" + @@ -4686,4 +4686,77 @@ static void foo(Object o) { "Syntax error on tokens, delete these tokens\n" + "----------\n"); } + + public void testIssue3066() { + runNegativeTest(new String[] { + "X.java", + """ + public record X(int x) { + public static void main(String[] args) { + Object o = new Object(); + switch (o) { + case X(int x): + default: + } + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 5) + case X(int x): + ^^^^^^^^^^^^^^^^ + Type Object cannot be safely cast to X + ---------- + """); + } + + public void testIssue3066_notApplicable() { + runNegativeTest(new String[] { + "X.java", + """ + public record X(int x) { + public static void main(String[] args) { + java.io.Serializable o = ""; + switch (o) { + case X(int x): + default: + } + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 5) + case X(int x): + ^^^^^^^^ + Type mismatch: cannot convert from Serializable to X + ---------- + """); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3173 + // [21][Enhanced Switch] False error about allegedly non-exhaustive switch + public void testIssue3173() { + runConformTest(new String[] { + "RecordPatternDemo.java", + """ + public class RecordPatternDemo { + public static void main(String[] args) { + record Box(T contents) { } + + Box> doubleBoxed = new Box<>(new Box<>("Contents")); + String unboxed = switch (doubleBoxed) { + case Box(Box(String s)) -> s; + }; + + System.out.println(unboxed); + } + } + """ + }, + "Contents"); + } } \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java index f3399e7895f..0b3d03ee0d2 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ResourceLeakAnnotatedTests.java @@ -13,11 +13,9 @@ *******************************************************************************/ package org.eclipse.jdt.core.tests.compiler.regression; -import java.util.List; import java.util.Map; import junit.framework.Test; import junit.framework.TestSuite; -import org.eclipse.jdt.core.tests.util.AbstractCompilerTest; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; @@ -36,63 +34,10 @@ public ResourceLeakAnnotatedTests(String name) { } public static Test suite() { TestSuite suite = new TestSuite(ResourceLeakAnnotatedTests.class.getName()); - // argument 'inheritedDepth' is not exposed in original API, therefore these helpers are copied below with this arg added buildMinimalComplianceTestSuite(FIRST_SUPPORTED_JAVA_VERSION, 1, suite, ResourceLeakAnnotatedTests.class); return suite; } -private static void buildMinimalComplianceTestSuite(int minimalCompliance, int inheritedDepth, TestSuite suite, Class evaluationTestClass) { - int complianceLevels = AbstractCompilerTest.getPossibleComplianceLevels(); - for (int[] map : complianceTestLevelMapping) { - if ((complianceLevels & map[0]) != 0) { - long complianceLevelForJavaVersion = ClassFileConstants.getComplianceLevelForJavaVersion(map[1]); - checkCompliance(evaluationTestClass, minimalCompliance, suite, complianceLevels, inheritedDepth, map[0], map[1], getVersionString(complianceLevelForJavaVersion)); - } - } -} -protected static void checkCompliance(Class evaluationTestClass, int minimalCompliance, TestSuite suite, int complianceLevels, - int inheritedDepth, int abstractCompilerTestCompliance, int classFileConstantsVersion, String release) { - int lev = complianceLevels & abstractCompilerTestCompliance; - if (lev != 0) { - if (lev < minimalCompliance) { - System.err.println("Cannot run "+evaluationTestClass.getName()+" at compliance " + release + "!"); - } else { - suite.addTest(buildUniqueComplianceTestSuite(evaluationTestClass, ClassFileConstants.getComplianceLevelForJavaVersion(classFileConstantsVersion), inheritedDepth)); - } - } -} -public static Test buildUniqueComplianceTestSuite(Class evaluationTestClass, long uniqueCompliance, int inheritedDepth) { - long highestLevel = highestComplianceLevels(); - if (highestLevel < uniqueCompliance) { - String complianceString; - if (highestLevel == ClassFileConstants.JDK10) - complianceString = "10"; - else if (highestLevel == ClassFileConstants.JDK9) - complianceString = "9"; - else if (highestLevel <= CompilerOptions.getFirstSupportedJdkLevel()) - complianceString = CompilerOptions.getFirstSupportedJavaVersion(); - else { - highestLevel = ClassFileConstants.getLatestJDKLevel(); - if (highestLevel > 0) { - complianceString = CompilerOptions.versionFromJdkLevel(highestLevel); - } else { - complianceString = "unknown"; - } - - } - - System.err.println("Cannot run "+evaluationTestClass.getName()+" at compliance "+complianceString+"!"); - return new TestSuite(); - } - TestSuite complianceSuite = new RegressionTestSetup(uniqueCompliance); - List tests = buildTestsList(evaluationTestClass, inheritedDepth); - for (int index=0, size=tests.size(); index getCompilerOptions() { Map options = super.getCompilerOptions(); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesSpecReviewTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesSpecReviewTest.java deleted file mode 100644 index 27f22cecdc6..00000000000 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypesSpecReviewTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/******************************************************************************* -* Copyright (c) 2024 Advantest Europe GmbH and others. -* -* This program and the accompanying materials -* are made available under the terms of the Eclipse Public License 2.0 -* which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-2.0/ -* -* SPDX-License-Identifier: EPL-2.0 -* -* Contributors: -* Srikanth Sankaran - initial implementation -*******************************************************************************/ - -package org.eclipse.jdt.core.tests.compiler.regression; - -import java.util.Map; -import junit.framework.Test; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; - -public class SealedTypesSpecReviewTest extends AbstractRegressionTest9 { - - static { -// TESTS_NUMBERS = new int [] { 40 }; -// TESTS_RANGE = new int[] { 1, -1 }; -// TESTS_NAMES = new String[] { "testBug564498_6"}; - } - - public static Class testClass() { - return SealedTypesSpecReviewTest.class; - } - public static Test suite() { - return buildMinimalComplianceTestSuite(testClass(), F_17); - } - public SealedTypesSpecReviewTest(String testName){ - super(testName); - } - - // Enables the tests to run individually - protected Map getCompilerOptions() { - Map defaultOptions = super.getCompilerOptions(); - defaultOptions.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_17); - defaultOptions.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_17); - defaultOptions.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_17); - defaultOptions.put(CompilerOptions.OPTION_ReportPreviewFeatures, CompilerOptions.IGNORE); - defaultOptions.put(CompilerOptions.OPTION_Store_Annotations, CompilerOptions.ENABLED); - return defaultOptions; - } - - @Override - protected void runConformTest(String[] testFiles, String expectedOutput) { - runConformTest(testFiles, expectedOutput, getCompilerOptions()); - } - - @Override - protected void runConformTest(String[] testFiles, String expectedOutput, Map customOptions) { - Runner runner = new Runner(); - runner.testFiles = testFiles; - runner.expectedOutputString = expectedOutput; - runner.vmArguments = new String[] {"--enable-preview"}; - runner.customOptions = customOptions; - runner.javacTestOptions = JavacTestOptions.forReleaseWithPreview("17"); - runner.runConformTest(); - } - @Override - protected void runNegativeTest(String[] testFiles, String expectedCompilerLog) { - runNegativeTest(testFiles, expectedCompilerLog, JavacTestOptions.forReleaseWithPreview("17")); - } - protected void runWarningTest(String[] testFiles, String expectedCompilerLog) { - runWarningTest(testFiles, expectedCompilerLog, (Map) null); - } - protected void runWarningTest(String[] testFiles, String expectedCompilerLog, Map customOptions) { - runWarningTest(testFiles, expectedCompilerLog, customOptions, null); - } - - protected void runWarningTest(String[] testFiles, String expectedCompilerLog, String expectedOutput) { - - Runner runner = new Runner(); - runner.testFiles = testFiles; - runner.expectedCompilerLog = expectedCompilerLog; - runner.expectedOutputString = expectedOutput; - runner.customOptions = getCompilerOptions(); - runner.vmArguments = new String[] {"--enable-preview"}; - runner.javacTestOptions = JavacTestOptions.forReleaseWithPreview("16"); - runner.runWarningTest(); - } - - protected void runWarningTest(String[] testFiles, String expectedCompilerLog, - Map customOptions, String javacAdditionalTestOptions) { - - Runner runner = new Runner(); - runner.testFiles = testFiles; - runner.expectedCompilerLog = expectedCompilerLog; - runner.customOptions = customOptions; - runner.vmArguments = new String[] {"--enable-preview"}; - runner.javacTestOptions = javacAdditionalTestOptions == null ? JavacTestOptions.forReleaseWithPreview("16") : - JavacTestOptions.forReleaseWithPreview("16", javacAdditionalTestOptions); - runner.runWarningTest(); - } - - - // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2709 - // [Sealed types] Disjointness behavior difference vis a vis javac - /* A class named C is disjoint from an interface named I if (i) it is not the case that C <: I, and (ii) one of the following cases applies: - – C is freely extensible (§8.1.1.2), and I is sealed, and C is disjoint from all of the permitted direct subclasses and subinterfaces of I. - */ - public void testIssue2709() { - runNegativeTest( - new String[] { - "X.java", - """ - public class X { - sealed interface I permits C1 {} - non-sealed class C1 implements I {} - class C2 extends C1 {} - class C3 {} - { - I i; - i = (I) (C1) null; - i = (I) (C2) null; - i = (I) (C3) null; - i = (C2) (C3) null; - i = (C1) (C3) null; - } - } - """ - }, - "----------\n" + - "1. ERROR in X.java (at line 10)\n" + - " i = (I) (C3) null;\n" + - " ^^^^^^^^^^^^^\n" + - "Cannot cast from X.C3 to X.I\n" + - "----------\n" + - "2. ERROR in X.java (at line 11)\n" + - " i = (C2) (C3) null;\n" + - " ^^^^^^^^^^^^^^\n" + - "Cannot cast from X.C3 to X.C2\n" + - "----------\n" + - "3. ERROR in X.java (at line 12)\n" + - " i = (C1) (C3) null;\n" + - " ^^^^^^^^^^^^^^\n" + - "Cannot cast from X.C3 to X.C1\n" + - "----------\n"); - } -} \ No newline at end of file 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 c77f38e51ef..2917bed322c 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 @@ -254,7 +254,7 @@ public void testBug562715_005() { "2. ERROR in X.java (at line 1)\n" + " sealed public sealed class 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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n"); } public void testBug562715_006() { @@ -271,7 +271,7 @@ public void testBug562715_006() { "1. ERROR in X.java (at line 1)\n" + " public sealed class 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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n" + "2. ERROR in X.java (at line 2)\n" + " public static sealed void main(String[] args){\n" + @@ -336,7 +336,7 @@ public void testBug562715_010() { "1. ERROR in X.java (at line 1)\n" + " public sealed class X permits {\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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n" + "2. ERROR in X.java (at line 1)\n" + " public sealed class X permits {\n" + @@ -370,7 +370,7 @@ public void testBug562715_011() { "2. ERROR in X.java (at line 2)\n" + " public sealed class 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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n" + "3. ERROR in X.java (at line 3)\n" + " public static sealed void main(String[] args){\n" + @@ -398,7 +398,7 @@ public void testBug562715_xxx() { "2. ERROR in X.java (at line 2)\n" + " public sealed class 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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n" + "3. ERROR in X.java (at line 3)\n" + " public static sealed void main(String[] args){\n" + @@ -419,12 +419,12 @@ public void testBug563806_001() { "1. ERROR in X.java (at line 1)\n" + " public sealed class X permits Y, Z{\n" + " ^\n" + - "Permitted class Y does not declare X as direct super class\n" + + "Permitted type Y does not declare X as a direct supertype\n" + "----------\n" + "2. ERROR in X.java (at line 1)\n" + " public sealed class X permits Y, Z{\n" + " ^\n" + - "Permitted class Z does not declare X as direct super class\n" + + "Permitted type Z does not declare X as a direct supertype\n" + "----------\n"); } public void testBug563806_002() { @@ -444,28 +444,28 @@ public void testBug563806_002() { "1. ERROR in p1\\X.java (at line 2)\n" + " public sealed class X permits Y{\n" + " ^\n" + - "Permitted class Y does not declare p1.X as direct super class\n" + + "Permitted type Y does not declare p1.X as a direct supertype\n" + "----------\n" + "2. ERROR in p1\\X.java (at line 5)\n" + " class Z extends X{}\n" + " ^\n" + - "The class Z with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Z with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n" + "3. ERROR in p1\\X.java (at line 5)\n" + " class Z extends X{}\n" + " ^\n" + - "The type Z extending a sealed class X should be a permitted subtype of X\n" + + "The class Z cannot extend the class X as it is not a permitted subtype of X\n" + "----------\n" + "----------\n" + "1. ERROR in p1\\A.java (at line 2)\n" + " public sealed class A extends X{}\n" + " ^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares A as its direct superclass or superinterface\n" + + "Sealed type A lacks a permits clause and no type from the same compilation unit declares A as its direct supertype\n" + "----------\n" + "2. ERROR in p1\\A.java (at line 2)\n" + " public sealed class A extends X{}\n" + " ^\n" + - "The type A extending a sealed class X should be a permitted subtype of X\n" + + "The class A cannot extend the class X as it is not a permitted subtype of X\n" + "----------\n"); } public void testBug563806_003() { @@ -486,7 +486,7 @@ public void testBug563806_003() { "2. ERROR in X.java (at line 3)\n" + " class Y implements X{}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } public void testBug563806_004() { @@ -513,7 +513,7 @@ public void testBug563806_004() { "3. ERROR in p1\\X.java (at line 4)\n" + " class Y implements X{}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } public void testBug563806_005() { @@ -528,12 +528,12 @@ public void testBug563806_005() { "1. ERROR in X.java (at line 1)\n" + " public sealed class X permits Y, Y{\n" + " ^\n" + - "Duplicate type Y for the type X in the permits clause\n" + + "Duplicate permitted type Y\n" + "----------\n" + "2. ERROR in X.java (at line 3)\n" + " class Y extends X {}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } public void testBug563806_006() { @@ -549,12 +549,12 @@ public void testBug563806_006() { "1. ERROR in p1\\X.java (at line 2)\n" + " public sealed class X permits Y, p1.Y{\n" + " ^^^^\n" + - "Duplicate type Y for the type X in the permits clause\n" + + "Duplicate permitted type Y\n" + "----------\n" + "2. ERROR in p1\\X.java (at line 4)\n" + " class Y extends X {}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } public void testBug563806_007() { @@ -569,7 +569,7 @@ public void testBug563806_007() { "1. ERROR in X.java (at line 3)\n" + " non-sealed class Y extends X {}\n" + " ^\n" + - "A class Y declared as non-sealed should have either a sealed direct superclass or a sealed direct superinterface\n" + + "The non-sealed class Y must have a sealed direct supertype\n" + "----------\n"); } public void testBug563806_008() { @@ -588,13 +588,13 @@ public void testBug563806_008() { "1. ERROR in p1\\X.java (at line 4)\n" + " class Y implements X{}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n" + "----------\n" + "1. ERROR in p2\\Y.java (at line 2)\n" + " non-sealed public interface Y {}\n" + " ^\n" + - "An interface Y declared as non-sealed should have a sealed direct superinterface\n" + + "The non-sealed interface Y must have a sealed direct superinterface\n" + "----------\n"); } public void testBug563806_009() { @@ -626,7 +626,7 @@ public void testBug563806_010() { "1. ERROR in p2\\Y.java (at line 2)\n" + " public final class Y extends p1.X{}\n" + " ^^^^\n" + - "Sealed type X and sub type Y in an unnamed module should be declared in the same package p1\n" + + "The class Y cannot extend the class X as it is not a permitted subtype of X\n" + "----------\n"); } public void testBug563806_011() { @@ -660,7 +660,7 @@ public void testBug563806_012() { "1. ERROR in p2\\Y.java (at line 2)\n" + " public final class Y implements p1.X{}\n" + " ^^^^\n" + - "Sealed type X and sub type Y in an unnamed module should be declared in the same package p1\n" + + "The type Y that implements the sealed interface X should be a permitted subtype of X\n" + "----------\n"); } public void testBug563806_013() { @@ -705,7 +705,7 @@ public void testBug563806_014() { "2. ERROR in p2\\Y.java (at line 2)\n" + " public interface Y extends p1.X{}\n" + " ^^^^\n" + - "Sealed type X and sub type Y in an unnamed module should be declared in the same package p1\n" + + "The type Y that extends the sealed interface X should be a permitted subtype of X\n" + "----------\n"); } public void testBug563806_015() { @@ -859,7 +859,7 @@ public void testBug563806_022() { "2. ERROR in p1\\X.java (at line 2)\n" + " public sealed class X permits Y, p2.Y {\n" + " ^^^^\n" + - "Permitted class Y does not declare p1.X as direct super class\n" + + "Permitted type Y does not declare p1.X as a direct supertype\n" + "----------\n"; runner.runNegativeTest(); } @@ -1070,12 +1070,7 @@ public void testBug563806_032() { "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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n"); } public void testBug563806_033() { @@ -1141,7 +1136,7 @@ public void testBug563806_035() { "1. ERROR in p1\\X.java (at line 2)\n" + " public non-sealed interface X {\n" + " ^\n" + - "An interface X declared as non-sealed should have a sealed direct superinterface\n" + + "The non-sealed interface X must have a sealed direct superinterface\n" + "----------\n"); } public void testBug563806_036() { @@ -1219,12 +1214,12 @@ public void testBug563806_039() { "1. ERROR in p1\\X.java (at line 2)\n" + " sealed class A{}\n" + " ^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares A as its direct superclass or superinterface\n" + + "Sealed type A lacks a permits clause and no type from the same compilation unit declares A as its direct supertype\n" + "----------\n" + "2. ERROR in p1\\X.java (at line 5)\n" + " class Y extends A{}\n" + " ^\n" + - "A local class Y cannot have a sealed direct superclass or a sealed direct superinterface A\n" + + "The local type Y may not have a sealed supertype A\n" + "----------\n"); } public void testBug564191_001() throws IOException, ClassFormatException { @@ -1329,7 +1324,7 @@ public void testBug564190_4() throws IOException, ClassFormatException { "1. ERROR in p1\\X.java (at line 3)\n" + " class Y extends X {}\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } // Test that implicit permitted member type with implicit permitted types @@ -1348,7 +1343,7 @@ public void testBug564190_5() throws IOException, ClassFormatException { "1. ERROR in p1\\X.java (at line 3)\n" + " sealed class Y extends X {}\n" + " ^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares Y as its direct superclass or superinterface\n" + + "Sealed type Y lacks a permits clause and no type from the same compilation unit declares Y as its direct supertype\n" + "----------\n"); } // Test that implicit permitted member type with explicit permits clause @@ -1367,7 +1362,7 @@ public void testBug564190_6() throws IOException, ClassFormatException { "1. ERROR in p1\\X.java (at line 3)\n" + " sealed class Y extends X permits Z {}\n" + " ^\n" + - "Permitted class Z does not declare p1.X.Y as direct super class\n" + + "Permitted type Z does not declare p1.X.Y as a direct supertype\n" + "----------\n"); } // Test that implicit permitted member type with explicit permits clause @@ -1383,7 +1378,7 @@ public void testBug564190_7() throws IOException, ClassFormatException { "1. ERROR in p1\\X.java (at line 2)\n" + " sealed interface SI {}\n" + " ^^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares SI as its direct superclass or superinterface\n" + + "Sealed type SI lacks a permits clause and no type from the same compilation unit declares SI as its direct supertype\n" + "----------\n"); } public void testBug564450_001() throws IOException, ClassFormatException { @@ -1402,7 +1397,7 @@ public void testBug564450_001() throws IOException, ClassFormatException { "1. ERROR in p1\\Y.java (at line 2)\n" + " class Y extends X {\n" + " ^\n" + - "The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" + + "The class Y with a sealed direct supertype X should be declared either final, sealed, or non-sealed\n" + "----------\n"); } public void testBug564047_001() throws CoreException, IOException { @@ -1439,12 +1434,12 @@ public void testBug564047_001() throws CoreException, IOException { "1. ERROR in src\\p\\X.java (at line 2)\n" + " public class X extends Y {\n" + " ^\n" + - "The class X with a sealed direct superclass or a sealed direct superinterface Y should be declared either final, sealed, or non-sealed\n" + + "The class X with a sealed direct supertype Y should be declared either final, sealed, or non-sealed\n" + "----------\n" + "2. ERROR in src\\p\\X.java (at line 2)\n" + " public class X extends Y {\n" + " ^\n" + - "The type X extending a sealed class Y should be a permitted subtype of Y\n" + + "The class X cannot extend the class Y as it is not a permitted subtype of Y\n" + "----------\n", libs, true); @@ -1584,8 +1579,8 @@ public void testBug564498_2() throws IOException, ClassFormatException { ""); String expectedOutput = "PermittedSubclasses:\n" + - " #22 p1/A$Z,\n" + - " #24 p1/A$SubY\n" + + " #22 p1/A$SubY,\n" + + " #24 p1/A$Z\n" + "}"; verifyClassFile(expectedOutput, "p1/A$Y.class", ClassFileBytesDisassembler.SYSTEM); expectedOutput = @@ -1612,9 +1607,9 @@ public void testBug564498_3() throws IOException, ClassFormatException { ""); String expectedOutput = "PermittedSubclasses:\n" + - " #24 p1/A$Y$SubInnerY,\n" + - " #26 p1/A$Z,\n" + - " #28 p1/A$SubY\n"; + " #24 p1/A$SubY,\n" + + " #26 p1/A$Y$SubInnerY,\n" + + " #28 p1/A$Z\n"; verifyClassFile(expectedOutput, "p1/A$Y.class", ClassFileBytesDisassembler.SYSTEM); } public void testBug564498_4() throws IOException, ClassFormatException { @@ -1661,7 +1656,7 @@ public void testBug564498_5() throws IOException, ClassFormatException { "2. ERROR in p1\\X.java (at line 7)\n" + " final class SubInnerY extends Y {}\n" + " ^\n" + - "The type SubInnerY extending a sealed class A.Y should be a permitted subtype of A.Y\n" + + "The class SubInnerY cannot extend the class A.Y as it is not a permitted subtype of A.Y\n" + "----------\n"); } // accept references of membertype without qualifier of enclosing type in permits clause @@ -5339,13 +5334,13 @@ public void testBug568758_001() { "1. ERROR in X.java (at line 1)\n" + " public 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" + + "Sealed type X lacks a permits clause and no type from the same compilation unit declares X as its direct supertype\n" + "----------\n" + "----------\n" + "1. ERROR in Y.java (at line 1)\n" + " public final class Y implements X{}\n" + " ^\n" + - "The type Y that implements a sealed interface X should be a permitted subtype of X\n" + + "The type Y that implements the sealed interface X should be a permitted subtype of X\n" + "----------\n"); } public void testBug569522_001() throws IOException, ClassFormatException { @@ -5428,7 +5423,7 @@ public void testBug568854_001() { "1. ERROR in X.java (at line 4)\n" + " record B() implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface X.Foo should be a permitted subtype of X.Foo\n" + + "The type B that implements the sealed interface X.Foo should be a permitted subtype of X.Foo\n" + "----------\n"); } public void testBug568854_002() { @@ -5445,7 +5440,7 @@ public void testBug568854_002() { "1. ERROR in X.java (at line 4)\n" + " record B() implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface Foo should be a permitted subtype of Foo\n" + + "The type B that implements the sealed interface Foo should be a permitted subtype of Foo\n" + "----------\n"); } public void testBug568854_003() { @@ -5462,7 +5457,7 @@ public void testBug568854_003() { "1. ERROR in X.java (at line 3)\n" + " record B() implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface Foo should be a permitted subtype of Foo\n" + + "The type B that implements the sealed interface Foo should be a permitted subtype of Foo\n" + "----------\n"); } public void testBug568854_004() { @@ -5479,12 +5474,12 @@ public void testBug568854_004() { "1. ERROR in X.java (at line 3)\n" + " class A implements Foo {}\n" + " ^\n" + - "The class A with a sealed direct superclass or a sealed direct superinterface X.Foo should be declared either final, sealed, or non-sealed\n" + + "The class A with a sealed direct supertype X.Foo should be declared either final, sealed, or non-sealed\n" + "----------\n" + "2. ERROR in X.java (at line 4)\n" + " final class B implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface X.Foo should be a permitted subtype of X.Foo\n" + + "The type B that implements the sealed interface X.Foo should be a permitted subtype of X.Foo\n" + "----------\n"); } public void testBug568854_005() { @@ -5501,12 +5496,12 @@ public void testBug568854_005() { "1. ERROR in X.java (at line 3)\n" + " class A implements Foo {}\n" + " ^\n" + - "The class A with a sealed direct superclass or a sealed direct superinterface Foo should be declared either final, sealed, or non-sealed\n" + + "The class A with a sealed direct supertype Foo should be declared either final, sealed, or non-sealed\n" + "----------\n" + "2. ERROR in X.java (at line 4)\n" + " final class B implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface Foo should be a permitted subtype of Foo\n" + + "The type B that implements the sealed interface Foo should be a permitted subtype of Foo\n" + "----------\n"); } public void testBug568854_006() { @@ -5523,12 +5518,12 @@ public void testBug568854_006() { "1. ERROR in X.java (at line 2)\n" + " class A implements Foo {}\n" + " ^\n" + - "The class A with a sealed direct superclass or a sealed direct superinterface Foo should be declared either final, sealed, or non-sealed\n" + + "The class A with a sealed direct supertype Foo should be declared either final, sealed, or non-sealed\n" + "----------\n" + "2. ERROR in X.java (at line 3)\n" + " final class B implements Foo {}\n" + " ^^^\n" + - "The type B that implements a sealed interface Foo should be a permitted subtype of Foo\n" + + "The type B that implements the sealed interface Foo should be a permitted subtype of Foo\n" + "----------\n"); } public void testBug568854_007() { @@ -5552,12 +5547,12 @@ public void testBug568854_007() { "1. ERROR in X.java (at line 5)\n" + " class Y implements I {}\n" + " ^\n" + - "A local class Y cannot have a sealed direct superclass or a sealed direct superinterface I\n" + + "The local type Y may not have a sealed supertype I\n" + "----------\n" + "2. ERROR in X.java (at line 10)\n" + " class Z implements I{}\n" + " ^\n" + - "A local class Z cannot have a sealed direct superclass or a sealed direct superinterface I\n" + + "The local type Z may not have a sealed supertype I\n" + "----------\n"); } public void testBug568854_008() { @@ -5581,12 +5576,12 @@ public void testBug568854_008() { "1. ERROR in X.java (at line 6)\n" + " class Y implements I {}\n" + " ^\n" + - "A local class Y cannot have a sealed direct superclass or a sealed direct superinterface I\n" + + "The local type Y may not have a sealed supertype I\n" + "----------\n" + "2. ERROR in X.java (at line 10)\n" + " class Z implements I{}\n" + " ^\n" + - "A local class Z cannot have a sealed direct superclass or a sealed direct superinterface I\n" + + "The local type Z may not have a sealed supertype I\n" + "----------\n"); } public void testBug571332_001() { @@ -5628,7 +5623,7 @@ public void testBug570605_001() { "1. ERROR in X.java (at line 6)\n" + " class L extends Y {}\n" + " ^\n" + - "A local class L cannot have a sealed direct superclass or a sealed direct superinterface Y\n" + + "The local type L may not have a sealed supertype Y\n" + "----------\n"); } public void testBug570218_001() { @@ -5674,12 +5669,12 @@ public void testBug572205_001() { "1. ERROR in X.java (at line 3)\n" + " class Circle implements Shape{}\n" + " ^^^^^\n" + - "A local class Circle cannot have a sealed direct superclass or a sealed direct superinterface X.Shape\n" + + "The local type Circle may not have a sealed supertype X.Shape\n" + "----------\n" + "2. ERROR in X.java (at line 5)\n" + " sealed interface Shape {}\n" + " ^^^^^\n" + - "Sealed class or interface lacks the permits clause and no class or interface from the same compilation unit declares Shape as its direct superclass or superinterface\n" + + "Sealed type Shape lacks a permits clause and no type from the same compilation unit declares Shape as its direct supertype\n" + "----------\n"); } public void testBug573450_001() { @@ -5771,7 +5766,7 @@ public void testBug573450_005() { "2. ERROR in X.java (at line 2)\n" + " final class Y extends X {}\n" + " ^\n" + - "The type Y extending a sealed class X should be a permitted subtype of X\n" + + "The class Y cannot extend the class X as it is not a permitted subtype of X\n" + "----------\n"); } public void testBug578619_1() { @@ -5811,7 +5806,7 @@ public void testBug578619_2() { "2. ERROR in Bug578619.java (at line 8)\n" + " non-sealed interface I3 extends I2 {}\n" + " ^^\n" + - "An interface I3 declared as non-sealed should have a sealed direct superinterface\n" + + "The non-sealed interface I3 must have a sealed direct superinterface\n" + "----------\n"); } // https://bugs.eclipse.org/bugs/show_bug.cgi?id=576378 @@ -5998,7 +5993,7 @@ public static void main(String [] args) { "2. ERROR in X.java (at line 2)\n" + " record B(int data) implements X {}\n" + " ^\n" + - "The type B that implements a sealed interface X should be a permitted subtype of X\n" + + "The type B that implements the sealed interface X should be a permitted subtype of X\n" + "----------\n"); } @@ -6307,7 +6302,7 @@ int foo(int non, int sealed) { "1. ERROR in X.java (at line 1)\n" + " non-sealed public class X {\n" + " ^\n" + - "A class X declared as non-sealed should have either a sealed direct superclass or a sealed direct superinterface\n" + + "The non-sealed class X must have a sealed direct supertype\n" + "----------\n"); } @@ -6400,4 +6395,463 @@ final non-sealed class Y extends X {} "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/3100 + // [Sealed types] Duplicate diagnostics for illegal modifier combination + public void testIssue3100() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed non-sealed interface X {} + final class Y implements X {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 1)\n" + + " public sealed non-sealed interface X {}\n" + + " ^\n" + + "The type X may have only one modifier out of sealed, non-sealed, and final\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3144 + // [Sealed types] Diagnostic can be more direct when a @FunctionalInterface is declared sealed + public void testIssue3144() { + runNegativeTest( + new String[] { + "I.java", + """ + @FunctionalInterface + public sealed interface I { void doit(); } + final class Y implements I { public void doit() {} } + """ + }, + "----------\n" + + "1. ERROR in I.java (at line 2)\n" + + " public sealed interface I { void doit(); }\n" + + " ^\n" + + "A functional interface may not be declared sealed\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3039 + // [Sealed types] Broken program crashes the compiler + public void testIssue3039() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed class X permits X.C { + private final static class C extends X implements I {} + } + + sealed interface I permits X.C {} + record R(X.C xc, R.C rc) { + private class C {} + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 5)\n" + + " sealed interface I permits X.C {}\n" + + " ^^^\n" + + "The type X.C is not visible\n" + + "----------\n" + + "2. ERROR in X.java (at line 6)\n" + + " record R(X.C xc, R.C rc) {\n" + + " ^^^\n" + + "The type X.C is not visible\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3039 + // [Sealed types] Broken program crashes the compiler + public void testIssue3039_2() { + runNegativeTest( + new String[] { + "X.java", + """ + public interface X { + + static Integer get(T object) { + return switch (object) { + case A ignored -> 42; + default -> 42; + }; + } + + public abstract sealed interface I2 permits , AB { + } + + + final class AB implements I2 {} + + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 1)\n" + + " public interface X {\n" + + " ^\n" + + "Syntax error, insert \"}\" to complete InterfaceBody\n" + + "----------\n" + + "2. ERROR in X.java (at line 3)\n" + + " static Integer get(T object) {\n" + + " ^^\n" + + "I2 cannot be resolved to a type\n" + + "----------\n" + + "3. ERROR in X.java (at line 5)\n" + + " case A ignored -> 42;\n" + + " ^^^^^^^^^\n" + + "The Java feature 'Pattern Matching in Switch' is only available with source level 21 and above\n" + + "----------\n" + + "4. ERROR in X.java (at line 8)\n" + + " }\n" + + " ^\n" + + "Syntax error on token \"}\", delete this token\n" + + "----------\n" + + "5. ERROR in X.java (at line 10)\n" + + " public abstract sealed interface I2 permits , AB {\n" + + " ^^\n" + + "Syntax error on token \"I2\", permits expected after this token\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + public void testIssue3121() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + abstract sealed class Maybe { + final class Maybe1 extends Maybe {} + final class Maybe2 extends Maybe implements SuperInt {} + } + + + abstract sealed class SurelyNot { + final class SurelyNot1 extends SurelyNot {} + final class SurelyNot2 extends SurelyNot {} + } + + abstract sealed class SurelyYes { + final class SurelyYes1 extends SurelyYes implements SuperInt {} + final class SurelyYes2 extends SurelyYes implements SuperInt {} + } + + class Test { + + void testMaybe(Maybe maybe, SurelyNot surelyNot, SurelyYes surelyYes) { + if (maybe == null || surelyNot == null || surelyYes == null) return; + if (maybe instanceof SuperInt sup) {} + if (surelyNot instanceof SuperInt sup) {} + if (surelyYes instanceof SuperInt sup) {} + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 24)\n" + + " if (surelyNot instanceof SuperInt sup) {}\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Incompatible conditional operand types SurelyNot and SuperInt\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + public void testIssue3121_2() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + class Outer { + abstract sealed class Maybe { + final class Maybe1 extends Maybe {} + } + } + + class Test { + + void testMaybe(Outer.Maybe maybe) { + if (maybe instanceof SuperInt sup) {} + return null; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 12)\n" + + " if (maybe instanceof SuperInt sup) {}\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Incompatible conditional operand types Outer.Maybe and SuperInt\n" + + "----------\n" + + "2. ERROR in X.java (at line 13)\n" + + " return null;\n" + + " ^^^^^^^^^^^^\n" + + "Void methods cannot return a value\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + public void testIssue3121_2_1() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + class Outer { + abstract sealed class Maybe { + final class Maybe1 extends Maybe implements SuperInt {} + } + } + + class Test { + + void testMaybe(Outer.Maybe maybe) { + if (maybe instanceof SuperInt sup) {} + return null; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 13)\n" + + " return null;\n" + + " ^^^^^^^^^^^^\n" + + "Void methods cannot return a value\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + public void testIssue3121_3() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + class Outer { + abstract sealed class Maybe { + final class Maybe1 extends Outer.Maybe {} + } + } + + class Test { + + void testMaybe(Outer.Maybe maybe) { + if (maybe == null) return; + if (maybe instanceof SuperInt sup) {} + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 13)\n" + + " if (maybe instanceof SuperInt sup) {}\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Incompatible conditional operand types Outer.Maybe and SuperInt\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + public void testIssue3121_4() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + class Outer { + abstract sealed class Maybe { + final class Maybe1 extends Outer.Maybe implements SuperInt {} + } + } + + class Test { + + void testMaybe(Outer.Maybe maybe) { + if (maybe == null) return; + if (maybe instanceof SuperInt sup) {} + return null; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 14)\n" + + " return null;\n" + + " ^^^^^^^^^^^^\n" + + "Void methods cannot return a value\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3121 + // [Sealed types] Regression in instanceof check for sealed generic classes + // NOTE: javac does not report error#1 but that looks like a defect + public void testIssue3121_5() { + runNegativeTest( + new String[] { + "X.java", + """ + interface SuperInt {} + + class Outer { + abstract sealed class Maybe { + final class Maybe1 extends Outer.Maybe implements SuperInt {} + } + } + + class Test { + + void testMaybe(Outer.Maybe maybe) { + if (maybe == null) return; + if (maybe instanceof SuperInt sup) {} + return null; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 13)\n" + + " if (maybe instanceof SuperInt sup) {}\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Incompatible conditional operand types Outer.Maybe and SuperInt\n" + + "----------\n" + + "2. ERROR in X.java (at line 14)\n" + + " return null;\n" + + " ^^^^^^^^^^^^\n" + + "Void methods cannot return a value\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3007 + // [Sealed types] Extra and spurious error messages with faulty type sealing + public void testIssue3007() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed class X extends Y permits Y { + int yield () { + return this.yield(); + } + } + + sealed class Y extends X permits X, Z { + + } + + final class Z extends Y {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 1)\n" + + " public sealed class X extends Y permits Y {\n" + + " ^\n" + + "The hierarchy of the type X is inconsistent\n" + + "----------\n" + + "2. ERROR in X.java (at line 7)\n" + + " sealed class Y extends X permits X, Z {\n" + + " ^\n" + + "Cycle detected: a cycle exists in the type hierarchy between Y and X\n" + + "----------\n" + + "3. ERROR in X.java (at line 11)\n" + + " final class Z extends Y {}\n" + + " ^\n" + + "The hierarchy of the type Z is inconsistent\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3007 + // [Sealed types] Extra and spurious error messages with faulty type sealing + public void testIssue3007_2() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X extends Y { + int yield () { + return this.yield(); + } + } + + class Y extends X { + + } + + final class Z extends Y {} + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 1)\n" + + " public class X extends Y {\n" + + " ^\n" + + "The hierarchy of the type X is inconsistent\n" + + "----------\n" + + "2. ERROR in X.java (at line 7)\n" + + " class Y extends X {\n" + + " ^\n" + + "Cycle detected: a cycle exists in the type hierarchy between Y and X\n" + + "----------\n" + + "3. ERROR in X.java (at line 11)\n" + + " final class Z extends Y {}\n" + + " ^\n" + + "The hierarchy of the type Z is inconsistent\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2709 + // [Sealed types] Disjointness behavior difference vis a vis javac + /* A class named C is disjoint from an interface named I if (i) it is not the case that C <: I, and (ii) one of the following cases applies: + – C is freely extensible (§8.1.1.2), and I is sealed, and C is disjoint from all of the permitted direct subclasses and subinterfaces of I. + */ + public void testIssue2709() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + sealed interface I permits C1 {} + non-sealed class C1 implements I {} + class C2 extends C1 {} + class C3 {} + { + I i; + i = (I) (C1) null; + i = (I) (C2) null; + i = (I) (C3) null; + i = (C2) (C3) null; + i = (C1) (C3) null; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 10)\n" + + " i = (I) (C3) null;\n" + + " ^^^^^^^^^^^^^\n" + + "Cannot cast from X.C3 to X.I\n" + + "----------\n" + + "2. ERROR in X.java (at line 11)\n" + + " i = (C2) (C3) null;\n" + + " ^^^^^^^^^^^^^^\n" + + "Cannot cast from X.C3 to X.C2\n" + + "----------\n" + + "3. ERROR in X.java (at line 12)\n" + + " i = (C1) (C3) null;\n" + + " ^^^^^^^^^^^^^^\n" + + "Cannot cast from X.C3 to X.C1\n" + + "----------\n"); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StackMapAttributeTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StackMapAttributeTest.java index 49939b89280..9d841ef817b 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StackMapAttributeTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StackMapAttributeTest.java @@ -25,7 +25,6 @@ import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.core.util.ClassFileBytesDisassembler; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; @SuppressWarnings({ "unchecked", "rawtypes" }) public class StackMapAttributeTest extends AbstractRegressionTest { @@ -8315,7 +8314,7 @@ public void testBug412203_a() throws Exception { "}\n", }, "", - getLibsWithNullAnnotations(CompilerOptions.getFirstSupportedJdkLevel()), + getLibsWithNullAnnotations(), true/*flush*/, null/*vmArgs*/, options, @@ -8437,7 +8436,7 @@ public void testBug412203_b() throws Exception { "}\n", }, "", - getLibsWithNullAnnotations(CompilerOptions.getFirstSupportedJdkLevel()), + getLibsWithNullAnnotations(), true/*flush*/, null/*vmArgs*/, options, @@ -8554,7 +8553,7 @@ public void testBug412203_c() throws Exception { "}\n", }, "", - getLibsWithNullAnnotations(CompilerOptions.getFirstSupportedJdkLevel()), + getLibsWithNullAnnotations(), true/*flush*/, null/*vmArgs*/, options, diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SuperAfterStatementsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SuperAfterStatementsTest.java index 22a950b01bf..1999735189b 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SuperAfterStatementsTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SuperAfterStatementsTest.java @@ -2186,4 +2186,173 @@ public class TestFlow { """; runner.runNegativeTest(); } + + public void testGH3094() { + Runner runner = new Runner(false); + runner.testFiles = new String[] { + "X.java", + """ + public class X { + class Nested extends X1 { + X1 xy; + class DeeplyNested extends NestedInX1 { + DeeplyNested(float f) { + Nested.super.x1.super(); // Error here + } + } + } + public static void main(String... args) { + Nested nest = new X().new Nested(); + nest.x1 = new X1(); + nest.new DeeplyNested(1.1f); + } + } + class X1 { + X1 x1; + class NestedInX1 {} + } + """ + }; + runner.runConformTest(); + } + + public void testGH3094_2() { + Runner runner = new Runner(false); + runner.testFiles = new String[] { + "X.java", + """ + public class X { + class Nested extends X1 { + X1 xy; + class DeeplyNested extends NestedInX1 { + DeeplyNested(float f) { + Nested.this.x1.super(); + } + } + } + public static void main(String... args) { + Nested nest = new X().new Nested(); + nest.x1 = new X1(); + nest.new DeeplyNested(1.1f); + } + } + class X1 { + X1 x1; + class NestedInX1 {} + } + """ + }; + runner.runConformTest(); + } + + public void testGH3094_3() { + Runner runner = new Runner(false); + runner.testFiles = new String[] { + "X.java", + """ + public class X { + class Nested extends X1 { + X1 xy; + Nested() { + class DeeplyNested extends NestedInX1 { + DeeplyNested(float f) { + Nested.this.x1.super(); + } + } + super(); + } + } + } + class X1 { + X1 x1; + class NestedInX1 {} + } + """ + }; + runner.expectedCompilerLog = + """ + ---------- + 1. WARNING in X.java (at line 5) + class DeeplyNested extends NestedInX1 { + ^^^^^^^^^^^^ + The type DeeplyNested is never used locally + ---------- + 2. ERROR in X.java (at line 7) + Nested.this.x1.super(); + ^^^^^^^^^^^^^^ + Cannot read field x1 in an early construction context + ---------- + """; + runner.runNegativeTest(); + } + + public void testGH3132() { + Runner runner = new Runner(); + runner.testFiles = new String[] { + "X.java", + """ + public class X { + class Nested { + Nested(Object o) {} + } + class AnotherNested extends Nested { + AnotherNested() { + super(new Object() { // Cannot instantiate class new Object(){} in an early construction context of class X.AnotherNested + }); + } + } + public static void main(String... args) { + new X().new AnotherNested(); + } + } + """ + }; + runner.runConformTest(); + } + + public void testGH3132_2() { + Runner runner = new Runner(); + runner.testFiles = new String[] { + "X.java", + """ + class O {} // demonstrates the the bug was not specific to j.l.Object + public class X { + class Nested extends O { + Nested(Object o) {} + } + class AnotherNested extends Nested { + AnotherNested() { + super(new O() { + }); + } + } + public static void main(String... args) { + new X().new AnotherNested(); + } + } + """ + }; + runner.runConformTest(); + } + + public void testGH3153() { + runConformTest(new String[] { + "X.java", + """ + public class X { + public static void main(String[] argv) { + class Inner { + Inner() { + class Local {} + new Local() {}; // Error: No enclosing instance of the type X is accessible in scope + super(); + } + } + new Inner(); + } + } + """ }, + ""); + } + } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java index 3a370ec0f32..fa5d2105530 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java @@ -29,7 +29,6 @@ public class SwitchPatternTest extends AbstractRegressionTest9 { // TESTS_NUMBERS = new int [] { 40 }; // TESTS_RANGE = new int[] { 1, -1 }; // TESTS_NAMES = new String[] { "testBug575053_002"}; -// TESTS_NAMES = new String[] { "testBug575571_1"}; } private static String previewLevel = "21"; @@ -2651,11 +2650,6 @@ public void testBug573921_7() { " case List s: \n" + " ^^^^^^^^^^^^^^\n" + "Type Object cannot be safely cast to List\n" + - "----------\n" + - "3. ERROR in X.java (at line 9)\n" + - " case List s: \n" + - " ^^^^^^^^^^^^^^\n" + - "This case label is dominated by one of the preceding case labels\n" + "----------\n"); } public void testBug573921_8() { @@ -7336,11 +7330,6 @@ case WrapperRec(ExhaustiveSwitch.Data data) when data.name.isEmpty() -> { } + " case WrapperRec(ExhaustiveSwitch.Data data) when data.name.isEmpty() -> { }\n" + " ^^^^^^^^^^^^^^^^\n" + "ExhaustiveSwitch cannot be resolved to a type\n" - + "----------\n" - + "4. ERROR in X.java (at line 12)\n" - + " case WrapperRec(ExhaustiveSwitch.Data data) when data.name.isEmpty() -> { }\n" - + " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n" - + "Record component with type Data is not compatible with type ExhaustiveSwitch.Data\n" + "----------\n"); } // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1955 @@ -8541,8 +8530,8 @@ public static void main(String[] args) { "42\n420\n4200"); } - public void testIssue3009_4() { // FIXME: this should not compile!!! - runConformTest( + public void testIssue3009_4() { + runNegativeTest( new String[] { "X.java", """ @@ -8572,7 +8561,12 @@ public static void main(String[] args) { } """ }, - "42\n420\n4200"); + "----------\n" + + "1. ERROR in X.java (at line 13)\r\n" + + " case H e -> 4200;\r\n" + + " ^^^^^^^^^^^^^^^\n" + + "Type J cannot be safely cast to H\n" + + "----------\n"); } public void testIssue3009_5() { @@ -8817,4 +8811,432 @@ public static void main(String[] args) { }, "42\n420"); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/1735 + // [Sealed types][Switch] Pattern switch - ECJ accepts code rejected by javac + public void testIssue1735() { + runNegativeTest( + new String[] { + "X.java", + """ + class X { + void foo(I ix) { + switch(ix) { + case A ay -> System.out.println(); + case B bx -> System.out.println(); + } + } + } + class Y extends X {} + class Z extends X {} + + sealed interface I permits A, B { + } + + final class B implements I { + } + + + final class A implements I { + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " case A ay -> System.out.println();\n" + + " ^^^^^^^\n" + + "Type I cannot be safely cast to A\n" + + "----------\n" + + "2. ERROR in X.java (at line 5)\n" + + " case B bx -> System.out.println();\n" + + " ^^^^^^^\n" + + "Type I cannot be safely cast to B\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3035 + // [switch][sealed types] ECJ fails to signal a completely dominated case arm + public void testIssue3035() { + runNegativeTest( + new String[] { + "X.java", + """ + abstract sealed class J permits X.S, A { + } + + final class A extends J { + } + + public class X { + + sealed class S extends J permits SS { + } + + final class SS extends S {} + + int testExhaustive(J ji) { + return switch (ji) { // Exhaustive! + case A a -> 42; + case S e -> 4200; + case SS e -> 420; + }; + } + + public static void main(String[] args) { + S xs = null; + System.out.println(new X().testExhaustive(new X().new S())); + J ji = new X().new SS(); + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 18)\n" + + " case SS e -> 420;\n" + + " ^^^^^^^^^^^^^^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2720 + // [Sealed Types + Enhanced Switch] Incorrect diagnostic about switch not being exhaustive + public void testIssue2720() { + runConformTest( + new String[] { + "X.java", + """ + sealed interface I { + + enum E implements I { + A, B, C; + } + } + + public class X { + + static void d(I i) { + switch (i) { // error: An enhanced switch statement should be exhaustive; a default label expected + case I.E.A -> { System.out.println("I.E.A"); } + case I.E.B -> { System.out.println("I.E.B"); } + case I.E.C -> { System.out.println("I.E.C"); } + } + } + + public static void main(String [] args) { + d(I.E.A); + d(I.E.B); + d(I.E.C); + } + } + """ + }, + "I.E.A\n" + + "I.E.B\n" + + "I.E.C"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2720 + // [Sealed Types + Enhanced Switch] Incorrect diagnostic about switch not being exhaustive + public void testIssue2720_2() { + runNegativeTest( + new String[] { + "X.java", + """ + sealed interface I { + + enum E implements I { + A, B, C; + } + + enum K implements I { + D, E, F; + } + } + + class Test { + + void d(I i) { + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + case I.K k -> {} + } + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + default -> {} + } + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + case I.K.D -> {} + case I.K.E -> {} + case I.K.F -> {} + default -> {} + } + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + case I.K.D -> {} + case I.K.E -> {} + default -> {} + } + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + case I.K.D -> {} + case I.K.E -> {} + } + switch (i) { + case I.E.A -> {} + case I.E.B -> {} + case I.E.C -> {} + case I.K.D -> {} + case I.K.E -> {} + case I.K.F -> {} + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 44)\n" + + " switch (i) {\n" + + " ^\n" + + "An enhanced switch statement should be exhaustive; a default label expected\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2720 + // [Sealed Types + Enhanced Switch] Incorrect diagnostic about switch not being exhaustive + public void testIssue2720_3() { + runConformTest( + new String[] { + "X.java", + """ + sealed interface I { + + enum E implements I { + A, B, C; + } + + enum K implements I { + D, E, F; + } + } + + public class X { + + static void d(I i) { + switch (i) { + case I.E.A -> { System.out.println("I.E.A"); } + case I.E.B -> { System.out.println("I.E.B"); } + case I.E.C -> { System.out.println("I.E.C"); } + case I.K.D -> { System.out.println("I.K.D"); } + case I.K.E -> { System.out.println("I.K.E"); } + case I.K.F -> { System.out.println("I.K.F"); } + } + } + + public static void main(String [] args) { + d(I.E.A); + d(I.E.B); + d(I.E.C); + d(I.K.D); + d(I.K.E); + d(I.K.F); + } + } + """ + }, + "I.E.A\n" + + "I.E.B\n" + + "I.E.C\n" + + "I.K.D\n" + + "I.K.E\n" + + "I.K.F"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096 + // [Switch][Sealed types] Bad static analysis with the old switch syntax + an exhautive pattern matching on a sealed type throws a MatchException + public void testIssue3096() { + runConformTest( + new String[] { + "X.java", + """ + public sealed interface X permits X.R { + record R(String s) implements X { + } + + public static void add(X x) { + switch (x) { + case R r: + if (r.s == null) { + throw new NullPointerException(); + } + } + } + + public static void main(String[] args) { + add(new R("bar")); + } + } + """ + }, + ""); + } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096 + // [Switch][Sealed types] Bad static analysis with the old switch syntax + an exhautive pattern matching on a sealed type throws a MatchException + public void testIssue3096_2() { + runConformTest( + new String[] { + "X.java", + """ + public sealed interface X permits X.R { + record R(String s) implements X { + } + + public static void add(X x) { + switch (x) { + case R r: + if (r.s == null) { + throw new NullPointerException(); + } + break; + } + } + + public static void main(String[] args) { + add(new R("bar")); + } + } + """ + }, + ""); + } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096 + // [Switch][Sealed types] Bad static analysis with the old switch syntax + an exhautive pattern matching on a sealed type throws a MatchException + public void testIssue3096_3() { + runConformTest( + new String[] { + "X.java", + """ + public sealed interface X permits X.R { + record R(String s) implements X { + } + + public static void add(X x) { + switch (x) { + case R r: + if (r.s == null) { + throw new NullPointerException(); + } + System.out.println("R"); + } + } + + public static void main(String[] args) { + add(new R("bar")); + } + } + """ + }, + "R"); + } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096 + // [Switch][Sealed types] Bad static analysis with the old switch syntax + an exhautive pattern matching on a sealed type throws a MatchException + public void testIssue3096_4() { + runConformTest( + new String[] { + "X.java", + """ + public sealed interface X permits X.R { + record R(String s) implements X { + } + + public static void add(X x) { + switch (x) { + case R r: + if (r.s == null) { + throw new NullPointerException(); + } + System.out.println("R"); + break; + } + } + + public static void main(String[] args) { + add(new R("bar")); + } + } + """ + }, + "R"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3096 + // [Switch][Sealed types] Bad static analysis with the old switch syntax + an exhautive pattern matching on a sealed type throws a MatchException + public void testIssue3096_full() { + runConformTest( + new String[] { + "EclipseBugFallThroughSwitch.java", + """ + public class EclipseBugFallThroughSwitch { + sealed interface I permits A, B {} + record A(String s) implements I {} + record B(String s) implements I {} + + public void add(I i) { + switch (i) { + case A a: + break; + case B b: + if (b.s == null) { + throw new NullPointerException(); + } + //break; // this fix the issue + } + } + + public static void main(String[] args) { + var container = new EclipseBugFallThroughSwitch(); + container.add(new B("bar")); + + // Exception in thread "main" java.lang.MatchException + // at EclipseBugFallThroughSwitch.add(EclipseBugFallThroughSwitch.java:9) + // at EclipseBugFallThroughSwitch.main(EclipseBugFallThroughSwitch.java:25) + } + } + """ + }, + ""); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3135 + // [Switch] default->null caused a building problem. + public void testIssue3135() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String[] args) { + int i = 3; + int[] arr = { 42, 2, 3 }; + System.out.println((switch (i) { + case 3 -> arr; + default -> null; // Replacing null with a non-null value can avoid this issue. + })[0]); + } + } + """ + }, + "42"); + } + } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest21.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java similarity index 95% rename from org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest21.java rename to org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java index 62172224a45..3dade7af915 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest21.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java @@ -16,17 +16,17 @@ import junit.framework.Test; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -public class SwitchPatternTest21 extends AbstractBatchCompilerTest { +public class SwitchPatternTest22 extends AbstractBatchCompilerTest { static { // TESTS_NAMES = new String [] { "testNaming" }; } public static Test suite() { - return buildMinimalComplianceTestSuite(SwitchPatternTest21.class, F_22); + return buildMinimalComplianceTestSuite(SwitchPatternTest22.class, F_22); } - public SwitchPatternTest21(String name) { + public SwitchPatternTest22(String name) { super(name); } @@ -956,4 +956,44 @@ public static void main(String[] args) { }, "A or B"); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3109 + // [Patterns] Mixed multiple pattern and when is not fully supported in switch expression + public void testIssue3109() { + runConformTest( + new String[] { + "Maybe.java", + """ + import java.util.function.Function; + import java.util.function.Predicate; + + public sealed interface Maybe { + + static Maybe of(T value) { + return new Some<>(value); + } + + static Maybe empty() { + return new None<>(); + } + + default Maybe filter(Predicate predicate){ + return switch (this) { + case Some(T value) when predicate.test(value) -> this; // unsupported line with 4 errors + case Some(_), None _ -> empty(); + }; + } + + default Maybe filter2(Predicate predicate){ + return switch (this) { + case Some(_), None _ -> empty(); //also unsupported with 1 error + }; + } + record Some(T value) implements Maybe {} + record None() implements Maybe{} + } + """ + }, + ""); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java index 0135b00b79e..ca941fc187b 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java @@ -2569,17 +2569,12 @@ public void test383643() { " ^\n" + "p cannot be resolved to a variable\n" + "----------\n" + - "2. WARNING in X.java (at line 4)\n" + - " switch (p) {\n" + - " ^\n" + - "The switch statement should have a default case\n" + - "----------\n" + - "3. ERROR in X.java (at line 5)\n" + + "2. ERROR in X.java (at line 5)\n" + " case ONE:\n" + " ^^^\n" + "ONE cannot be resolved to a variable\n" + "----------\n" + - "4. ERROR in X.java (at line 8)\n" + + "3. ERROR in X.java (at line 8)\n" + " case TWO:\n" + " ^^^\n" + "TWO cannot be resolved to a variable\n" + diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java index b5aa194849e..f798f1f9598 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/TestAll.java @@ -232,7 +232,6 @@ public static Test suite() { // add 17 specific test here (check duplicates) ArrayList since_17 = new ArrayList(); since_17.add(SealedTypesTests.class); - since_17.add(SealedTypesSpecReviewTest.class); since_17.add(InstanceofPrimaryPatternTest.class); since_17.add(BatchCompilerTest_17.class); @@ -247,13 +246,15 @@ public static Test suite() { since_21.add(RecordPatternProjectTest.class); since_21.add(NullAnnotationTests21.class); since_21.add(BatchCompilerTest_21.class); + since_21.add(JEP441SnippetsTest.class); + // add 21 specific test here (check duplicates) ArrayList since_22 = new ArrayList(); // since_22.add(SuperAfterStatementsTest.class); since_22.add(UnnamedPatternsAndVariablesTest.class); since_22.add(UseOfUnderscoreJava22Test.class); - since_22.add(SwitchPatternTest21.class); + since_22.add(SwitchPatternTest22.class); ArrayList since_23 = new ArrayList(); since_23.add(SuperAfterStatementsTest.class); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationContextWrapperTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationContextWrapperTest.java index 439a6d09ee4..45b22282938 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationContextWrapperTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationContextWrapperTest.java @@ -182,6 +182,7 @@ private Map compileAndDeploy15(String source, String className, private void refreshProject() throws Exception { this.project.getProject().refreshLocal(IResource.DEPTH_INFINITE, null); + waitForAutoBuild(); // wait for builds to complete. } private void removeTempClass(String className) { diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationTest.java index e94b70531ad..f7e826b0ad5 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/eval/EvaluationTest.java @@ -20,6 +20,13 @@ import java.util.Map; import junit.framework.Test; import junit.framework.TestSuite; + +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.tests.junit.extension.StopableTestCase; @@ -40,6 +47,7 @@ import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.eval.EvaluationContext; import org.eclipse.jdt.internal.eval.EvaluationResult; import org.eclipse.jdt.internal.eval.GlobalVariable; @@ -569,4 +577,40 @@ public void stop() { } } } + + public void waitForAutoBuild() { + if (isWorkspaceRuleAlreadyInUse(getWorkspaceRoot())) { + // Don't wait holding workspace lock on FAMILY_AUTO_BUILD, because + // we might deadlock with AutoBuildOffJob + System.out.println("\n\nAborted waitForAutoBuild() because running with the workspace rule\n\n"); + return; + } + boolean wasInterrupted = false; + do { + try { + Job.getJobManager().wakeUp(ResourcesPlugin.FAMILY_AUTO_BUILD); + Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, null); + JavaModelManager.getIndexManager().waitForIndex(isIndexDisabledForTest(), null); + wasInterrupted = false; + } catch (OperationCanceledException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + wasInterrupted = true; + } + } while (wasInterrupted); + } + + private static boolean isWorkspaceRuleAlreadyInUse(ISchedulingRule rule) { + ISchedulingRule currentJobRule = Job.getJobManager().currentRule(); + boolean workspaceRuleActive = currentJobRule != null && rule.contains(currentJobRule); + return workspaceRuleActive; + } + + private IWorkspaceRoot getWorkspaceRoot() { + return getWorkspace().getRoot(); + } + + private IWorkspace getWorkspace() { + return ResourcesPlugin.getWorkspace(); + } } diff --git a/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9.jar b/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9.jar index ffedd500d72..0fe91046f6f 100644 Binary files a/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9.jar and b/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9.jar differ diff --git a/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9src.zip b/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9src.zip index d04c399924b..636debe16aa 100644 Binary files a/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9src.zip and b/org.eclipse.jdt.core.tests.model/JCL/converterJclMin9src.zip differ diff --git a/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF index bf7f3f4b751..894edc7c2a5 100644 --- a/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core.tests.model/META-INF/MANIFEST.MF @@ -27,7 +27,6 @@ Require-Bundle: org.eclipse.core.resources;bundle-version="[3.2.0,4.0.0)", org.eclipse.text;bundle-version="[3.2.0,4.0.0)", com.ibm.icu;bundle-version="3.4.4", org.eclipse.core.filesystem;bundle-version="[1.2.0,2.0.0)", - org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional, org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional Bundle-RequiredExecutionEnvironment: JavaSE-17 Eclipse-BundleShape: dir diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousPatternsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousPatternsTests.java index 7613e60ac0c..5e2cd4b72ec 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousPatternsTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousPatternsTests.java @@ -55,12 +55,15 @@ public static Class[] getAllTestClasses() { ASTRewritingInstanceOfPatternExpressionTest.class, ASTRewritingSwitchPatternTest.class, ResolveTests12To15.class, - SwitchPatternTest21.class, + SwitchPatternTest22.class, JavaSearchBugs19Tests.class, CompletionTestsForRecordPattern.class, NullAnnotationTests21.class, ComplianceDiagnoseTest.class, InstanceofExpressionTest.class, + PrimitiveInPatternsTest.class, + PrimitiveInPatternsTestSH.class, + JEP441SnippetsTest.class, }; } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSealedTypeTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSealedTypeTests.java index e8a6f24d4ad..5b5e0b257c5 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSealedTypeTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSealedTypeTests.java @@ -51,9 +51,9 @@ public static Class[] getAllTestClasses() { RecordsRestrictedClassTest.class, ScannerTest.class, SealedTypesTests.class, - SealedTypesSpecReviewTest.class, + JEP441SnippetsTest.class, SwitchPatternTest.class, - SwitchPatternTest21.class, + SwitchPatternTest22.class, UnnamedPatternsAndVariablesTest.class, ASTRewritingTypeDeclTest.class, ASTConverter_15Test.class, diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java index 13ba3b937c9..2ba7513ef62 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java @@ -21,11 +21,12 @@ import junit.framework.TestSuite; import org.eclipse.jdt.core.tests.compiler.parser.ComplianceDiagnoseTest; import org.eclipse.jdt.core.tests.compiler.regression.InstanceofPrimaryPatternTest; +import org.eclipse.jdt.core.tests.compiler.regression.JEP441SnippetsTest; import org.eclipse.jdt.core.tests.compiler.regression.PatternMatching16Test; import org.eclipse.jdt.core.tests.compiler.regression.RecordPatternTest; import org.eclipse.jdt.core.tests.compiler.regression.SwitchExpressionsYieldTest; import org.eclipse.jdt.core.tests.compiler.regression.SwitchPatternTest; -import org.eclipse.jdt.core.tests.compiler.regression.SwitchPatternTest21; +import org.eclipse.jdt.core.tests.compiler.regression.SwitchPatternTest22; import org.eclipse.jdt.core.tests.compiler.regression.SwitchTest; import org.eclipse.jdt.core.tests.compiler.regression.UnnamedPatternsAndVariablesTest; import org.eclipse.jdt.core.tests.dom.ConverterTestSetup; @@ -44,7 +45,7 @@ public static Class[] getAllTestClasses() { return new Class[] { SwitchPatternTest.class, - SwitchPatternTest21.class, + SwitchPatternTest22.class, SwitchTest.class, SwitchExpressionsYieldTest.class, @@ -52,6 +53,7 @@ public static Class[] getAllTestClasses() { InstanceofPrimaryPatternTest.class, PatternMatching16Test.class, UnnamedPatternsAndVariablesTest.class, + JEP441SnippetsTest.class, JavaSearchBugs14SwitchExpressionTests.class, ASTRewritingSwitchExpressionsTest.class, diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTest.java index 3220c21f3f9..ebcef50186b 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2017 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -14,8 +14,12 @@ package org.eclipse.jdt.core.tests.dom; import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Hashtable; import java.util.List; import java.util.Map; +import java.util.Set; import junit.framework.Test; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IClassFile; @@ -24,8 +28,10 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.*; @SuppressWarnings("rawtypes") @@ -1324,4 +1330,119 @@ public void testBug381503() throws CoreException, IOException { deleteProject("P"); } } +public void testGH3047() throws Exception { + Hashtable options = JavaCore.getDefaultOptions(); + options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_9); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_9); + options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_9); + + createJava9Project("P"); + createFolder("P/src"); + + String srcFolderInWS = "/P/src"; + createFolder(srcFolderInWS + "/resources/examples/mockito"); + String srcFilePathInWS = srcFolderInWS + "/resources/examples/mockito/MockingFromFinder.java"; + createFile(srcFilePathInWS, + """ + package examples.mockito; + public class MockingFromFinder{}""" + ); + srcFilePathInWS = srcFolderInWS + "/resources/examples/mockito/MockingWhileAdding.java"; + createFile(srcFilePathInWS, + """ + package examples.mockito; + public class MockingWhileAdding { + public static void calculateWithAdder(int x, int y) { + IOperation adder = new Adder()::execute; + } + public interface IOperation { + int execute(int x, int y); + } + public static class Adder implements IOperation { + public int execute(int x, int y) { + return x+y; + } + } + }""" + ); + String[] paths = new String[2]; + paths[0] = getWorkspacePath() + "P/src/resources/examples/mockito/MockingFromFinder.java"; + paths[1] = getWorkspacePath() + "P/src/resources/examples/mockito/MockingWhileAdding.java"; + @SuppressWarnings("deprecation") + ASTParser parser = ASTParser.newParser(AST_INTERNAL_JLS9); + parser.setCompilerOptions(options); + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setEnvironment(null, new String[] {getWorkspacePath() + "P/src/resources"}, null, false); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + try { + parser.createASTs(paths, null, new String[] {}, null, null); + fail("Expected exception was not thrown"); + } catch (IllegalStateException ise) { + assertEquals("Missing system library", ise.getMessage()); + } +} +public void testGH3047_2() throws Exception { + Hashtable options = JavaCore.getDefaultOptions(); + options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_9); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_9); + options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_9); + + IJavaProject javaProject = createJavaProject("P", new String[] {""}, new String[] {"CONVERTER_JCL18_LIB"}, "1.8"); + createFolder("P/src"); + + String srcFolderInWS = "/P/src"; + createFolder(srcFolderInWS + "/resources/examples/mockito"); + String srcFilePathInWS = srcFolderInWS + "/resources/examples/mockito/MockingFromFinder.java"; + createFile(srcFilePathInWS, + """ + package examples.mockito; + public class MockingFromFinder{}""" + ); + srcFilePathInWS = srcFolderInWS + "/resources/examples/mockito/MockingWhileAdding.java"; + createFile(srcFilePathInWS, + """ + package examples.mockito; + public class MockingWhileAdding { + public static void calculateWithAdder(int x, int y) { + IOperation adder = new Adder()::execute; + } + public interface IOperation { + int execute(int x, int y); + } + public static class Adder implements IOperation { + public int execute(int x, int y) { + return x+y; + } + } + }""" + ); + String[] paths = new String[2]; + paths[0] = getWorkspacePath() + "P/src/resources/examples/mockito/MockingFromFinder.java"; + paths[1] = getWorkspacePath() + "P/src/resources/examples/mockito/MockingWhileAdding.java"; + @SuppressWarnings("deprecation") + ASTParser parser = ASTParser.newParser(AST_INTERNAL_JLS9); + parser.setProject(javaProject); + parser.setCompilerOptions(options); + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setEnvironment(null, new String[] {getWorkspacePath() + "P/src/resources"}, null, false); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + Set expectedProblems = new HashSet<>(Arrays.asList( + "Pb(324) The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files", + "Pb(140) Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor" + )); + Set actualProblems = new HashSet<>(); + class MyFileASTRequestor extends FileASTRequestor { + @Override + public void acceptAST(String sourceFilePath, CompilationUnit cu) { + for (IProblem prob : cu.getProblems()) + actualProblems.add(prob.toString()); + } + } + parser.createASTs(paths, null, new String[] {}, new MyFileASTRequestor() {}, null); + assertEquals(expectedProblems, actualProblems); +} } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTestJLS8.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTestJLS8.java index 1cc4005fb56..1c3e803d9fd 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTestJLS8.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterBugsTestJLS8.java @@ -21,14 +21,7 @@ import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.MethodInvocation; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFlattener; import org.eclipse.jdt.internal.core.dom.rewrite.RewriteEventStore; @@ -1162,4 +1155,48 @@ Map foo(Class clazz) { deleteProject("P"); } } + +public void testGH3064() throws CoreException { + try { + createJavaProject("P", new String[] { "" }, new String[] { "CONVERTER_JCL_LIB" }, "", "1.8", true); + createFolder("P/src/test"); + createFile("/P/src/test/Action.java", """ + package test; + public class Action, ShardRequest> { + private final Request request; + + protected ShardRequest newShardRequest(Request request) {return null;} // can also be omitted + + protected void performOperation(final int shardIndex) { + ShardRequest shardRequest = newShardRequest(request); + shardRequest.setParentTask(foobar); + } + } + """); + ICompilationUnit cuAction = getCompilationUnit("P/src/test/Action.java"); + ASTParser parser = createASTParser(); + parser.setResolveBindings(true); + parser.setBindingsRecovery(true); + parser.setSource(cuAction); + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + TypeDeclaration type = (TypeDeclaration) cu.types().get(0); + Object decl2 = type.bodyDeclarations().get(2); + assertEquals(MethodDeclaration.class, decl2.getClass()); + Object stat0 = ((MethodDeclaration) decl2).getBody().statements().get(0); + Object stat1 = ((MethodDeclaration) decl2).getBody().statements().get(1); + assertEquals(ExpressionStatement.class, stat1.getClass()); + Expression expr = ((ExpressionStatement) stat1).getExpression(); + assertEquals(MethodInvocation.class, expr.getClass()); + Expression receiver = ((MethodInvocation) expr).getExpression(); + IBinding binding1 = ((SimpleName) receiver).resolveBinding(); + assertNotNull(binding1); + assertEquals(VariableDeclarationStatement.class, stat0.getClass()); + VariableDeclarationFragment frag = (VariableDeclarationFragment) ((VariableDeclarationStatement) stat0) + .fragments().get(0); + IBinding binding0 = frag.getName().resolveBinding(); + assertEquals(binding0, binding1); + } finally { + deleteProject("P"); + } +} } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterRecoveryTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterRecoveryTest.java index a85e51425c1..e2741ecf4bc 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterRecoveryTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverterRecoveryTest.java @@ -1085,4 +1085,74 @@ public void test0021() throws JavaModelException { List statements = block.statements(); assertEquals("wrong size", 0, statements.size()); //$NON-NLS-1$ } + + public void testGH3155() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Converter18/src/test/TestDependsOnClass.java", + """ + package test; + public class TestDependsOnClass { + public TestDependsOnClass(@Qualifier(value= ) Object mybean) { } + } + """); + ASTNode result = runConversion(getJLS8(), this.workingCopies[0], true, true); + + assertASTNodeEquals( + """ + package test; + public class TestDependsOnClass { + public TestDependsOnClass( @Qualifier(value=$missing$) Object mybean){ + } + } + """, + result); + + ASTNode node = getASTNode((CompilationUnit) result, 0, 0); + assertNotNull(node); + assertTrue("Not a method declaration", node.getNodeType() == ASTNode.METHOD_DECLARATION); //$NON-NLS-1$ + MethodDeclaration methodDeclaration = (MethodDeclaration) node; + assertTrue(methodDeclaration.isConstructor()); + assertEquals(1, methodDeclaration.parameters().size()); + SingleVariableDeclaration param = (SingleVariableDeclaration) methodDeclaration.parameters().get(0); + assertEquals(1, param.modifiers().size()); + IExtendedModifier mod = (IExtendedModifier) param.modifiers().get(0); + assertTrue(mod.isAnnotation()); + assertEquals("Qualifier", ((Annotation) mod).getTypeName().toString()); + } + + public void testGH3156() throws JavaModelException { + this.workingCopies = new ICompilationUnit[1]; + this.workingCopies[0] = getWorkingCopy( + "/Converter18/src/test/X.java", + """ + package test; + public class X { + @Value(spring.) private String value1; + } + """); + ASTNode result = runConversion(getJLS8(), this.workingCopies[0], true, true); + + assertASTNodeEquals( + """ + package test; + public class X { + @Value(spring.$missing$) private String value1; + } + """, + result); + + ASTNode node = getASTNode((CompilationUnit) result, 0, 0); + assertNotNull(node); + assertTrue("Not a field declaration", node.getNodeType() == ASTNode.FIELD_DECLARATION); //$NON-NLS-1$ + FieldDeclaration fieldDeclaration = (FieldDeclaration) node; + IExtendedModifier mod = (IExtendedModifier) fieldDeclaration.modifiers().get(0); + assertTrue(mod.isAnnotation()); + Annotation annotation = (Annotation) mod; + assertEquals("Value", annotation.getTypeName().toString()); + assertTrue(annotation.isSingleMemberAnnotation()); + Expression value = ((SingleMemberAnnotation) annotation).getValue(); + assertEquals(QualifiedName.class, value.getClass()); + assertEquals("spring.$missing$", value.toString()); + } } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter_16Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter_16Test.java index 8a2b553bdc4..86687f81b8c 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter_16Test.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ASTConverter_16Test.java @@ -824,4 +824,68 @@ public void test_RecordSemicolon() throws JavaModelException { } + + public void testRecord013() throws JavaModelException { + if (!isJRE16) { + System.err.println("Test " + getName() + " requires a JRE 16"); + return; + } + String code = """ + record Test(String name) { + public static Builder builder() {return null;} + public static final class Builder {} + } + """; + this.workingCopy = getWorkingCopy("/Converter_16/src/X.java", true/*resolve*/); + ASTNode node = buildAST( + code, + this.workingCopy); + assertEquals("Not a compilation unit", ASTNode.COMPILATION_UNIT, node.getNodeType()); + CompilationUnit compilationUnit = (CompilationUnit) node; + assertProblemsSize(compilationUnit, 0); + node = ((AbstractTypeDeclaration)compilationUnit.types().get(0)); + assertEquals("Not a Record Declaration", ASTNode.RECORD_DECLARATION, node.getNodeType()); + RecordDeclaration record = (RecordDeclaration)node; + + List RecordComponents = record.recordComponents(); + assertEquals("Not a Single Variable Declaration Declaration", ASTNode.SINGLE_VARIABLE_DECLARATION, RecordComponents.get(0).getNodeType()); + + List bodyDeclaration = record.bodyDeclarations(); + MethodDeclaration md = (MethodDeclaration) bodyDeclaration.get(0); + TypeDeclaration td = (TypeDeclaration) bodyDeclaration.get(1); + assertEquals("Not a MethodDeclaration", ASTNode.METHOD_DECLARATION, md.getNodeType()); + assertEquals("Not a TypeDeclaration", ASTNode.TYPE_DECLARATION, td.getNodeType()); + } + + public void testClass002() throws CoreException { + if (!isJRE16) { + System.err.println("Test "+getName()+" requires a JRE 16"); + return; + } + String code = """ + class Test { + public static Builder builder() {return null;} + public static final class Builder {} + } + """; + this.workingCopy = getWorkingCopy("/Converter_16/src/X.java", true/*resolve*/); + ASTNode node = buildAST( + code, + this.workingCopy); + assertEquals("Not a compilation unit", ASTNode.COMPILATION_UNIT, node.getNodeType()); + CompilationUnit compilationUnit = (CompilationUnit) node; + assertProblemsSize(compilationUnit, 0); + List types = compilationUnit.types(); + assertEquals("No. of Types is not 1", types.size(), 1); + AbstractTypeDeclaration type = types.get(0); + assertTrue("type not a type", type instanceof TypeDeclaration); + TypeDeclaration typeDecl = (TypeDeclaration)type; + assertTrue("type not a class", !typeDecl.isInterface()); + + List bodyDeclaration = typeDecl.bodyDeclarations(); + MethodDeclaration md = (MethodDeclaration) bodyDeclaration.get(0); + TypeDeclaration td = (TypeDeclaration) bodyDeclaration.get(1); + assertEquals("Not a MethodDeclaration", ASTNode.METHOD_DECLARATION, md.getNodeType()); + assertEquals("Not a TypeDeclaration", ASTNode.TYPE_DECLARATION, td.getNodeType()); + } } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ConverterTestSetup.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ConverterTestSetup.java index 0b233fd06ea..bc44e0e5dcd 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ConverterTestSetup.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ConverterTestSetup.java @@ -221,6 +221,14 @@ public void setUpJCLClasspathVariables(String compliance, boolean useFullJCL) th new IPath[] {getConverterJCLPath("14"), getConverterJCLSourcePath("14"), getConverterJCLRootSourcePath()}, null); } + } else if ("15".equals(compliance)) { + if (JavaCore.getClasspathVariable("CONVERTER_JCL15_LIB") == null) { + setupExternalJCL("converterJclMin15"); + JavaCore.setClasspathVariables( + new String[] {"CONVERTER_JCL15_LIB", "CONVERTER_JCL15_SRC", "CONVERTER_JCL15_SRCROOT"}, + new IPath[] {getConverterJCLPath("15"), getConverterJCLSourcePath("15"), getConverterJCLRootSourcePath()}, + null); + } } else if ("17".equals(compliance)) { if (JavaCore.getClasspathVariable("CONVERTER_JCL_17_LIB") == null) { setupExternalJCL("converterJclMin17"); diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ModuleImportASTConverterTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ModuleImportASTConverterTest.java index a0490a78fdf..d6d4c8ffe50 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ModuleImportASTConverterTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/ModuleImportASTConverterTest.java @@ -24,8 +24,12 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ImplicitTypeDeclaration; import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import junit.framework.Test; @@ -109,4 +113,30 @@ void m() { assertEquals("Incorrect name", "java.lang.System.out", imp.getName().toString()); } } + + public void test002() throws CoreException { + String contents = """ + /** */ + void main() { + System.out.println("Eclipse"); + } + """; + this.workingCopy = getWorkingCopy("/Converter_23/src/X.java", true/*resolve*/); + ASTNode node = buildAST(contents, this.workingCopy); + assertEquals("Wrong type of statement", ASTNode.COMPILATION_UNIT, node.getNodeType()); + CompilationUnit compilationUnit = (CompilationUnit) node; + ImplicitTypeDeclaration implicitTypeDeclaration = (ImplicitTypeDeclaration) compilationUnit.types().get(0); + assertEquals("Not an ImplicitTypeDeclaration Type", implicitTypeDeclaration.getNodeType(), ASTNode.UNNAMED_CLASS); + assertEquals("Not an ImplicitTypeDeclaration Name Type", implicitTypeDeclaration.getName().getNodeType(), ASTNode.SIMPLE_NAME); + assertEquals("Identifier is not empty String", implicitTypeDeclaration.getName().getIdentifier(), ""); + MethodDeclaration bodyDeclaration = (MethodDeclaration) implicitTypeDeclaration.bodyDeclarations().get(0); + assertEquals("Not a Method Declaration", bodyDeclaration.getNodeType(), ASTNode.METHOD_DECLARATION); + assertEquals("Method Declaration start is not one", bodyDeclaration.getStartPosition(), 1); + Javadoc javaDoc = bodyDeclaration.getJavadoc(); + assertEquals("Not a JavaDoc", javaDoc.getNodeType(), ASTNode.JAVADOC); + assertEquals("JavaDoc startPosition is not One", javaDoc.getStartPosition(), 1); + Block block = bodyDeclaration.getBody(); + assertEquals("Not a Block", block.getNodeType(), ASTNode.BLOCK); + assertEquals("Block startPosition is not correct", block.getStartPosition(), 21); + } } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java index ae19e17c604..f11ced185d6 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java @@ -16396,4 +16396,97 @@ public void testGH1473c() throws JavaModelException { String input = getCompilationUnit("Formatter", "", "testGH1473", "in.java").getSource(); formatSource(input, getCompilationUnit("Formatter", "", "testGH1473", "C_out.java").getSource()); } +// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3070 +// [Formatter] leading space added to conditional statements following an unnamed variable +public void testIssue3070() { + setComplianceLevel(CompilerOptions.VERSION_23); + String source = + """ + class Example { + private void foo() { + var a = false; + + try { + } catch (Exception _) { // <- the unnamed variable triggers the issue + } + + if (a) { // <- no leading space before the variable name + } + } + } + """; + formatSource(source, + """ + class Example { + private void foo() { + var a = false; + + try { + } catch (Exception _) { // <- the unnamed variable triggers the issue + } + + if (a) { // <- no leading space before the variable name + } + } + } + """); +} +public void testIssue3070_2() { + setComplianceLevel(CompilerOptions.VERSION_23); + String source = + """ + class Example { + private void foo() { + var a = false; + + try { + } catch (Exception e) { // <- the unnamed variable triggers the issue + } + + if (a) { // <- no leading space before the variable name + } + } + } + """; + formatSource(source, + """ + class Example { + private void foo() { + var a = false; + + try { + } catch (Exception e) { // <- the unnamed variable triggers the issue + } + + if (a) { // <- no leading space before the variable name + } + } + } + """); +} +//https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2977 +//[23] Formatter Support for Implicit classes - investigate +public void testIssue2977() { + setComplianceLevel(CompilerOptions.VERSION_23); + this.formatterOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED); + String source = + """ + import java.util.List; + String greeting() { return "Hello, World!"; } + void main( ) {println(greeting());} + """; + formatSource(source, + """ + import java.util.List; + + String greeting() { + return "Hello, World!"; + } + + void main() { + println(greeting()); + } + """, + CodeFormatter.K_COMPILATION_UNIT); +} } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java index db4beec66ab..436e8f3e9c5 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java @@ -661,7 +661,7 @@ protected void addClassFolder(IJavaProject javaProject, String folderRelativePat } protected void addExternalLibrary(IJavaProject javaProject, String jarPath, String[] pathAndContents, String[] nonJavaResources, String compliance) throws Exception { - String[] claspath = getJCL15PlusLibraryIfNeeded(compliance); + String[] claspath = getJCLLibrary(compliance); org.eclipse.jdt.core.tests.util.Util.createJar(pathAndContents, nonJavaResources, jarPath, claspath, compliance); addLibraryEntry(javaProject, new Path(jarPath), true/*exported*/); } @@ -750,7 +750,7 @@ protected IProject createLibrary( IProject project = javaProject.getProject(); String projectLocation = project.getLocation().toOSString(); String jarPath = projectLocation + File.separator + jarName; - String[] claspath = getJCL15PlusLibraryIfNeeded(compliance); + String[] claspath = getJCLLibrary(compliance); org.eclipse.jdt.core.tests.util.Util.createJar(pathAndContents, nonJavaResources, jarPath, claspath, compliance, options); if (pathAndContents != null && pathAndContents.length != 0) { String sourceZipPath = projectLocation + File.separator + sourceZipName; @@ -2540,13 +2540,10 @@ public void ensureChildExists(IParent container, IJavaElement child) throws Java } } - protected String[] getJCL15PlusLibraryIfNeeded(String compliance) throws JavaModelException, IOException { - if (compliance.charAt(compliance.length()-1) >= '8' && (AbstractCompilerTest.getPossibleComplianceLevels() & AbstractCompilerTest.F_1_8) != 0) { - // ensure that the JCL 18 lib is setup (i.e. that the jclMin18.jar is copied) - setUpJCLClasspathVariables("1.8"); - return new String[] {getExternalJCLPathString("1.8")}; - } - return null; + protected String[] getJCLLibrary(String compliance) throws JavaModelException, IOException { + // ensure that the requested JCL lib is setup (i.e. that the jclMinXY.jar is copied) + setUpJCLClasspathVariables(compliance); + return new String[] {getExternalJCLPathString(compliance)}; } /** * Returns the specified compilation unit in the given project, root, and diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ClasspathTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ClasspathTests.java index c310b214a5f..678c38fb312 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ClasspathTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ClasspathTests.java @@ -7455,7 +7455,7 @@ public void testBug576735a() throws Exception { projectLocation + File.separator + "libMissing.jar"); // create another jar depending on libMissing.jar: - String[] classpath = getJCL15PlusLibraryIfNeeded("1.8"); + String[] classpath = getJCLLibrary("1.8"); classpath = Arrays.copyOf(classpath, classpath.length+1); classpath[classpath.length-1] = projectLocation + File.separator + "libMissing.jar"; Util.createJar( diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations17Test.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations17Test.java index 89c2bb089d3..50dc3b52bfd 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations17Test.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ExternalAnnotations17Test.java @@ -37,10 +37,10 @@ import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.tests.compiler.regression.AbstractNullAnnotationTest; import org.eclipse.jdt.core.util.ExternalAnnotationUtil; import org.eclipse.jdt.core.util.ExternalAnnotationUtil.MergeStrategy; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -import org.osgi.framework.Bundle; public class ExternalAnnotations17Test extends ExternalAnnotations18Test { @@ -62,6 +62,11 @@ public static Test suite() { return buildModelTestSuite(ExternalAnnotations17Test.class, BYTECODE_DECLARATION_ORDER); } + @Override + public void setUpSuite() throws Exception { + super.setUpSuite(); + this.ANNOTATION_LIB = AbstractNullAnnotationTest.getAnnotationV1LibPath(); + } /** * @deprecated */ @@ -69,14 +74,6 @@ static int getJLS8() { return AST.JLS8; } - /** - * @deprecated indirectly uses deprecated class PackageAdmin - */ - @Override - protected Bundle[] getAnnotationBundles() { - return org.eclipse.jdt.core.tests.Activator.getPackageAdmin().getBundles("org.eclipse.jdt.annotation", "[1.1.0,2.0.0)"); - } - @Override public String getSourceWorkspacePath() { // we read individual projects from within this folder: diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaSearchImplicitTypeDeclarationTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaSearchImplicitTypeDeclarationTests.java new file mode 100644 index 00000000000..3231d6db7f9 --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/JavaSearchImplicitTypeDeclarationTests.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.core.tests.model; + +import java.io.IOException; +import junit.framework.Test; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.ReferenceMatch; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.TypeReferenceMatch; + +public class JavaSearchImplicitTypeDeclarationTests extends JavaSearchTests { + public JavaSearchImplicitTypeDeclarationTests(String name) { + super(name); + this.endChar = ""; + } + public static Test suite() { + return buildModelTestSuite(JavaSearchImplicitTypeDeclarationTests.class, BYTECODE_DECLARATION_ORDER); + } + class TestCollector extends JavaSearchResultCollector { + public void acceptSearchMatch(SearchMatch searchMatch) throws CoreException { + super.acceptSearchMatch(searchMatch); + } + } + + class ReferenceCollector extends JavaSearchResultCollector { + protected void writeLine() throws CoreException { + super.writeLine(); + ReferenceMatch refMatch = (ReferenceMatch) this.match; + IJavaElement localElement = refMatch.getLocalElement(); + if (localElement != null) { + this.line.append("+["); + if (localElement.getElementType() == IJavaElement.ANNOTATION) { + this.line.append('@'); + this.line.append(localElement.getElementName()); + this.line.append(" on "); + this.line.append(localElement.getParent().getElementName()); + } else { + this.line.append(localElement.getElementName()); + } + this.line.append(']'); + } + } + } + + class TypeReferenceCollector extends ReferenceCollector { + protected void writeLine() throws CoreException { + super.writeLine(); + TypeReferenceMatch typeRefMatch = (TypeReferenceMatch) this.match; + IJavaElement[] others = typeRefMatch.getOtherElements(); + int length = others==null ? 0 : others.length; + if (length > 0) { + this.line.append("+["); + for (int i=0; i0) this.line.append(','); + if (other.getElementType() == IJavaElement.ANNOTATION) { + this.line.append('@'); + this.line.append(other.getElementName()); + this.line.append(" on "); + this.line.append(other.getParent().getElementName()); + } else { + this.line.append(other.getElementName()); + } + } + this.line.append(']'); + } + } + } + + protected IJavaProject setUpJavaProject(final String projectName, String compliance, boolean useFullJCL) throws CoreException, IOException { + // copy files in project from source workspace to target workspace + IJavaProject setUpJavaProject = super.setUpJavaProject(projectName, compliance, useFullJCL); + return setUpJavaProject; + } + + IJavaSearchScope getJavaSearchScope() { + return SearchEngine.createJavaSearchScope(new IJavaProject[] {getJavaProject("JavaSearchBugs")}); + } + + IJavaSearchScope getJavaSearchScopeBugs(String packageName, boolean addSubpackages) throws JavaModelException { + if (packageName == null) return getJavaSearchScope(); + return getJavaSearchPackageScope("JavaSearchBugs", packageName, addSubpackages); + } + + public ICompilationUnit getWorkingCopy(String path, String source) throws JavaModelException { + if (this.wcOwner == null) { + this.wcOwner = new WorkingCopyOwner() {}; + } + return getWorkingCopy(path, source, this.wcOwner); + } + + @Override + public void setUpSuite() throws Exception { + JAVA_PROJECT = setUpJavaProject("JavaSearchBugs", "23"); + JAVA_PROJECT.setOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED); + super.setUpSuite(); + } + + public void tearDownSuite() throws Exception { + deleteProject("JavaSearchBugs"); + super.tearDownSuite(); + } + + protected void setUp () throws Exception { + super.setUp(); + this.resultCollector = new TestCollector(); + this.resultCollector.showAccuracy(true); + } + + public void test_001() throws CoreException { + this.workingCopies = new ICompilationUnit[1]; + String code = """ + /** + * Hello + */ + void main() { + System.out.println("sasi"); + abc(); + } + void abc() { + System.out.println("abc"); + } + """; + this.workingCopies[0] = getWorkingCopy("/JavaSearchBugs/src/X.java", code); + IJavaProject javaProject = this.workingCopies[0].getJavaProject(); + String old = javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true); + try { + search("abc", METHOD, ALL_OCCURRENCES, EXACT_RULE); + assertSearchResults("src/X.java void X.main() [abc()] EXACT_MATCH\n" + + "src/X.java void X.abc() [abc] EXACT_MATCH"); + } finally { + javaProject.setOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, old); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java index 10e4796e962..82a9c043e96 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/NullAnnotationModelTests.java @@ -44,6 +44,7 @@ import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.*; +import org.eclipse.jdt.core.tests.compiler.regression.AbstractNullAnnotationTest; import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.osgi.framework.Bundle; @@ -76,9 +77,7 @@ public void setUp() throws Exception { File bundleFile = FileLocator.getBundleFileLocation(bundles[0]).get(); this.ANNOTATION_LIB = bundleFile.isDirectory() ? bundleFile.getPath()+"/bin" : bundleFile.getPath(); - bundles = org.eclipse.jdt.core.tests.Activator.getPackageAdmin().getBundles("org.eclipse.jdt.annotation", "[1.1.0,2.0.0)"); - bundleFile = FileLocator.getBundleFileLocation(bundles[0]).get(); - this.ANNOTATION_LIB_V1 = bundleFile.isDirectory() ? bundleFile.getPath()+"/bin" : bundleFile.getPath(); + this.ANNOTATION_LIB_V1 = AbstractNullAnnotationTest.getAnnotationV1LibPath(); } protected String testJarPath(String jarName) throws IOException { diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerTests9.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerTests9.java index 0309cf31146..d4cbf8d70a8 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerTests9.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerTests9.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2021 IBM Corporation and others. + * Copyright (c) 2000, 2024 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -402,6 +402,65 @@ public void testTerminalDeprecation2() throws CoreException, IOException { deleteProject("P1"); } } +public void testSinceDeprecation() throws CoreException, IOException { + try { + IJavaProject p1 = createJava9Project("P1"); + String x1Source = "package p;\n" + + "public class X1 {\n" + + "@Deprecated(since=\"10\")\n" + + "public void foo() {}\n" + + "}"; + String x2Source = "package p;\n" + + "public class X2 {\n" + + " public Object field;\n" + + " @Deprecated(since=\"9\")\n" + + " public void m() {}\n" + + "}\n"; + String[] allJarSources = (isJRE9) + ? new String[] { + "p/X1.java", + x1Source, + "/P1/src/p/X2.java", + x2Source } + : new String[] { + "java/lang/Deprecated.java", + "package java.lang;\n" + + "public @interface Deprecated {\n" + + " String since default \"\";" + + " boolean forRemoval() default false;" + + "}\n", + "p/X1.java", + x1Source, + "/P1/src/p/X2.java", + x2Source }; + createJar( + allJarSources, + p1.getProject().getLocation().append("lib.jar").toOSString(), + null, + "9"); + p1.getProject().refreshLocal(2, null); + addLibraryEntry(p1, "/P1/lib.jar", false); + + setUpWorkingCopy("/P1/src/Y.java", + "public class Y {\n" + + " Object foo(p.X1 x1, p.X2 x2) {\n" + + " x2.m();\n" + + " x1.foo();\n" + + " return x2.field;\n" + + " }\n" + + "}\n"); + assertProblems( + "Unexpected problems", + "----------\n" + + "1. WARNING in /P1/src/Y.java (at line 3)\n" + + " x2.m();\n" + + " ^^^\n" + + "The method m() from the type X2 is deprecated since version 9\n" + + "----------\n"); + } finally { + deleteProject("P1"); + } +} public void testBug540541() throws CoreException, IOException { if (!isJRE9) return; IJavaProject project1 = null; diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/RunJavaSearchTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/RunJavaSearchTests.java index fb716c288b5..2cc5f10eeb9 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/RunJavaSearchTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/RunJavaSearchTests.java @@ -86,6 +86,7 @@ public static Test suite() { allClasses.add(JavaSearchNameEnvironmentTest.class); allClasses.add(JavaSearchSuperAfterStatementTests.class); allClasses.add(JavaSearchIssue190Test.class); + allClasses.add(JavaSearchImplicitTypeDeclarationTests.class); // Reset forgotten subsets of tests TestCase.TESTS_PREFIX = null; diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SealedTypeModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SealedTypeModelTests.java index e4382202806..f77df3ada5b 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SealedTypeModelTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SealedTypeModelTests.java @@ -144,7 +144,7 @@ public void test004() throws Exception { createFile( "/SealedTypes/src/X.java", fileContent); ICompilationUnit unit = getCompilationUnit("/SealedTypes/src/X.java"); IType[] types = unit.getTypes(); - assertEquals("Incorret no of types", 3, types.length); + assertEquals("Incorrect no of types", 3, types.length); for (IType iType : types) { if (iType.getElementName().equals("I")) { assertTrue("modifier should contain sealed", iType.isSealed()); @@ -160,6 +160,137 @@ public void test004() throws Exception { deleteProject("SealedTypes"); } } + // Test implicitly permitted sub types in Source Type + public void test004_2() throws Exception { + String[] permitted = new String[] {"Maybe.Maybe1", "Maybe.Maybe2"}; + try { + IJavaProject project = createJavaProject("SealedTypes"); + project.open(null); + String fileContent = + """ + interface SuperInt {} + + abstract sealed class Maybe { + final class Maybe1 extends Maybe {} + final class Maybe2 extends Maybe implements SuperInt {} + } + + class Test { + + void testMaybe(Maybe maybe) { + if (maybe == null) return; + } + } + """; + + createFile( "/SealedTypes/src/X.java", fileContent); + ICompilationUnit unit = getCompilationUnit("/SealedTypes/src/X.java"); + IType[] types = unit.getTypes(); + assertEquals("Incorrect no of types", 3, types.length); + for (IType iType : types) { + if (iType.getElementName().equals("Maybe")) { + assertTrue("modifier should contain sealed", iType.isSealed()); + String[] permittedSubtypeNames = iType.getPermittedSubtypeNames(); + assertEquals("incorrect permitted sub types", permitted.length, permittedSubtypeNames.length); + for (int i = 0; i < permitted.length; i++) { + assertEquals("incorrect permitted sub type", permitted[i], permittedSubtypeNames[i]); + } + } + } + } + finally { + deleteProject("SealedTypes"); + } + } + + // Test implicitly permitted sub types in Source Type + public void test004_3() throws Exception { + String[] permitted = new String[] {"Maybe.Maybe1", "Maybe.Maybe2"}; + try { + IJavaProject project = createJavaProject("SealedTypes"); + project.open(null); + String fileContent = + """ + interface SuperInt {} + + sealed interface Maybe { + final class Maybe1 implements Maybe {} + non-sealed interface Maybe2 extends Maybe {} + } + + class Test { + + void testMaybe(Maybe maybe) { + if (maybe == null) return; + } + } + """; + + createFile( "/SealedTypes/src/X.java", fileContent); + ICompilationUnit unit = getCompilationUnit("/SealedTypes/src/X.java"); + IType[] types = unit.getTypes(); + assertEquals("Incorrect no of types", 3, types.length); + for (IType iType : types) { + if (iType.getElementName().equals("Maybe")) { + assertTrue("modifier should contain sealed", iType.isSealed()); + String[] permittedSubtypeNames = iType.getPermittedSubtypeNames(); + assertEquals("incorrect permitted sub types", permitted.length, permittedSubtypeNames.length); + for (int i = 0; i < permitted.length; i++) { + assertEquals("incorrect permitted sub type", permitted[i], permittedSubtypeNames[i]); + } + } + } + } + finally { + deleteProject("SealedTypes"); + } + } + + // Test implicitly permitted sub types in Source Type + public void test004_4() throws Exception { + String[] permitted = new String[] {"Maybe.Maybe1", "Maybe.Maybe2"}; + try { + IJavaProject project = createJavaProject("SealedTypes"); + project.open(null); + String fileContent = + """ + interface SuperInt {} + + abstract sealed class Maybe { + final class Maybe1 extends Maybe {} + final class Maybe2 extends Maybe implements SuperInt {} + } + + class Test { + + void testMaybe(Maybe maybe) { + if (maybe == null) return; + zork(); + } + } + """; + + createFile( "/SealedTypes/src/X.java", fileContent); + ICompilationUnit unit = getCompilationUnit("/SealedTypes/src/X.java"); + IType[] types = unit.getTypes(); + assertEquals("Incorrect no of types", 3, types.length); + for (IType iType : types) { + if (iType.getElementName().equals("Maybe")) { + assertTrue("modifier should contain sealed", iType.isSealed()); + String[] permittedSubtypeNames = iType.getPermittedSubtypeNames(); + assertEquals("incorrect permitted sub types", permitted.length, permittedSubtypeNames.length); + for (int i = 0; i < permitted.length; i++) { + assertEquals("incorrect permitted sub type", permitted[i], permittedSubtypeNames[i]); + } + } + } + } + finally { + deleteProject("SealedTypes"); + } + } + + // Test explicitly permitted sub types in binary public void test005() throws Exception { String[] permitted = new String[] {"p.X", "p.Y"}; @@ -376,7 +507,7 @@ public void test009() throws Exception { "1. ERROR in /SealedTypes/src/p/X.java (at line 2)\n" + " public non-sealed class X {}\n" + " ^\n" + - "A class X declared as non-sealed should have either a sealed direct superclass or a sealed direct superinterface\n" + + "The non-sealed class X must have a sealed direct supertype\n" + "----------\n", this.problemRequestor); } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingImplicitTypeDeclarationTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingImplicitTypeDeclarationTest.java new file mode 100644 index 00000000000..7c8a6170115 --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingImplicitTypeDeclarationTest.java @@ -0,0 +1,288 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.core.tests.rewrite.describing; + +import java.util.List; +import junit.framework.Test; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.*; +import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; +import org.eclipse.jdt.core.dom.rewrite.ListRewrite; + +public class ASTRewritingImplicitTypeDeclarationTest extends ASTRewritingTest{ + + public ASTRewritingImplicitTypeDeclarationTest(String name, int apiLevel) { + super(name, apiLevel); + } + + public static Test suite() { + return createSuite(ASTRewritingImplicitTypeDeclarationTest.class, 23); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + if (this.apiLevel == AST.JLS23 ) { + this.project1.setOption(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_23); + this.project1.setOption(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_23); + this.project1.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_23); + this.project1.setOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.ENABLED); + } + } + + public void test001() throws Exception { + AST ast = AST.newAST(AST.JLS23, true); + // Create CompilationUnit + CompilationUnit compilationUnit = ast.newCompilationUnit(); + + ImplicitTypeDeclaration implicitTypeDeclaration = ast.newImplicitTypeDeclaration(); + + Javadoc javaDoc= ast.newJavadoc(); + TextElement textElem= ast.newTextElement(); + textElem.setText("Hello"); + TagElement tagElement= ast.newTagElement(); + tagElement.fragments().add(textElem); + javaDoc.tags().add(tagElement); + implicitTypeDeclaration.setJavadoc(javaDoc); + + QualifiedName qualifiedName = ast.newQualifiedName(ast.newName("System"), ast.newSimpleName("out")); + MethodInvocation methodInvocation = ast.newMethodInvocation(); + methodInvocation.setExpression(qualifiedName); + methodInvocation.setName(ast.newSimpleName("println")); + + StringLiteral literal = ast.newStringLiteral(); + literal.setLiteralValue("Eclipse"); + methodInvocation.arguments().add(literal); + ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation); + + Block block= ast.newBlock(); + block.statements().add(expressionStatement); + MethodDeclaration methodDeclaration = ast.newMethodDeclaration(); + methodDeclaration.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID)); + methodDeclaration.setName(ast.newSimpleName("main")); + methodDeclaration.setBody(block); + implicitTypeDeclaration.bodyDeclarations().add(methodDeclaration); + // Add Implicity Type class to compilation unit + compilationUnit.types().add(implicitTypeDeclaration); + + StringBuilder buf = new StringBuilder(); + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append(" void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append(" }\n"); + + assertEqualString(compilationUnit.toString(), buf.toString()); + } + + //javaDoc + public void test002() throws Exception { + AST ast = AST.newAST(AST.JLS23, true); + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + StringBuilder buf = new StringBuilder(); + buf= new StringBuilder(); + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append("}\n"); + + ICompilationUnit cu= pack1.createCompilationUnit("X.java", buf.toString(), false, null); + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + + ImplicitTypeDeclaration implicitTypeDeclaration= findImplicitDeclaration(astRoot, ""); + List methodDeclarationsList = implicitTypeDeclaration.bodyDeclarations(); + MethodDeclaration methodDeclaration = methodDeclarationsList.get(0); + { + + Javadoc javaDoc = methodDeclaration.getJavadoc(); + + Javadoc newJavaDoc= ast.newJavadoc(); + TextElement textElem= ast.newTextElement(); + textElem.setText("Eclipse"); + TagElement tagElement= ast.newTagElement(); + tagElement.fragments().add(textElem); + newJavaDoc.tags().add(tagElement); + + rewrite.replace(javaDoc, newJavaDoc, null); + } + + String preview = evaluateRewrite(cu, rewrite); + buf= new StringBuilder(); + + buf.append("/**\n"); + buf.append(" * Eclipse\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append("}\n"); + + assertEqualString(preview, buf.toString()); + + { + Javadoc javaDoc = methodDeclaration.getJavadoc(); + Javadoc newJavaDoc = null; + + rewrite.replace(javaDoc, newJavaDoc, null); + } + + preview = evaluateRewrite(cu, rewrite); + buf= new StringBuilder(); + + buf.append("void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append("}\n"); + + assertEqualString(preview, buf.toString()); + } + + //adding more MEthodDeclaration + public void test003() throws Exception { + AST ast = AST.newAST(AST.JLS23, true); + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + StringBuilder buf = new StringBuilder(); + buf= new StringBuilder(); + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append("}\n"); + + ICompilationUnit cu= pack1.createCompilationUnit("X.java", buf.toString(), false, null); + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + ImplicitTypeDeclaration implicitTypeDeclaration= findImplicitDeclaration(astRoot, ""); + { + MethodInvocation methodInvocation = ast.newMethodInvocation(); + methodInvocation.setName(ast.newSimpleName("println")); + + StringLiteral literal = ast.newStringLiteral(); + literal.setLiteralValue("abc"); + + QualifiedName qualifiedName = ast.newQualifiedName(ast.newName("System"), ast.newSimpleName("out")); + + methodInvocation.setExpression(qualifiedName); + methodInvocation.arguments().add(literal); + + ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation); + + Block block = ast.newBlock(); + block.statements().add(expressionStatement); + + MethodDeclaration methodDeclaration = ast.newMethodDeclaration(); + methodDeclaration.setBody(block); + methodDeclaration.setName(ast.newSimpleName("abc")); + methodDeclaration.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID)); + + ListRewrite listRewrite= rewrite.getListRewrite(implicitTypeDeclaration, ImplicitTypeDeclaration.BODY_DECLARATIONS_PROPERTY); + listRewrite.insertAt(methodDeclaration, 1, null); + } + + String preview = evaluateRewrite(cu, rewrite); + buf= new StringBuilder(); + + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"Eclipse\");\n"); + buf.append("}\n"); + buf.append("\n"); + buf.append("void abc() {\n"); + buf.append(" System.out.println(\"abc\");\n"); + buf.append("}\n"); + + assertEqualString(preview, buf.toString()); + } + public void test004() throws Exception { + AST ast = AST.newAST(AST.JLS23, true); + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + StringBuilder buf = new StringBuilder(); + buf= new StringBuilder(); + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"main\");\n"); + buf.append("}\n"); + buf.append("void abc(){\n"); + buf.append(" System.out.println(\"abc\");\n"); + buf.append("}\n"); + + ICompilationUnit cu= pack1.createCompilationUnit("X.java", buf.toString(), false, null); + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + ImplicitTypeDeclaration implicitTypeDeclaration= findImplicitDeclaration(astRoot, ""); + List bodyDeclaration = implicitTypeDeclaration.bodyDeclarations(); + System.out.println("sasi"); + { + + rewrite.remove(bodyDeclaration.get(1), null);//remove one method + + MethodInvocation methodInvocation = ast.newMethodInvocation(); + methodInvocation.setName(ast.newSimpleName("println")); + + StringLiteral literal = ast.newStringLiteral(); + literal.setLiteralValue("xyz"); + + QualifiedName qualifiedName = ast.newQualifiedName(ast.newName("System"), ast.newSimpleName("out")); + + methodInvocation.setExpression(qualifiedName); + methodInvocation.arguments().add(literal); + + ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation); + + Block block = ast.newBlock(); + block.statements().add(expressionStatement); + + MethodDeclaration methodDeclaration = ast.newMethodDeclaration(); + methodDeclaration.setBody(block); + methodDeclaration.setName(ast.newSimpleName("xyz")); + methodDeclaration.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID)); + + ListRewrite listRewrite= rewrite.getListRewrite(implicitTypeDeclaration, ImplicitTypeDeclaration.BODY_DECLARATIONS_PROPERTY); + listRewrite.insertAt(methodDeclaration, 1, null); + + String preview = evaluateRewrite(cu, rewrite); + buf= new StringBuilder(); + + buf.append("/** \n"); + buf.append(" * Hello\n"); + buf.append(" */\n"); + buf.append("void main(){\n"); + buf.append(" System.out.println(\"main\");\n"); + buf.append("}\n"); + buf.append("void xyz() {\n"); + buf.append(" System.out.println(\"xyz\");\n"); + buf.append("}\n"); + + assertEqualString(preview, buf.toString()); + + } + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingRecordDeclarationTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingRecordDeclarationTest.java index 5cea43fa836..91774731dd0 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingRecordDeclarationTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingRecordDeclarationTest.java @@ -1402,5 +1402,132 @@ public void testRecord_028() throws Exception { } + public void testRecord_029_a() throws Exception { + if (checkAPILevel()) { + return; + } + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + + String code = """ + package test1; + record Test(String name) { + public static Builder builder() {} + public static final class Builder {} + } + """; + + ICompilationUnit cu= pack1.createCompilationUnit("Test.java", code, false, null); + + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + List methods= astRoot.types(); + MethodDeclaration methodDecl= findMethodDeclaration(methods.get(0), "builder"); + { + rewrite.remove(methodDecl, null); + } + + String preview= evaluateRewrite(cu, rewrite); + + String reWriteCode = """ + package test1; + record Test(String name) { + public static final class Builder {} + } + """; + + assertEqualString(preview, reWriteCode); + } + + public void testRecord_029_b() throws Exception { + if (checkAPILevel()) { + return; + } + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + + String code = """ + package test1; + public class Test { + public static Builder builder() {} + public static final class Builder {} + } + """; + + ICompilationUnit cu= pack1.createCompilationUnit("Test.java", code, false, null); + + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + List methods= astRoot.types(); + MethodDeclaration methodDecl= findMethodDeclaration(methods.get(0), "builder"); + { + rewrite.remove(methodDecl, null); + } + + String preview= evaluateRewrite(cu, rewrite); + + String reWriteCode = """ + package test1; + public class Test { + public static final class Builder {} + } + """; + + assertEqualString(preview, reWriteCode); + } + + public void testRecord_029_c() throws Exception { + if (checkAPILevel()) { + return; + } + IPackageFragment pack1= this.sourceFolder.createPackageFragment("test1", false, null); + + String code = """ + package test1; + record Test(String name) { + public static Builder builder() {} + public static final class Builder {} + public static Builder builderNew() {} + public static final class builderNew { + builderNew(){ + System.out.println("Test"); + } + } + } + """; + + ICompilationUnit cu= pack1.createCompilationUnit("Test.java", code, false, null); + + CompilationUnit astRoot= createAST(cu); + ASTRewrite rewrite= ASTRewrite.create(astRoot.getAST()); + + assertTrue("Parse errors", (astRoot.getFlags() & ASTNode.MALFORMED) == 0); + List methods= astRoot.types(); + MethodDeclaration methodDecl= findMethodDeclaration(methods.get(0), "builder"); + MethodDeclaration methodDeclNew= findMethodDeclaration(methods.get(0), "builderNew"); + { + rewrite.remove(methodDecl, null); + rewrite.remove(methodDeclNew, null); + } + + String preview= evaluateRewrite(cu, rewrite); + + String reWriteCode = """ + package test1; + record Test(String name) { + public static final class Builder {} + public static final class builderNew { + builderNew(){ + System.out.println("Test"); + } + } + } + """; + + assertEqualString(preview, reWriteCode); + } + } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingTest.java index dcfc5187067..728508b8534 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingTest.java @@ -446,4 +446,15 @@ protected static MethodDeclaration createNewMethod(AST ast, String name, boolean return decl; } + public static ImplicitTypeDeclaration findImplicitDeclaration(CompilationUnit astRoot, String simpleTypeName) { + List types= astRoot.types(); + for (int i= 0; i < types.size(); i++) { + ImplicitTypeDeclaration elem= (ImplicitTypeDeclaration) types.get(i); + if (simpleTypeName.equals(elem.getName().getIdentifier())) { + return elem; + } + } + return null; + } + } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewrite_RecordTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewrite_RecordTest.java index 3db4f9604fe..71248344102 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewrite_RecordTest.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/rewrite/describing/ImportRewrite_RecordTest.java @@ -63,7 +63,7 @@ public static Test suite() { protected void setUp() throws Exception { super.setUp(); - IJavaProject proj= createJavaProject(PROJECT, new String[] {"src"}, new String[] {"JCL18_LIB"}, "bin", "15"); + IJavaProject proj= createJavaProject(PROJECT, new String[] {"src"}, new String[] {"JCL14_LIB"}, "bin", "15"); proj.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE); proj.setOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4"); proj.setOption(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_16); diff --git a/org.eclipse.jdt.core.tests.model/workspace/Converter9/.classpath b/org.eclipse.jdt.core.tests.model/workspace/Converter9/.classpath index 3522bc0c3f5..6da169624e4 100644 --- a/org.eclipse.jdt.core.tests.model/workspace/Converter9/.classpath +++ b/org.eclipse.jdt.core.tests.model/workspace/Converter9/.classpath @@ -1,6 +1,10 @@ - + + + + + diff --git a/org.eclipse.jdt.core.tests.model/workspace/Converter_15_1/.classpath b/org.eclipse.jdt.core.tests.model/workspace/Converter_15_1/.classpath index 3522bc0c3f5..49c53688466 100644 --- a/org.eclipse.jdt.core.tests.model/workspace/Converter_15_1/.classpath +++ b/org.eclipse.jdt.core.tests.model/workspace/Converter_15_1/.classpath @@ -1,6 +1,10 @@ - + + + + + diff --git a/org.eclipse.jdt.core/.settings/.api_filters b/org.eclipse.jdt.core/.settings/.api_filters index 624c222fef6..eaffdf5e945 100644 --- a/org.eclipse.jdt.core/.settings/.api_filters +++ b/org.eclipse.jdt.core/.settings/.api_filters @@ -258,6 +258,12 @@ + + + + + + @@ -272,6 +278,12 @@ + + + + + + diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionElementNotifier.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionElementNotifier.java index 9f157f1aec6..f95a60088c4 100644 --- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionElementNotifier.java +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/CompletionElementNotifier.java @@ -201,8 +201,8 @@ protected void notifySourceElementRequestor(ImportReference importReference, boo } @Override - protected void notifySourceElementRequestor(TypeDeclaration typeDeclaration, boolean notifyTypePresence, TypeDeclaration declaringType, ImportReference currentPackage) { + protected void notifySourceElementRequestor(CompilationUnitDeclaration parsedUnit, TypeDeclaration typeDeclaration, boolean notifyTypePresence, TypeDeclaration declaringType, ImportReference currentPackage) { if (typeDeclaration instanceof CompletionOnAnnotationOfType) return; - super.notifySourceElementRequestor(typeDeclaration, notifyTypePresence, declaringType, currentPackage); + super.notifySourceElementRequestor(parsedUnit, typeDeclaration, notifyTypePresence, declaringType, currentPackage); } } diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java new file mode 100644 index 00000000000..f9c77b4ee8f --- /dev/null +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCodeSelector.java @@ -0,0 +1,651 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaModelStatusConstants; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.ILocalVariable; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IParent; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.IPackageBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodReference; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeMethodReference; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; +import org.eclipse.jdt.internal.core.AnnotatableInfo; +import org.eclipse.jdt.internal.core.CompilationUnit; +import org.eclipse.jdt.internal.core.DOMToModelPopulator; +import org.eclipse.jdt.internal.core.JavaElement; +import org.eclipse.jdt.internal.core.LocalVariable; +import org.eclipse.jdt.internal.core.SourceField; +import org.eclipse.jdt.internal.core.SourceMethod; +import org.eclipse.jdt.internal.core.search.BasicSearchEngine; +import org.eclipse.jdt.internal.core.search.TypeNameMatchRequestorWrapper; +import org.eclipse.jdt.internal.core.util.Util; + +/** + * A util to select relevant IJavaElement from a DOM (as opposed to {@link SelectionEngine} + * which processes it using lower-level ECJ parser) + */ +public class DOMCodeSelector { + + private final CompilationUnit unit; + private final WorkingCopyOwner owner; + + public DOMCodeSelector(CompilationUnit unit, WorkingCopyOwner owner) { + this.unit = unit; + this.owner = owner; + } + + public IJavaElement[] codeSelect(int offset, int length) throws JavaModelException { + if (offset < 0) { + throw new JavaModelException(new IndexOutOfBoundsException(offset), IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS); + } + if (offset + length > this.unit.getSource().length()) { + throw new JavaModelException(new IndexOutOfBoundsException(offset + length), IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS); + } + org.eclipse.jdt.core.dom.CompilationUnit currentAST = this.unit.getOrBuildAST(this.owner); + if (currentAST == null) { + return new IJavaElement[0]; + } + String rawText = this.unit.getSource().substring(offset, offset + length); + int initialOffset = offset, initialLength = length; + boolean insideComment = ((List)currentAST.getCommentList()).stream() + .anyMatch(comment -> comment.getStartPosition() <= initialOffset && comment.getStartPosition() + comment.getLength() >= initialOffset + initialLength); + if (!insideComment) { // trim whitespaces and surrounding comments + boolean changed = false; + do { + changed = false; + if (length > 0 && Character.isWhitespace(this.unit.getSource().charAt(offset))) { + offset++; + length--; + changed = true; + } + if (length > 0 && Character.isWhitespace(this.unit.getSource().charAt(offset + length - 1))) { + length--; + changed = true; + } + List comments = currentAST.getCommentList(); + // leading comment + int offset1 = offset, length1 = length; + OptionalInt leadingCommentEnd = comments.stream().filter(comment -> { + int commentEndOffset = comment.getStartPosition() + comment.getLength() -1; + return comment.getStartPosition() <= offset1 && commentEndOffset > offset1 && commentEndOffset < offset1 + length1 - 1; + }).mapToInt(comment -> comment.getStartPosition() + comment.getLength() - 1) + .findAny(); + if (length > 0 && leadingCommentEnd.isPresent()) { + changed = true; + int newStart = leadingCommentEnd.getAsInt(); + int removedLeading = newStart + 1 - offset; + offset = newStart + 1; + length -= removedLeading; + } + // Trailing comment + int offset2 = offset, length2 = length; + OptionalInt trailingCommentStart = comments.stream().filter(comment -> { + return comment.getStartPosition() >= offset2 + && comment.getStartPosition() < offset2 + length2 + && comment.getStartPosition() + comment.getLength() > offset2 + length2; + }).mapToInt(Comment::getStartPosition) + .findAny(); + if (length > 0 && trailingCommentStart.isPresent()) { + changed = true; + int newEnd = trailingCommentStart.getAsInt(); + int removedTrailing = offset + length - 1 - newEnd; + length -= removedTrailing; + } + } while (changed); + } + String trimmedText = rawText.trim(); + NodeFinder finder = new NodeFinder(currentAST, offset, length); + final ASTNode node = finder.getCoveredNode() != null && finder.getCoveredNode().getStartPosition() > offset && finder.getCoveringNode().getStartPosition() + finder.getCoveringNode().getLength() > offset + length ? + finder.getCoveredNode() : + finder.getCoveringNode(); + if (node instanceof TagElement tagElement && TagElement.TAG_INHERITDOC.equals(tagElement.getTagName())) { + ASTNode javadocNode = node; + while (javadocNode != null && !(javadocNode instanceof Javadoc)) { + javadocNode = javadocNode.getParent(); + } + if (javadocNode instanceof Javadoc javadoc) { + ASTNode parent = javadoc.getParent(); + IBinding binding = resolveBinding(parent); + if (binding instanceof IMethodBinding methodBinding) { + var typeBinding = methodBinding.getDeclaringClass(); + if (typeBinding != null) { + List types = new ArrayList<>(Arrays.asList(typeBinding.getInterfaces())); + if (typeBinding.getSuperclass() != null) { + types.add(typeBinding.getSuperclass()); + } + while (!types.isEmpty()) { + ITypeBinding type = types.remove(0); + for (IMethodBinding m : Arrays.stream(type.getDeclaredMethods()).filter(methodBinding::overrides).toList()) { + if (m.getJavaElement() instanceof IMethod methodElement && methodElement.getJavadocRange() != null) { + return new IJavaElement[] { methodElement }; + } else { + types.addAll(Arrays.asList(type.getInterfaces())); + if (type.getSuperclass() != null) { + types.add(type.getSuperclass()); + } + } + } + } + } + IJavaElement element = methodBinding.getJavaElement(); + if (element != null) { + return new IJavaElement[] { element }; + } + } + } + } + org.eclipse.jdt.core.dom.ImportDeclaration importDecl = findImportDeclaration(node); + if (node instanceof ExpressionMethodReference emr && + emr.getExpression().getStartPosition() + emr.getExpression().getLength() <= offset && offset + length <= emr.getName().getStartPosition()) { + if (!(rawText.isEmpty() || rawText.equals(":") || rawText.equals("::"))) { //$NON-NLS-1$ //$NON-NLS-2$ + return new IJavaElement[0]; + } + if (emr.getParent() instanceof MethodInvocation methodInvocation) { + int index = methodInvocation.arguments().indexOf(emr); + return new IJavaElement[] {methodInvocation.resolveMethodBinding().getParameterTypes()[index].getDeclaredMethods()[0].getJavaElement()}; + } + if (emr.getParent() instanceof VariableDeclaration variableDeclaration) { + ITypeBinding requestedType = variableDeclaration.resolveBinding().getType(); + if (requestedType.getDeclaredMethods().length == 1 + && requestedType.getDeclaredMethods()[0].getJavaElement() instanceof IMethod overridenMethod) { + return new IJavaElement[] { overridenMethod }; + } + } + } + if (node instanceof LambdaExpression lambda) { + if (!(rawText.isEmpty() || rawText.equals("-") || rawText.equals(">") || rawText.equals("->"))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return new IJavaElement[0]; // as requested by some tests + } + if (lambda.resolveMethodBinding() != null + && lambda.resolveMethodBinding().getMethodDeclaration() != null + && lambda.resolveMethodBinding().getMethodDeclaration().getJavaElement() != null) { + return new IJavaElement[] { lambda.resolveMethodBinding().getMethodDeclaration().getJavaElement() }; + } + } + if (importDecl != null && importDecl.isStatic()) { + IBinding importBinding = importDecl.resolveBinding(); + if (importBinding instanceof IMethodBinding methodBinding) { + ArrayDeque overloadedMethods = Stream.of(methodBinding.getDeclaringClass().getDeclaredMethods()) // + .filter(otherMethodBinding -> methodBinding.getName().equals(otherMethodBinding.getName())) // + .map(IMethodBinding::getJavaElement) // + .filter(IJavaElement::exists) + .collect(Collectors.toCollection(ArrayDeque::new)); + IJavaElement[] reorderedOverloadedMethods = new IJavaElement[overloadedMethods.size()]; + Iterator reverseIterator = overloadedMethods.descendingIterator(); + for (int i = 0; i < reorderedOverloadedMethods.length; i++) { + reorderedOverloadedMethods[i] = reverseIterator.next(); + } + return reorderedOverloadedMethods; + } + return new IJavaElement[] { importBinding.getJavaElement() }; + } else if (findTypeDeclaration(node) == null) { + IBinding binding = resolveBinding(node); + if (binding != null && !binding.isRecovered()) { + if (node instanceof SuperMethodInvocation && // on `super` + binding instanceof IMethodBinding methodBinding && + methodBinding.getDeclaringClass() instanceof ITypeBinding typeBinding && + typeBinding.getJavaElement() instanceof IType type) { + return new IJavaElement[] { type }; + } + if (binding instanceof IPackageBinding packageBinding + && trimmedText.length() > 0 + && !trimmedText.equals(packageBinding.getName()) + && packageBinding.getName().startsWith(trimmedText)) { + // resolved a too wide node for package name, restrict to selected name only + IJavaElement fragment = this.unit.getJavaProject().findPackageFragment(trimmedText); + if (fragment != null) { + return new IJavaElement[] { fragment }; + } + } + // workaround https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2177 + if (binding instanceof IVariableBinding variableBinding && + variableBinding.getDeclaringMethod() instanceof IMethodBinding declaringMethod && + declaringMethod.isCompactConstructor() && + Arrays.stream(declaringMethod.getParameterNames()).anyMatch(variableBinding.getName()::equals) && + declaringMethod.getDeclaringClass() instanceof ITypeBinding recordBinding && + recordBinding.isRecord() && + recordBinding.getJavaElement() instanceof IType recordType && + recordType.getField(variableBinding.getName()) instanceof SourceField field) { + // the parent must be the field and not the method + return new IJavaElement[] { new LocalVariable(field, + variableBinding.getName(), + 0, // must be 0 for subsequent call to LocalVariableLocator.matchLocalVariable() to work + field.getSourceRange().getOffset() + field.getSourceRange().getLength() - 1, + field.getNameRange().getOffset(), + field.getNameRange().getOffset() + field.getNameRange().getLength() - 1, + field.getTypeSignature(), + null, + field.getFlags(), + true) }; + } + if (binding instanceof ITypeBinding typeBinding && + typeBinding.isIntersectionType()) { + return Arrays.stream(typeBinding.getTypeBounds()) + .map(ITypeBinding::getJavaElement) + .filter(Objects::nonNull) + .toArray(IJavaElement[]::new); + } + IJavaElement element = binding.getJavaElement(); + if (element != null && (element instanceof IPackageFragment || element.exists())) { + return new IJavaElement[] { element }; + } + if (binding instanceof ITypeBinding typeBinding) { + if (this.unit.getJavaProject() != null) { + IType type = this.unit.getJavaProject().findType(typeBinding.getQualifiedName()); + if (type != null) { + return new IJavaElement[] { type }; + } + } + // fallback to calling index, inspired/copied from SelectionEngine + IJavaElement[] indexMatch = findTypeInIndex(typeBinding.getPackage() != null ? typeBinding.getPackage().getName() : null, typeBinding.getName()); + if (indexMatch.length > 0) { + return indexMatch; + } + } + if (binding instanceof IVariableBinding variableBinding && variableBinding.getDeclaringMethod() != null && variableBinding.getDeclaringMethod().isCompactConstructor()) { + // workaround for JavaSearchBugs15Tests.testBug558812_012 + if (variableBinding.getDeclaringMethod().getJavaElement() instanceof IMethod method) { + Optional parameter = Arrays.stream(method.getParameters()).filter(param -> Objects.equals(param.getElementName(), variableBinding.getName())).findAny(); + if (parameter.isPresent()) { + return new IJavaElement[] { parameter.get() }; + } + } + } + if (binding instanceof IMethodBinding methodBinding && + methodBinding.isSyntheticRecordMethod() && + methodBinding.getDeclaringClass().getJavaElement() instanceof IType recordType && + recordType.getField(methodBinding.getName()) instanceof IField field) { + return new IJavaElement[] { field }; + } + ASTNode bindingNode = currentAST.findDeclaringNode(binding); + if (bindingNode != null) { + IJavaElement parent = this.unit.getElementAt(bindingNode.getStartPosition()); + if (parent != null && bindingNode instanceof SingleVariableDeclaration variableDecl) { + return new IJavaElement[] { DOMToModelPopulator.toLocalVariable(variableDecl, (JavaElement)parent) }; + } + } + } + } + // fallback: crawl the children of this unit + IJavaElement currentElement = this.unit; + boolean newChildFound; + int finalOffset = offset; + int finalLength = length; + do { + newChildFound = false; + if (currentElement instanceof IParent parentElement) { + Optional candidate = Stream.of(parentElement.getChildren()) + .filter(ISourceReference.class::isInstance) + .map(ISourceReference.class::cast) + .filter(sourceRef -> { + try { + ISourceRange elementRange = sourceRef.getSourceRange(); + return elementRange != null + && elementRange.getOffset() >= 0 + && elementRange.getOffset() <= finalOffset + && elementRange.getOffset() + elementRange.getLength() >= finalOffset + finalLength; + } catch (JavaModelException e) { + return false; + } + }).map(IJavaElement.class::cast) + .findAny(); + if (candidate.isPresent()) { + newChildFound = true; + currentElement = candidate.get(); + } + } + } while (newChildFound); + if (currentElement instanceof JavaElement impl && + impl.getElementInfo() instanceof AnnotatableInfo annotable && + annotable.getNameSourceStart() >= 0 && + annotable.getNameSourceStart() <= offset && + annotable.getNameSourceEnd() + 1 /* end exclusive vs offset inclusive */ >= offset) { + return new IJavaElement[] { currentElement }; + } + if (insideComment) { + String toSearch = trimmedText.isBlank() ? findWord(offset) : trimmedText; + String resolved = ((List)currentAST.imports()).stream() + .map(org.eclipse.jdt.core.dom.ImportDeclaration::getName) + .map(Name::toString) + .filter(importedPackage -> importedPackage.endsWith(toSearch)) + .findAny() + .orElse(toSearch); + if (this.unit.getJavaProject().findType(resolved) instanceof IType type) { + return new IJavaElement[] { type }; + } + } + // failback to lookup search + ASTNode currentNode = node; + while (currentNode != null && !(currentNode instanceof Type)) { + currentNode = currentNode.getParent(); + } + if (currentNode instanceof Type parentType) { + if (this.unit.getJavaProject() != null) { + StringBuilder buffer = new StringBuilder(); + Util.getFullyQualifiedName(parentType, buffer); + IType type = this.unit.getJavaProject().findType(buffer.toString()); + if (type != null) { + return new IJavaElement[] { type }; + } + } + String packageName = parentType instanceof QualifiedType qType ? qType.getQualifier().toString() : + parentType instanceof SimpleType sType ? + sType.getName() instanceof QualifiedName qName ? qName.getQualifier().toString() : + null : + null; + String simpleName = parentType instanceof QualifiedType qType ? qType.getName().toString() : + parentType instanceof SimpleType sType ? + sType.getName() instanceof SimpleName sName ? sName.getIdentifier() : + sType.getName() instanceof QualifiedName qName ? qName.getName().toString() : + null : + null; + IJavaElement[] indexResult = findTypeInIndex(packageName, simpleName); + if (indexResult.length > 0) { + return indexResult; + } + } + // no good idea left + return new IJavaElement[0]; + } + + static IBinding resolveBinding(ASTNode node) { + if (node instanceof MethodDeclaration decl) { + return decl.resolveBinding(); + } + if (node instanceof MethodInvocation invocation) { + return invocation.resolveMethodBinding(); + } + if (node instanceof VariableDeclaration decl) { + return decl.resolveBinding(); + } + if (node instanceof FieldAccess access) { + return access.resolveFieldBinding(); + } + if (node instanceof Type type) { + return type.resolveBinding(); + } + if (node instanceof Name aName) { + ClassInstanceCreation newInstance = findConstructor(aName); + if (newInstance != null) { + var constructorBinding = newInstance.resolveConstructorBinding(); + if (constructorBinding != null) { + var constructorElement = constructorBinding.getJavaElement(); + if (constructorElement != null) { + boolean hasSource = true; + try { + hasSource = ((ISourceReference)constructorElement.getParent()).getSource() != null; + } catch (Exception e) { + hasSource = false; + } + if ((constructorBinding.getParameterTypes().length > 0 /*non-default*/ || + constructorElement instanceof SourceMethod || !hasSource)) { + return constructorBinding; + } + } else if (newInstance.resolveTypeBinding().isAnonymous()) { + // it's not in the anonymous class body, check for constructor decl in parent types + + ITypeBinding superclassBinding = newInstance.getType().resolveBinding(); + + while (superclassBinding != null) { + Optional potentialConstructor = Stream.of(superclassBinding.getDeclaredMethods()) // + .filter(methodBinding -> methodBinding.isConstructor() && matchSignatures(constructorBinding, methodBinding)) + .findFirst(); + if (potentialConstructor.isPresent()) { + IMethodBinding theConstructor = potentialConstructor.get(); + if (theConstructor.isDefaultConstructor()) { + return theConstructor.getDeclaringClass(); + } + return theConstructor; + } + superclassBinding = superclassBinding.getSuperclass(); + } + return null; + } + } + } + if (node.getParent() instanceof ExpressionMethodReference exprMethodReference && exprMethodReference.getName() == node) { + return resolveBinding(exprMethodReference); + } + if (node.getParent() instanceof TypeMethodReference typeMethodReference && typeMethodReference.getName() == node) { + return resolveBinding(typeMethodReference); + } + IBinding res = aName.resolveBinding(); + if (res != null) { + return res; + } + return resolveBinding(aName.getParent()); + } + if (node instanceof org.eclipse.jdt.core.dom.LambdaExpression lambda) { + return lambda.resolveMethodBinding(); + } + if (node instanceof ExpressionMethodReference methodRef) { + IMethodBinding methodBinding = methodRef.resolveMethodBinding(); + try { + if (methodBinding == null) { + return null; + } + IMethod methodModel = ((IMethod)methodBinding.getJavaElement()); + boolean allowExtraParam = true; + if ((methodModel.getFlags() & Flags.AccStatic) != 0) { + allowExtraParam = false; + if (methodRef.getExpression() instanceof ClassInstanceCreation) { + return null; + } + } + + // find the type that the method is bound to + ITypeBinding type = null; + ASTNode cursor = methodRef; + while (type == null && cursor != null) { + if (cursor.getParent() instanceof VariableDeclarationFragment declFragment) { + type = declFragment.resolveBinding().getType(); + } + else if (cursor.getParent() instanceof MethodInvocation methodInvocation) { + IMethodBinding methodInvocationBinding = methodInvocation.resolveMethodBinding(); + int index = methodInvocation.arguments().indexOf(cursor); + type = methodInvocationBinding.getParameterTypes()[index]; + } else { + cursor = cursor.getParent(); + } + } + + IMethodBinding boundMethod = type.getDeclaredMethods()[0]; + + if (boundMethod.getParameterTypes().length != methodBinding.getParameterTypes().length && (!allowExtraParam || boundMethod.getParameterTypes().length != methodBinding.getParameterTypes().length + 1)) { + return null; + } + } catch (JavaModelException e) { + return null; + } + return methodBinding; + } + if (node instanceof MethodReference methodRef) { + return methodRef.resolveMethodBinding(); + } + if (node instanceof org.eclipse.jdt.core.dom.TypeParameter typeParameter) { + return typeParameter.resolveBinding(); + } + if (node instanceof SuperConstructorInvocation superConstructor) { + return superConstructor.resolveConstructorBinding(); + } + if (node instanceof ConstructorInvocation constructor) { + return constructor.resolveConstructorBinding(); + } + if (node instanceof org.eclipse.jdt.core.dom.Annotation annotation) { + return annotation.resolveTypeBinding(); + } + if (node instanceof SuperMethodInvocation superMethod) { + return superMethod.resolveMethodBinding(); + } + return null; + } + + private static ClassInstanceCreation findConstructor(ASTNode node) { + while (node != null && !(node instanceof ClassInstanceCreation)) { + ASTNode parent = node.getParent(); + if ((parent instanceof SimpleType type && type.getName() == node) || + (parent instanceof ClassInstanceCreation constructor && constructor.getType() == node) || + (parent instanceof ParameterizedType parameterized && parameterized.getType() == node)) { + node = parent; + } else { + node = null; + } + } + return (ClassInstanceCreation)node; + } + + private static AbstractTypeDeclaration findTypeDeclaration(ASTNode node) { + ASTNode cursor = node; + while (cursor != null && (cursor instanceof Type || cursor instanceof Name)) { + cursor = cursor.getParent(); + } + if (cursor instanceof AbstractTypeDeclaration typeDecl && typeDecl.getName() == node) { + return typeDecl; + } + return null; + } + + private static org.eclipse.jdt.core.dom.ImportDeclaration findImportDeclaration(ASTNode node) { + while (node != null && !(node instanceof org.eclipse.jdt.core.dom.ImportDeclaration)) { + node = node.getParent(); + } + return (org.eclipse.jdt.core.dom.ImportDeclaration)node; + } + + private static boolean matchSignatures(IMethodBinding invocation, IMethodBinding declaration) { + if (declaration.getTypeParameters().length == 0) { + return invocation.isSubsignature(declaration); + } + if (invocation.getParameterTypes().length != declaration.getParameterTypes().length) { + return false; + } + for (int i = 0; i < invocation.getParameterTypes().length; i++) { + if (declaration.getParameterTypes()[i].isTypeVariable()) { + if (declaration.getParameterTypes()[i].getTypeBounds().length > 0) { + ITypeBinding[] bounds = declaration.getParameterTypes()[i].getTypeBounds(); + for (int j = 0; j < bounds.length; j++) { + if (!invocation.getParameterTypes()[i].isSubTypeCompatible(bounds[j])) { + return false; + } + } + } + } else if (!invocation.getParameterTypes()[i].isSubTypeCompatible(declaration.getParameterTypes()[i])) { + return false; + } + + } + return true; + } + + private IJavaElement[] findTypeInIndex(String packageName, String simpleName) throws JavaModelException { + List indexMatch = new ArrayList<>(); + TypeNameMatchRequestor requestor = new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(org.eclipse.jdt.core.search.TypeNameMatch match) { + indexMatch.add(match.getType()); + } + }; + IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaProject[] { this.unit.getJavaProject() }); + new SearchEngine(this.owner).searchAllTypeNames( + packageName != null ? packageName.toCharArray() : null, + SearchPattern.R_EXACT_MATCH, + simpleName.toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + IJavaSearchConstants.TYPE, + scope, + requestor, + IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, + new NullProgressMonitor()); + if (!indexMatch.isEmpty()) { + return indexMatch.toArray(IJavaElement[]::new); + } + scope = BasicSearchEngine.createWorkspaceScope(); + new BasicSearchEngine(this.owner).searchAllTypeNames( + packageName != null ? packageName.toCharArray() : null, + SearchPattern.R_EXACT_MATCH, + simpleName.toCharArray(), + SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, + IJavaSearchConstants.TYPE, + scope, + new TypeNameMatchRequestorWrapper(requestor, scope), + IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, + new NullProgressMonitor()); + if (!indexMatch.isEmpty()) { + return indexMatch.toArray(IJavaElement[]::new); + } + return new IJavaElement[0]; + } + + private String findWord(int offset) throws JavaModelException { + int start = offset; + String source = this.unit.getSource(); + while (start >= 0 && Character.isJavaIdentifierPart(source.charAt(start))) start--; + int end = offset + 1; + while (end < source.length() && Character.isJavaIdentifierPart(source.charAt(end))) end++; + return source.substring(start, end); + } +} diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java index 03cb94be41a..8b65b699a6f 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/AST.java @@ -3215,6 +3215,19 @@ public TryStatement newTryStatement() { return new TryStatement(this); } + /** + * Creates an unparented class declaration node owned by this AST. + * The name of the class is an unspecified, but legal, name; + * no modifiers; no doc comment; no superclass or superinterfaces; + * and an empty class body. + * + * @return a new unparented type declaration node + * @since 3.40 + */ + public ImplicitTypeDeclaration newImplicitTypeDeclaration() { + return new ImplicitTypeDeclaration(this); + } + /** * Creates an unparented class declaration node owned by this AST. * The name of the class is an unspecified, but legal, name; diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java index e6299685ec0..f1959124dfb 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTConverter.java @@ -315,6 +315,7 @@ protected void buildBodyDeclarations( } else { methodsIndex++; + continue; } } @@ -5897,9 +5898,6 @@ protected void setModifiers(List modifiers, org.eclipse.jdt.internal.compiler.as case TerminalTokens.TokenNamenon_sealed: modifier = createModifier(Modifier.ModifierKeyword.NON_SEALED_KEYWORD); break; - case TerminalTokens.TokenNameRestrictedIdentifierWhen: - modifier = createModifier(Modifier.ModifierKeyword.WHEN_KEYWORD); - break; case TerminalTokens.TokenNameAT : // we have an annotation if (annotations != null && indexInAnnotations < annotations.length) { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java index 9c0024e1469..a156cd00975 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java @@ -24,21 +24,17 @@ import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.jdt.core.IClassFile; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.ITypeRoot; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.*; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; import org.eclipse.jdt.internal.compiler.parser.RecoveryScannerData; import org.eclipse.jdt.internal.compiler.parser.Scanner; @@ -272,8 +268,49 @@ private List getClasspath() throws IllegalStateException { } catch (IllegalArgumentException e) { throw new IllegalStateException("invalid environment settings", e); //$NON-NLS-1$ } + if ((this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0) { + checkForSystemLibrary(allClasspaths); + } return allClasspaths; } + + private void checkForSystemLibrary(List allClasspaths) { + boolean hasSystemLibrary = true; // default for 1.8 setting without a valid project + boolean hasModule = false; + Throwable exception = null; + String compliance = this.compilerOptions.get(JavaCore.COMPILER_COMPLIANCE); + if (CompilerOptions.versionToJdkLevel(compliance) >= ClassFileConstants.JDK9) { + hasSystemLibrary = allClasspaths.stream().anyMatch(cp -> cp.getModule(TypeConstants.JAVA_DOT_BASE) != null); + if (!hasSystemLibrary && this.project != null) { + // not found in allClasspaths, try this.project instead: + try { + // try module java.base: + for (IPackageFragmentRoot root : this.project.getAllPackageFragmentRoots()) { + IModuleDescription moduleDescription = root.getModuleDescription(); + if (moduleDescription != null) { + hasModule = true; + if (moduleDescription.getElementName().equals(String.valueOf(TypeConstants.JAVA_DOT_BASE))) { + hasSystemLibrary = true; + break; + } + } + } + } catch (JavaModelException e) { + exception = e; + } + if (!hasModule) { + try { + // if no modules try class java.lang.Object: + hasSystemLibrary = this.project.findType(String.valueOf(TypeConstants.CharArray_JAVA_LANG_OBJECT)) != null; + } catch (JavaModelException e) { + exception = e; + } + } + } + if (!hasSystemLibrary) + throw new IllegalStateException("Missing system library", exception); //$NON-NLS-1$ + } + } /** * Sets all the setting to their default values. */ diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Modifier.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Modifier.java index 8c392199f6c..369c802eac9 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Modifier.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/Modifier.java @@ -110,11 +110,7 @@ public static class ModifierKeyword { * @since 3.24 */ public static final ModifierKeyword SEALED_KEYWORD = new ModifierKeyword("sealed", SEALED);//$NON-NLS-1$ - /** - * @since 3.32 - * @noreference preview feature - */ - public static final ModifierKeyword WHEN_KEYWORD = new ModifierKeyword("when", WHEN);//$NON-NLS-1$ + /** * @since 3.24 */ @@ -348,13 +344,7 @@ public String toString() { * @since 3.24 */ public static final int NON_SEALED = 0x1000; - /** - * "when" modifier constant (bit mask). - * Applicable only to types. - * @since 3.32 - * @noreference preview feature - */ - public static final int WHEN = 0x2000; + /** * "module" modifier constant (bit mask). * Applicable only to imports. diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TryStatement.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TryStatement.java index 7c38bebcc62..3b33862ad50 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TryStatement.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/TryStatement.java @@ -360,7 +360,7 @@ public void setFinally(Block block) { /** * Returns the live ordered list of resources for this try statement (added in JLS4 API). * - *

A resource is either a {@link VariableDeclarationExpression} or (since JLS9) a {@link Name}.

+ *

A resource is either a {@link VariableDeclarationExpression} or (since JLS9) a {@link Name} or a {@link FieldAccess} or a {@link SuperFieldAccess}.

* * @return the live list of resources (element type: {@link Expression}). * In the deprecated JLS4 and JLS8 APIs, this used to be diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/NaiveASTFlattener.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/NaiveASTFlattener.java index 2b65c3a90d6..ffc404a2c9d 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/NaiveASTFlattener.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/NaiveASTFlattener.java @@ -1912,6 +1912,25 @@ public boolean visit(TryStatement node) { return false; } + @Override + public boolean visit(ImplicitTypeDeclaration node) { + //javaDoc + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + + //bodyDeclaration + this.indent++; + for (Object element : node.bodyDeclarations()) { + BodyDeclaration d = (BodyDeclaration) element; + d.accept(this); + } + this.indent--; + printIndent(); + + return false; + } + @Override public boolean visit(TypeDeclaration node) { if (node.getJavadoc() != null) { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java index 76c09181b52..94d00d72b08 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteAnalyzer.java @@ -1849,6 +1849,20 @@ public boolean visit(CompilationUnit node) { return false; } + @Override + public boolean visit(ImplicitTypeDeclaration node) { + if (!hasChildrenChanges(node)) { + return doVisitUnchangedChildren(node); + } + //javaDoc + rewriteJavadoc(node, ImplicitTypeDeclaration.JAVADOC_PROPERTY); + + int startIndent= getIndent(node.getStartPosition()) + 1; + int startPos= node.getStartPosition(); + rewriteParagraphList(node, ImplicitTypeDeclaration.BODY_DECLARATIONS_PROPERTY, startPos, startIndent, -1, 2); + return false; + } + @Override public boolean visit(TypeDeclaration node) { if (!hasChildrenChanges(node)) { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFlattener.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFlattener.java index 3326d9fd4c0..d53c75bc0cf 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFlattener.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFlattener.java @@ -1209,6 +1209,17 @@ public boolean visit(TryStatement node) { return false; } + @Override + public boolean visit(ImplicitTypeDeclaration node) { + ASTNode javadoc= getChildNode(node, TypeDeclaration.JAVADOC_PROPERTY); + if (javadoc != null) { + javadoc.accept(this); + } + + visitList(node, TypeDeclaration.BODY_DECLARATIONS_PROPERTY, null); + return false; + } + @Override public boolean visit(TypeDeclaration node) { int apiLevel= node.getAST().apiLevel(); diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java index cc151b0391b..05d0814b859 100644 --- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java +++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/TokenManager.java @@ -19,6 +19,7 @@ import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameNotAToken; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameStringLiteral; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameTextBlock; +import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameUNDERSCORE; import java.util.ArrayList; import java.util.HashMap; @@ -175,6 +176,9 @@ public int findIndex(int positionInSource, int tokenType, boolean forward) { if (tokenType == TerminalTokens.getRestrictedKeyword(toString(t))) break; } + if (t.tokenType == TokenNameUNDERSCORE && tokenType == TokenNameIdentifier) { + break; + } index += forward ? 1 : -1; } return index; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java index b85095d2d71..4ba14ab80d3 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/JavaCore.java @@ -458,6 +458,19 @@ public final class JavaCore extends Plugin { * @category CompilerOptionID */ public static final String COMPILER_PB_UNUSED_LOCAL = PLUGIN_ID + ".compiler.problem.unusedLocal"; //$NON-NLS-1$ + /** + * Compiler option ID: Reporting Unused Lambda Parameter. + *

When enabled, the compiler will issue an error or a warning for unused lambda + * parameters (that is, lambda parameters never read from).

+ *
+ *
Option id:
"org.eclipse.jdt.core.compiler.problem.unusedLambdaParameter"
+ *
Possible values:
{ "error", "warning", "info", "ignore" }
+ *
Default:
"warning"
+ *
+ * @category CompilerOptionID + * @since 3.40 + */ + public static final String COMPILER_PB_UNUSED_LAMBDA_PARAMETER = PLUGIN_ID + ".compiler.problem.unusedLambdaParameter"; //$NON-NLS-1$ /** * Compiler option ID: Reporting Unused Parameter. *

When enabled, the compiler will issue an error or a warning for unused method diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementNotifier.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementNotifier.java index 68c5f243e25..fd4b6a4ff0c 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementNotifier.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementNotifier.java @@ -14,6 +14,7 @@ package org.eclipse.jdt.internal.compiler; import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ISourceElementRequestor.ParameterInfo; @@ -52,12 +53,12 @@ public TypeDeclaration peekDeclaringType() { } @Override public boolean visit(TypeDeclaration typeDeclaration, BlockScope scope) { - notifySourceElementRequestor(typeDeclaration, true, peekDeclaringType(), this.currentPackage); + notifySourceElementRequestor(null, typeDeclaration, true, peekDeclaringType(), this.currentPackage); return false; // don't visit members as this was done during notifySourceElementRequestor(...) } @Override public boolean visit(TypeDeclaration typeDeclaration, ClassScope scope) { - notifySourceElementRequestor(typeDeclaration, true, peekDeclaringType(), this.currentPackage); + notifySourceElementRequestor(null, typeDeclaration, true, peekDeclaringType(), this.currentPackage); return false; // don't visit members as this was done during notifySourceElementRequestor(...) } } @@ -136,8 +137,44 @@ protected char[] getSuperclassName(TypeDeclaration typeDeclaration) { TypeReference superclass = typeDeclaration.superclass; return superclass != null ? CharOperation.concatWith(superclass.getParameterizedTypeName(), '.') : null; } -protected char[][] getPermittedSubTypes(TypeDeclaration typeDeclaration) { - return extractTypeReferences(typeDeclaration.permittedTypes); +private void gatherPermittedTypesOf(TypeDeclaration potentialSubtype, TypeDeclaration sealedType, List list, char [] prefix) { + if (potentialSubtype != sealedType) { + char[][] qName = potentialSubtype.superclass == null ? null : potentialSubtype.superclass.getTypeName(); + if (qName != null && CharOperation.equals(qName[qName.length - 1], sealedType.name)) { + char [] subTypeName = CharOperation.concat(prefix, potentialSubtype.name, '.'); + list.add(subTypeName); + } + if (potentialSubtype.superInterfaces != null) { + for (TypeReference ref : potentialSubtype.superInterfaces) { + qName = ref.getTypeName(); + if (CharOperation.equals(qName[qName.length - 1], sealedType.name)) { + char [] subTypeName = CharOperation.concat(prefix, potentialSubtype.name, '.'); + list.add(subTypeName); + break; + } + } + } + } + if (potentialSubtype.memberTypes != null) { + for (int i = 0, size = potentialSubtype.memberTypes.length; i < size; i++) { + char [] prefixNow = CharOperation.concat(prefix, potentialSubtype.name, '.'); + gatherPermittedTypesOf(potentialSubtype.memberTypes[i], sealedType, list, prefixNow); + } + } +} +protected char[][] getPermittedSubTypes(CompilationUnitDeclaration parsedUnit, TypeDeclaration sealedType) { + + if (sealedType.permittedTypes != null) + return extractTypeReferences(sealedType.permittedTypes); + + // compute implicit permitted types on the fly. + List list = new ArrayList(); + if (parsedUnit != null) { // == null for local types. + for (TypeDeclaration type : parsedUnit.types) { + gatherPermittedTypesOf(type, sealedType, list, CharOperation.NO_CHAR); + } + } + return list.toArray(new char[list.size()][]); } protected char[][] getThrownExceptions(AbstractMethodDeclaration methodDeclaration) { return extractTypeReferences(methodDeclaration.thrownExceptions); @@ -455,7 +492,7 @@ public void notifySourceElementRequestor( notifySourceElementRequestor(importRef, false); } } else if (node instanceof TypeDeclaration && !new String(parsedUnit.getFileName()).endsWith(TypeConstants.MODULE_INFO_FILE_NAME_STRING)) { - notifySourceElementRequestor((TypeDeclaration)node, true, null, currentPackage); + notifySourceElementRequestor(parsedUnit, (TypeDeclaration)node, true, null, currentPackage); } else if (node instanceof ModuleDeclaration) { notifySourceElementRequestor(parsedUnit.moduleDeclaration); } @@ -640,7 +677,7 @@ protected void notifySourceElementRequestor(ModuleDeclaration moduleDeclaration) // } // //} -protected void notifySourceElementRequestor(TypeDeclaration typeDeclaration, boolean notifyTypePresence, TypeDeclaration declaringType, ImportReference currentPackage) { +protected void notifySourceElementRequestor(CompilationUnitDeclaration parsedUnit, TypeDeclaration typeDeclaration, boolean notifyTypePresence, TypeDeclaration declaringType, ImportReference currentPackage) { if (CharOperation.equals(TypeConstants.PACKAGE_INFO_NAME, typeDeclaration.name)) return; @@ -704,7 +741,7 @@ protected void notifySourceElementRequestor(TypeDeclaration typeDeclaration, boo typeInfo.extraFlags = ExtraFlags.getExtraFlags(typeDeclaration); typeInfo.node = typeDeclaration; if ((currentModifiers & ExtraCompilerModifiers.AccSealed) != 0) { - typeInfo.permittedSubtypes = getPermittedSubTypes(typeDeclaration); + typeInfo.permittedSubtypes = getPermittedSubTypes(parsedUnit, typeDeclaration); } switch (kind) { case TypeDeclaration.CLASS_DECL : @@ -776,7 +813,7 @@ protected void notifySourceElementRequestor(TypeDeclaration typeDeclaration, boo break; case 2 : memberTypeIndex++; - notifySourceElementRequestor(nextMemberDeclaration, true, null, currentPackage); + notifySourceElementRequestor(parsedUnit, nextMemberDeclaration, true, null, currentPackage); break; } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementParser.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementParser.java index b0ef7dae9e3..f20b91acc67 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementParser.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/compiler/SourceElementParser.java @@ -13,9 +13,7 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.compiler.CategorizedProblem; @@ -26,11 +24,6 @@ import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; import org.eclipse.jdt.internal.compiler.lookup.Binding; -import org.eclipse.jdt.internal.compiler.lookup.BlockScope; -import org.eclipse.jdt.internal.compiler.lookup.ClassScope; -import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; -import org.eclipse.jdt.internal.compiler.lookup.Scope; -import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; @@ -975,77 +968,7 @@ protected QualifiedNameReference newQualifiedNameReference(char[][] tokens, long protected SingleNameReference newSingleNameReference(char[] source, long positions) { return new SingleNameReference(source, positions); } -private static class DummyTypeReference extends TypeReference { - char[] token; - DummyTypeReference(char[] name) { - this.token = name; - } - @Override - public TypeReference augmentTypeWithAdditionalDimensions(int additionalDimensions, - Annotation[][] additionalAnnotations, boolean isVarargs) { - // TODO Auto-generated method stub - return null; - } - @Override - public char[] getLastToken() { - return this.token; - } - @Override - protected TypeBinding getTypeBinding(Scope scope) { - return null; - } - @Override - public char[][] getTypeName() { - return new char[][] {this.token}; - } - @Override - public void traverse(ASTVisitor visitor, BlockScope scope) { - // TODO Auto-generated method stub - } - - @Override - public void traverse(ASTVisitor visitor, ClassScope scope) { - // TODO Auto-generated method stub - } - @Override - public StringBuilder printExpression(int indent, StringBuilder output) { - return output.append(this.token); - } - @Override - public String toString() { - return new String(this.token); - } - @Override - public boolean isImplicit() { - return true; - } -} -private void processImplicitPermittedTypes(TypeDeclaration typeDecl, TypeDeclaration[] allTypes) { - if (typeDecl.permittedTypes == null && - (typeDecl.modifiers & ExtraCompilerModifiers.AccSealed) != 0) { - List list = new ArrayList(); - for (TypeDeclaration type : allTypes) { - if (type != typeDecl) { - char[][] qName = type.superclass == null ? null : type.superclass.getTypeName(); - if (qName != null && - CharOperation.equals(qName[qName.length -1], typeDecl.name)) { - list.add(new DummyTypeReference(type.name)); - } - if (type.superInterfaces != null) { - for (TypeReference ref : type.superInterfaces) { - qName = ref.getTypeName(); - if (CharOperation.equals(qName[qName.length -1], typeDecl.name)) { - list.add(new DummyTypeReference(type.name)); - break; - } - } - } - } - } - typeDecl.permittedTypes = list.toArray(new TypeReference[list.size()]); - } -} public CompilationUnitDeclaration parseCompilationUnit( ICompilationUnit unit, boolean fullParse, @@ -1060,12 +983,7 @@ public CompilationUnitDeclaration parseCompilationUnit( this.reportReferenceInfo = fullParse; CompilationResult compilationUnitResult = new CompilationResult(unit, 0, 0, this.options.maxProblemsPerUnit); parsedUnit = parse(unit, compilationUnitResult); - TypeDeclaration[] types = parsedUnit.types; - if (types != null) { - for (TypeDeclaration typeDecl : types) { - processImplicitPermittedTypes(typeDecl, types); - } - } + if (pm != null && pm.isCanceled()) throw new OperationCanceledException(Messages.operation_cancelled); if (this.scanner.recordLineSeparator) { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java index faf422c6a8b..33c3aa174cd 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/ASTHolderCUInfo.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; public class ASTHolderCUInfo extends CompilationUnitElementInfo { - int astLevel; + public int astLevel; boolean resolveBindings; int reconcileFlags; Map problems = null; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java index ce9cfeb44c6..17a149aa1a3 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java @@ -602,7 +602,7 @@ public String[] getSuperInterfaceNames() throws JavaModelException { @Override public String[] getPermittedSubtypeNames() throws JavaModelException { IBinaryType info = getElementInfo(); - char[][] names= info.getPermittedSubtypeNames(); + char[][] names= info.getPermittedSubtypesNames(); int length; if (names == null || (length = names.length) == 0) { return CharOperation.NO_STRINGS; @@ -770,12 +770,11 @@ public boolean isRecord() throws JavaModelException { } /** * @see IType#isSealed() - * @noreference This method is not intended to be referenced by clients as it is a part of Java preview feature. */ @Override public boolean isSealed() throws JavaModelException { IBinaryType info = getElementInfo(); - char[][] names = info.getPermittedSubtypeNames(); + char[][] names = info.getPermittedSubtypesNames(); return (names != null && names.length > 0); } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java index d62b5ea23e6..945fde479f7 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnit.java @@ -38,6 +38,8 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.internal.codeassist.DOMCodeSelector; import org.eclipse.jdt.internal.compiler.IProblemFactory; import org.eclipse.jdt.internal.compiler.SourceElementParser; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -61,10 +63,12 @@ * @see ICompilationUnit */ public class CompilationUnit extends Openable implements ICompilationUnit, org.eclipse.jdt.internal.compiler.env.ICompilationUnit, SuffixConstants { + public static boolean DOM_BASED_OPERATIONS = Boolean.getBoolean(CompilationUnit.class.getSimpleName() + ".DOM_BASED_OPERATIONS"); //$NON-NLS-1$ private static final IImportDeclaration[] NO_IMPORTS = new IImportDeclaration[0]; protected final String name; public final WorkingCopyOwner owner; + private org.eclipse.jdt.core.dom.CompilationUnit ast; /** * Constructs a handle to a compilation unit with the given name in the @@ -393,8 +397,38 @@ public IJavaElement[] codeSelect(int offset, int length) throws JavaModelExcepti */ @Override public IJavaElement[] codeSelect(int offset, int length, WorkingCopyOwner workingCopyOwner) throws JavaModelException { - return super.codeSelect(this, offset, length, workingCopyOwner); + if (DOM_BASED_OPERATIONS) { + return new DOMCodeSelector(this, workingCopyOwner).codeSelect(offset, length); + } else { + return super.codeSelect(this, offset, length, workingCopyOwner); + } } + +public org.eclipse.jdt.core.dom.CompilationUnit getOrBuildAST(WorkingCopyOwner workingCopyOwner) throws JavaModelException { + if (this.ast != null) { + return this.ast; + } + Map options = getOptions(true); + ASTParser parser = ASTParser.newParser(new AST(options).apiLevel()); // go through AST constructor to convert options to apiLevel + parser.setWorkingCopyOwner(workingCopyOwner); + parser.setSource(this); + // greedily enable everything assuming the AST will be used extensively for edition + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setCompilerOptions(options); + if (parser.createAST(null) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + this.ast = newAST; + } + return this.ast; +} + +@Override +public void bufferChanged(BufferChangedEvent event) { + this.ast = null; + super.bufferChanged(event); +} + /** * @see IWorkingCopy#commit(boolean, IProgressMonitor) * @deprecated @@ -1462,6 +1496,17 @@ public Map getCustomOptions() { if (this.owner != null) { try { Map customOptions = this.getCompilationUnitElementInfo().getCustomOptions(); + IJavaProject parentProject = getJavaProject(); + Map parentOptions = parentProject == null ? JavaCore.getOptions() : parentProject.getOptions(true); + if (JavaCore.ENABLED.equals(parentOptions.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES)) && + AST.newAST(parentOptions).apiLevel() < AST.getJLSLatest()) { + // Disable preview features for older Java releases as it causes the compiler to fail later + if (customOptions != null) { + customOptions.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.DISABLED); + } else { + customOptions = Map.of(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.DISABLED); + } + } return customOptions == null ? Collections.emptyMap() : customOptions; } catch (JavaModelException e) { // do nothing diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java new file mode 100644 index 00000000000..737cdaf7be3 --- /dev/null +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/DOMToModelPopulator.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core; + +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.internal.core.util.Util; + +/** + * Process an AST to populate a tree of IJavaElement->JavaElementInfo. + * DOM-first approach to what legacy implements through ECJ parser and CompilationUnitStructureRequestor + */ +public class DOMToModelPopulator extends ASTVisitor { + + public static LocalVariable toLocalVariable(SingleVariableDeclaration parameter, JavaElement parent) { + return toLocalVariable(parameter, parent, parameter.getParent() instanceof MethodDeclaration); + } + + private static LocalVariable toLocalVariable(SingleVariableDeclaration parameter, JavaElement parent, boolean isParameter) { + return new LocalVariable(parent, + parameter.getName().getIdentifier(), + getStartConsideringLeadingComments(parameter), + parameter.getStartPosition() + parameter.getLength() - 1, + parameter.getName().getStartPosition(), + parameter.getName().getStartPosition() + parameter.getName().getLength() - 1, + Util.getSignature(parameter.getType()), + null, // should be populated while navigating children + toModelFlags(parameter.getModifiers(), false), + isParameter); + } + + private static int getStartConsideringLeadingComments(ASTNode node) { + int start = node.getStartPosition(); + var unit = domUnit(node); + int index = unit.firstLeadingCommentIndex(node); + if (index >= 0 && index <= unit.getCommentList().size()) { + Comment comment = (Comment)unit.getCommentList().get(index); + start = comment.getStartPosition(); + } + return start; + } + + private static org.eclipse.jdt.core.dom.CompilationUnit domUnit(ASTNode node) { + while (node != null && !(node instanceof org.eclipse.jdt.core.dom.CompilationUnit)) { + node = node.getParent(); + } + return (org.eclipse.jdt.core.dom.CompilationUnit)node; + } + + private static int toModelFlags(int domModifiers, boolean isDeprecated) { + int res = 0; + if (Modifier.isAbstract(domModifiers)) res |= Flags.AccAbstract; + if (Modifier.isDefault(domModifiers)) res |= Flags.AccDefaultMethod; + if (Modifier.isFinal(domModifiers)) res |= Flags.AccFinal; + if (Modifier.isNative(domModifiers)) res |= Flags.AccNative; + if (Modifier.isNonSealed(domModifiers)) res |= Flags.AccNonSealed; + if (Modifier.isPrivate(domModifiers)) res |= Flags.AccPrivate; + if (Modifier.isProtected(domModifiers)) res |= Flags.AccProtected; + if (Modifier.isPublic(domModifiers)) res |= Flags.AccPublic; + if (Modifier.isSealed(domModifiers)) res |= Flags.AccSealed; + if (Modifier.isStatic(domModifiers)) res |= Flags.AccStatic; + if (Modifier.isStrictfp(domModifiers)) res |= Flags.AccStrictfp; + if (Modifier.isSynchronized(domModifiers)) res |= Flags.AccSynchronized; + if (Modifier.isTransient(domModifiers)) res |= Flags.AccTransient; + if (Modifier.isVolatile(domModifiers)) res |= Flags.AccVolatile; + if (isDeprecated) res |= Flags.AccDeprecated; + return res; + } + +} diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java index a16c96a8103..0d8dc47af17 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java @@ -705,7 +705,6 @@ public boolean isRecord() throws JavaModelException { } /** * @see IType#isSealed() - * @noreference This method is not intended to be referenced by clients as it is a part of Java preview feature. */ @Override public boolean isSealed() throws JavaModelException { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java index 6e0a27ccee4..d64ef2a63bd 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/util/Util.java @@ -1288,7 +1288,7 @@ public static String getDeclaringTypeSignature(String key) { /* * Appends to the given buffer the fully qualified name (as it appears in the source) of the given type */ - private static void getFullyQualifiedName(Type type, StringBuilder buffer) { + public static void getFullyQualifiedName(Type type, StringBuilder buffer) { switch (type.getNodeType()) { case ASTNode.ARRAY_TYPE: ArrayType arrayType = (ArrayType) type; diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/IJavaSearchConstants.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/IJavaSearchConstants.java index f88831918b3..be02fa86704 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/IJavaSearchConstants.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/IJavaSearchConstants.java @@ -493,7 +493,7 @@ public interface IJavaSearchConstants { int METHOD_REFERENCE_EXPRESSION = 0x10000000; /** - * Return only type references used as a permit type (Java 15) + * Return only type references used as a permit type (Java 17) *

* When this flag is set, only {@link TypeReferenceMatch} matches will be * returned. diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/SearchPattern.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/SearchPattern.java index d0fac3a6456..d79657ebae0 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/SearchPattern.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/core/search/SearchPattern.java @@ -1954,7 +1954,7 @@ public static SearchPattern createPattern(IJavaElement element, int limitTo) { * Return only method reference expressions (e.g. A :: foo). * * {@link IJavaSearchConstants#PERMITTYPE_TYPE_REFERENCE PERMITTYPE_TYPE_REFERENCE} - * Return only type references used as a permit type. + * Return only type references used as a permitted type. * * * diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java index 682db632773..78d98fa94cc 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/JavaSearchDocument.java @@ -87,7 +87,7 @@ public String getEncoding() { } return null; } - private IFile getFile() { + public IFile getFile() { if (this.file == null) this.file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(getPath())); return this.file; diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java new file mode 100644 index 00000000000..2df2152b4f4 --- /dev/null +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/DOMToIndexVisitor.java @@ -0,0 +1,345 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core.search.indexing; + +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.dom.*; + +class DOMToIndexVisitor extends ASTVisitor { + + private SourceIndexer sourceIndexer; + + private char[] packageName; + private List enclosingTypes = new LinkedList<>(); + + public DOMToIndexVisitor(SourceIndexer sourceIndexer) { + super(true); + this.sourceIndexer = sourceIndexer; + } + + private AbstractTypeDeclaration currentType() { + return this.enclosingTypes.get(this.enclosingTypes.size() - 1); + } + + @Override + public boolean visit(PackageDeclaration packageDeclaration) { + this.packageName = packageDeclaration.getName().toString().toCharArray(); + return false; + } + + @Override + public boolean visit(TypeDeclaration type) { + char[][] enclosing = type.isLocalTypeDeclaration() ? IIndexConstants.ONE_ZERO_CHAR : + this.enclosingTypes.stream().map(AbstractTypeDeclaration::getName).map(SimpleName::getIdentifier).map(String::toCharArray).toArray(char[][]::new); + char[][] parameterTypeSignatures = ((List)type.typeParameters()).stream() + .map(TypeParameter::getName) + .map(Name::toString) + .map(name -> Signature.createTypeSignature(name, false)) + .map(String::toCharArray) + .toArray(char[][]::new); + if (type.isInterface()) { + this.sourceIndexer.addInterfaceDeclaration(type.getModifiers(), this.packageName, simpleName(type.getName()), enclosing, ((List)type.superInterfaceTypes()).stream().map(this::name).toArray(char[][]::new), parameterTypeSignatures, isSecondary(type)); + } else { + this.sourceIndexer.addClassDeclaration(type.getModifiers(), this.packageName, simpleName(type.getName()), enclosing, type.getSuperclassType() == null ? null : name(type.getSuperclassType()), + ((List)type.superInterfaceTypes()).stream().map(this::name).toArray(char[][]::new), parameterTypeSignatures, isSecondary(type)); + if (type.bodyDeclarations().stream().noneMatch(member -> member instanceof MethodDeclaration method && method.isConstructor())) { + this.sourceIndexer.addDefaultConstructorDeclaration(type.getName().getIdentifier().toCharArray(), + this.packageName, type.getModifiers(), 0); + } + if (type.getSuperclassType() != null) { + this.sourceIndexer.addConstructorReference(name(type.getSuperclassType()), 0); + } + } + this.enclosingTypes.add(type); + // TODO other types + return true; + } + @Override + public void endVisit(TypeDeclaration type) { + this.enclosingTypes.remove(type); + } + + @Override + public boolean visit(EnumDeclaration type) { + char[][] enclosing = this.enclosingTypes.stream().map(AbstractTypeDeclaration::getName).map(SimpleName::getIdentifier).map(String::toCharArray).toArray(char[][]::new); + this.sourceIndexer.addEnumDeclaration(type.getModifiers(), this.packageName, type.getName().getIdentifier().toCharArray(), enclosing, Enum.class.getName().toCharArray(), ((List)type.superInterfaceTypes()).stream().map(this::name).toArray(char[][]::new), isSecondary(type)); + this.enclosingTypes.add(type); + return true; + } + @Override + public void endVisit(EnumDeclaration type) { + this.enclosingTypes.remove(type); + } + @Override + public boolean visit(EnumConstantDeclaration enumConstant) { + this.sourceIndexer.addFieldDeclaration(currentType().getName().getIdentifier().toCharArray(), enumConstant.getName().getIdentifier().toCharArray()); + this.sourceIndexer.addConstructorReference(currentType().getName().getIdentifier().toCharArray(), enumConstant.arguments().size()); + return true; + } + + @Override + public boolean visit(AnnotationTypeDeclaration type) { + char[][] enclosing = this.enclosingTypes.stream().map(AbstractTypeDeclaration::getName).map(SimpleName::getIdentifier).map(String::toCharArray).toArray(char[][]::new); + this.sourceIndexer.addAnnotationTypeDeclaration(type.getModifiers(), this.packageName, type.getName().getIdentifier().toCharArray(), enclosing, isSecondary(type)); + this.enclosingTypes.add(type); + return true; + } + @Override + public void endVisit(AnnotationTypeDeclaration type) { + this.enclosingTypes.remove(type); + } + + private boolean isSecondary(AbstractTypeDeclaration type) { + return type.getParent() instanceof CompilationUnit && + !Objects.equals(type.getName().getIdentifier() + ".java", Path.of(this.sourceIndexer.document.getPath()).getFileName().toString()); //$NON-NLS-1$ + } + + @Override + public boolean visit(RecordDeclaration recordDecl) { + // copied processing of TypeDeclaration + this.sourceIndexer.addClassDeclaration(recordDecl.getModifiers(), this.packageName, recordDecl.getName().getIdentifier().toCharArray(), null, null, + ((List)recordDecl.superInterfaceTypes()).stream().map(this::name).toArray(char[][]::new), null, false); + return true; + } + + @Override + public boolean visit(MethodDeclaration method) { + char[] methodName = method.getName().getIdentifier().toCharArray(); + char[][] parameterTypes = ((List)method.parameters()).stream() + .filter(SingleVariableDeclaration.class::isInstance) + .map(SingleVariableDeclaration.class::cast) + .map(SingleVariableDeclaration::getType) + .map(this::name) + .toArray(char[][]::new); + char[] returnType = name(method.getReturnType2()); + char[][] exceptionTypes = ((List)method.thrownExceptionTypes()).stream() + .map(this::name) + .toArray(char[][]::new); + char[][] parameterNames = ((List)method.parameters()).stream() + .map(VariableDeclaration::getName) + .map(SimpleName::getIdentifier) + .map(String::toCharArray) + .toArray(char[][]::new); + if (!method.isConstructor()) { + this.sourceIndexer.addMethodDeclaration(methodName, parameterTypes, returnType, exceptionTypes); + this.sourceIndexer.addMethodDeclaration(this.enclosingTypes.get(this.enclosingTypes.size() - 1).getName().getIdentifier().toCharArray(), + null /* TODO: fully qualified name of enclosing type? */, + methodName, + parameterTypes.length, + null, + parameterTypes, + parameterNames, + returnType, + method.getModifiers(), + this.packageName, + 0 /* TODO What to put here? */, + exceptionTypes, + 0 /* TODO ExtraFlags.IsLocalType ? */); + } else { + this.sourceIndexer.addConstructorDeclaration(method.getName().toString().toCharArray(), + method.parameters().size(), + null, parameterTypes, parameterNames, method.getModifiers(), this.packageName, currentType().getModifiers(), exceptionTypes, 0); + } + return true; + } + + @Override + public boolean visit(ImportDeclaration node) { + if (node.isStatic() && !node.isOnDemand()) { + this.sourceIndexer.addMethodReference(simpleName(node.getName()), 0); + } else if (!node.isOnDemand()) { + this.sourceIndexer.addTypeReference(node.getName().toString().toCharArray()); + } + return true; + } + + @Override + public boolean visit(FieldDeclaration field) { + char[] typeName = name(field.getType()); + for (VariableDeclarationFragment fragment: (List)field.fragments()) { + this.sourceIndexer.addFieldDeclaration(typeName, fragment.getName().getIdentifier().toCharArray()); + } + return true; + } + + @Override + public boolean visit(MethodInvocation methodInvocation) { + this.sourceIndexer.addMethodReference(methodInvocation.getName().getIdentifier().toCharArray(), methodInvocation.arguments().size()); + return true; + } + + @Override + public boolean visit(ExpressionMethodReference methodInvocation) { + int argsCount = 0; + if (this.sourceIndexer.document.shouldIndexResolvedDocument()) { + IMethodBinding binding = methodInvocation.resolveMethodBinding(); + if (binding != null) { + argsCount = binding.getParameterTypes().length; + } + } + this.sourceIndexer.addMethodReference(methodInvocation.getName().getIdentifier().toCharArray(), argsCount); + return true; + } + @Override + public boolean visit(TypeMethodReference methodInvocation) { + int argsCount = 0; + if (this.sourceIndexer.document.shouldIndexResolvedDocument()) { + IMethodBinding binding = methodInvocation.resolveMethodBinding(); + if (binding != null) { + argsCount = binding.getParameterTypes().length; + } + } + this.sourceIndexer.addMethodReference(methodInvocation.getName().getIdentifier().toCharArray(), argsCount); + return true; + } + @Override + public boolean visit(SuperMethodInvocation methodInvocation) { + this.sourceIndexer.addMethodReference(methodInvocation.getName().getIdentifier().toCharArray(), methodInvocation.arguments().size()); + return true; + } + @Override + public boolean visit(SuperMethodReference methodInvocation) { + int argsCount = 0; + if (this.sourceIndexer.document.shouldIndexResolvedDocument()) { + IMethodBinding binding = methodInvocation.resolveMethodBinding(); + if (binding != null) { + argsCount = binding.getParameterTypes().length; + } + } + this.sourceIndexer.addMethodReference(methodInvocation.getName().getIdentifier().toCharArray(), argsCount); + return true; + } + @Override + public boolean visit(ClassInstanceCreation methodInvocation) { + this.sourceIndexer.addConstructorReference(name(methodInvocation.getType()), methodInvocation.arguments().size()); + if (methodInvocation.getAnonymousClassDeclaration() != null) { + this.sourceIndexer.addClassDeclaration(0, this.packageName, new char[0], IIndexConstants.ONE_ZERO_CHAR, name(methodInvocation.getType()), null, null, false); + this.sourceIndexer.addTypeReference(name(methodInvocation.getType())); + } + return true; + } + @Override + public boolean visit(CreationReference methodInvocation) { + int argsCount = 0; + if (this.sourceIndexer.document.shouldIndexResolvedDocument()) { + IMethodBinding binding = methodInvocation.resolveMethodBinding(); + if (binding != null) { + argsCount = binding.getParameterTypes().length; + } + } + this.sourceIndexer.addConstructorReference(name(methodInvocation.getType()), argsCount); + return true; + } + + @Override + public boolean visit(SuperConstructorInvocation node) { + char[] superClassName = Object.class.getName().toCharArray(); + if (currentType() instanceof TypeDeclaration decl && decl.getSuperclassType() != null) { + superClassName = name(decl.getSuperclassType()); + } + this.sourceIndexer.addConstructorReference(superClassName, node.arguments().size()); + return true; + } + + private char[] name(Type type) { + if (type == null) { + return null; + } + if (type instanceof PrimitiveType primitive) { + return primitive.toString().toCharArray(); + } + if (type instanceof SimpleType simpleType) { + return simpleName(simpleType.getName()); + } + if (type instanceof ParameterizedType parameterized) { +// String res = new String(name(parameterized.getType())); +// res += '<'; +// res += ((List)parameterized.typeArguments()).stream() +// .map(this::name) +// .map(String::new) +// .collect(Collectors.joining(",")); //$NON-NLS-1$ +// res += '>'; +// return res.toCharArray(); + return name(parameterized.getType()); + } +// if (type instanceof ArrayType arrayType) { +// char[] res = name(arrayType.getElementType()); +// res = Arrays.copyOf(res, res.length + 2 * arrayType.getDimensions()); +// for (int i = 0; i < arrayType.getDimensions(); i++) { +// res[res.length - 1 - 2 * i] = ']'; +// res[res.length - 1 - 2 * i - 1] = '['; +// } +// return res; +// } +// if (type instanceof QualifiedType qualifiedType) { +// return simpleName(qualifiedType.getName()); +// } + return type.toString().toCharArray(); + } + + @Override + public boolean visit(SimpleType type) { + this.sourceIndexer.addTypeReference(name(type)); + return true; + } + @Override + public boolean visit(QualifiedType type) { + this.sourceIndexer.addTypeReference(name(type)); + return true; + } + @Override + public boolean visit(SimpleName name) { + this.sourceIndexer.addNameReference(name.getIdentifier().toCharArray()); + return true; + } + // TODO (cf SourceIndexer and SourceIndexerRequestor) + // * Module: addModuleDeclaration/addModuleReference/addModuleExportedPackages + // * Lambda: addIndexEntry/addClassDeclaration + // * FieldReference + // * Deprecated + // * Javadoc + + @Override + public boolean visit(MethodRef methodRef) { + this.sourceIndexer.addMethodReference(methodRef.getName().getIdentifier().toCharArray(), methodRef.parameters().size()); + this.sourceIndexer.addConstructorReference(methodRef.getName().getIdentifier().toCharArray(), methodRef.parameters().size()); + return true; + } + @Override + public boolean visit(MemberRef memberRef) { + this.sourceIndexer.addFieldReference(memberRef.getName().getIdentifier().toCharArray()); + this.sourceIndexer.addTypeReference(memberRef.getName().getIdentifier().toCharArray()); + return true; + } + + @Override + public boolean visit(LambdaExpression node) { + var binding = node.resolveMethodBinding(); + if (binding != null) { + this.sourceIndexer.addMethodReference(binding.getName().toCharArray(), binding.getParameterTypes().length); + } + return true; + } + + private static char[] simpleName(Name name) { + if (name instanceof SimpleName simple) { + return simple.getIdentifier().toCharArray(); + } + if (name instanceof QualifiedName qualified) { + return simpleName(qualified.getName()); + } + return null; + } +} diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java index 7d79204e333..0aee998679d 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/indexing/SourceIndexer.java @@ -15,13 +15,21 @@ import static org.eclipse.jdt.internal.core.JavaModelManager.trace; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.MethodReference; import org.eclipse.jdt.core.search.SearchDocument; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; @@ -54,6 +62,7 @@ import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.SourceTypeElementInfo; import org.eclipse.jdt.internal.core.jdom.CompilationUnit; +import org.eclipse.jdt.internal.core.search.JavaSearchDocument; import org.eclipse.jdt.internal.core.search.matching.JavaSearchNameEnvironment; import org.eclipse.jdt.internal.core.search.matching.MethodPattern; import org.eclipse.jdt.internal.core.search.processing.JobManager; @@ -88,6 +97,10 @@ public SourceIndexer(SearchDocument document) { } @Override public void indexDocument() { + if (Boolean.getBoolean(getClass().getSimpleName() + ".DOM_BASED_INDEXER")) { //$NON-NLS-1$ + indexDocumentFromDOM(); + return; + } // Create a new Parser String documentPath = this.document.getPath(); SourceElementParser parser = this.document.getParser(); @@ -213,6 +226,12 @@ private void purgeMethodStatements(TypeDeclaration type) { @Override public void indexResolvedDocument() { + if (Boolean.getBoolean(getClass().getSimpleName() + ".DOM_BASED_INDEXER")) { //$NON-NLS-1$ + // just re-run indexing, but with the resolved document (and its bindings) + indexDocumentFromDOM(); + return; + } + try { if (DEBUG) { trace(new String(this.cud.compilationResult.fileName) + ':'); @@ -271,4 +290,53 @@ public void indexResolvedDocument() { } } } + + /** + * @return whether the operation was successful + */ + boolean indexDocumentFromDOM() { + if (this.document instanceof JavaSearchDocument javaSearchDoc) { + IFile file = javaSearchDoc.getFile(); + try { + if (JavaProject.hasJavaNature(file.getProject())) { + IJavaProject javaProject = JavaCore.create(file.getProject()); + // Do NOT call javaProject.getElement(pathToJavaFile) as it can loop inside index + // when there are multiple package root/source folders, and then cause deadlock + // so we go finer grain by picking the right fragment first (so index call shouldn't happen) + IPackageFragment fragment = javaProject.findPackageFragment(file.getFullPath().removeLastSegments(1)); + if (fragment.getCompilationUnit(file.getName()) instanceof org.eclipse.jdt.internal.core.CompilationUnit modelUnit) { + // TODO check element info: if has AST and flags are set sufficiently, just reuse instead of rebuilding + ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); // we don't seek exact compilation the more tolerant the better here + astParser.setSource(modelUnit); + astParser.setStatementsRecovery(true); + astParser.setResolveBindings(this.document.shouldIndexResolvedDocument()); + astParser.setProject(javaProject); + org.eclipse.jdt.core.dom.ASTNode dom = astParser.createAST(null); + if (dom != null) { + dom.accept(new DOMToIndexVisitor(this)); + dom.accept( + new ASTVisitor() { + @Override + public boolean preVisit2(org.eclipse.jdt.core.dom.ASTNode node) { + if (SourceIndexer.this.document.shouldIndexResolvedDocument()) { + return false; // interrupt + } + if (node instanceof MethodReference || node instanceof org.eclipse.jdt.core.dom.LambdaExpression) { + SourceIndexer.this.document.requireIndexingResolvedDocument(); + return false; + } + return true; + } + }); + return true; + } + } + } + } catch (Exception ex) { + ILog.get().error("Failed to index document from DOM for " + this.document.getPath(), ex); //$NON-NLS-1$ + } + } + ILog.get().warn("Could not convert DOM to Index for " + this.document.getPath()); //$NON-NLS-1$ + return false; + } } diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java index e86877530f1..1bd7e0df46a 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocator.java @@ -3199,20 +3199,9 @@ protected void reportMatching(TypeDeclaration type, IJavaElement parent, int acc } } TypeReference[] permittedTypes = type.permittedTypes; - if (permittedTypes != null) { - for (int i = 0, l = permittedTypes.length; i < l; i++) { - reportMatchingSuperOrPermit(permittedTypes[i], enclosingElement, type.binding, nodeSet, matchedClassContainer); - TypeReference typeReference = type.permittedTypes[i]; - Annotation[][] annotations = typeReference != null ? typeReference.annotations : null; - if (annotations != null) { - for (Annotation[] annotation : annotations) { - if (annotation == null) continue; - reportMatching(annotation, enclosingElement, null, type.binding, nodeSet, matchedClassContainer, enclosesElement); - } - } - } + for (int i = 0, length = permittedTypes == null ? 0 : permittedTypes.length; i < length; i++) { + reportMatchingSuperOrPermit(permittedTypes[i], enclosingElement, type.binding, nodeSet, matchedClassContainer); } - } // filter out element not in hierarchy scope diff --git a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocatorParser.java b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocatorParser.java index c0941013693..e74e8675313 100644 --- a/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocatorParser.java +++ b/org.eclipse.jdt.core/search/org/eclipse/jdt/internal/core/search/matching/MatchLocatorParser.java @@ -949,22 +949,17 @@ protected void consumeWildcardBoundsSuper() { } } -private void updatePatternLocaterMatch() { +@Override +protected void consumePermittedTypes() { + super.consumePermittedTypes(); if ((this.patternFineGrain & IJavaSearchConstants.PERMITTYPE_TYPE_REFERENCE) != 0) { TypeDeclaration td = (TypeDeclaration) this.astStack[this.astPtr]; - TypeReference[] permittedTypes = td.permittedTypes; - for (TypeReference pt : permittedTypes) { + for (TypeReference pt : td.permittedTypes) { this.patternLocator.match(pt, this.nodeSet); } } } -@Override -protected void consumePermittedTypes() { - super.consumePermittedTypes(); - updatePatternLocaterMatch(); -} - @Override protected TypeReference augmentTypeWithAdditionalDimensions(TypeReference typeRef, int additionalDimensions, Annotation [][] additionalAnnotations, boolean isVarargs) { TypeReference result = super.augmentTypeWithAdditionalDimensions(typeRef, additionalDimensions, additionalAnnotations, isVarargs); diff --git a/pom.xml b/pom.xml index ed9b192c1f8..69a65c4daaf 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ org.eclipse.jdt.annotation - org.eclipse.jdt.annotation_v1 + org.eclipse.jdt.core.compiler.batch org.eclipse.jdt.core org.eclipse.jdt.core.formatterapp